P1sec / pycrate

A Python library to ease the development of encoders and decoders for various protocols and file formats; contains ASN.1 and CSN.1 compilers.
GNU Lesser General Public License v2.1
381 stars 132 forks source link

Boilerplate Tuple for ASN1 object? #127

Closed nickvsnetworking closed 2 years ago

nickvsnetworking commented 3 years ago

Hi folks,

Apologies in advance for what may very well be a dumb question, I've had a read over the Wiki and the outputted code and it's still not clear to me:

I'm playing with the 3GPP SBc Interface which is ASN.1 encoded,

Using the magic of Pycrate I've created a Python file from the ASN1 definition and using it I'm able to take hex data containing an encoded ASN.1 message and turn it into a Python tuple - fantastic.

##Decoding ASN.1 encoded Hex SBc data to a tuple
import binascii
import SBC
sbc_hex = '000240080000010001400105'

sbc_asn1_object = SBC.SBC_AP_PDU_Descriptions.SBC_AP_PDU
sbc_asn1_object.from_aper(binascii.unhexlify(sbc_hex))
sbc_decoded = sbc_asn1_object.get_val()
print(sbc_decoded)

And I'm also able to encode a tuple back into ASN.1 using the _setval() function:

##Encoding a Tuple to ASN.1 encoded Hex SBc data
value = ('initiatingMessage', {'procedureCode': 2, 'criticality': 'ignore', 'value': ('Error-Indication', {'protocolIEs': [{'id': 1, 'criticality': 'ignore', 'value': ('Cause', 5)}]})})
sbc_asn1_object.set_val(value)
print(str(binascii.hexlify(sbc_asn1_object.to_aper())))

Considering the ASN.1 definition defines what elements need to be present and which are optional, I'm wondering if there is a function to generate list the required elements to be fed into set_val?

Manually populating the tuples from the elements in the spec seems above what my mind can process at this point in the year,

SBC.asn.txt SBC.py.txt

p1-bmu commented 3 years ago

You may re-read https://github.com/P1sec/pycrate/wiki/Using-the-pycrate-asn1-runtime#rrc-3g : I just added a short section on attributes indicating optional components of constructed types. Besides of this, there is no magic however, as a protocol developer, you are required to know the content of the message structures to fill them appropriately.

Moreover, those 2 exiting issues may be related to yours: https://github.com/P1sec/pycrate/issues/104 https://github.com/P1sec/pycrate/issues/83 They were unfortunately never solved or completed.

Sorry to say, but maybe your development can wait till January.

p1-bmu commented 3 years ago

Maybe you could already contribute to the project by submitting the SBC-AP ASN.1 protocol definition to the pycrate_asn1dir subdirectory ? That would be very nice. Also apart from this, thank you for all the valuable information you provide on your blog !

nickvsnetworking commented 3 years ago

Thanks @p1-bmu ,

I'll send a PR with the latest ASN1 definition for SBc-AP and some examples when I've got this working.

As you suggested calling the names returned a list of what I needed to populate which is exactly what I was after: >>> SBC.SBC_AP_PDU_Contents.Write_Replace_Warning_Indication_IEs

<Write-Replace-Warning-Indication-IEs ([SBC-AP-PROTOCOL-IES] CLASS): ASN1Set(root=[{'id': 5, 'criticality': 'reject', 'Value': <Value ([Message-Identifier] BIT STRING)>, 'presence': 'mandatory'}, {'id': 11, 'criticality': 'reject', 'Value': <Value ([Serial-Number] BIT STRING)>, 'presence': 'mandatory'}, {'id': 23, 'criticality': 'reject', 'Value': <Value ([Broadcast-Scheduled-Area-List] SEQUENCE)>, 'presence': 'optional'}], ext=[])>

And with fresh eyes and another read over the documentation I managed to make some progress!

To create a Write-Replace-Warning-Request I need to include the two mandatory protocolIEs which are:

Which I set in a list named _protocolies and try and compile using the set_val() function:

import SBC
sbc_asn1_object = SBC.SBC_AP_PDU_Descriptions.SBC_AP_PDU
#id 5 [Message-Identifier] BIT STRING
#id 11 [Serial-Number] BIT STRING
#Contents generated from running: SBC.SBC_AP_PDU_Contents.Write_Replace_Warning_Indication_IEs
protocol_ies = []                                                                #Empty list to store protocol IEs
protocol_ies.append({'id': 5, u'Value': (int(4379), 16)})                        #Message-Identifier
protocol_ies.append({'id': 11, u'Value': (int(4379), 16)})                       #Serial-Number

value = ('initiatingMessage', \
         {'procedureCode': 0, 'criticality': 'ignore', \
          'value': ('Write-Replace-Warning-Request', {'protocolIEs': protocol_ies  })})

sbc_asn1_object.set_val(value)
sbc_hex_out = binascii.hexlify(sbc_asn1_object.to_aper())
sbc_hex_out = sbc_hex_out.decode("utf-8")

However when I try to run it I get the below, which I think relates to the BIT STRING encoding on the Message-Identifier, which I think I've got correct? (With (int(4379), 16) being a 2-tuple of positive int (bit string uint value, bit string length) as per the docs)

    self._cont._safechk_val(v)
  File "/usr/local/lib/python3.8/dist-packages/pycrate-0.4-py3.8.egg/pycrate_asn1rt/asnobj_construct.py", line 748, in _safechk_val
    raise(ASN1ObjErr('{0}: invalid value, {1!r}'.format(self.fullname(), val)))
