asn1PERser


Nameasn1PERser JSON
Version 0.4.3 PyPI version JSON
download
home_pagehttps://github.com/erupikus/asn1PERser
SummaryParse ASN.1 schemas into Python code and encode/decode them using PER encoder
upload_time2023-07-14 12:35:25
maintainer
docs_urlNone
authorMaciej Pikuła
requires_python
license
keywords asn asn1 asn.1 per decoder encoder
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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:
1. [Examples](#examples)
2. [Decoding from string](#decoding-from-string)
3. [Dictionary creation](#dictionary-creation)
4. [Additional info](#additional-info)
5. [History](#history)
6. [Known bugs](#known-bugs)

## Examples
Following ASN.1 schema:

```python
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:
```python
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/':
```python
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:
```python
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:
```python
'''
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:
```python
'''
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...:')
print(decoded)
print('...can be accessed like dictionary:')
print(bytes(decoded['data']['payload'][0]).hex())
print('\n')

print('...can be transformed into dictionary:')
print(decoded.to_dict())
```

above will output:
```
encoded data bytes as hex string:
70d90320f0f0f880dead40beef40feed00aac0bbbbbbbb


data message structure as string...:
SimpleMessage:
 data=Data:
  sequenceNumber=55555
  swRelease=rel2
  macroId=986895
  payload=Payload:
   0xdead   0xbeef   0xfeed   0xaa   0xbbbbbbbb


...can be accessed like dictionary:
dead


...can be transformed into dictionary:
{'data': OrderedDict([('sequenceNumber', 55555), ('swRelease', 'rel2'), ('macroId', 986895), ('payload', ['dead', 'beef', 'feed', 'aa', 'bbbbbbbb'])])}
```

Next message:
```python
'''
simple_message SimpleMessage ::= data : {
    sequenceNumber    256,
    swRelease  rel3,
    payload {
        'DEADBEEF'H
    }
}
'''
data_payload = Payload()
data_payload.extend([Message(hexValue='DEADBEEF')])

msg_data = Data()
msg_data['sequenceNumber'] = SequenceNumber(256)
msg_data['swRelease'] = Data.swRelease('rel3')
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...:')
print(decoded)
print('...can be accessed like dictionary:')
print(decoded['data']['swRelease'])
print('\n')

print('...can be transformed into dictionary:')
print(decoded.to_dict())
```

above will output:
```
encoded data bytes as hex string:
60010043deadbeef


data message structure as string...:
SimpleMessage:
 data=Data:
  sequenceNumber=256
  swRelease=rel3
  payload=Payload:
   0xdeadbeef


...can be accessed like dictionary:
rel3


...can be transformed into dictionary:
{'data': OrderedDict([('sequenceNumber', 256), ('swRelease', 'rel3'), ('payload', ['deadbeef'])])}
```

## Decoding from string
When encoded bytes are given as __string__ use _bytearray.fromhex()_:

```

pure_string = str('70d90320f0f0f880dead40beef40feed00aac0bbbbbbbb')
real_bytes = bytearray.fromhex(pure_string)

decoded = decode(asn1Spec=SimpleMessage(), per_stream=real_bytes)
print(decoded)
```

above will output:
```
SimpleMessage:
 data=Data:
  sequenceNumber=55555
  swRelease=rel2
  macroId=986895
  payload=Payload:
   0xdead   0xbeef   0xfeed   0xaa   0xbbbbbbbb
```

## Dictionary creation
  Data above can be accessed like dictionary but it is not a dictionary.
  Method '__to_dict__' was added to make dictionary creation simple:

```
msg_dict = decoded.to_dict()
print(type(msg_dict))
print(msg_dict)
```

output:
```
<class 'dict'>
{'data': OrderedDict([('sequenceNumber', 55555), ('swRelease', 'rel2'), ('macroId', 986895), ('payload', ['dead', 'beef', 'feed', 'aa', 'bbbbbbbb'])])}
```

To better show created structure, json.dumps() can be used:
```
print(json.dumps(msg_dict, indent=2))
```

output:
```
{
  "data": {
    "sequenceNumber": 55555,
    "swRelease": "rel2",
    "macroId": 986895,
    "payload": [
      "dead",
      "beef",
      "feed",
      "aa",
      "bbbbbbbb"
    ]
  }
}
```

## Additional info

For more examples please see library tests:
- parsing tests, located in test/parsing/ folder.
- coding/encoding tests, located in test/per folder.

All above examples and tests where checked using:
https://asn1.io/asn1playground/

This library inherits extensively from pyasn1 library so BER and other encoding should also work here.

Parsing may take time - when trying to parse about 2000 lines of ASN.1 it took 15-20 minutes.
Tests for parsing also take time.

asn1PERser uses logging library (for now only in decoder.py module).
By default logging.NullHandler() is set as library top handler so no logs are visible.
If you want to enable logging call asn1perser_logging_setup with (at least) handler
parameter set to something else than logging.NullHandler:

```
import logging
from asn1PERser import asn1perser_logging_setup

asn1perser_logging_setup(logging.StreamHandler())
```

Above will print logging.INFO level logs to console.

## History

See CHANGES file.

## Known bugs
- Defining BitStringType type with default hexValue set to empty string (''), like:
```
...
d15     BIT STRING DEFAULT ''H,
...
```
will parse to Python code but running it will cause exception:
```
pyasn1.error.PyAsn1Error: BitStringType.fromHexString() error: invalid literal for int() with base 16: ''
```

- This will parse, but sorting algorith may not sort it corretly:
```
    
    MySeq2 ::= MySeq1   -- ok since one level of nesting is ok

    MySeq3 ::= MySeq1   -- ok since one level of nesting is ok
    
    MySeq1 ::= SEQUENCE {
        d00     INTEGER
    }

    MySeq4 ::= MySeq2   -- NOT OK! 2nd level of nesting 
```
This will parse into:
```python
MySeq4 = MySeq2


class MySeq1(SequenceType):
    rootComponent = AdditiveNamedTypes(
        NamedType('d00', IntegerType()),
    )
    componentType = rootComponent


MySeq3 = MySeq1


MySeq2 = MySeq1
```
so Python will not find MySeq2 - this will lead to NameError.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/erupikus/asn1PERser",
    "name": "asn1PERser",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "asn asn1 asn.1 PER decoder encoder",
    "author": "Maciej Piku\u0142a",
    "author_email": "erupikus@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/30/17/a0760623e02c5e90def5f864d860b132d368eb92acb597c07494eb69c8b2/asn1PERser-0.4.3.tar.gz",
    "platform": null,
    "description": "# asn1PERser\r\nThis library can parse ASN.1 text schemas into Python code. ASN.1 PER (Aligned) decoder/encoder is included to use with parsed schemas.\r\n\r\nSupported ASN.1 types and their constraints are:\r\n\r\n| ASN.1 Type   | Single value | Value range | Value size | can be extended (...) | used Python contraint class |\r\n|:------------:|:------------:|:-----------:|:----------:|:---------------------:|:---------------------------:|\r\n| INTEGER      |      X       |     X       |            |       X               |       ValueRange            |\r\n| BOOLEAN      |              |             |            |                       |                             |\r\n| ENUMERATED   |              |             |            |       X               |       ExtensionMarker       |\r\n| BIT STRING   |      X       |             |     X      |       X               |       ValueSize             |\r\n| OCTET STRING |      X       |             |     X      |       X               |       ValueSize             |\r\n| CHOICE       |              |             |            |       X               |       ExtensionMarker       |\r\n| SEQUENCE     |              |             |            |       X               |       ExtensionMarker       |\r\n| SEQUENCE OF  |      X       |             |     X      |       X               |       SequenceOfValueSize   |\r\n\r\nTable of contents:\r\n1. [Examples](#examples)\r\n2. [Decoding from string](#decoding-from-string)\r\n3. [Dictionary creation](#dictionary-creation)\r\n4. [Additional info](#additional-info)\r\n5. [History](#history)\r\n6. [Known bugs](#known-bugs)\r\n\r\n## Examples\r\nFollowing ASN.1 schema:\r\n\r\n```python\r\nasn_schema = '''\\\r\nSimpleProtocol DEFINITIONS AUTOMATIC TAGS ::=\r\nBEGIN\r\nSimpleMessage ::= CHOICE {\r\n    start    Start,\r\n    stop     Stop,\r\n    alive    Alive,\r\n    data     Data,\r\n    ...\r\n}\r\n\r\nStart ::= SEQUENCE {\r\n    sequenceNumber    SequenceNumber,\r\n    timestamp         UTC-Timestamp,\r\n    srcPort           Port,\r\n    dstPort           Port\r\n}\r\n\r\nStop ::= SEQUENCE {\r\n    sequenceNumber    SequenceNumber,\r\n    timestamp         UTC-Timestamp,\r\n    srcPort           Port,\r\n    dstPort           Port\r\n}\r\n\r\nAlive ::= SEQUENCE {\r\n    timestamp         UTC-Timestamp,\r\n    ...\r\n}\r\n\r\nData ::= SEQUENCE {\r\n    sequenceNumber    SequenceNumber,\r\n    swRelease         ENUMERATED {rel1, rel2, rel3, ...},\r\n    macroId           BIT STRING (SIZE(20)) OPTIONAL,\r\n    payload           Payload\r\n}\r\n\r\nPort ::= INTEGER (10000..65535)\r\n\r\nSequenceNumber ::= INTEGER (0..65535)\r\n\r\nUTC-Timestamp ::= SEQUENCE {\r\n    seconds     INTEGER (0..4294967295),\r\n    useconds    INTEGER (0..4294967295)\r\n}\r\n\r\nPayload ::= SEQUENCE (SIZE(1..5)) OF Message\r\n\r\nMessage ::= OCTET STRING (SIZE(1..4))\r\n\r\nEND\r\n'''\r\n```\r\n\r\ncan be parsed into Python code like this:\r\n```python\r\nfrom asn1PERser import parse_asn1_schema\r\n\r\n\r\nparse_asn1_schema(asn1_schema=asn_schema, output_folder=r'C:/my_code/')\r\n```\r\n\r\nAbove code will create file 'SimpleProtocol.py' in folder (which must exist) 'C:/my_code/':\r\n```python\r\nfrom pyasn1.type.namedtype import NamedType, NamedTypes, OptionalNamedType, DefaultedNamedType\r\nfrom pyasn1.type.namedval import NamedValues\r\nfrom asn1PERser.classes.data.builtin import *\r\nfrom asn1PERser.classes.types.type import AdditiveNamedTypes\r\nfrom asn1PERser.classes.types.constraint import MIN, MAX, NoConstraint, ExtensionMarker, SequenceOfValueSize, \\\r\n    ValueRange, SingleValue, ValueSize, ConstraintOr, ConstraintAnd\r\n\r\n\r\nclass Port(IntegerType):\r\n    subtypeSpec = ValueRange(10000, 65535)\r\n\r\n\r\nclass SequenceNumber(IntegerType):\r\n    subtypeSpec = ValueRange(0, 65535)\r\n\r\n\r\nclass Message(OctetStringType):\r\n    subtypeSpec = ValueSize(1, 4)\r\n\r\n\r\nclass UTC_Timestamp(SequenceType):\r\n    class seconds(IntegerType):\r\n        subtypeSpec = ValueRange(0, 4294967295)\r\n\r\n    class useconds(IntegerType):\r\n        subtypeSpec = ValueRange(0, 4294967295)\r\n\r\n    rootComponent = AdditiveNamedTypes(\r\n        NamedType('seconds', seconds()),\r\n        NamedType('useconds', useconds()),\r\n    )\r\n    componentType = rootComponent\r\n\r\n\r\nclass Start(SequenceType):\r\n    rootComponent = AdditiveNamedTypes(\r\n        NamedType('sequenceNumber', SequenceNumber()),\r\n        NamedType('timestamp', UTC_Timestamp()),\r\n        NamedType('srcPort', Port()),\r\n        NamedType('dstPort', Port()),\r\n    )\r\n    componentType = rootComponent\r\n\r\n\r\nclass Stop(SequenceType):\r\n    rootComponent = AdditiveNamedTypes(\r\n        NamedType('sequenceNumber', SequenceNumber()),\r\n        NamedType('timestamp', UTC_Timestamp()),\r\n        NamedType('srcPort', Port()),\r\n        NamedType('dstPort', Port()),\r\n    )\r\n    componentType = rootComponent\r\n\r\n\r\nclass Alive(SequenceType):\r\n    subtypeSpec = ExtensionMarker(True)\r\n    rootComponent = AdditiveNamedTypes(\r\n        NamedType('timestamp', UTC_Timestamp()),\r\n    )\r\n    componentType = rootComponent\r\n\r\n\r\nclass Payload(SequenceOfType):\r\n    subtypeSpec = SequenceOfValueSize(1, 5)\r\n    componentType = Message()\r\n\r\n\r\nclass Data(SequenceType):\r\n    class swRelease(EnumeratedType):\r\n        subtypeSpec = ExtensionMarker(True)\r\n        enumerationRoot = NamedValues(\r\n            ('rel1', 0),\r\n            ('rel2', 1),\r\n            ('rel3', 2),\r\n        )\r\n        namedValues = enumerationRoot\r\n\r\n    class macroId(BitStringType):\r\n        subtypeSpec = ValueSize(20, 20)\r\n\r\n    rootComponent = AdditiveNamedTypes(\r\n        NamedType('sequenceNumber', SequenceNumber()),\r\n        NamedType('swRelease', swRelease()),\r\n        OptionalNamedType('macroId', macroId()),\r\n        NamedType('payload', Payload()),\r\n    )\r\n    componentType = rootComponent\r\n\r\n\r\nclass SimpleMessage(ChoiceType):\r\n    subtypeSpec = ExtensionMarker(True)\r\n    rootComponent = AdditiveNamedTypes(\r\n        NamedType('start', Start()),\r\n        NamedType('stop', Stop()),\r\n        NamedType('alive', Alive()),\r\n        NamedType('data', Data()),\r\n    )\r\n    componentType = rootComponent\r\n\r\n\r\n\r\n```\r\n\r\nWhen schema is parsed it can be used - message can be created, encoded and decoded, using PER encoder/decoder, into bytes\r\nor Python structure:\r\n```python\r\nfrom asn1PERser import encode, decode\r\nfrom SimpleProtocol import *\r\n\r\n\r\n'''\r\nsimple_message SimpleMessage ::= alive : {\r\n    timestamp {\r\n        seconds 1557528149,\r\n        useconds 12345\r\n    }\r\n}\r\n'''\r\nutc_timestamp = UTC_Timestamp()\r\nutc_timestamp['seconds'] = UTC_Timestamp.seconds(1557528149)\r\nutc_timestamp['useconds'] = UTC_Timestamp.useconds(12345)\r\n\r\nmsg_alive = Alive()\r\nmsg_alive['timestamp'] = utc_timestamp\r\n\r\nsimple_message = SimpleMessage()\r\nsimple_message['alive'] = msg_alive\r\n\r\nper_bytes = encode(asn1Spec=simple_message)\r\nprint('encoded alive bytes as hex string:')\r\nprint(per_bytes.hex())  # py2: print(binascii.hexlify(per_bytes))\r\nprint('\\n')\r\n\r\ndecoded = decode(per_stream=per_bytes, asn1Spec=SimpleMessage())\r\nprint('decoded alive message structure as string...:')\r\nprint(decoded)\r\n\r\nprint('...can be accessed like dictionary:')\r\nprint(decoded['alive']['timestamp']['seconds'])\r\nprint('\\n')\r\n\r\nprint('...can be transformed into dictionary:')\r\nprint(decoded.to_dict())\r\n```\r\n\r\nabove will output:\r\n```\r\nencoded alive bytes as hex string:\r\n4c5cd5fe55403039\r\n\r\n\r\ndecoded alive message structure as string...:\r\nSimpleMessage:\r\n alive=Alive:\r\n  timestamp=UTC_Timestamp:\r\n   seconds=1557528149\r\n   useconds=12345\r\n\r\n\r\n\r\n...can be accessed like dictionary:\r\n1557528149\r\n\r\n\r\n...can be transformed into dictionary:\r\n{'alive': OrderedDict([('timestamp', OrderedDict([('seconds', 1557528149), ('useconds', 12345)]))])}\r\n```\r\n\r\nNext message:\r\n```python\r\n'''\r\nsimple_message SimpleMessage ::= start : {\r\n    sequenceNumber    10,\r\n    timestamp {\r\n        seconds 1557528149,\r\n        useconds 12345\r\n    },\r\n    srcPort    65533,\r\n    dstPort    10000\r\n}\r\n'''\r\n\r\nutc_timestamp = UTC_Timestamp()\r\nutc_timestamp['seconds'] = UTC_Timestamp.seconds(1557528149)\r\nutc_timestamp['useconds'] = UTC_Timestamp.useconds(12345)\r\n\r\nmsg_start = Start()\r\nmsg_start['sequenceNumber'] = SequenceNumber(10)\r\nmsg_start['timestamp'] = utc_timestamp\r\nmsg_start['srcPort'] = Port(65533)\r\nmsg_start['dstPort'] = Port(10000)\r\n\r\nsimple_message = SimpleMessage()\r\nsimple_message['start'] = msg_start\r\n\r\nper_bytes = encode(asn1Spec=simple_message)\r\nprint('encoded start bytes as hex string:')\r\nprint(per_bytes.hex())  # py2: print(binascii.hexlify(per_bytes))\r\nprint('\\n')\r\n\r\ndecoded = decode(per_stream=per_bytes, asn1Spec=SimpleMessage())\r\nprint('start message structure as string...:')\r\nprint(decoded)\r\nprint('...can be accessed like dictionary:')\r\nprint(decoded['start']['srcPort'])\r\nprint('\\n')\r\n\r\nprint('...can be transformed into dictionary:')\r\nprint(decoded.to_dict())\r\n```\r\n\r\nabove will output:\r\n```\r\nencoded start bytes as hex string:\r\n00000ac05cd5fe55403039d8ed0000\r\n\r\n\r\nstart message structure as string...:\r\nSimpleMessage:\r\n start=Start:\r\n  sequenceNumber=10\r\n  timestamp=UTC_Timestamp:\r\n   seconds=1557528149\r\n   useconds=12345\r\n\r\n  srcPort=65533\r\n  dstPort=10000\r\n\r\n\r\n...can be accessed like dictionary:\r\n65533\r\n\r\n\r\n...can be transformed into dictionary:\r\n{'start': OrderedDict([('sequenceNumber', 10), ('timestamp', OrderedDict([('seconds', 1557528149), ('useconds', 12345)])), ('srcPort', 65533), ('dstPort', 10000)])}\r\n```\r\n\r\nNext message:\r\n```python\r\n'''\r\nsimple_message SimpleMessage ::= data : {\r\n    sequenceNumber    55555,\r\n    swRelease  rel2,\r\n    macroId    '11110000111100001111'B,\r\n    payload {\r\n        'DEAD'H,\r\n        'BEEF'H,\r\n        'FEED'H,\r\n        'AA'H,\r\n        'BBBBBBBB'H\r\n    }\r\n}\r\n'''\r\n\r\ndata_payload = Payload()\r\ndata_payload.extend([Message(hexValue='DEAD')])\r\ndata_payload.extend([Message(hexValue='BEEF')])\r\ndata_payload.extend([Message(hexValue='FEED')])\r\ndata_payload.extend([Message(hexValue='AA')])\r\ndata_payload.extend([Message(hexValue='BBBBBBBB')])\r\n\r\nmsg_data = Data()\r\nmsg_data['sequenceNumber'] = SequenceNumber(55555)\r\nmsg_data['swRelease'] = Data.swRelease('rel2')\r\nmsg_data['macroId'] = Data.macroId(binValue='11110000111100001111')\r\nmsg_data['payload'] = data_payload\r\n\r\nsimple_message = SimpleMessage()\r\nsimple_message['data'] = msg_data\r\n\r\nper_bytes = encode(asn1Spec=simple_message)\r\nprint('encoded data bytes as hex string:')\r\nprint(per_bytes.hex())  # py2: print(binascii.hexlify(per_bytes))\r\nprint('\\n')\r\n\r\ndecoded = decode(per_stream=per_bytes, asn1Spec=SimpleMessage())\r\nprint('data message structure as string...:')\r\nprint(decoded)\r\nprint('...can be accessed like dictionary:')\r\nprint(bytes(decoded['data']['payload'][0]).hex())\r\nprint('\\n')\r\n\r\nprint('...can be transformed into dictionary:')\r\nprint(decoded.to_dict())\r\n```\r\n\r\nabove will output:\r\n```\r\nencoded data bytes as hex string:\r\n70d90320f0f0f880dead40beef40feed00aac0bbbbbbbb\r\n\r\n\r\ndata message structure as string...:\r\nSimpleMessage:\r\n data=Data:\r\n  sequenceNumber=55555\r\n  swRelease=rel2\r\n  macroId=986895\r\n  payload=Payload:\r\n   0xdead   0xbeef   0xfeed   0xaa   0xbbbbbbbb\r\n\r\n\r\n...can be accessed like dictionary:\r\ndead\r\n\r\n\r\n...can be transformed into dictionary:\r\n{'data': OrderedDict([('sequenceNumber', 55555), ('swRelease', 'rel2'), ('macroId', 986895), ('payload', ['dead', 'beef', 'feed', 'aa', 'bbbbbbbb'])])}\r\n```\r\n\r\nNext message:\r\n```python\r\n'''\r\nsimple_message SimpleMessage ::= data : {\r\n    sequenceNumber    256,\r\n    swRelease  rel3,\r\n    payload {\r\n        'DEADBEEF'H\r\n    }\r\n}\r\n'''\r\ndata_payload = Payload()\r\ndata_payload.extend([Message(hexValue='DEADBEEF')])\r\n\r\nmsg_data = Data()\r\nmsg_data['sequenceNumber'] = SequenceNumber(256)\r\nmsg_data['swRelease'] = Data.swRelease('rel3')\r\nmsg_data['payload'] = data_payload\r\n\r\nsimple_message = SimpleMessage()\r\nsimple_message['data'] = msg_data\r\n\r\nper_bytes = encode(asn1Spec=simple_message)\r\nprint('encoded data bytes as hex string:')\r\nprint(per_bytes.hex())  # py2: print(binascii.hexlify(per_bytes))\r\nprint('\\n')\r\n\r\ndecoded = decode(per_stream=per_bytes, asn1Spec=SimpleMessage())\r\nprint('data message structure as string...:')\r\nprint(decoded)\r\nprint('...can be accessed like dictionary:')\r\nprint(decoded['data']['swRelease'])\r\nprint('\\n')\r\n\r\nprint('...can be transformed into dictionary:')\r\nprint(decoded.to_dict())\r\n```\r\n\r\nabove will output:\r\n```\r\nencoded data bytes as hex string:\r\n60010043deadbeef\r\n\r\n\r\ndata message structure as string...:\r\nSimpleMessage:\r\n data=Data:\r\n  sequenceNumber=256\r\n  swRelease=rel3\r\n  payload=Payload:\r\n   0xdeadbeef\r\n\r\n\r\n...can be accessed like dictionary:\r\nrel3\r\n\r\n\r\n...can be transformed into dictionary:\r\n{'data': OrderedDict([('sequenceNumber', 256), ('swRelease', 'rel3'), ('payload', ['deadbeef'])])}\r\n```\r\n\r\n## Decoding from string\r\nWhen encoded bytes are given as __string__ use _bytearray.fromhex()_:\r\n\r\n```\r\n\r\npure_string = str('70d90320f0f0f880dead40beef40feed00aac0bbbbbbbb')\r\nreal_bytes = bytearray.fromhex(pure_string)\r\n\r\ndecoded = decode(asn1Spec=SimpleMessage(), per_stream=real_bytes)\r\nprint(decoded)\r\n```\r\n\r\nabove will output:\r\n```\r\nSimpleMessage:\r\n data=Data:\r\n  sequenceNumber=55555\r\n  swRelease=rel2\r\n  macroId=986895\r\n  payload=Payload:\r\n   0xdead   0xbeef   0xfeed   0xaa   0xbbbbbbbb\r\n```\r\n\r\n## Dictionary creation\r\n  Data above can be accessed like dictionary but it is not a dictionary.\r\n  Method '__to_dict__' was added to make dictionary creation simple:\r\n\r\n```\r\nmsg_dict = decoded.to_dict()\r\nprint(type(msg_dict))\r\nprint(msg_dict)\r\n```\r\n\r\noutput:\r\n```\r\n<class 'dict'>\r\n{'data': OrderedDict([('sequenceNumber', 55555), ('swRelease', 'rel2'), ('macroId', 986895), ('payload', ['dead', 'beef', 'feed', 'aa', 'bbbbbbbb'])])}\r\n```\r\n\r\nTo better show created structure, json.dumps() can be used:\r\n```\r\nprint(json.dumps(msg_dict, indent=2))\r\n```\r\n\r\noutput:\r\n```\r\n{\r\n  \"data\": {\r\n    \"sequenceNumber\": 55555,\r\n    \"swRelease\": \"rel2\",\r\n    \"macroId\": 986895,\r\n    \"payload\": [\r\n      \"dead\",\r\n      \"beef\",\r\n      \"feed\",\r\n      \"aa\",\r\n      \"bbbbbbbb\"\r\n    ]\r\n  }\r\n}\r\n```\r\n\r\n## Additional info\r\n\r\nFor more examples please see library tests:\r\n- parsing tests, located in test/parsing/ folder.\r\n- coding/encoding tests, located in test/per folder.\r\n\r\nAll above examples and tests where checked using:\r\nhttps://asn1.io/asn1playground/\r\n\r\nThis library inherits extensively from pyasn1 library so BER and other encoding should also work here.\r\n\r\nParsing may take time - when trying to parse about 2000 lines of ASN.1 it took 15-20 minutes.\r\nTests for parsing also take time.\r\n\r\nasn1PERser uses logging library (for now only in decoder.py module).\r\nBy default logging.NullHandler() is set as library top handler so no logs are visible.\r\nIf you want to enable logging call asn1perser_logging_setup with (at least) handler\r\nparameter set to something else than logging.NullHandler:\r\n\r\n```\r\nimport logging\r\nfrom asn1PERser import asn1perser_logging_setup\r\n\r\nasn1perser_logging_setup(logging.StreamHandler())\r\n```\r\n\r\nAbove will print logging.INFO level logs to console.\r\n\r\n## History\r\n\r\nSee CHANGES file.\r\n\r\n## Known bugs\r\n- Defining BitStringType type with default hexValue set to empty string (''), like:\r\n```\r\n...\r\nd15     BIT STRING DEFAULT ''H,\r\n...\r\n```\r\nwill parse to Python code but running it will cause exception:\r\n```\r\npyasn1.error.PyAsn1Error: BitStringType.fromHexString() error: invalid literal for int() with base 16: ''\r\n```\r\n\r\n- This will parse, but sorting algorith may not sort it corretly:\r\n```\r\n    \r\n    MySeq2 ::= MySeq1   -- ok since one level of nesting is ok\r\n\r\n    MySeq3 ::= MySeq1   -- ok since one level of nesting is ok\r\n    \r\n    MySeq1 ::= SEQUENCE {\r\n        d00     INTEGER\r\n    }\r\n\r\n    MySeq4 ::= MySeq2   -- NOT OK! 2nd level of nesting \r\n```\r\nThis will parse into:\r\n```python\r\nMySeq4 = MySeq2\r\n\r\n\r\nclass MySeq1(SequenceType):\r\n    rootComponent = AdditiveNamedTypes(\r\n        NamedType('d00', IntegerType()),\r\n    )\r\n    componentType = rootComponent\r\n\r\n\r\nMySeq3 = MySeq1\r\n\r\n\r\nMySeq2 = MySeq1\r\n```\r\nso Python will not find MySeq2 - this will lead to NameError.\r\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "Parse ASN.1 schemas into Python code and encode/decode them using PER encoder",
    "version": "0.4.3",
    "project_urls": {
        "Homepage": "https://github.com/erupikus/asn1PERser"
    },
    "split_keywords": [
        "asn",
        "asn1",
        "asn.1",
        "per",
        "decoder",
        "encoder"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0d0e2f6ef4e88c92c0510f14489b6df313c9afc33fc24200c3501806797e9841",
                "md5": "42d690cf76bb69c91947932616ef244e",
                "sha256": "33ac8a26564ae7abc5266e475b32630a29982d10f51204a619a4ad03409dc87b"
            },
            "downloads": -1,
            "filename": "asn1PERser-0.4.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "42d690cf76bb69c91947932616ef244e",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 106369,
            "upload_time": "2023-07-14T12:35:24",
            "upload_time_iso_8601": "2023-07-14T12:35:24.107026Z",
            "url": "https://files.pythonhosted.org/packages/0d/0e/2f6ef4e88c92c0510f14489b6df313c9afc33fc24200c3501806797e9841/asn1PERser-0.4.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3017a0760623e02c5e90def5f864d860b132d368eb92acb597c07494eb69c8b2",
                "md5": "d926c4bcdf9037026024db71566d0516",
                "sha256": "9f648fb1ae38c53161965edcbbce1c5475ffd53f04378cb52d25eeb458936468"
            },
            "downloads": -1,
            "filename": "asn1PERser-0.4.3.tar.gz",
            "has_sig": false,
            "md5_digest": "d926c4bcdf9037026024db71566d0516",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 69876,
            "upload_time": "2023-07-14T12:35:25",
            "upload_time_iso_8601": "2023-07-14T12:35:25.733866Z",
            "url": "https://files.pythonhosted.org/packages/30/17/a0760623e02c5e90def5f864d860b132d368eb92acb597c07494eb69c8b2/asn1PERser-0.4.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-07-14 12:35:25",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "erupikus",
    "github_project": "asn1PERser",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "asn1perser"
}
        
Elapsed time: 0.09142s