Last active
May 12, 2017 08:08
-
-
Save Konctantin/d1fa4470cb7fb30595984f9a536f9e68 to your computer and use it in GitHub Desktop.
asn1 parser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| unit ASN1; | |
| interface | |
| uses System.SysUtils, System.Generics.Collections, RegularExpressions, Classes, Math; | |
| TYPE | |
| {$REGION 'TAsn1Class'} | |
| TAsn1Class = ( | |
| acUniversal = 0, // The type is native to ASN.1. | |
| acApplication = 64, // The type is only valid for one specific application | |
| acContextSpecific = 128, // Meaning of this type depends on the context (such as within a sequence, set or choice) | |
| acPrivate = 192 // Defined in private specifications | |
| ); | |
| {$ENDREGION 'TAsn1Class'} | |
| {$REGION 'TAsn1NodeType'} | |
| TAsn1NodeType = ( | |
| antPrimitive = 0, // ASN.1 type contains primitive value such as Utf8String, Integer etc. | |
| antConstructedStart = 1, // ASN.1 type contains another ASN.1 type. This could be Sequence, Set etc. | |
| antConstructedEnd = 2, // Virtual type of node indicating that ASN.1 constructed node has been parsed completely. | |
| antDocumentStart = 3, // Virtual type of node indicating that parser is at the beginning of ASN.1 structure. | |
| antDocumentEnd = 4 // Virtual type of node indicating that parsing of ASN.1 structure has been completed. | |
| ); | |
| {$ENDREGION 'TAsn1NodeType'} | |
| {$REGION 'TAsn1Type'} | |
| TAsn1Type = ( | |
| atEoc = 0, | |
| atBoolean = 1, | |
| atInteger = 2, | |
| atBitString = 3, | |
| atOctetString = 4, | |
| atNull = 5, | |
| atObjectIdentifier = 6, | |
| atObjectDescriptor = 7, | |
| atExternal = 8, | |
| atReal = 9, | |
| atEnumerated = 10, | |
| atEmbeddedPdv = 11, | |
| atUtf8String = 12, | |
| atRelativeOid = 13, | |
| atSequence = 16, | |
| atSet = 17, | |
| atNumericString = 18, | |
| atPrintableString = 19, | |
| atT61String = 20, | |
| atVideotexString = 21, | |
| atIa5String = 22, | |
| atUtcTime = 23, | |
| atGeneralizedTime = 24, | |
| atGraphicString = 25, | |
| atVisibleString = 26, | |
| atGeneralString = 27, | |
| atUniversalString = 28, | |
| atCharacterString = 29, | |
| atBmpString = 30, | |
| atLongForm = 31 | |
| ); | |
| {$ENDREGION 'TAsn1Type'} | |
| /// <summary> | |
| /// Class holds information about parsed ASN.1 node. | |
| /// </summary> | |
| TAsn1Node = class | |
| private | |
| m_numericStringRegex, m_printableStringRegex : TRegex; | |
| m_childNodes : TList<TAsn1Node>; | |
| m_raw : TBytes; | |
| function IsNodeByOid(oid: string): boolean; | |
| public | |
| Asn1Class : TAsn1Class; | |
| Constructed : boolean; | |
| Tag : TAsn1Type; | |
| /// <summary> | |
| /// Length of ASN.1 node data. Identifier and Length octets of TLV are excluded. It is length of the Value octets. | |
| /// </summary> | |
| DataLength : integer; | |
| /// <summary> | |
| /// Start position of ASN.1 node in the inner stream. | |
| /// </summary> | |
| StartPosition : Int64; | |
| /// <summary> | |
| /// End position of ASN.1 node in the inner stream. | |
| /// </summary> | |
| EndPosition : Int64; | |
| /// <summary> | |
| /// Flag indicating indefinite length of ASN.1 node. | |
| /// </summary> | |
| HasIndefiniteLength : boolean; | |
| /// <summary> | |
| /// Position to the inner stream where Value octets begin. | |
| /// </summary> | |
| DataOffsetToStream : Int64; | |
| /// <summary> | |
| /// Type of the ASN.1 node | |
| /// </summary> | |
| NodeType : TAsn1NodeType; | |
| constructor Create(); overload; | |
| /// <summary> | |
| /// Value of ASN.1 node in bytes. This property is not set automatically by Reader. | |
| /// User should set it by calling specific method on Reader to read the value. | |
| /// </summary> | |
| property RawValue: TBytes read m_raw; | |
| /// <summary> | |
| /// Gets list of childs of this ASN.1 node. | |
| /// </summary> | |
| property ChildNodes : TList<TAsn1Node> read m_childNodes; | |
| /// <summary> | |
| /// Flag indicating that this node is not <see cref="Asn1NodeType.Primitive"/> | |
| /// </summary> | |
| function IsConstructed : boolean; | |
| /// <summary> | |
| /// Return the value of the given node as string. | |
| /// </summary> | |
| function AsOid : string; | |
| /// <summary> | |
| /// Read the value of the given node as string. | |
| /// </summary> | |
| function ReadContentAsString : string; | |
| /// <summary> | |
| /// Read the value of the given node as numeric string. | |
| /// </summary> | |
| function ReadContentAsNumericString : string; | |
| /// <summary> | |
| /// Read the value of the given node as IA5string. | |
| /// </summary> | |
| function ReadContentAsIA5String : string; | |
| /// <summary> | |
| /// Read the value of the given node as Printable string. | |
| /// </summary> | |
| function ReadContentAsPrintableString : string; | |
| /// <summary> | |
| /// Read the value of the given node as T61string. | |
| /// </summary> | |
| function ReadContentAsT61String : string; | |
| /// <summary> | |
| /// Read the value of the given node as Graphic string. | |
| /// </summary> | |
| function ReadContentAsGraphicString : string; | |
| /// <summary> | |
| /// Read the value of the given node as General string. | |
| /// </summary> | |
| function ReadContentAsGeneralString : string; | |
| /// <summary> | |
| /// Read the value of the given node as BMPstring. | |
| /// </summary> | |
| function ReadContentAsBmpString : string; | |
| /// <summary> | |
| /// Read the value of the given node as Universalstring. | |
| /// </summary> | |
| function ReadContentAsUniversalString : string; | |
| /// <summary> | |
| /// Read the value of the given node as Boolean. | |
| /// </summary> | |
| function ReadContentAsBoolean : boolean; | |
| /// <summary> | |
| /// Read the value of the given node as BitString. | |
| /// </summary> | |
| function ReadContentAsBitString : TBytes; | |
| /// <summary> | |
| /// Return the value of the given node as Real (double). | |
| /// </summary> | |
| function AsReal : double; | |
| /// <summary> | |
| /// Return the value of the given node as string. | |
| /// </summary> | |
| function AsString : string; | |
| /// <summary> | |
| /// Return the value of the given node as TDateTime. | |
| /// </summary> | |
| function AsDateTime : TDateTime; | |
| function GetElementsByOid(oid : string) : TList<TAsn1Node>; | |
| end; | |
| /// <summary> | |
| /// Implementation works on top o a stream. The stream should be seekable. | |
| /// </summary> | |
| TAsnReader = class | |
| private | |
| m_nodeStack : TStack<TAsn1Node>; | |
| m_current : TAsn1Node; | |
| m_stream : TStream; | |
| /// <summary> | |
| /// Parse ASN.1 node and sets the result to <see cref="InternalNode"/> class. | |
| /// </summary> | |
| procedure InternalReadElement; | |
| function DetermineLengthOfIndefiniteLengthNode : integer; | |
| function ReadLength : integer; | |
| procedure ReadIdentifierOctet(identifierOctet : integer; var constr: boolean; var cls : TAsn1Class; var tag : TAsn1Type); | |
| function getDepth:integer; | |
| public | |
| constructor Create(aStream : TStream); | |
| destructor Destroy; override; | |
| /// <summary> | |
| /// Gets current node. Usable when reading of content of a ASN.1 tag is not so important at the moment. | |
| /// </summary> | |
| property CurrentNode : TAsn1Node read m_current; | |
| /// <summary> | |
| /// Inner stream on top of which this class operates. | |
| /// </summary> | |
| property Stream : TStream read m_stream; | |
| /// <summary> | |
| /// Value indicating the actual depth of parser in ASN.1 structure. | |
| /// </summary> | |
| property Depth : integer read getDepth; | |
| /// <summary> | |
| /// Parse next node in ASN.1 structure. | |
| /// </summary> | |
| function Read : TAsn1Node; | |
| /// <summary> | |
| /// Parse whole ASN.1 structure. <see cref="TInternalNode"/> class holds all the mappings. Values of the tags are not read. | |
| /// </summary> | |
| function ReadToEnd : TAsn1Node; | |
| /// <summary> | |
| /// Get raw ASN.1 node data (whole TLV => Identifier, Length and Value octets). | |
| /// </summary> | |
| /// <param name="node">Node that shall be read.</param> | |
| function ExtractAsn1NodeAsRawData(node : TAsn1Node) : TBytes; | |
| procedure SaveContentToFile(fileName : TFileName); | |
| procedure SaveContentToStream(stream : TStream); | |
| end; | |
| THelpers = class | |
| public | |
| class function ParseEncodedOid(oid : TBytes):string; | |
| end; | |
| TSreamHelper = class helper for TStream | |
| public | |
| function ReadByte : byte; | |
| function Eof : boolean; | |
| end; | |
| implementation | |
| { TAsn1Node } | |
| {$REGION 'TAsn1Node'} | |
| function TAsn1Node.AsDateTime: TDateTime; | |
| begin | |
| result := 0; | |
| end; | |
| function TAsn1Node.AsReal: double; | |
| begin | |
| result := 0; | |
| end; | |
| function TAsn1Node.AsString: string; | |
| begin | |
| case Tag of | |
| atEoc: ; | |
| atBoolean: ; | |
| atInteger: ; | |
| atBitString: ; | |
| atOctetString: ; | |
| atNull: ; | |
| atObjectIdentifier: ; | |
| atObjectDescriptor: ; | |
| atExternal: ; | |
| atReal: ; | |
| atEnumerated: ; | |
| atEmbeddedPdv: ; | |
| atUtf8String: result := TEncoding.UTF8.GetString(m_raw, 0, length(m_raw)); | |
| atRelativeOid: ; | |
| atSequence: ; | |
| atSet: ; | |
| atNumericString: ; | |
| atPrintableString: ; | |
| atT61String: ; | |
| atVideotexString: ; | |
| atIa5String: ; | |
| atUtcTime: ; | |
| atGeneralizedTime: ; | |
| atGraphicString: ; | |
| atVisibleString: ; | |
| atGeneralString: ; | |
| atUniversalString: ; | |
| atCharacterString: ; | |
| atBmpString: ; | |
| atLongForm: ; | |
| end; | |
| end; | |
| constructor TAsn1Node.Create; | |
| begin | |
| m_childNodes := TList<TAsn1Node>.Create; | |
| m_numericStringRegex := TRegex.Create('^[\d ]*$', []); | |
| m_printableStringRegex := TRegex.Create('^[a-zA-Z0-9 \''\(\)\+\,\-\.\/\:\=\?]*$', []); | |
| end; | |
| function TAsn1Node.IsNodeByOid(oid:string):boolean; | |
| begin | |
| result := (Tag = TAsn1Type.atObjectIdentifier) | |
| and (AsOid = oid) | |
| end; | |
| function TAsn1Node.GetElementsByOid(oid: string): TList<TAsn1Node>; | |
| var childNode : TAsn1Node; | |
| procedure GetChild(node:TAsn1Node); | |
| var subNode : TAsn1Node; | |
| begin | |
| if node.IsNodeByOid(oid) then begin | |
| result.Add(node); | |
| end; | |
| for subNode in node.ChildNodes do begin | |
| GetChild(subNode); | |
| end; | |
| end; | |
| begin | |
| result := TList<TAsn1Node>.Create; | |
| for childNode in ChildNodes do begin | |
| GetChild(childNode); | |
| end; | |
| end; | |
| function TAsn1Node.IsConstructed: boolean; | |
| begin | |
| result := NodeType <> TAsn1NodeType.antPrimitive; | |
| end; | |
| function TAsn1Node.ReadContentAsBitString: TBytes; | |
| var padBitesPart : byte; i : integer; | |
| begin | |
| if m_raw = nil then | |
| raise Exception.Create('m_raw'); | |
| if Tag <> TAsn1Type.atBitString then | |
| raise Exception.Create('Can only read value of BITSTRING node.'); | |
| padBitesPart := m_raw[0]; | |
| if padBitesPart > 7 then | |
| raise Exception.Create('Value in initial octet shall be an unsigned integer in the range zero to seven'); | |
| SetLength(result, DataLength - 1); | |
| for I := 1 to length(result)-1 do | |
| result[i] := m_raw[i]; | |
| end; | |
| function TAsn1Node.ReadContentAsBmpString: string; | |
| begin | |
| if m_raw = nil then | |
| raise Exception.Create('m_raw'); | |
| if Tag <> TAsn1Type.atBmpString then | |
| raise Exception.Create('Can only read value of BmpString node.'); | |
| result := TEncoding.BigEndianUnicode.GetString(m_raw, 0, length(m_raw)); | |
| end; | |
| function TAsn1Node.ReadContentAsBoolean: boolean; | |
| begin | |
| if m_raw = nil then | |
| raise Exception.Create('m_raw'); | |
| if Tag <> TAsn1Type.atBoolean then | |
| raise Exception.Create('Can only read value of BOOLEAN node.'); | |
| if length(m_raw) <> 1 then | |
| raise Exception.Create('Boolean ASN.1 type should have single octet value.'); | |
| // BER Encoding rules | |
| // If the boolean value is FALSE the octet shall be zero. | |
| result := (m_raw[0] <> 0); | |
| end; | |
| function TAsn1Node.ReadContentAsGeneralString: string; | |
| begin | |
| if Tag <> TAsn1Type.atGeneralString then | |
| raise Exception.Create('Can only read value of GeneralString node.'); | |
| result := ReadContentAsString(); | |
| end; | |
| function TAsn1Node.ReadContentAsGraphicString: string; | |
| begin | |
| if Tag <> TAsn1Type.atGraphicString then | |
| raise Exception.Create('Can only read value of GraphicString node.'); | |
| result := ReadContentAsString(); | |
| end; | |
| function TAsn1Node.ReadContentAsIA5String: string; | |
| begin | |
| if Tag <> TAsn1Type.atIa5String then | |
| raise Exception.Create('Can only read value of Ia5String node.'); | |
| result := ReadContentAsString(); | |
| end; | |
| function TAsn1Node.ReadContentAsNumericString: string; | |
| begin | |
| if Tag <> TAsn1Type.atNumericString then | |
| raise Exception.Create('Can only read value of NumericString node.'); | |
| result := ReadContentAsString(); | |
| if not m_numericStringRegex.IsMatch(result) then | |
| raise Exception.Create('Numeric string can contain only these characters: 0 1 2 3 4 5 6 7 8 9 0 SPACE.'); | |
| end; | |
| function TAsn1Node.AsOid: string; | |
| begin | |
| if m_raw = nil then | |
| raise Exception.Create('m_raw'); | |
| if Tag <> TAsn1Type.atObjectIdentifier then | |
| raise Exception.Create('Can only read value of OBJECT IDENTIFIER node.'); | |
| result := THelpers.ParseEncodedOid(m_raw); | |
| end; | |
| function TAsn1Node.ReadContentAsPrintableString: string; | |
| begin | |
| if Tag <> TAsn1Type.atPrintableString then | |
| raise Exception.Create('Can only read value of PrintableString node.'); | |
| result := ReadContentAsString(); | |
| if not m_printableStringRegex.IsMatch(result) then | |
| raise Exception.Create('Found invalid character in input value for Printable string. Please refer to ITU-T X.680 specification.'); | |
| end; | |
| function TAsn1Node.ReadContentAsString: string; | |
| begin | |
| if m_raw = nil then | |
| raise Exception.Create('m_raw'); | |
| result := TEncoding.UTF8.GetString(m_raw, 0, length(m_raw)); | |
| end; | |
| function TAsn1Node.ReadContentAsT61String: string; | |
| begin | |
| if Tag <> TAsn1Type.atT61String then | |
| raise Exception.Create('Can only read value of T61String node.'); | |
| result := ReadContentAsString(); | |
| end; | |
| function TAsn1Node.ReadContentAsUniversalString: string; | |
| var enc : TEncoding; | |
| begin | |
| if m_raw = nil then | |
| raise Exception.Create('m_raw'); | |
| if Tag <> TAsn1Type.atUniversalString then | |
| raise Exception.Create('Can only read value of UniversalString node.'); | |
| enc := TEncoding.GetEncoding('utf-32BE'); | |
| if enc = nil then | |
| raise Exception.Create('UTF-32 encoding is not supported on this platform.'); | |
| result := enc.GetString(m_raw, 0, length(m_raw)); | |
| end; | |
| {$ENDREGION 'TAsn1Node'} | |
| { TAsnReader } | |
| {$REGION 'TAsnReader'} | |
| constructor TAsnReader.Create(aStream: TStream); | |
| begin | |
| self.m_stream := aStream; | |
| m_nodeStack := TStack<TAsn1Node>.Create; | |
| m_current := TAsn1Node.Create; | |
| m_current.NodeType := TAsn1NodeType.antDocumentStart; | |
| end; | |
| destructor TAsnReader.Destroy; | |
| begin | |
| if assigned(m_nodeStack) then begin | |
| m_nodeStack.Clear; | |
| FreeAndNil(m_nodeStack); | |
| end; | |
| if Assigned(m_current) then begin | |
| FreeAndNil(m_current); | |
| end; | |
| end; | |
| function TAsnReader.DetermineLengthOfIndefiniteLengthNode: integer; | |
| var streamPosition : int64; | |
| firstByteofEocfound : boolean; | |
| oneByte : byte; | |
| begin | |
| result := 0; | |
| streamPosition := m_stream.Position; | |
| firstByteofEocfound := false; | |
| while not m_stream.Eof do begin | |
| oneByte := m_stream.ReadByte(); | |
| // found possible EOC (0x00 00) | |
| if oneByte = 0 then begin | |
| if firstByteofEocfound then begin | |
| m_stream.Seek(streamPosition, soBeginning); | |
| exit; | |
| end; | |
| firstByteofEocfound := true; | |
| end else begin | |
| firstByteofEocfound := false; | |
| inc(result); | |
| end; | |
| end; | |
| end; | |
| function TAsnReader.ExtractAsn1NodeAsRawData(node: TAsn1Node): TBytes; | |
| begin | |
| if node = nil then | |
| raise Exception.Create('node'); | |
| SetLength(result, node.EndPosition - node.StartPosition); | |
| m_stream.Seek(node.StartPosition, soBeginning); | |
| m_stream.Read(result, 0, length(result)); | |
| end; | |
| function TAsnReader.getDepth: integer; | |
| begin | |
| result := m_nodeStack.Count; | |
| end; | |
| procedure TAsnReader.ReadIdentifierOctet(identifierOctet : integer; var constr: boolean; var cls : TAsn1Class; var tag : TAsn1Type); | |
| var moreBytes : boolean; | |
| begin | |
| // | | ( 63 -------------------------------> | | |
| // | | 32 | ( 31 -----------------------> | | |
| // ----------------------------------------------------------------- | |
| // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | | |
| // | class | P/C | Tag Number | | |
| // ----------------------------------------------------------------- | |
| // Tag number allows values from 0 to 31. 31 states long-form | |
| moreBytes := (byte(identifierOctet) and ord(TAsn1Type.atLongForm)) = ord(TAsn1Type.atLongForm); | |
| while moreBytes do begin | |
| m_stream.ReadByte(); | |
| raise Exception.Create('NotSupportedException'); | |
| // TODO parse multi byte tag | |
| //moreBytes = (b & 0x80) != 0; | |
| end; | |
| constr := (byte(identifierOctet) and $20) <> 0; // bit 6 contains P/C (Primitive/Constructed) flag. 6bit => value 0x20 (32 decimal) | |
| cls := TAsn1Class(byte(identifierOctet) and -64); | |
| tag := TAsn1Type(byte(identifierOctet) and 31); | |
| end; | |
| procedure TAsnReader.InternalReadElement; | |
| var nodeStartPosition : int64; | |
| len : integer; hasIndefiniteLengthForm : boolean; | |
| val : integer; | |
| constr: boolean; | |
| cls : TAsn1Class; | |
| tag : TAsn1Type; | |
| begin | |
| if (m_nodeStack.Count > 0) and (m_stream.Position >= m_nodeStack.Peek.EndPosition) then begin | |
| m_current := TAsn1Node.Create(); | |
| m_current.DataLength := 0; | |
| m_current.EndPosition := m_stream.Position; | |
| m_current.DataOffsetToStream := m_stream.Position; | |
| m_current.NodeType := TAsn1NodeType.antConstructedEnd; | |
| m_current.Asn1Class := m_nodeStack.Peek.Asn1Class; | |
| m_current.Constructed := m_nodeStack.Peek.Constructed; | |
| m_current.Tag := m_nodeStack.Peek.Tag; | |
| exit; | |
| end; | |
| nodeStartPosition := m_stream.Position; | |
| if m_stream.Eof then begin | |
| m_current := TAsn1Node.Create; | |
| m_current.NodeType := TAsn1NodeType.antDocumentEnd; | |
| exit; | |
| end; | |
| val := m_stream.ReadByte(); | |
| ReadIdentifierOctet(val, constr, cls, tag); | |
| if (m_current.NodeType = TAsn1NodeType.antDocumentEnd) then | |
| exit; | |
| len := ReadLength(); | |
| if len >= m_stream.Size then | |
| raise Exception.Create('Length of ASN.1 node exceeds length of current stream.'); | |
| hasIndefiniteLengthForm := false; | |
| if len = -1 then begin | |
| hasIndefiniteLengthForm := true; | |
| len := DetermineLengthOfIndefiniteLengthNode(); | |
| end; | |
| m_current := TAsn1Node.Create; | |
| m_current.Asn1Class := cls; | |
| m_current.Constructed := constr; | |
| m_current.Tag := tag; | |
| m_current.StartPosition := nodeStartPosition; | |
| m_current.HasIndefiniteLength := hasIndefiniteLengthForm; | |
| m_current.DataLength := len; | |
| m_current.DataOffsetToStream := m_stream.Position; | |
| m_current.EndPosition := m_stream.Position + ifthen(hasIndefiniteLengthForm, len+2, len); | |
| if constr then | |
| m_current.NodeType := TAsn1NodeType.antConstructedStart | |
| else | |
| m_current.NodeType := TAsn1NodeType.antPrimitive; | |
| if m_current.NodeType = TAsn1NodeType.antPrimitive then begin | |
| SetLength(m_current.m_raw, m_current.DataLength); | |
| m_stream.Seek(m_current.DataOffsetToStream, soBeginning); | |
| m_stream.Read(m_current.m_raw, 0, m_current.DataLength); | |
| end; | |
| end; | |
| function TAsnReader.ReadLength: integer; | |
| var lengthStart : byte; | |
| numBytesLength : integer; | |
| isMultiByteLength : boolean; | |
| begin | |
| // Length Octets | |
| // read one byte from stream. This should be the Length. Length can be encoded in short or long form. | |
| lengthStart := m_stream.ReadByte(); | |
| // 0xff => the value 11111111 (binary) shall not be used | |
| if lengthStart = $FF then | |
| raise Exception.Create('Invalid length format. The value 11111111 (binary) shall not be used.'); | |
| result := 0; | |
| // indefinite form of length | |
| if lengthStart = $80 then | |
| exit(-1); // will be corrected later | |
| // check if length is encoded in short form or long form | |
| isMultiByteLength := (lengthStart and $80) <> 0; | |
| // short form length | |
| if not isMultiByteLength then | |
| result := lengthStart | |
| else begin // long form length | |
| // All bits of the subsequent octets form the encoding of an unsigned binary integer equal to the number of octets in the contents octets. | |
| // get number of octets | |
| numBytesLength := lengthStart and $7F; | |
| // in c# sizeof(int) is 4 bytes. We can not interpret more. We could use long, but reading data from stream expects int, not long | |
| // TODO: maybe support long instead of int in Length | |
| if numBytesLength > 4 then | |
| raise Exception.Create('Invalid length value (too long)'); | |
| // read the value of length in long form | |
| while numBytesLength > 0 do begin | |
| result := (result shl 8) or byte(m_stream.ReadByte); | |
| inc(numBytesLength, -1); | |
| end; | |
| end; | |
| end; | |
| function TAsnReader.Read: TAsn1Node; | |
| begin | |
| case m_current.NodeType of | |
| TAsn1NodeType.antDocumentStart : begin | |
| InternalReadElement(); | |
| end; | |
| TAsn1NodeType.antPrimitive: begin | |
| m_stream.Seek(m_current.EndPosition, soBeginning); | |
| InternalReadElement(); | |
| end; | |
| TAsn1NodeType.antConstructedStart: begin | |
| m_nodeStack.Push(m_current); | |
| InternalReadElement(); | |
| end; | |
| TAsn1NodeType.antConstructedEnd: begin | |
| m_nodeStack.Pop(); | |
| InternalReadElement(); | |
| end; | |
| end; | |
| result := m_current; | |
| end; | |
| function TAsnReader.ReadToEnd: TAsn1Node; | |
| var stack : TStack<TAsn1Node>; | |
| current, next : TAsn1Node; | |
| begin | |
| stack := TStack<TAsn1Node>.Create; | |
| stack.Push(m_current); | |
| result := m_current; | |
| current := m_current; | |
| next := m_current; | |
| while next.NodeType <> TAsn1NodeType.antDocumentEnd do begin | |
| next := self.Read(); | |
| // only construction node or primitive node will be placed in the map | |
| if (next.NodeType = TAsn1NodeType.antConstructedStart) or (next.NodeType = TAsn1NodeType.antPrimitive) then | |
| current.ChildNodes.Add(next); | |
| // if we hit construction node, there will probably be child nodes under it. | |
| // Push current head node in stack and set new current head | |
| if next.NodeType = TAsn1NodeType.antConstructedStart then begin | |
| m_nodeStack.Push(current); | |
| current := next; | |
| end; | |
| // if we hit construction end node then return to previous head node | |
| // as we completed mapping of inner structure of construction node | |
| if next.NodeType = TAsn1NodeType.antConstructedEnd then | |
| current := m_nodeStack.Pop(); | |
| end; | |
| // after all the mapping there should be only document start node in stack | |
| if stack.Count <> 1 then | |
| raise Exception.Create('There should be DocumentStart node in stack. Something is not right.'); | |
| if stack.Peek.NodeType <> TAsn1NodeType.antDocumentStart then | |
| raise Exception.Create('There should be DocumentStart node in stack. Something is not right.'); | |
| stack.Pop(); | |
| FreeAndNil(stack); | |
| end; | |
| procedure TAsnReader.SaveContentToFile(fileName: TFileName); | |
| begin | |
| // | |
| end; | |
| procedure TAsnReader.SaveContentToStream(stream: TStream); | |
| begin | |
| end; | |
| {$ENDREGION 'TAsnReader'} | |
| { THelpert } | |
| class function THelpers.ParseEncodedOid(oid: TBytes): string; | |
| var list : TList<string>; current, I : integer; | |
| begin | |
| list := TList<string>.Create; | |
| // first byte | |
| // The numerical value of the first subidentifier is derived from the values of the first two object identifier | |
| // components in the object identifier value being encoded, using the formula: (X*40) + Y | |
| if length(oid) > 0 then begin | |
| list.Add( IntToStr( trunc(oid[0] / 40) ) ); | |
| list.Add( IntToStr( oid[0] mod 40 ) ); | |
| end; | |
| // Each subidentifier is represented as a series of (one or more) octets. Bit 8 of each octet indicates whether it is the last in | |
| // the series: bit 8 of the last octet is zero; bit 8 of each preceding octet is one. Bits 7 to 1 of the octets in the series | |
| // collectively encode the subidentifier. Conceptually, these groups of bits are concatenated to form an unsigned binary | |
| // number whose most significant bit is bit 7 of the first octet and whose least significant bit is bit 1 of the last octet. The | |
| // subidentifier shall be encoded in the fewest possible octets, that is, the leading octet of the subidentifier shall not have | |
| // the value 80 HEX. | |
| current := 0; | |
| for I := 1 to length(oid)-1 do begin | |
| current := (current shl 7) or oid[i] and $7F; | |
| //check if last byte | |
| if ((oid[i] and $80) = 0) then begin | |
| list.Add(IntToStr(current)); | |
| current := 0; | |
| end; | |
| end; | |
| result := string.Join('.', list.ToArray); | |
| list.Clear; | |
| FreeAndNil(list); | |
| end; | |
| { TSreamHelper } | |
| function TSreamHelper.Eof: boolean; | |
| begin | |
| result := not (self.Position < self.size); | |
| end; | |
| function TSreamHelper.ReadByte: byte; | |
| begin | |
| self.Read(result, 1); | |
| end; | |
| end. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment