Python

 

 

 

 

Python - ASN Encode/Decode

 

If you are working in the protocol of cellular communication, you may be pretty familiar with a special data structure called ASN.

 

ASN.1 (Abstract Syntax Notation One) is a standard and notation that describes rules and structures for representing, encoding, transmitting, and decoding data in telecommunications and computer networking. ASN.1 is widely used in various fields such as telecommunication and security systems because it provides a method for data structures to be described in a manner that is independent of machine-specific encoding techniques.

 

Python has a special package named asn1tools that helps you work with ASN.1. It's like a translator between the ASN.1 system and Python, so you can understand and work with ASN.1 data in Python.

 

 

 

Major functionality of asn1tools package

 

This package provides several core functionalities:

 

Encoding and Decoding: 'asn1tools' can encode and decode ASN.1 data using various encoding rules such as BER (Basic Encoding Rules), DER (Distinguished Encoding Rules), PER (Packed Encoding Rules), and XER (XML Encoding Rules), among others. This is one of the key features of the package as it allows for the conversion of complex ASN.1 data into Python data types and vice versa.

 

Validation: It's possible to validate your ASN.1 data against a schema to ensure it adheres to the expected structure and data types. This is important for ensuring data integrity and preventing errors that can arise from malformed data.

 

Compilation: 'asn1tools' can compile ASN.1 definitions. This process involves translating the abstract syntax into a format that can be processed by a computer. Once an ASN.1 schema is compiled, it can be used to encode, decode, and validate data.

 

 

 

Sample Program

 

Writing Python script for ASN manipulation in general goes through several procedures as described below.

 

  • Install the asn1tools package: Before you start, you need to install asn1tools. You can do this by running the command pip install asn1tools in your terminal or command prompt.
  • Create your ASN.1 schema: The schema is a set of rules that describes your data structure. You need to create this schema and save it in a .asn file. The structure and rules depend on the type of data you're working with.
  • Compile your schema: Once you've created your ASN.1 schema, you need to compile it using asn1tools. This means turning your schema into a form that Python can understand. You do this by using the asn1tools.compile_files('your_file.asn') function in Python, replacing 'your_file.asn' with the name of your schema file.
  • Encode your data: If you have data that you want to convert into ASN.1 format, you can encode it. You use the encode function in asn1tools for this, and you specify the encoding rule you want to use (for example, 'ber' or 'per').
  • Decode your data: If you have ASN.1 data that you want to convert back into Python data, you can decode it. You use the decode function in asn1tools for this, and again specify the encoding rule that was used.
  • Validate your data: If you want to check that your ASN.1 data follows the rules set out in your schema, you can validate it. You use the validate function in asn1tools for this. If your data doesn't follow the rules, asn1tools will tell you.

 

 

Example 1 > Simplified MIB ASN

 

Following is an example for asn1tools program for 5G NR MIB message.

 

First you need to define ASN schema as shown in the following example.  Except the first 2 lines and last one lines (i.e, DEFINITION BEGIN and END, I just copied it from 3GPP 38.331 with a little modification for pdcch-ConfigSIB1 OCTET STRING,) to make the ASN schema simpler. In next example, I am going to use the exactly same schema from 38.331 without any modification.

ASN_5G_MIB.asn

BCCH-BCH-Message DEFINITIONS AUTOMATIC TAGS ::=

BEGIN

 

MIB ::= SEQUENCE {

    systemFrameNumber BIT STRING (SIZE (6)),

    subCarrierSpacingCommon ENUMERATED {scs15or60, scs30or120},

    ssb-SubcarrierOffset INTEGER (0..15),

    dmrs-TypeA-Position ENUMERATED {pos2, pos3},

    pdcch-ConfigSIB1 OCTET STRING,

    cellBarred ENUMERATED {barred, notBarred},

    intraFreqReselection ENUMERATED {allowed, notAllowed},

    spare BIT STRING (SIZE (1))

}

 

BCCH-BCH-MessageType ::= CHOICE {

    mib MIB,

    messageClassExtension SEQUENCE {}

}

 

BCCH-BCH-Message ::= SEQUENCE {

    message BCCH-BCH-MessageType

}

 

END

 

 

Script

import asn1tools

from pprint import pprint

 

def bytes_to_hex_string(byte_array):

    return ''.join('{:02x}'.format(b) for b in byte_array)

 

def hex_to_bytes(hex_string):

    return bytes.fromhex(hex_string)

 

# Compile the ASN.1 specification. Note that the ASN_5G_MIB.asn gets complied here.

spec = asn1tools.compile_files('ASN_5G_MIB.asn')

 

# Create a sample MIB message

message = {

    'message': ('mib', {

        'systemFrameNumber': (b'(', 6),

        'subCarrierSpacingCommon': 'scs15or60',

        'ssb-SubcarrierOffset': 5,

        'dmrs-TypeA-Position': 'pos2',

        'pdcch-ConfigSIB1': b'\x0c',

        'cellBarred': 'notBarred',

        'intraFreqReselection': 'allowed',

        'spare': (b'\x00', 8)

    })

}

 

