Commit e57fa13f authored by Luke Mewburn's avatar Luke Mewburn
Browse files

compat_asn1: improve constraint checking

Check for differences in non-SIZE constraints and DEFAULTs.
parent e0e0bac5
Loading
Loading
Loading
Loading
Loading
+92 −37
Original line number Diff line number Diff line
@@ -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
@@ -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}")
@@ -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(
@@ -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())
@@ -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()
@@ -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:
@@ -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)
+1 −1
Original line number Diff line number Diff line
@@ -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
+6 −1
Original line number Diff line number Diff line
@@ -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]'",
@@ -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]'",
@@ -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": [