pycrate_asn1rt.err.ASN1ObjErr: Write-Replace-Warning-Request.protocolIEs._item_: invalid value, {'id': 5, 'Value': (4379, 16)}

Any pointers? I know it's probably something dead simple. (The upper case V in value is used in the SBC.py library, which is compiled from the ASN1 definition attached to the first post.

I parsed some S1 messages with some of the example code, and looked at the Maco-eNodeB-ID as that's also encoded as BIT STRING and confirmed the way I'm formatting it is the same as on the S1 messages, so I'm a bit out of ideas?

PyCrate_issue_127_SBc.py.txt SBC.py.txt SBC.asn.txt

p1-bmu commented 3 years ago

This is a special case with all RAN protocols, where the Value field of each ProtocolIE is an OPEN type, and then depend of its id. So you need to provide the reference to the specific type you put in Value:

protocol_ies.append({'id': 5, 'Value': ('Message-Identifier', (4379, 16))})
protocol_ies.append({'id': 11, 'Value': ('Serial-Number', (4379, 16))})
nickvsnetworking commented 3 years ago

Thanks again @p1-bmu ,

That makes sense we need to define the reference to the type of value,

I just tried running it with the changes you specified, same result though:

File "/usr/local/lib/python3.8/dist-packages/pycrate-0.4-py3.8.egg/pycrate_asn1rt/asnobj_construct.py", line 748, in _safechk_val
    raise(ASN1ObjErr('{0}: invalid value, {1!r}'.format(self.fullname(), val)))

pycrate_asn1rt.err.ASN1ObjErr: Write-Replace-Warning-Request.protocolIEs._item_: invalid value, {'id': 5, 'Value': ('Message-Identifier', (4379, 16))}

Is there something else I'm missing?

I've attached the simplified Python script and contents of SBc.py generated by PyCrate for the ASN.1 dict,

I'd appreciate any pointers, I know I've got a lot more IEs to populate after this, but hopefully once I know how to format one I can format all the others accordingly.

Issue_127.zip

p1-bmu commented 3 years ago

I guess you need to set also the criticality component in each protocolIE (see the definition of the ProtocolIE-Field object).

p1-bmu commented 2 years ago

Any additional feedback @nickvsnetworking ? Or I'll close this.

nickvsnetworking commented 2 years ago

Hi! I got there in the end with this, here's the code I used for anyone trying this themselves:

##Write-Replace-Warning-Request
list_of_tais = [\
    {'tai': {'pLMNidentity': bytes.fromhex(plmn_hex), 'tAC': b'\x4BB'}}]

warning_area_list = ('cell-ID-List', [{'pLMNidentity':  bytes.fromhex(plmn_hex), 'cell-ID': (167772161, 28)}, {'pLMNidentity':  bytes.fromhex(plmn_hex), 'cell-ID': (167772162, 28), 'iE-Extensions': [{'id': 199, 'criticality': 'reject', 'extensionValue': ('_unk_004', b'\x99\x99\x99\x99')}]}])
value = ('initiatingMessage', {'procedureCode': 0, 'criticality': 'reject', 'value': \
                               ('Write-Replace-Warning-Request', {'protocolIEs': [\
                                   {'id': 5, 'criticality': 'reject', 'value': ('Message-Identifier', (4370, 16))}, \
                                   {'id': 11, 'criticality': 'reject', 'value': ('Serial-Number', (43995, 16))}, \
                                   #{'id': 14, 'criticality': 'reject', 'value': ('List-of-TAIs', list_of_tais)}, \
                                   #{'id': 15, 'criticality': 'ignore', 'value': ('Warning-Area-List', warning_area_list)}, \
                                   {'id': 10, 'criticality': 'reject', 'value': ('Repetition-Period', 30)}, \
                                   #{'id': 21, 'criticality': 'reject', 'value': ('Extended-Repetition-Period', 8192)}, \
                                   {'id': 7, 'criticality': 'reject', 'value': ('Number-of-Broadcasts-Requested', 1)}, \
                                   {'id': 18, 'criticality': 'ignore', 'value': ('Warning-Type', b'\x01\x02')}, \
                                   #{'id': 17, 'criticality': 'ignore', 'value': ('Warning-Security-Information', b'\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1')}, \
                                   #{'id': 3, 'criticality': 'ignore', 'value': ('Data-Coding-Scheme', (85, 8))}, \
                                   #{'id': 16, 'criticality': 'ignore', 'value': ('Warning-Message-Content', b'\x01' + b'Test Alert Message - Please ignore')}, \
                                   #The phrase Emergency!
                                   {'id': 16, 'criticality': 'ignore', 'value': ('Warning-Message-Content', bytes.fromhex('01c576597e2ebbc7f950a8d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d1000a'))}, \

                                   #{'id': 19, 'criticality': 'ignore', 'value': ('Omc-Id', b'\x01')}, \
                                   #{'id': 20, 'criticality': 'reject', 'value': ('Concurrent-Warning-Message-Indicator', 'true')}\
                                   ]})})
sbc_asn1_object.set_val(value)
sbc_hex_out = binascii.hexlify(sbc_asn1_object.to_aper())
sbc_hex_out = sbc_hex_out.decode("utf-8")