# This is not mandatory. this is to show how to modify a specific IE after the initial definition.

message['message'][1]['subCarrierSpacingCommon'] = 'scs30or120'

message['message'][1]['ssb-SubcarrierOffset'] = 6

message['message'][1]['pdcch-ConfigSIB1'] = b'\x0e'

 

# Encode and Validate the message

try:

    encoded_data = spec.encode('BCCH-BCH-Message', message)

    print("Validation successful.")

    print(f"Encoded data: {encoded_data}")

except asn1tools.EncodeError as e:

    print("Validation failed: ", e)

 

 

# This is not mandatory. This is just to get the hex tring which we are more familiar with

encoded_hex_string = bytes_to_hex_string(encoded_data)

print("Encoded hex string:", encoded_hex_string)

 

# This is to show how to convert the human readable hex string back to byte array which is used by decode()

# function.

encoded_bytes = hex_to_bytes(encoded_hex_string)

 

# Decode the encoded data back to a BCCH-BCH-Message instance

decoded_data = spec.decode('BCCH-BCH-Message', encoded_bytes)

print(f"Decoded data: {decoded_data}")

pprint(decoded_data, sort_dicts=False)

 

# This is to show how to extract a specific IE (information Elements) from the decoded ASN.

pdcch_config = decoded_data['message'][1]['pdcch-ConfigSIB1']

print("pdcch_config = ", pdcch_config )

 

dmrs_TypeA_pos = decoded_data['message'][1]['dmrs-TypeA-Position']

print("dmrs-TypeA-Position = ", dmrs_TypeA_pos )

Result

Validation successful.

 

#  print(f"Encoded data: {encoded_data}")

Encoded data: b'0\x1e\xa0\x1c\xa0\x1a\x80\x02\x02(\x81\x01\x01\x82\x01\x06\x83\x01\x00\x84\x01\x0e\x85\

x01\x01\x86\x01\x00\x87\x02\x00\x00'

 

# print("Encoded hex string:", encoded_hex_string)

Encoded hex string: 301ea01ca01a8002022881010182010683010084010e85010186010087020000

 

# print(f"Decoded data: {decoded_data}")

Decoded data: {'message': ('mib', {'systemFrameNumber': (bytearray(b'('), 6), 'subCarrierSpacingCommon': 'scs30or120', 'ssb-SubcarrierOffset': 6, 'dmrs-TypeA-Position': 'pos2', 'pdcch-ConfigSIB1': b'\x0e', 'cellBarred': 'notBarred', 'intraFreqReselection': 'allowed', 'spare': (bytearray(b'\x00'), 8)})}

 

# pprint(decoded_data, sort_dicts=False)

{'message': ('mib',

             {'systemFrameNumber': (bytearray(b'('), 6),

              'subCarrierSpacingCommon': 'scs30or120',

              'ssb-SubcarrierOffset': 6,

              'dmrs-TypeA-Position': 'pos2',

              'pdcch-ConfigSIB1': b'\x0e',

              'cellBarred': 'notBarred',

              'intraFreqReselection': 'allowed',

              'spare': (bytearray(b'\x00'), 8)})}

 

# print("pdcch_config = ", pdcch_config )

pdcch_config =  b'\x0e'

 

# print("dmrs-TypeA-Position = ", dmrs_TypeA_pos )

dmrs-TypeA-Position =  pos2

 

 

Example 2 > Original MIB ASN

 

ASN_5G_RRC.asn

-- ASN1START

-- TAG-NR-RRC-DEFINITIONS-START

NR-RRC-Definitions DEFINITIONS AUTOMATIC TAGS ::=

BEGIN

-- TAG-NR-RRC-DEFINITIONS-STOP

-- ASN1STOP

 

-- ASN1START

-- TAG-BCCH-BCH-MESSAGE-START

 

BCCH-BCH-Message ::= SEQUENCE {

    message BCCH-BCH-MessageType

}

BCCH-BCH-MessageType ::= CHOICE {

    mib MIB,

    messageClassExtension SEQUENCE {}

}

-- TAG-BCCH-BCH-MESSAGE-STOP

-- ASN1STOP

 

-- ASN1START

-- TAG-MIB-START

MIB ::= SEQUENCE {

    systemFrameNumber BIT STRING (SIZE (6)),

    subCarrierSpacingCommon ENUMERATED {scs15or60, scs30or120},

    ssb-SubcarrierOffset INTEGER (0..15),

    dmrs-TypeA-Position ENUMERATED {pos2, pos3},

    pdcch-ConfigSIB1 PDCCH-ConfigSIB1,

    cellBarred ENUMERATED {barred, notBarred},

    intraFreqReselection ENUMERATED {allowed, notAllowed},

    spare BIT STRING (SIZE (1))

}

-- TAG-MIB-STOP

-- ASN1STOP

 

-- ASN1START

-- TAG-PDCCH-CONFIGSIB1-START

PDCCH-ConfigSIB1 ::= SEQUENCE {

    controlResourceSetZero ControlResourceSetZero,

    searchSpaceZero SearchSpaceZero

}

