Loading testing/compat_asn1.py +92 −37 Original line number Diff line number Diff line Loading @@ -28,6 +28,15 @@ def dict_swap(dictionary: dict) -> dict: return {value: key for key, value in dictionary.items()} def repr_constraint(constraint: pa_asnobj.ASN1Range | int) -> str: """Return constraint as LOWER..UPPER (for ASN1Range) or VALUE (for int).""" if type(constraint) is int: return str(constraint) lb = str(constraint.lb) if constraint.lb is not None else "MIN" ub = str(constraint.ub) if constraint.ub is not None else "MAX" return f"{lb}..{ub}" def repr_modname(asnobj: pa_asnobj.ASN1Obj) -> str: """Return name of `asnobj` as MODULE.NAME or NAME.""" result = asnobj._name Loading Loading @@ -212,12 +221,18 @@ class CompatCheck: return result def _types_already_seen( self, fa_type: pa_asnobj.ASN1Obj, fb_type: pa_asnobj.ASN1Obj self, fa_type: pa_asnobj.ASN1Obj, fb_type: pa_asnobj.ASN1Obj, seenprefix: str = "", ) -> bool: """Return True if fa_type and fb_type have already been seen.""" """Return True if `fa_type` and `fb_type` have already been seen. `seenprefix` allows for different seen check namespaces. """ fa_repr = repr_type_tagpath(fa_type) fb_repr = repr_type_tagpath(fb_type) seenkey = f"{fa_repr} and {fb_repr}" seenkey = f"{seenprefix}{fa_repr} and {fb_repr}" logging.info(f"Seen check {seenkey}") if seenkey in self._seen: logging.info(f"Skipping, already seen {seenkey}") Loading @@ -225,32 +240,53 @@ class CompatCheck: self._seen.add(seenkey) return False def _compare_enum(self, fa_enum: pa_asnobj.ENUM, fb_enum: pa_asnobj.ENUM) -> None: """Compare ASN.1 ENUMERATED `fa_enum` and `fb_enum`.""" assert fa_enum.TYPE == pa_asnobj.TYPE_ENUM assert fb_enum.TYPE == pa_asnobj.TYPE_ENUM def _decode_constraints(self, asnobj: pa_asnobj.ASN1Obj) -> str: """Return representation of any constraints and defaults of `asnobj`.""" coding = asnobj.TYPE if coding == pa_asnobj.TYPE_OID: return coding fa_desc = repr_type_name(fa_enum) fb_desc = repr_type_name(fb_enum) logging.info(f"Comparing enum {fa_desc} with {fb_desc}") constraints = asnobj.get_const() if self._types_already_seen(fa_enum, fb_enum): return sz = [C for C in constraints if C["type"] == pa_asnobj.CONST_SIZE] if sz: # SIZE constraint reducesz = pycrate_asn1c.setobj.reduce_setdicts(sz) for constraint in reducesz.root: coding = f"{coding} (SIZE ({repr_constraint(constraint=constraint)}))" fa_tags = dict_swap(fa_enum.get_cont()) fb_tags = dict_swap(fb_enum.get_cont()) val = [C for C in constraints if C["type"] == pa_asnobj.CONST_VAL] if val: # Value constraint reduceval = pycrate_asn1c.setobj.reduce_setdicts(val) for constraint in reduceval.root: coding = f"{coding} ({repr_constraint(constraint=constraint)})" for tag, ca_name in fa_tags.items(): cb_name = fb_tags.get(tag, None) if cb_name is None: self._process_error( f"{fa_desc} missing enum '{ca_name}({tag})'", ) continue # tag not in fb_enum, stop checking # Add DEFAULT (if present) if asnobj._flag is not None and pa_asnobj.FLAG_DEF in asnobj._flag: defval = asnobj._to_asn1(asnobj._flag[pa_asnobj.FLAG_DEF]) coding = f"{coding} DEFAULT {defval}" if ca_name != cb_name: return coding def _compare_constraints( self, fa_type: pa_asnobj.ASN1Obj, fb_type: pa_asnobj.ASN1Obj ) -> None: """Compare ASN.1 constraints of `fa_type` and `fb_type`.""" fa_desc = repr_type_name(fa_type) fb_desc = repr_type_name(fb_type) logging.info(f"Comparing constraint of {fa_desc} with {fb_desc}") if self._types_already_seen( fa_type=fa_type, fb_type=fb_type, seenprefix="CONSTRAINT: " ): return fa_constraint = self._decode_constraints(asnobj=fa_type) fb_constraint = self._decode_constraints(asnobj=fb_type) if fa_constraint != fb_constraint: self._process_error( f"{fa_desc}.{ca_name}({tag}) renamed to '{cb_name}({tag})'", f"{fa_desc} constraint '{fa_constraint}' changed to '{fb_constraint}'", ) def _compare_container( Loading @@ -264,7 +300,7 @@ class CompatCheck: fb_desc = repr_type_name(fb_type) logging.info(f"Comparing container {fa_desc} with {fb_desc}") if self._types_already_seen(fa_type, fb_type): if self._types_already_seen(fa_type=fa_type, fb_type=fb_type): return fa_tags = self._container_to_tag_dict(container=fa_type.get_cont()) Loading Loading @@ -322,7 +358,7 @@ class CompatCheck: # TODO: .name may end with "._item_"; removesuffix? if self._types_already_seen(fa_type, fb_type): if self._types_already_seen(fa_type=fa_type, fb_type=fb_type): return fa_of = fa_type.get_cont() Loading @@ -330,6 +366,34 @@ class CompatCheck: self._compare_asntype(fa_type=fa_of, fb_type=fb_of) def _compare_enum(self, fa_enum: pa_asnobj.ENUM, fb_enum: pa_asnobj.ENUM) -> None: """Compare ASN.1 ENUMERATED `fa_enum` and `fb_enum`.""" assert fa_enum.TYPE == pa_asnobj.TYPE_ENUM assert fb_enum.TYPE == pa_asnobj.TYPE_ENUM fa_desc = repr_type_name(fa_enum) fb_desc = repr_type_name(fb_enum) logging.info(f"Comparing enum {fa_desc} with {fb_desc}") if self._types_already_seen(fa_type=fa_enum, fb_type=fb_enum): return fa_tags = dict_swap(fa_enum.get_cont()) fb_tags = dict_swap(fb_enum.get_cont()) for tag, ca_name in fa_tags.items(): cb_name = fb_tags.get(tag, None) if cb_name is None: self._process_error( f"{fa_desc} missing enum '{ca_name}({tag})'", ) continue # tag not in fb_enum, stop checking if ca_name != cb_name: self._process_error( f"{fa_desc}.{ca_name}({tag}) renamed to '{cb_name}({tag})'", ) def _compare_asntype( self, fa_type: pa_asnobj.ASN1Obj, fb_type: pa_asnobj.ASN1Obj ) -> None: Loading @@ -345,16 +409,7 @@ class CompatCheck: # No further processing of this type can be performed return if fa_type.TYPE in pa_asnobj.TYPE_CONST_SIZE: # Check constraint text as is. # TODO: enhanced to decode each constraint fa_const = [c["text"] for c in fa_type.get_const()] fb_const = [c["text"] for c in fb_type.get_const()] if fa_const != fb_const: self._process_error( f"{fa_desc} constraint '{','.join(fa_const)}' changed to '{','.join(fb_const)}'", ) self._compare_constraints(fa_type=fa_type, fb_type=fb_type) if fa_type.TYPE is pa_asnobj.TYPE_ENUM: self._compare_enum(fa_enum=fa_type, fb_enum=fb_type) Loading testing/dependencies/asn/IPAccessPDUCompatCheck.asn +1 −1 Original line number Diff line number Diff line Loading @@ -84,7 +84,7 @@ IPIRIContents ::= SEQUENCE -- When internetAccessType is Wireless LAN, this field should contain a string which -- uniquely identifies the wireless accesspoint within the SvP domain -- New implementations are encouraged to use the location [24] parameter where possible. pOPPortNumber [8] INTEGER (0..4294967295) OPTIONAL, pOPPortNumber [8] INTEGER (0..2147483647) OPTIONAL, -- COMPAT: constraint change -- The POP port number used by the target callBackNumber [9] UTF8String (SIZE (1..20)) OPTIONAL, -- The number used to call-back the target Loading testing/test_config.json +6 −1 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ "33128/r15/TS33128Payloads.asn": [ "TS33128Payloads.XIRIPayload.relativeOID[1] renamed to 'xIRIPayloadOID[1]'", "TS33128Payloads.GlobalRANNodeID.aNNodeID[2] type 'CHOICE' changed to 'TS33128Payloads.ANNodeID'", "TS33128Payloads.BarometricPressure constraint 'INTEGER (30000..155000)' changed to 'INTEGER (30000..115000)'", "TS33128Payloads.SMSMessage.transferStatus[4] renamed to 'linkTransferStatus[4]'", "TS33128Payloads.LALSReport missing field 'pEI[2]'", "TS33128Payloads.IRIPayload.relativeOID[1] renamed to 'iRIPayloadOID[1]'", Loading Loading @@ -104,8 +105,11 @@ "compat_files": { "33128/r17/TS33128Payloads.asn": [ "TS33128Payloads.EUTRALocation.ageOfLocationInfo[3] type 'INTEGER' changed to 'TS33128Payloads.AgeOfLocation'", "TS33128Payloads.EUTRALocation.ageOfLocationInfo[3] constraint 'INTEGER' changed to 'INTEGER (0..32767)'", "TS33128Payloads.NRLocation.ageOfLocationInfo[3] type 'INTEGER' changed to 'TS33128Payloads.AgeOfLocation'", "TS33128Payloads.NRLocation.ageOfLocationInfo[3] constraint 'INTEGER' changed to 'INTEGER (0..32767)'", "TS33128Payloads.N3GALocation.ageOfLocationInfo[11] type 'INTEGER' changed to 'TS33128Payloads.AgeOfLocation'", "TS33128Payloads.N3GALocation.ageOfLocationInfo[11] constraint 'INTEGER' changed to 'INTEGER (0..32767)'", "TS33128Payloads.PointUncertaintyCircle.uncertainty[2] renamed to 'deprecatedUncertainty[2]'", "TS33128Payloads.UncertaintyEllipse.semiMajor[1] renamed to 'deprecatedSemiMajor[1]'", "TS33128Payloads.UncertaintyEllipse.semiMinor[2] renamed to 'deprecatedSemiMinor[2]'", Loading Loading @@ -223,7 +227,8 @@ "IPAccessPDU.IPTruncatedPacketRENAME new field 'shouldBeOptional[2]' is not OPTIONAL", "Type IPTruncatedPacket not present", "IPAccessPDU.AccessEventType missing enum 'startOfInterceptionWithSessionActive(7)'", "IPAccessPDU.IPIRIContents.targetNetworkID[5] constraint 'SIZE (1..20)' changed to 'SIZE (1..40)'" "IPAccessPDU.IPIRIContents.targetNetworkID[5] constraint 'UTF8String (SIZE (1..20))' changed to 'UTF8String (SIZE (1..40))'", "IPAccessPDU.IPIRIContents.pOPPortNumber[8] constraint 'INTEGER (0..4294967295)' changed to 'INTEGER (0..2147483647)'" ] }, "dependencies": [ Loading Loading
testing/compat_asn1.py +92 −37 Original line number Diff line number Diff line Loading @@ -28,6 +28,15 @@ def dict_swap(dictionary: dict) -> dict: return {value: key for key, value in dictionary.items()} def repr_constraint(constraint: pa_asnobj.ASN1Range | int) -> str: """Return constraint as LOWER..UPPER (for ASN1Range) or VALUE (for int).""" if type(constraint) is int: return str(constraint) lb = str(constraint.lb) if constraint.lb is not None else "MIN" ub = str(constraint.ub) if constraint.ub is not None else "MAX" return f"{lb}..{ub}" def repr_modname(asnobj: pa_asnobj.ASN1Obj) -> str: """Return name of `asnobj` as MODULE.NAME or NAME.""" result = asnobj._name Loading Loading @@ -212,12 +221,18 @@ class CompatCheck: return result def _types_already_seen( self, fa_type: pa_asnobj.ASN1Obj, fb_type: pa_asnobj.ASN1Obj self, fa_type: pa_asnobj.ASN1Obj, fb_type: pa_asnobj.ASN1Obj, seenprefix: str = "", ) -> bool: """Return True if fa_type and fb_type have already been seen.""" """Return True if `fa_type` and `fb_type` have already been seen. `seenprefix` allows for different seen check namespaces. """ fa_repr = repr_type_tagpath(fa_type) fb_repr = repr_type_tagpath(fb_type) seenkey = f"{fa_repr} and {fb_repr}" seenkey = f"{seenprefix}{fa_repr} and {fb_repr}" logging.info(f"Seen check {seenkey}") if seenkey in self._seen: logging.info(f"Skipping, already seen {seenkey}") Loading @@ -225,32 +240,53 @@ class CompatCheck: self._seen.add(seenkey) return False def _compare_enum(self, fa_enum: pa_asnobj.ENUM, fb_enum: pa_asnobj.ENUM) -> None: """Compare ASN.1 ENUMERATED `fa_enum` and `fb_enum`.""" assert fa_enum.TYPE == pa_asnobj.TYPE_ENUM assert fb_enum.TYPE == pa_asnobj.TYPE_ENUM def _decode_constraints(self, asnobj: pa_asnobj.ASN1Obj) -> str: """Return representation of any constraints and defaults of `asnobj`.""" coding = asnobj.TYPE if coding == pa_asnobj.TYPE_OID: return coding fa_desc = repr_type_name(fa_enum) fb_desc = repr_type_name(fb_enum) logging.info(f"Comparing enum {fa_desc} with {fb_desc}") constraints = asnobj.get_const() if self._types_already_seen(fa_enum, fb_enum): return sz = [C for C in constraints if C["type"] == pa_asnobj.CONST_SIZE] if sz: # SIZE constraint reducesz = pycrate_asn1c.setobj.reduce_setdicts(sz) for constraint in reducesz.root: coding = f"{coding} (SIZE ({repr_constraint(constraint=constraint)}))" fa_tags = dict_swap(fa_enum.get_cont()) fb_tags = dict_swap(fb_enum.get_cont()) val = [C for C in constraints if C["type"] == pa_asnobj.CONST_VAL] if val: # Value constraint reduceval = pycrate_asn1c.setobj.reduce_setdicts(val) for constraint in reduceval.root: coding = f"{coding} ({repr_constraint(constraint=constraint)})" for tag, ca_name in fa_tags.items(): cb_name = fb_tags.get(tag, None) if cb_name is None: self._process_error( f"{fa_desc} missing enum '{ca_name}({tag})'", ) continue # tag not in fb_enum, stop checking # Add DEFAULT (if present) if asnobj._flag is not None and pa_asnobj.FLAG_DEF in asnobj._flag: defval = asnobj._to_asn1(asnobj._flag[pa_asnobj.FLAG_DEF]) coding = f"{coding} DEFAULT {defval}" if ca_name != cb_name: return coding def _compare_constraints( self, fa_type: pa_asnobj.ASN1Obj, fb_type: pa_asnobj.ASN1Obj ) -> None: """Compare ASN.1 constraints of `fa_type` and `fb_type`.""" fa_desc = repr_type_name(fa_type) fb_desc = repr_type_name(fb_type) logging.info(f"Comparing constraint of {fa_desc} with {fb_desc}") if self._types_already_seen( fa_type=fa_type, fb_type=fb_type, seenprefix="CONSTRAINT: " ): return fa_constraint = self._decode_constraints(asnobj=fa_type) fb_constraint = self._decode_constraints(asnobj=fb_type) if fa_constraint != fb_constraint: self._process_error( f"{fa_desc}.{ca_name}({tag}) renamed to '{cb_name}({tag})'", f"{fa_desc} constraint '{fa_constraint}' changed to '{fb_constraint}'", ) def _compare_container( Loading @@ -264,7 +300,7 @@ class CompatCheck: fb_desc = repr_type_name(fb_type) logging.info(f"Comparing container {fa_desc} with {fb_desc}") if self._types_already_seen(fa_type, fb_type): if self._types_already_seen(fa_type=fa_type, fb_type=fb_type): return fa_tags = self._container_to_tag_dict(container=fa_type.get_cont()) Loading Loading @@ -322,7 +358,7 @@ class CompatCheck: # TODO: .name may end with "._item_"; removesuffix? if self._types_already_seen(fa_type, fb_type): if self._types_already_seen(fa_type=fa_type, fb_type=fb_type): return fa_of = fa_type.get_cont() Loading @@ -330,6 +366,34 @@ class CompatCheck: self._compare_asntype(fa_type=fa_of, fb_type=fb_of) def _compare_enum(self, fa_enum: pa_asnobj.ENUM, fb_enum: pa_asnobj.ENUM) -> None: """Compare ASN.1 ENUMERATED `fa_enum` and `fb_enum`.""" assert fa_enum.TYPE == pa_asnobj.TYPE_ENUM assert fb_enum.TYPE == pa_asnobj.TYPE_ENUM fa_desc = repr_type_name(fa_enum) fb_desc = repr_type_name(fb_enum) logging.info(f"Comparing enum {fa_desc} with {fb_desc}") if self._types_already_seen(fa_type=fa_enum, fb_type=fb_enum): return fa_tags = dict_swap(fa_enum.get_cont()) fb_tags = dict_swap(fb_enum.get_cont()) for tag, ca_name in fa_tags.items(): cb_name = fb_tags.get(tag, None) if cb_name is None: self._process_error( f"{fa_desc} missing enum '{ca_name}({tag})'", ) continue # tag not in fb_enum, stop checking if ca_name != cb_name: self._process_error( f"{fa_desc}.{ca_name}({tag}) renamed to '{cb_name}({tag})'", ) def _compare_asntype( self, fa_type: pa_asnobj.ASN1Obj, fb_type: pa_asnobj.ASN1Obj ) -> None: Loading @@ -345,16 +409,7 @@ class CompatCheck: # No further processing of this type can be performed return if fa_type.TYPE in pa_asnobj.TYPE_CONST_SIZE: # Check constraint text as is. # TODO: enhanced to decode each constraint fa_const = [c["text"] for c in fa_type.get_const()] fb_const = [c["text"] for c in fb_type.get_const()] if fa_const != fb_const: self._process_error( f"{fa_desc} constraint '{','.join(fa_const)}' changed to '{','.join(fb_const)}'", ) self._compare_constraints(fa_type=fa_type, fb_type=fb_type) if fa_type.TYPE is pa_asnobj.TYPE_ENUM: self._compare_enum(fa_enum=fa_type, fb_enum=fb_type) Loading
testing/dependencies/asn/IPAccessPDUCompatCheck.asn +1 −1 Original line number Diff line number Diff line Loading @@ -84,7 +84,7 @@ IPIRIContents ::= SEQUENCE -- When internetAccessType is Wireless LAN, this field should contain a string which -- uniquely identifies the wireless accesspoint within the SvP domain -- New implementations are encouraged to use the location [24] parameter where possible. pOPPortNumber [8] INTEGER (0..4294967295) OPTIONAL, pOPPortNumber [8] INTEGER (0..2147483647) OPTIONAL, -- COMPAT: constraint change -- The POP port number used by the target callBackNumber [9] UTF8String (SIZE (1..20)) OPTIONAL, -- The number used to call-back the target Loading
testing/test_config.json +6 −1 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ "33128/r15/TS33128Payloads.asn": [ "TS33128Payloads.XIRIPayload.relativeOID[1] renamed to 'xIRIPayloadOID[1]'", "TS33128Payloads.GlobalRANNodeID.aNNodeID[2] type 'CHOICE' changed to 'TS33128Payloads.ANNodeID'", "TS33128Payloads.BarometricPressure constraint 'INTEGER (30000..155000)' changed to 'INTEGER (30000..115000)'", "TS33128Payloads.SMSMessage.transferStatus[4] renamed to 'linkTransferStatus[4]'", "TS33128Payloads.LALSReport missing field 'pEI[2]'", "TS33128Payloads.IRIPayload.relativeOID[1] renamed to 'iRIPayloadOID[1]'", Loading Loading @@ -104,8 +105,11 @@ "compat_files": { "33128/r17/TS33128Payloads.asn": [ "TS33128Payloads.EUTRALocation.ageOfLocationInfo[3] type 'INTEGER' changed to 'TS33128Payloads.AgeOfLocation'", "TS33128Payloads.EUTRALocation.ageOfLocationInfo[3] constraint 'INTEGER' changed to 'INTEGER (0..32767)'", "TS33128Payloads.NRLocation.ageOfLocationInfo[3] type 'INTEGER' changed to 'TS33128Payloads.AgeOfLocation'", "TS33128Payloads.NRLocation.ageOfLocationInfo[3] constraint 'INTEGER' changed to 'INTEGER (0..32767)'", "TS33128Payloads.N3GALocation.ageOfLocationInfo[11] type 'INTEGER' changed to 'TS33128Payloads.AgeOfLocation'", "TS33128Payloads.N3GALocation.ageOfLocationInfo[11] constraint 'INTEGER' changed to 'INTEGER (0..32767)'", "TS33128Payloads.PointUncertaintyCircle.uncertainty[2] renamed to 'deprecatedUncertainty[2]'", "TS33128Payloads.UncertaintyEllipse.semiMajor[1] renamed to 'deprecatedSemiMajor[1]'", "TS33128Payloads.UncertaintyEllipse.semiMinor[2] renamed to 'deprecatedSemiMinor[2]'", Loading Loading @@ -223,7 +227,8 @@ "IPAccessPDU.IPTruncatedPacketRENAME new field 'shouldBeOptional[2]' is not OPTIONAL", "Type IPTruncatedPacket not present", "IPAccessPDU.AccessEventType missing enum 'startOfInterceptionWithSessionActive(7)'", "IPAccessPDU.IPIRIContents.targetNetworkID[5] constraint 'SIZE (1..20)' changed to 'SIZE (1..40)'" "IPAccessPDU.IPIRIContents.targetNetworkID[5] constraint 'UTF8String (SIZE (1..20))' changed to 'UTF8String (SIZE (1..40))'", "IPAccessPDU.IPIRIContents.pOPPortNumber[8] constraint 'INTEGER (0..4294967295)' changed to 'INTEGER (0..2147483647)'" ] }, "dependencies": [ Loading