//+-------------------------------------------------------------------------------+
//| Copyright (c) 2003 Liping Dai. All rights reserved.                           |
//| Web: www.lipingshare.com                                                      |
//| Email: lipingshare@yahoo.com                                                  |
//|                                                                               |
//| Copyright and Permission Details:                                             |
//| =================================                                             |
//| Permission is hereby granted, free of charge, to any person obtaining a copy  |
//| of this software and associated documentation files (the "Software"), to deal |
//| in the Software without restriction, including without limitation the rights  |
//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the   |
//| Software, subject to the following conditions:                                |
//|                                                                               |
//| 1. Redistributions of source code must retain the above copyright notice, this|
//| list of conditions and the following disclaimer.                              |
//|                                                                               |
//| 2. Redistributions in binary form must reproduce the above copyright notice,  |
//| this list of conditions and the following disclaimer in the documentation     |
//| and/or other materials provided with the distribution.                        |
//|                                                                               |
//| THE SOFTWARE PRODUCT IS PROVIDED �AS IS� WITHOUT WARRANTY OF ANY KIND,        |
//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED         |
//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR        |
//| A PARTICULAR PURPOSE.                                                         |
//+-------------------------------------------------------------------------------+
using System;
using System.IO;
using System.Text;
using Microsoft.Win32;
namespace LipingShare.LCLib.Asn1Processor
{
    /// 
    /// Utility functions.
    /// 
    internal class Asn1Util
    {
        /// 
        /// Check if the string is ASN.1 encoded hex string.
        /// 
        /// The string.
        /// true:Yes, false:No.
        public static bool IsAsn1EncodedHexStr(string dataStr)
        {
            bool retval = false;
            try
            {
                byte[] data = HexStrToBytes(dataStr);
                if (data.Length > 0)
                {
                    Asn1Node node = new Asn1Node();
                    retval = node.LoadData(data);
                }
            }
            catch
            {
                retval = false;
            }
            return retval;
        }
        /// 
        /// Format a string to have certain line length and character group length.
        /// Sample result FormatString(xstr,32,2):
        /// 07 AE 0B E7 84 5A D4 6C 6A BD DF 8F 89 88 9E F1
        /// 
        /// source string.
        /// line length.
        /// group length.
        /// 
        public static string FormatString(string inStr, int lineLen, int groupLen)
        {
            char[] tmpCh = new char[inStr.Length * 2];
            int i, c = 0, linec = 0;
            int gc = 0;
            for (i = 0; i < inStr.Length; i++)
            {
                tmpCh[c++] = inStr[i];
                gc++;
                linec++;
                if (gc >= groupLen && groupLen > 0)
                {
                    tmpCh[c++] = ' ';
                    gc = 0;
                }
                if (linec >= lineLen)
                {
                    tmpCh[c++] = '\r';
                    tmpCh[c++] = '\n';
                    linec = 0;
                }
            }
            string retval = new string(tmpCh);
            retval = retval.TrimEnd('\0');
            retval = retval.TrimEnd('\n');
            retval = retval.TrimEnd('\r');
            return retval;
        }
        /// 
        /// Generate a string by duplicating  xch.
        /// 
        /// duplicate times.
        /// the duplicated character.
        /// 
        public static string GenStr(int len, char xch)
        {
            char[] ch = new char[len];
            for (int i = 0; i < len; i++)
            {
                ch[i] = xch;
            }
            return new string(ch);
        }
        /// 
        /// Convert byte array to a  integer.
        /// 
        /// 
        /// 
        public static long BytesToLong(byte[] bytes)
        {
            long tempInt = 0;
            for (int i = 0; i < bytes.Length; i++)
            {
                tempInt = tempInt << 8 | bytes[i];
            }
            return tempInt;
        }
        /// 
        /// Convert a ASCII byte array to string, also filter out the null characters.
        /// 
        /// 
        /// 
        public static string BytesToString(byte[] bytes)
        {
            string retval = "";
            if (bytes == null || bytes.Length < 1) return retval;
            char[] cretval = new char[bytes.Length];
            for (int i = 0, j = 0; i < bytes.Length; i++)
            {
                if (bytes[i] != '\0')
                {
                    cretval[j++] = (char)bytes[i];
                }
            }
            retval = new string(cretval);
            retval = retval.TrimEnd('\0');
            return retval;
        }
        /// 
        /// Convert ASCII string to byte array.
        /// 
        /// 
        /// 
        public static byte[] StringToBytes(string msg)
        {
            byte[] retval = new byte[msg.Length];
            for (int i = 0; i < msg.Length; i++)
            {
                retval[i] = (byte)msg[i];
            }
            return retval;
        }
        /// 
        /// Compare source and target byte array.
        /// 
        /// 
        /// 
        /// 
        public static bool IsEqual(byte[] source, byte[] target)
        {
            if (source == null) return false;
            if (target == null) return false;
            if (source.Length != target.Length) return false;
            for (int i = 0; i < source.Length; i++)
            {
                if (source[i] != target[i]) return false;
            }
            return true;
        }
        /// 
        /// Constant hex digits array.
        /// 
        static char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7',
                                    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        /// 
        /// Convert a byte array to hex string.
        /// 
        /// source array.
        /// hex string.
        public static string ToHexString(byte[] bytes)
        {
            if (bytes == null) return "";
            char[] chars = new char[bytes.Length * 2];
            int b, i;
            for (i = 0; i < bytes.Length; i++)
            {
                b = bytes[i];
                chars[i * 2] = hexDigits[b >> 4];
                chars[i * 2 + 1] = hexDigits[b & 0xF];
            }
            return new string(chars);
        }
        /// 
        /// Check if the character is a valid hex digits.
        /// 
        /// source character.
        /// true:Valid, false:Invalid.
        public static bool IsValidHexDigits(char ch)
        {
            bool retval = false;
            for (int i = 0; i < hexDigits.Length; i++)
            {
                if (hexDigits[i] == ch)
                {
                    retval = true;
                    break;
                }
            }
            return retval;
        }
        /// 
        /// Get hex digits value.
        /// 
        /// source character.
        /// hex digits value.
        public static byte GetHexDigitsVal(char ch)
        {
            byte retval = 0;
            for (int i = 0; i < hexDigits.Length; i++)
            {
                if (hexDigits[i] == ch)
                {
                    retval = (byte)i;
                    break;
                }
            }
            return retval;
        }
        /// 
        /// Convert hex string to byte array.
        /// 
        /// Source hex string.
        /// return byte array.
        public static byte[] HexStrToBytes(string hexStr)
        {
            hexStr = hexStr.Replace(" ", "");
            hexStr = hexStr.Replace("\r", "");
            hexStr = hexStr.Replace("\n", "");
            hexStr = hexStr.ToUpper();
            if ((hexStr.Length % 2) != 0) throw new Exception("Invalid Hex string: odd length.");
            int i;
            for (i = 0; i < hexStr.Length; i++)
            {
                if (!IsValidHexDigits(hexStr[i]))
                {
                    throw new Exception("Invalid Hex string: included invalid character [" +
                        hexStr[i] + "]");
                }
            }
            int bc = hexStr.Length / 2;
            byte[] retval = new byte[bc];
            int b1, b2, b;
            for (i = 0; i < bc; i++)
            {
                b1 = GetHexDigitsVal(hexStr[i * 2]);
                b2 = GetHexDigitsVal(hexStr[i * 2 + 1]);
                b = ((b1 << 4) | b2);
                retval[i] = (byte)b;
            }
            return retval;
        }
        /// 
        /// Check if the source string is a valid hex string.
        /// 
        /// source string.
        /// true:Valid, false:Invalid.
        public static bool IsHexStr(string hexStr)
        {
            byte[] bytes = null;
            try
            {
                bytes = HexStrToBytes(hexStr);
            }
            catch
            {
                return false;
            }
            if (bytes == null || bytes.Length < 0)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        private const string PemStartStr = "-----BEGIN";
        private const string PemEndStr = "-----END";
        /// 
        /// Check if the source string is PEM formated string.
        /// 
        /// source string.
        /// true:Valid, false:Invalid.
        public static bool IsPemFormated(string pemStr)
        {
            byte[] data = null;
            try
            {
                data = PemToBytes(pemStr);
            }
            catch
            {
                return false;
            }
            return (data.Length > 0);
        }
        /// 
        /// Check if a file is PEM formated.
        /// 
        /// source file name.
        /// true:Yes, false:No.
        public static bool IsPemFormatedFile(string fileName)
        {
            bool retval = false;
            try
            {
                FileStream fs = new FileStream(fileName, System.IO.FileMode.Open);
                byte[] data = new byte[fs.Length];
                fs.Read(data, 0, data.Length);
                fs.Close();
                string dataStr = Asn1Util.BytesToString(data);
                retval = IsPemFormated(dataStr);
            }
            catch
            {
                retval = false;
            }
            return retval;
        }
        /// 
        /// Convert PEM formated string into  and set the Stream position to 0.
        /// 
        /// source string.
        /// output stream.
        public static Stream PemToStream(string pemStr)
        {
            byte[] bytes = PemToBytes(pemStr);
            MemoryStream retval = new MemoryStream(bytes);
            retval.Position = 0;
            return retval;
        }
        /// 
        /// Convert PEM formated string into byte array.
        /// 
        /// source string.
        /// output byte array.
        public static byte[] PemToBytes(string pemStr)
        {
            byte[] retval = null;
            string[] lines = pemStr.Split('\n');
            string base64Str = "";
            bool started = false, ended = false;
            string cline = "";
            for (int i = 0; i < lines.Length; i++)
            {
                cline = lines[i].ToUpper();
                if (cline == "") continue;
                if (cline.Length > PemStartStr.Length)
                {
                    if (!started && cline.Substring(0, PemStartStr.Length) == PemStartStr)
                    {
                        started = true;
                        continue;
                    }
                }
                if (cline.Length > PemEndStr.Length)
                {
                    if (cline.Substring(0, PemEndStr.Length) == PemEndStr)
                    {
                        ended = true;
                        break;
                    }
                }
                if (started)
                {
                    base64Str += lines[i];
                }
            }
            if (!(started && ended))
            {
                throw new Exception("'BEGIN'/'END' line is missing.");
            }
            base64Str = base64Str.Replace("\r", "");
            base64Str = base64Str.Replace("\n", "");
            base64Str = base64Str.Replace("\n", " ");
            retval = Convert.FromBase64String(base64Str);
            return retval;
        }
        /// 
        /// Convert byte array to PEM formated string.
        /// 
        /// 
        /// 
        public static string BytesToPem(byte[] data)
        {
            return BytesToPem(data, "");
        }
        /// 
        /// Retrieve PEM file heading.
        /// 
        /// source file name.
        /// heading string.
        public static string GetPemFileHeader(string fileName)
        {
            try
            {
                FileStream fs = new FileStream(fileName, FileMode.Open);
                byte[] data = new byte[fs.Length];
                fs.Read(data, 0, data.Length);
                fs.Close();
                string dataStr = Asn1Util.BytesToString(data);
                return GetPemHeader(dataStr);
            }
            catch
            {
                return "";
            }
        }
        /// 
        /// Retrieve PEM heading from a PEM formated string.
        /// 
        /// source string.
        /// heading string.
        public static string GetPemHeader(string pemStr)
        {
            string[] lines = pemStr.Split('\n');
            bool started = false;
            string cline = "";
            for (int i = 0; i < lines.Length; i++)
            {
                cline = lines[i].ToUpper().Replace("\r", "");
                if (cline == "") continue;
                if (cline.Length > PemStartStr.Length)
                {
                    if (!started && cline.Substring(0, PemStartStr.Length) == PemStartStr)
                    {
                        started = true;
                        string retstr = lines[i].Substring(PemStartStr.Length,
                                lines[i].Length -
                                PemStartStr.Length).Replace("-----", "");
                        return retstr.Replace("\r", "");
                    }
                }
                else
                {
                    continue;
                }
            }
            return "";
        }
        /// 
        /// Convert byte array to PEM formated string and set the heading as pemHeader.
        /// 
        /// source array.
        /// PEM heading.
        /// PEM formated string.
        public static string BytesToPem(byte[] data, string pemHeader)
        {
            if (pemHeader == null || pemHeader.Length < 1)
            {
                pemHeader = "ASN.1 Editor Generated PEM File";
            }
            string retval = "";
            if (pemHeader.Length > 0 && pemHeader[0] != ' ')
            {
                pemHeader = " " + pemHeader;
            }
            retval = Convert.ToBase64String(data);
            retval = FormatString(retval, 64, 0);
            retval = "-----BEGIN" + pemHeader + "-----\r\n" +
                     retval +
                     "\r\n-----END" + pemHeader + "-----\r\n";
            return retval;
        }
        /// 
        /// Calculate how many bits is enough to hold ivalue.
        /// 
        /// source value.
        /// bits number.
        public static int BitPrecision(ulong ivalue)
        {
            if (ivalue == 0) return 0;
            int l = 0, h = 8 * 4; // 4: sizeof(ulong)
            while (h - l > 1)
            {
                int t = (int)(l + h) / 2;
                if ((ivalue >> t) != 0)
                    l = t;
                else
                    h = t;
            }
            return h;
        }
        /// 
        /// Calculate how many bytes is enough to hold the value.
        /// 
        /// input value.
        /// bytes number.
        public static int BytePrecision(ulong value)
        {
            int i;
            for (i = 4; i > 0; --i) // 4: sizeof(ulong)
                if ((value >> (i - 1) * 8) != 0)
                    break;
            return i;
        }
        /// 
        /// ASN.1 DER length encoder.
        /// 
        /// result output stream.
        /// source length.
        /// result bytes.
        public static int DERLengthEncode(Stream xdata, ulong length)
        {
            int i = 0;
            if (length <= 0x7f)
            {
                xdata.WriteByte((byte)length);
                i++;
            }
            else
            {
                xdata.WriteByte((byte)(BytePrecision(length) | 0x80));
                i++;
                for (int j = BytePrecision((ulong)length); j > 0; --j)
                {
                    xdata.WriteByte((byte)(length >> (j - 1) * 8));
                    i++;
                }
            }
            return i;
        }
        /// 
        /// ASN.1 DER length decoder.
        /// 
        /// Source stream.
        /// Output parameter.
        /// Output length.
        public static long DerLengthDecode(Stream bt, ref bool isIndefiniteLength)
        {
            isIndefiniteLength = false;
            long length = 0;
            byte b;
            b = (byte)bt.ReadByte();
            if ((b & 0x80) == 0)
            {
                length = b;
            }
            else
            {
                long lengthBytes = b & 0x7f;
                if (lengthBytes == 0)
                {
                    isIndefiniteLength = true;
                    return -2; // Indefinite length.
                }
                length = 0;
                while (lengthBytes-- > 0)
                {
                    if ((length >> (8 * (4 - 1))) > 0) // 4: sizeof(long)
                    {
                        return -1; // Length overflow.
                    }
                    b = (byte)bt.ReadByte();
                    length = (length << 8) | b;
                }
                if (length <= 0x7f)
                {
                    return -1; // Indicated false node
                }
            }
            return length;
        }
        /// 
        /// Decode tag value to return tag name.
        /// 
        /// input tag.
        /// tag name.
        static public string GetTagName(byte tag)
        {
            string retval = "";
            if ((tag & Asn1TagClasses.CLASS_MASK) != 0)
            {
                switch (tag & Asn1TagClasses.CLASS_MASK)
                {
                    case Asn1TagClasses.CONTEXT_SPECIFIC:
                        retval += "CONTEXT SPECIFIC (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() + ")";
                        break;
                    case Asn1TagClasses.APPLICATION:
                        retval += "APPLICATION (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() + ")";
                        break;
                    case Asn1TagClasses.PRIVATE:
                        retval += "PRIVATE (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() + ")";
                        break;
                    case Asn1TagClasses.CONSTRUCTED:
                        retval += "CONSTRUCTED (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() + ")";
                        break;
                    case Asn1TagClasses.UNIVERSAL:
                        retval += "UNIVERSAL (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() + ")";
                        break;
                }
            }
            else
            {
                switch (tag & Asn1Tag.TAG_MASK)
                {
                    case Asn1Tag.BOOLEAN:
                        retval += "BOOLEAN";
                        break;
                    case Asn1Tag.INTEGER:
                        retval += "INTEGER";
                        break;
                    case Asn1Tag.BIT_STRING:
                        retval += "BIT STRING";
                        break;
                    case Asn1Tag.OCTET_STRING:
                        retval += "OCTET STRING";
                        break;
                    case Asn1Tag.TAG_NULL:
                        retval += "NULL";
                        break;
                    case Asn1Tag.OBJECT_IDENTIFIER:
                        retval += "OBJECT IDENTIFIER";
                        break;
                    case Asn1Tag.OBJECT_DESCRIPTOR:
                        retval += "OBJECT DESCRIPTOR";
                        break;
                    case Asn1Tag.RELATIVE_OID:
                        retval += "RELATIVE-OID";
                        break;
                    case Asn1Tag.EXTERNAL:
                        retval += "EXTERNAL";
                        break;
                    case Asn1Tag.REAL:
                        retval += "REAL";
                        break;
                    case Asn1Tag.ENUMERATED:
                        retval += "ENUMERATED";
                        break;
                    case Asn1Tag.UTF8_STRING:
                        retval += "UTF8 STRING";
                        break;
                    case (Asn1Tag.SEQUENCE):
                        retval += "SEQUENCE";
                        break;
                    case (Asn1Tag.SET):
                        retval += "SET";
                        break;
                    case Asn1Tag.NUMERIC_STRING:
                        retval += "NUMERIC STRING";
                        break;
                    case Asn1Tag.PRINTABLE_STRING:
                        retval += "PRINTABLE STRING";
                        break;
                    case Asn1Tag.T61_STRING:
                        retval += "T61 STRING";
                        break;
                    case Asn1Tag.VIDEOTEXT_STRING:
                        retval += "VIDEOTEXT STRING";
                        break;
                    case Asn1Tag.IA5_STRING:
                        retval += "IA5 STRING";
                        break;
                    case Asn1Tag.UTC_TIME:
                        retval += "UTC TIME";
                        break;
                    case Asn1Tag.GENERALIZED_TIME:
                        retval += "GENERALIZED TIME";
                        break;
                    case Asn1Tag.GRAPHIC_STRING:
                        retval += "GRAPHIC STRING";
                        break;
                    case Asn1Tag.VISIBLE_STRING:
                        retval += "VISIBLE STRING";
                        break;
                    case Asn1Tag.GENERAL_STRING:
                        retval += "GENERAL STRING";
                        break;
                    case Asn1Tag.UNIVERSAL_STRING:
                        retval += "UNIVERSAL STRING";
                        break;
                    case Asn1Tag.BMPSTRING:
                        retval += "BMP STRING";
                        break;
                    default:
                        retval += "UNKNOWN TAG";
                        break;
                };
            }
            return retval;
        }
#if UNITYIAP_DISABLED
        /// 
        /// Read registry information from local machine entrys.
        /// 
        /// 
        /// 
        /// 
        static public object ReadRegInfo(string path, string name)
        {
            object retval = null;
            Microsoft.Win32.RegistryKey regKey;
            regKey = Registry.LocalMachine.OpenSubKey(path, false);
            if (regKey!=null)
            {
                retval = regKey.GetValue(name);
            }
            return retval;
        }
        /// 
        /// Write information into local machine registry entry.
        /// 
        /// 
        /// 
        /// 
        static public void WriteRegInfo(string path, string name, object data)
        {
            Microsoft.Win32.RegistryKey regKey;
            regKey = Registry.LocalMachine.OpenSubKey(path, true);
            if (regKey == null)
            {
                regKey = Registry.LocalMachine.CreateSubKey(path);
            }
            if (regKey != null)
            {
                regKey.SetValue(name, data);
            }
        }
#endif
        /// 
        /// Constructor.
        /// 
        private Asn1Util()
        {
            //Private constructor.
        }
    }
}