Asn1PERser
Parse ASN.1 schemas into Python code and encode/decode them using PER encoder
Install / Use
/learn @erupikus/Asn1PERserREADME
asn1PERser
This library can parse ASN.1 text schemas into Python code. ASN.1 PER (Aligned) decoder/encoder is included to use with parsed schemas.
Supported ASN.1 types and their constraints are:
| ASN.1 Type | Single value | Value range | Value size | can be extended (...) | used Python contraint class | |:------------:|:------------:|:-----------:|:----------:|:---------------------:|:---------------------------:| | INTEGER | X | X | | X | ValueRange | | BOOLEAN | | | | | | | ENUMERATED | | | | X | ExtensionMarker | | BIT STRING | X | | X | X | ValueSize | | OCTET STRING | X | | X | X | ValueSize | | CHOICE | | | | X | ExtensionMarker | | SEQUENCE | | | | X | ExtensionMarker | | SEQUENCE OF | X | | X | X | SequenceOfValueSize |
Table of contents:
Examples
Following ASN.1 schema:
asn_schema = '''\
SimpleProtocol DEFINITIONS AUTOMATIC TAGS ::=
BEGIN
SimpleMessage ::= CHOICE {
start Start,
stop Stop,
alive Alive,
data Data,
...
}
Start ::= SEQUENCE {
sequenceNumber SequenceNumber,
timestamp UTC-Timestamp,
srcPort Port,
dstPort Port
}
Stop ::= SEQUENCE {
sequenceNumber SequenceNumber,
timestamp UTC-Timestamp,
srcPort Port,
dstPort Port
}
Alive ::= SEQUENCE {
timestamp UTC-Timestamp,
...
}
Data ::= SEQUENCE {
sequenceNumber SequenceNumber,
swRelease ENUMERATED {rel1, rel2, rel3, ...},
macroId BIT STRING (SIZE(20)) OPTIONAL,
payload Payload
}
Port ::= INTEGER (10000..65535)
SequenceNumber ::= INTEGER (0..65535)
UTC-Timestamp ::= SEQUENCE {
seconds INTEGER (0..4294967295),
useconds INTEGER (0..4294967295)
}
Payload ::= SEQUENCE (SIZE(1..5)) OF Message
Message ::= OCTET STRING (SIZE(1..4))
END
'''
can be parsed into Python code like this:
from asn1PERser import parse_asn1_schema
parse_asn1_schema(asn1_schema=asn_schema, output_folder=r'C:/my_code/')
Above code will create file 'SimpleProtocol.py' in folder (which must exist) 'C:/my_code/':
from pyasn1.type.namedtype import NamedType, NamedTypes, OptionalNamedType, DefaultedNamedType
from pyasn1.type.namedval import NamedValues
from asn1PERser.classes.data.builtin import *
from asn1PERser.classes.types.type import AdditiveNamedTypes
from asn1PERser.classes.types.constraint import MIN, MAX, NoConstraint, ExtensionMarker, SequenceOfValueSize, \
ValueRange, SingleValue, ValueSize, ConstraintOr, ConstraintAnd
class Port(IntegerType):
subtypeSpec = ValueRange(10000, 65535)
class SequenceNumber(IntegerType):
subtypeSpec = ValueRange(0, 65535)
class Message(OctetStringType):
subtypeSpec = ValueSize(1, 4)
class UTC_Timestamp(SequenceType):
class seconds(IntegerType):
subtypeSpec = ValueRange(0, 4294967295)
class useconds(IntegerType):
subtypeSpec = ValueRange(0, 4294967295)
rootComponent = AdditiveNamedTypes(
NamedType('seconds', seconds()),
NamedType('useconds', useconds()),
)
componentType = rootComponent
class Start(SequenceType):
rootComponent = AdditiveNamedTypes(
NamedType('sequenceNumber', SequenceNumber()),
NamedType('timestamp', UTC_Timestamp()),
NamedType('srcPort', Port()),
NamedType('dstPort', Port()),
)
componentType = rootComponent
class Stop(SequenceType):
rootComponent = AdditiveNamedTypes(
NamedType('sequenceNumber', SequenceNumber()),
NamedType('timestamp', UTC_Timestamp()),
NamedType('srcPort', Port()),
NamedType('dstPort', Port()),
)
componentType = rootComponent
class Alive(SequenceType):
subtypeSpec = ExtensionMarker(True)
rootComponent = AdditiveNamedTypes(
NamedType('timestamp', UTC_Timestamp()),
)
componentType = rootComponent
class Payload(SequenceOfType):
subtypeSpec = SequenceOfValueSize(1, 5)
componentType = Message()
class Data(SequenceType):
class swRelease(EnumeratedType):
subtypeSpec = ExtensionMarker(True)
enumerationRoot = NamedValues(
('rel1', 0),
('rel2', 1),
('rel3', 2),
)
namedValues = enumerationRoot
class macroId(BitStringType):
subtypeSpec = ValueSize(20, 20)
rootComponent = AdditiveNamedTypes(
NamedType('sequenceNumber', SequenceNumber()),
NamedType('swRelease', swRelease()),
OptionalNamedType('macroId', macroId()),
NamedType('payload', Payload()),
)
componentType = rootComponent
class SimpleMessage(ChoiceType):
subtypeSpec = ExtensionMarker(True)
rootComponent = AdditiveNamedTypes(
NamedType('start', Start()),
NamedType('stop', Stop()),
NamedType('alive', Alive()),
NamedType('data', Data()),
)
componentType = rootComponent
When schema is parsed it can be used - message can be created, encoded and decoded, using PER encoder/decoder, into bytes or Python structure:
from asn1PERser import encode, decode
from SimpleProtocol import *
'''
simple_message SimpleMessage ::= alive : {
timestamp {
seconds 1557528149,
useconds 12345
}
}
'''
utc_timestamp = UTC_Timestamp()
utc_timestamp['seconds'] = UTC_Timestamp.seconds(1557528149)
utc_timestamp['useconds'] = UTC_Timestamp.useconds(12345)
msg_alive = Alive()
msg_alive['timestamp'] = utc_timestamp
simple_message = SimpleMessage()
simple_message['alive'] = msg_alive
per_bytes = encode(asn1Spec=simple_message)
print('encoded alive bytes as hex string:')
print(per_bytes.hex()) # py2: print(binascii.hexlify(per_bytes))
print('\n')
decoded = decode(per_stream=per_bytes, asn1Spec=SimpleMessage())
print('decoded alive message structure as string...:')
print(decoded)
print('...can be accessed like dictionary:')
print(decoded['alive']['timestamp']['seconds'])
print('\n')
print('...can be transformed into dictionary:')
print(decoded.to_dict())
above will output:
encoded alive bytes as hex string:
4c5cd5fe55403039
decoded alive message structure as string...:
SimpleMessage:
alive=Alive:
timestamp=UTC_Timestamp:
seconds=1557528149
useconds=12345
...can be accessed like dictionary:
1557528149
...can be transformed into dictionary:
{'alive': OrderedDict([('timestamp', OrderedDict([('seconds', 1557528149), ('useconds', 12345)]))])}
Next message:
'''
simple_message SimpleMessage ::= start : {
sequenceNumber 10,
timestamp {
seconds 1557528149,
useconds 12345
},
srcPort 65533,
dstPort 10000
}
'''
utc_timestamp = UTC_Timestamp()
utc_timestamp['seconds'] = UTC_Timestamp.seconds(1557528149)
utc_timestamp['useconds'] = UTC_Timestamp.useconds(12345)
msg_start = Start()
msg_start['sequenceNumber'] = SequenceNumber(10)
msg_start['timestamp'] = utc_timestamp
msg_start['srcPort'] = Port(65533)
msg_start['dstPort'] = Port(10000)
simple_message = SimpleMessage()
simple_message['start'] = msg_start
per_bytes = encode(asn1Spec=simple_message)
print('encoded start bytes as hex string:')
print(per_bytes.hex()) # py2: print(binascii.hexlify(per_bytes))
print('\n')
decoded = decode(per_stream=per_bytes, asn1Spec=SimpleMessage())
print('start message structure as string...:')
print(decoded)
print('...can be accessed like dictionary:')
print(decoded['start']['srcPort'])
print('\n')
print('...can be transformed into dictionary:')
print(decoded.to_dict())
above will output:
encoded start bytes as hex string:
00000ac05cd5fe55403039d8ed0000
start message structure as string...:
SimpleMessage:
start=Start:
sequenceNumber=10
timestamp=UTC_Timestamp:
seconds=1557528149
useconds=12345
srcPort=65533
dstPort=10000
...can be accessed like dictionary:
65533
...can be transformed into dictionary:
{'start': OrderedDict([('sequenceNumber', 10), ('timestamp', OrderedDict([('seconds', 1557528149), ('useconds', 12345)])), ('srcPort', 65533), ('dstPort', 10000)])}
Next message:
'''
simple_message SimpleMessage ::= data : {
sequenceNumber 55555,
swRelease rel2,
macroId '11110000111100001111'B,
payload {
'DEAD'H,
'BEEF'H,
'FEED'H,
'AA'H,
'BBBBBBBB'H
}
}
'''
data_payload = Payload()
data_payload.extend([Message(hexValue='DEAD')])
data_payload.extend([Message(hexValue='BEEF')])
data_payload.extend([Message(hexValue='FEED')])
data_payload.extend([Message(hexValue='AA')])
data_payload.extend([Message(hexValue='BBBBBBBB')])
msg_data = Data()
msg_data['sequenceNumber'] = SequenceNumber(55555)
msg_data['swRelease'] = Data.swRelease('rel2')
msg_data['macroId'] = Data.macroId(binValue='11110000111100001111')
msg_data['payload'] = data_payload
simple_message = SimpleMessage()
simple_message['data'] = msg_data
per_bytes = encode(asn1Spec=simple_message)
print('encoded data bytes as hex string:')
print(per_bytes.hex()) # py2: print(binascii.hexlify(per_bytes))
print('\n')
decoded = decode(per_stream=per_bytes, asn1Spec=SimpleMessage())
print('data message structure as string...:'