-- TAG-PDCCH-

-- ASN1STOP

 

-- ASN1START

-- TAG-CONTROLRESOURCESETZERO-START

ControlResourceSetZero ::= INTEGER (0..15)

-- TAG-CONTROLRESOURCESETZERO-STOP

-- ASN1STOP

 

-- ASN1START

-- TAG-SEARCHSPACEZERO-START

SearchSpaceZero ::= INTEGER (0..15)

-- TAG-SEARCHSPACEZERO-STOP

-- ASN1STOP

 

END

 

 

Script

import asn1tools

from pprint import pprint

 

def bytes_to_hex_string(byte_array):

    return ''.join('{:02x}'.format(b) for b in byte_array)

 

def hex_to_bytes(hex_string):

    return bytes.fromhex(hex_string)

 

# Compile the ASN.1 specification

spec = asn1tools.compile_files('ASN_5G_RRC.asn')

 

# Create a sample MIB message

message = {

    'message': ('mib', {

        'systemFrameNumber': (b'(', 6),

        'subCarrierSpacingCommon': 'scs15or60',

        'ssb-SubcarrierOffset': 5,

        'dmrs-TypeA-Position': 'pos2',

        'pdcch-ConfigSIB1': {'controlResourceSetZero': 1, 'searchSpaceZero': 1},

        'cellBarred': 'notBarred',

        'intraFreqReselection': 'allowed',

        'spare': (b'\x00', 8)

    })

}

 

# This is not mandatory. this is to show how to modify a specific IE after the initial definition.

message['message'][1]['subCarrierSpacingCommon'] = 'scs30or120'

message['message'][1]['ssb-SubcarrierOffset'] = 6

message['message'][1]['pdcch-ConfigSIB1'] = {'controlResourceSetZero': 2, 'searchSpaceZero': 2}

 

# Encode and Validate the message

encoded_data = b''

try:

    encoded_data = spec.encode('BCCH-BCH-Message', message)

    print("Validation successful.")

    print(f"Encoded data: {encoded_data}")

except asn1tools.EncodeError as e:

    print("Validation failed: ", e)

 

 

# This is not mandatory. This is just to get the hex string which we are more familiar with

encoded_hex_string = bytes_to_hex_string(encoded_data)

print("Encoded hex string:", encoded_hex_string)

 

# This is to show how to convert the human readable hex string back to byte array which is used by decode()

# function.

encoded_bytes = hex_to_bytes(encoded_hex_string)

 

# Decode the encoded data back to a BCCH-BCH-Message instance

decoded_data = spec.decode('BCCH-BCH-Message', encoded_bytes)

print(f"Decoded data: {decoded_data}")

pprint(decoded_data, sort_dicts=False)

 

# This is to show how to extract a specific IE (information Elements) from the decoded ASN.

pdcch_config = decoded_data['message'][1]['pdcch-ConfigSIB1']

print("pdcch_config = ", pdcch_config )

 

dmrs_TypeA_pos = decoded_data['message'][1]['dmrs-TypeA-Position']

print("dmrs-TypeA-Position = ", dmrs_TypeA_pos )

Result

Validation successful.

 

#  print(f"Encoded data: {encoded_data}")

Encoded data: b'0#\xa0!\xa0\x1f\x80\x02\x02(\x81\x01\x01\x82\x01\x06\x83\x01\x00\xa4\x06\x80\x01\x02\x81\

x01\x02\x85\x01\x01\x86\x01\x00\x87\x02\x00\x00'

 

# print("Encoded hex string:", encoded_hex_string)

Encoded hex string: 3023a021a01f80020228810101820106830100a40680010281010285010186010087020000

 

# print(f"Decoded data: {decoded_data}")

Decoded data: {'message': ('mib', {'systemFrameNumber': (bytearray(b'('), 6), 'subCarrierSpacingCommon': 'scs30or120', 'ssb-SubcarrierOffset': 6, 'dmrs-TypeA-Position': 'pos2', 'pdcch-ConfigSIB1': {'controlResourceSetZero': 2, 'searchSpaceZero': 2}, 'cellBarred': 'notBarred', 'intraFreqReselection': 'allowed', 'spare': (bytearray(b'\x00'), 8)})}

 

# pprint(decoded_data, sort_dicts=False)

{'message': ('mib',

             {'systemFrameNumber': (bytearray(b'('), 6),

              'subCarrierSpacingCommon': 'scs30or120',

              'ssb-SubcarrierOffset': 6,

              'dmrs-TypeA-Position': 'pos2',

              'pdcch-ConfigSIB1': {'controlResourceSetZero': 2,

                                   'searchSpaceZero': 2},

              'cellBarred': 'notBarred',

              'intraFreqReselection': 'allowed',

              'spare': (bytearray(b'\x00'), 8)})}

 

# print("pdcch_config = ", pdcch_config )

pdcch_config =  {'controlResourceSetZero': 2, 'searchSpaceZero': 2}

 

# print("dmrs-TypeA-Position = ", dmrs_TypeA_pos )

dmrs-TypeA-Position =  pos2