ASN.1 Made Simple — Constraints

In its most basic form, a constraint limits a set of valid values for a given type or field. By limiting the set of values, some constraints could result in a smaller encoding size. For example, a value for INTEGER (0 .. 200) can be encoded into a single byte.

Here we'll show examples of some basic constraints that apply to a single field (or a single type), as well as more complex constraints that include multiple fields of a constructed type (for example, the value of one field determines whether another field is present).

Basic Constraints

Permitted Alphabet (applicable to strings)
 
       HardToReadChars ::= IA5String (FROM("8BI10OD5S"))

Pattern (RegEx-like)
 
       LicensePlate ::= IA5String (PATTERN "[0-9]#4(-[A-Z]#2)?") -- NNNN[-NN]

Value Size (applicable to strings, sequence/set of)
 
       LicensePlate ::= IA5String (SIZE (4..7))
       CarPark ::= SEQUENCE SIZE (1..25) OF LicensePlate

Value Range (applicable to scalar types)
 
       CarSpeed ::= INTEGER (0..200)

Single Value (applicable to all types)
 
       WarningColors ::= UTF8String ("Red" | "Yellow")
       InfoColors ::= UTF8String ("Blue" | "White")
       CitySpeedLimit ::= INTEGER (25 | 30 | 40)
       HighwaySpeedLimit ::= INTEGER (40 | 50 | 60 | 70)

Contained Subtype (applicable to all types)
 
       SignColors ::= UTF8String (InfoColors UNION WarningColors)
       SpeedLimitSigns ::= INTEGER (CitySpeedLimit | HighwaySpeedLimit | 10 | 65)
       RuralSpeedLimit ::= INTEGER (CitySpeedLimit INTERSECTION HighwaySpeedLimit)

Containing/Encoded By (applicable to octet/bit strings)
 
       PerInside ::= OCTET STRING (
                          CONTAINING Doc 
                          ENCODED BY { joint-iso-itu-t asn1(1) 
                                       packed-encoding(3) 
                                       basic(0) 
                                       unaligned(1)}) 

       pdf OBJECT IDENTIFIER ::= { iso(1) 
                                   member-body(2) 
                                   us(840) 
                                   adobe(113583) 
                                   acrobat(1)}

       Doc ::= OCTET STRING (ENCODED BY pdf)

Conditional Dependency of Fields

Let's say we're defining an Address structure to be used for various countries. We know that postal code formats depend on the country, for example, in the United States it consists of 5 or 9 digits, in Canada it consists of 6 alternating alphanumeric characters, etc. Below we show an ASN.1 schema alongside a JSON Schema example. The ASN.1 schema uses the WITH COMPONENTS constraint (also known as an inner subtype constraint), which is applicable only to structured types and can constrain component values and the presence of optional components.

ASN.1 Schema

Schema DEFINITIONS AUTOMATIC TAGS ::= BEGIN

Address ::= SEQUENCE {
   street-address UTF8String,
   country UTF8String -- see a note below, 
   postal-code UTF8String
} ((WITH COMPONENTS {
       ..., 
       country ("USA"), 
       postal-code (PATTERN "[0-9]#5(-[0-9]#4)?")
    } 
  | WITH COMPONENTS {
       ..., 
       country ("Canada"), 
       postal-code (PATTERN "[0-9][A-Z][0-9] 
                                [A-Z][0-9][A-Z]")
    }  
  | WITH COMPONENTS {
       ..., 
       country ("Netherlands"), 
       postal-code (PATTERN "[0-9]#4 [A-Z]#2")
    }
  ))

END
JSON Schema

{
  "type": "object",
  "properties": {
    "street_address": {
      "type": "string"
    },
    "country": {
      "enum": ["USA", "Canada", "Netherlands"]
    }
  },
  "allOf": [
    {
      "if": {
        "properties": { "country": { "const": 
                                          "USA" } }
      },
      "then": {
        "properties": { "postal_code": { "pattern":
                         "[0-9]{5}(-[0-9]{4})?" } }
      }
    },
    {
      "if": {
        "properties": { "country": { "const": 
                                       "Canada" } }
      },
      "then": {
        "properties": { "postal_code": { "pattern": 
              "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
      }
    },
    {
      "if": {
        "properties": { "country": { "const": 
                                  "Netherlands" } }
      },
      "then": {
        "properties": { "postal_code": { "pattern": 
                            "[0-9]{4} [A-Z]{2}" } }
      }
    }
  ]

Note: It is also possible to define 'country' as ENUMERATED, but that would require mapping enum item names to JSON strings, for example, country [JER: TEXT united-states AS "USA", canada AS "Canada", netherlands AS "Netherlands"] ENUMERATED {united-states, canada, netherlands}. Then the constraints will also reference the enums as WITH COMPONENTS {..., country (united-states), postal-code (PATTERN "[0-9]#5(-[0-9]#4)?")}

For the above schema(s) the following JSON values are valid:

{
"street_address": "300 E. Street SW, Suite 5R30",
"country": "USA",
"postal_code": "20546"
}
{
"street_address": "6767 Route de l'Aeroport",
"country": "Canada",
"postal_code": "J3Y 8Y9"
}


This JSON is invalid:

{
"street_address": "6767 Route de l'Aeroport",
"country": "Canada",
"postal_code": "12345"
}


Here is another example of the WITH COMPONENTS constraint. Let's say we define a car's features:

 
       CarFeatures ::= SEQUENCE     {
              passengers            INTEGER OPTIONAL,
              maxspeed              INTEGER OPTIONAL }


Now we want to ensure that specific kinds of cars include certain features:

 
       SchoolBus ::= CarFeatures 
                      (WITH COMPONENTS {passengers (10..50) PRESENT, maxspeed ABSENT} ) 

       RaceCar  ::= CarFeatures 
                      (WITH COMPONENTS {..., maxspeed (300) PRESENT} )

We can see that a SchoolBus requires a certain number of passengers and the maxspeed is not used, while a Race car requires an exact maxspeed, yet no other restrictions.