-
Notifications
You must be signed in to change notification settings - Fork 9
Using the pycrate csn1 translator and runtime
CSN.1 is a notation used specifically to describe signaling messages' structures for GPRS and GMR-1 systems. Complete explanation on how it works are available on the csn1.info web site. As CSN.1 seems not used anymore in new technologies, there is not so much litterature about it.
CSN.1 is a notation to describe structures bit by bit. It makes no assumptions about types of values described: this is up to the developer to cast the values extracted according to a CSN.1 definition. To summerize, CSN.1 describes fields' names and how long in terms of bits they are, that's all !
The CSN.1 translator is a tool that translates CSN.1 syntax to Python syntax. The result can then be used with the pycrate CSN.1 runtime, in order to encode and decode buffers according to the structure described in the CSN.1 description.
The current code for handling CSN.1 (translation and runtime) is available in pycrate_csn1/. Python modules for handling specific CSN.1 objects for GPRS signalling are available in pycrate_csn1dir/. The modules for handling CSN.1 objects for GMR-1 signalling are available in pycrate_gmr1_csn1.
For the GPRS technology, all CSN.1 definitions have been extracted automatically from TS 44.018 (GSM/EDGE Radio Resource Control protocol), TS 44.060 (GPRS RLC / MAC protocol) and TS 24.008 (2G / 3G core network protocols). For the GMR-1 technology, they have been extracted from TS 101 376-4-8 (Mobile Radio Interface Layer 3), TS 101 376-4-12 (RLC/MAC protocol) and TS 101 376-4-13 (RRC protocol). Those definitions have been corrected syntaxically and also sometimes semantically in order to be processable (this is actually a fact that the CSN.1 specifications in the 3GPP and ETSI documents contain many errors).
Then, they have been translated to Python thanks to the CSN.1 to Python translator (see below). All those Python modules are now available for encoding / decoding values and buffers !
There is a current limitation in the handling of error's alternatives starting with "!", compared to standard alternatives starting with "|". The code translator parses those error's alternatives fine, just like any alternative, but drops them when generating the Python code. This is because the runtime for encoding / decoding is kept simple and does not propose specific handlers for dealing with decoding errors.
In order to translate a CSN.1 description to Python, we can extract it from the specification, for example the file pycrate_csn1dir/24008/classmark_3_value_part.csn which contains the CSN.1 definition of the MS Classmark 3.
The CSN.1 translator lies in the file pycrate_csn1/trans.py. The function translate_text() must be used to translate the CSN.1 definition:
>>> csntxt = open('./pycrate_csn1dir/mscm3.csn').read()
>>> from pycrate_csn1.trans import translate_text
>>> help(translate_text)
[...]
>>> Objs, ext, pytxt = translate_text(csntxt)
>>> list(Objs.keys())
['ecsd_multi_slot_capability',
'hscsd_multi_slot_capability',
'r_support',
'single_band_support',
'a5_bits',
'ms_positioning_method_capability',
'_8_psk_struct',
'classmark_3_value_part',
'ms_measurement_capability']
>>> print(pytxt)
# code automatically generated by pycrate_csn1
# change object type with type=CSN1T_BSTR (default type is CSN1T_UINT) in init
# add dict for value interpretation with dic={...} in CSN1Bit init
# add dict for key interpretation with kdic={...} in CSN1Alt init
from pycrate_csn1.csnobj import *
spare_bit = CSN1Bit(name='spare_bit')
Spare_bit = spare_bit
Spare_Bit = spare_bit
spare_bits = CSN1Bit(name='spare_bits', num=-1)
Spare_bits = spare_bits
Spare_Bits = spare_bits
ecsd_multi_slot_capability = CSN1Bit(name='ecsd_multi_slot_capability', bit=5)
[...]
classmark_3_value_part = CSN1List(name='classmark_3_value_part', trunc=True, list=[
CSN1Ref(obj=spare_bit),
CSN1Alt(alt={
'000': ('multiband_supported', [
CSN1Ref(obj=a5_bits)]),
'001': ('multiband_supported', [
CSN1Ref(obj=a5_bits),
[...]
The resulting text (in pytxt in the example) can be pasted into a Python source file to have to corresponding object working within Python, ready for encoding and decoding.
Here is an example on how to decode and re-encode a buffer corresponding to the MS Network Capability information element, as defined in the TS 24.008 specification.
In pycrate, the translated CSN.1 objects are available in the pycrate_csn1dir/ directory. We just need to import the definition from here:
>>> from binascii import hexlify, unhexlify
>>> csnbuf = unhexlify('e5e034')
>>> from pycrate_csn1dir.ms_network_capability_value_part import ms_network_capability_value_part
>>> MSNetCap = ms_network_capability_value_part.MS_network_capability_value_part.clone()
>>> help(MSNetCap)
[...]
>>> MSNetCap.from_bytes(csnbuf)
>>> MSNetCap.get_val()
[1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 0, 0, 0, 0], 0, 0, 0, 1, 1, 0, 1, 0, 0]
>>> MSNetCap
<ms_network_capability_value_part: [<<gea1_bits: 1>>[...]<geran_network_sharing_capability: 0>]>
>>> print(MSNetCap.show())
<ms_network_capability_value_part: [
<<gea1_bits: 1>>
<sm_capabilities_via_dedicated_channels: 1>
<sm_capabilities_via_gprs_channels: 1>
<ucs2_support: 0>
<ss_screening_indicator: 1>
<solsa_capability: 0>
<revision_level_indicator: 1>
<pfc_feature_mode: 1>
<<extended_gea_bits: [
<gea_2: 1>
<gea_3: 1>
<gea_4: 0>
<gea_5: 0>
<gea_6: 0>
<gea_7: 0>]>>
<lcs_va_capability: 0>
<ps_inter_rat_ho_from_geran_to_utran_iu_mode_capability: 0>
<ps_inter_rat_ho_from_geran_to_e_utran_s1_mode_capability: 0>
<emm_combined_procedures_capability: 1>
<isr_support: 1>
<srvcc_to_geran_utran_capability: 0>
<epc_capability: 1>
<nf_capability: 0>
<geran_network_sharing_capability: 0>]>
>>> val = MSNetCap()
>>> # we will set all GEA bits to 1
>>> val[8]
[1, 1, 0, 0, 0, 0]
>>> val[8] = [1, 1, 1, 1, 1, 1]
>>> val
[1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 1, 1, 1, 1], 0, 0, 0, 1, 1, 0, 1, 0, 0]
>>> MSNetCap.set_val(val)
>>> print(MSNetCap.show())
<ms_network_capability_value_part: [
<<gea1_bits: 1>>
<sm_capabilities_via_dedicated_channels: 1>
<sm_capabilities_via_gprs_channels: 1>
<ucs2_support: 0>
<ss_screening_indicator: 1>
<solsa_capability: 0>
<revision_level_indicator: 1>
<pfc_feature_mode: 1>
<<extended_gea_bits: [
<gea_2: 1>
<gea_3: 1>
<gea_4: 1>
<gea_5: 1>
<gea_6: 1>
<gea_7: 1>]>>
<lcs_va_capability: 0>
<ps_inter_rat_ho_from_geran_to_utran_iu_mode_capability: 0>
<ps_inter_rat_ho_from_geran_to_e_utran_s1_mode_capability: 0>
<emm_combined_procedures_capability: 1>
<isr_support: 1>
<srvcc_to_geran_utran_capability: 0>
<epc_capability: 1>
<nf_capability: 0>
<geran_network_sharing_capability: 0>]>
>>> hexlify(MSNetCap.to_bytes())
b'e5fe34'
>>> MSNetCap.get_bl()
24
Here we see that, basically, a CSN.1 object in pycrate is used just like an Envelope object, as introduced here. CSN.1 objects' values are (list of) unsigned integers values, with the exception of CSN.1 objects which define an alternative to several possibilities depending of a given bit-string; in this case, this bit-string is kept as is in the object's value.
The following methods are the most important to work with CSN.1 objects:
- to_bytes() / from_bytes(), to decode and encode bytes' buffers
- get_val() (or simply calling the object's instance) / set_val() to get and set values
- get_bl() to get the length in bits corresponding to the value set
- show() to get a nice printable representation of the object's value
- bin() and hex() are also available to get a binary or hex-stream of the object's value
- clone() returns an independent clone of an object
The following example is about decoding, modifying and reencoding an MS Radio Access Capability information element, as defined in the TS 24.008 specification. The CSN.1 object is quite more complex than the previous example:
>>> from binascii import hexlify, unhexlify
>>> csnbuf = unhexlify('1a53432b259ef9890040009dd9c633120080013a332c662401000260')
>>> from pycrate_csn1dir.ms_ra_capability_value_part import ms_ra_capability_value_part
>>> MSRadioCap = ms_ra_capability_value_part.clone()
>>> MSRadioCap
<ms_ra_capability_value_part(CSN1List)>
>>> MSRadioCap.from_bytes(csnbuf)
>>> print(MSRadioCap.show())
<ms_ra_capability_value_part: [
<<ms_ra_capability_value_part_struct: [
<{'0001' (GSM E --note that GSM E covers GSM P): [
<access_capabilities: <access_capabilities_struct: [
<length: 82>
<[
<access_capabilities: <content: [
<rf_power_capability: 4>
<{'1', [
<a5_bits: <a5_bits: [
<a5_1: 1>
<a5_2: 0>
<a5_3: 1>
<a5_4: 0>
<a5_5: 0>
<a5_6: 0>
<a5_7: 0>]>>]}>
<es_ind: 1>
<ps: 1>
<vgcs: 0>
<vbs: 0>
<{'1', [
<multislot_capability: <multislot_capability_struct: [
<{'0', []}>
<{'1', [
<gprs_multislot_class: 12>
<gprs_extended_dynamic_allocation_capability: 1>]}>
<{'0', []}>
<{'0', []}>
<{'1', [
<egprs_multislot_class: 12>
<egprs_extended_dynamic_allocation_capability: 1>]}>
<{'1', [
<dtm_gprs_multi_slot_class: 3>
<single_slot_dtm: 0>
<{'1', [
<dtm_egprs_multi_slot_class: 3>]}>]}>]>>]}>
<{'1', [
<_8psk_power_capability: 2>]}>
<compact_interference_measurement_capability: 0>
<revision_level_indicator: 1>
<umts_fdd_radio_access_technology_capability: 1>
<umts_3_84_mcps_tdd_radio_access_technology_capability: 0>
<cdma_2000_radio_access_technology_capability: 0>
<umts_1_28_mcps_tdd_radio_access_technology_capability: 0>
<geran_feature_package_1: 1>
<{'0', []}>
<modulation_based_multislot_class_support: 0>
<{'1', [
<high_multislot_capability: 0>]}>
<'0'>
<gmsk_multislot_power_profile: 0>
<_8_psk_multislot_power_profile: 0>
<multiple_tbf_capability: 0>
<downlink_advanced_receiver_performance: 1>
<extended_rlc_mac_control_message_segmentation_capability: 0>
<dtm_enhancements_capability: 0>
<{'0', []}>
<ps_handover_capability: 0>
<dtm_handover_capability: 0>
<{'0', []}>
<flexible_timeslot_assignment: 0>
<gan_ps_handover_capability: 0>
<rlc_non_persistent_mode: 0>
<reduced_latency_capability: 0>
<uplink_egprs2: 0>
<downlink_egprs2: 0>
<e_utra_fdd_support: 1>
<e_utra_tdd_support: 0>
<geran_to_e_utra_support_in_geran_packet_transfer_mode: 1>
<priority_based_reselection_support: 1>]>>
<[]>]>]>>]}>
<{'1', [
<<ms_ra_capability_value_part_struct: [
<{'0111' (GSM 850): [
<access_capabilities: <access_capabilities_struct: [
<length: 51>
<[
<access_capabilities: <content: [
<rf_power_capability: 4>
<{'0', []}>
<es_ind: 1>
<ps: 1>
<vgcs: 0>
<vbs: 0>
<{'0', []}>
<{'1', [
<_8psk_power_capability: 2>]}>
<compact_interference_measurement_capability: 0>
<revision_level_indicator: 1>
<umts_fdd_radio_access_technology_capability: 1>
<umts_3_84_mcps_tdd_radio_access_technology_capability: 0>
<cdma_2000_radio_access_technology_capability: 0>
<umts_1_28_mcps_tdd_radio_access_technology_capability: 0>
<geran_feature_package_1: 1>
<{'0', []}>
<modulation_based_multislot_class_support: 0>
<{'1', [
<high_multislot_capability: 0>]}>
<'0'>
<gmsk_multislot_power_profile: 0>
<_8_psk_multislot_power_profile: 0>
<multiple_tbf_capability: 0>
<downlink_advanced_receiver_performance: 1>
<extended_rlc_mac_control_message_segmentation_capability: 0>
<dtm_enhancements_capability: 0>
<{'0', []}>
<ps_handover_capability: 0>
<dtm_handover_capability: 0>
<{'0', []}>
<flexible_timeslot_assignment: 0>
<gan_ps_handover_capability: 0>
<rlc_non_persistent_mode: 0>
<reduced_latency_capability: 0>
<uplink_egprs2: 0>
<downlink_egprs2: 0>
<e_utra_fdd_support: 1>
<e_utra_tdd_support: 0>
<geran_to_e_utra_support_in_geran_packet_transfer_mode: 1>
<priority_based_reselection_support: 1>]>>
<[]>]>]>>]}>
<{'1', [
<<ms_ra_capability_value_part_struct: [
<{'0100' (GSM 1900): [
<access_capabilities: <access_capabilities_struct: [
<length: 51>
<[
<access_capabilities: <content: [
<rf_power_capability: 1>
<{'0', []}>
<es_ind: 1>
<ps: 1>
<vgcs: 0>
<vbs: 0>
<{'0', []}>
<{'1', [
<_8psk_power_capability: 2>]}>
<compact_interference_measurement_capability: 0>
<revision_level_indicator: 1>
<umts_fdd_radio_access_technology_capability: 1>
<umts_3_84_mcps_tdd_radio_access_technology_capability: 0>
<cdma_2000_radio_access_technology_capability: 0>
<umts_1_28_mcps_tdd_radio_access_technology_capability: 0>
<geran_feature_package_1: 1>
<{'0', []}>
<modulation_based_multislot_class_support: 0>
<{'1', [
<high_multislot_capability: 0>]}>
<'0'>
<gmsk_multislot_power_profile: 0>
<_8_psk_multislot_power_profile: 0>
<multiple_tbf_capability: 0>
<downlink_advanced_receiver_performance: 1>
<extended_rlc_mac_control_message_segmentation_capability: 0>
<dtm_enhancements_capability: 0>
<{'0', []}>
<ps_handover_capability: 0>
<dtm_handover_capability: 0>
<{'0', []}>
<flexible_timeslot_assignment: 0>
<gan_ps_handover_capability: 0>
<rlc_non_persistent_mode: 0>
<reduced_latency_capability: 0>
<uplink_egprs2: 0>
<downlink_egprs2: 0>
<e_utra_fdd_support: 1>
<e_utra_tdd_support: 0>
<geran_to_e_utra_support_in_geran_packet_transfer_mode: 1>
<priority_based_reselection_support: 1>]>>
<[]>]>]>>]}>
<{'0', []}>]>>]}>]>>]}>]>>
<[<spare_bits: [0,
0,
0,
0]>]>]>
>>> val = MSRadioCap()
>>> val
[[['0001', [...] [[0, 0, 0, 0]]]
>>> val[0][1][1][1] # GSM 1900 support
['1', [['0100', [51, [[1, ['0'], 1, 1, 0, 0, ['0'], ['1', 2], 0, 1, 1, 0, 0, 0, 1, ['0'], 0, ['1', 0], '0', 0, 0, 0, 1, 0, 0, ['0'], 0, 0, ['0'], 0, 0, 0, 0, 0, 0, 1, 0, 1, 1], []]]], ['0']]]
>>> # deleting GSM 1900 support indication by setting a '0', instead of '1', [...]
>>> del val[0][1][1][1][1]
>>> val[0][1][1][1][0] = '0'
>>> val[0][1][1][1]
['0']
>>> MSRadioCap.set_val(val)
>>> MSRadioCap.get_bl()
161
>>> # adding 3 spare bits at the end to byte-align the structure
>>> val[1]
[[0, 0, 0, 0]]
>>> val[1][0] = [0, 0, 0, 0, 0, 0, 0]
>>> val[1]
[[0, 0, 0, 0, 0, 0, 0]]
>>> MSRadioCap.set_val(val)
>>> print(MSRadioCap.show())
<ms_ra_capability_value_part: [
<<ms_ra_capability_value_part_struct: [
<{'0001' (GSM E --note that GSM E covers GSM P): [
<access_capabilities: <access_capabilities_struct: [
<length: 82>
<[
<access_capabilities: <content: [
<rf_power_capability: 4>
<{'1', [
[...]
<[]>]>]>>]}>
<{'1', [
<<ms_ra_capability_value_part_struct: [
<{'0111' (GSM 850): [
<access_capabilities: <access_capabilities_struct: [
<length: 51>
<[
[...]
<[]>]>]>>]}>
<{'0', []}>]>>]}>]>>
<[<spare_bits: [0,
0,
0,
0,
0,
0,
0]>]>]>
>>> hexlify(MSRadioCap.to_bytes())
b'1a53432b259ef9890040009dd9c633120080013000'
The CSN.1 runtime in pycrate_csn1/csnobj.py supports two different customizations.
The default behaviour of the runtime is to the handle CSN.1 Bit objects' values as unsigned integer. It is however possible to change this to have all basic values handled as bit-strings, by setting the CSN1Bit class attribute _type to CSN1T_BSTR instead of CSN1T_UINT.
>>> from pycrate_csn1.csnobj import CSN1Bit, CSN1T_BSTR, CSN1T_UINT
>>> CSN1Bit._type = CSN1T_BSTR
>>> MSRadioCap = msracap.MS_RA_capability_value_part.clone()
>>> MSRadioCap.from_bytes(csnbuf)
>>> MSRadioCap()
[[['0001', ['1010010', [['100', ['1', ['1', '0', '1', '0', '0', '0', '0']], [...] []]]], ['0']]]]]], [['0', '0', '0', '0']]]
For CSN.1 alternatives' objects (defined with the class CSN1Alt), it is possible to specify a dictionnary of lookups between alternatives' selectors and human-readable strings. This helps when printing the value representation, by enhancing the name of the selected part.
Such an enhancement is made for instance in the Python definition pycrate_csn1dir/ms_ra_capability_value_part.py. A dict _AccessTechnoType_dict is defined manually in the beginning of the Python module, and passed as argument of the CSN1Alt instance within the ms_ra_capability_value_part_struct object:
ms_ra_capability_value_part_struct = CSN1List(name='ms_ra_capability_value_part_struct', list=[
CSN1Alt(kdic=_AccessTechnoType_dict, alt={
'0000': ('access_technology_type', [
CSN1Ref(obj=access_capabilities_struct)]),
[...]
}),
CSN1Alt(alt={
'0': ('', []),
'1': ('', [
CSN1SelfRef()])})])
Then, when representing a value with this object, we get an enhanced description, for instance:
{'0111' (GSM 850): [...]}
instead of simply:
{'0111': [...]}