tson.io

Non-normative report

ASN.1 (X.680–X.683) → TSON

Revision 32·view raw ↗

Mapping ASN.1 (X.680–X.683 abstract syntax) → TSON Schema

Status: Non-normative report · Prepared against TSON 2026 Revision 32 (Working Draft) · Input to the Revision 33 development cycle. References to “planned additions” describe design directions under consideration for future revisions, not committed specification. Produced with AI assistance (Anthropic Claude) in collaboration with the TSON author; citations of external standards should be verified against primary sources before normative use. Schema source files are cited with the .tn extension per the current draft-period convention (.tn1 is reserved for the finalized version 1 release).

Scope and framing

Same framing as the prior reports: one-directional, strictness-first, no round-trip requirement. Per the decision to defer encodings, this covers the abstract syntax only — X.680 (types, constraints, extensibility), X.681 (information object classes), X.682 (table constraints), X.683 (parameterization). BER/DER/PER/OER/XER/JER are out of scope, with two footnotes: tags are preserved as annotations (they’re the ASN.1 analogue of protobuf field numbers), and JER (X.697, the JSON encoding rules) is the natural eventual validation target, playing the role protojson played in the protobuf report.

One observation before the tables, because it frames everything: ASN.1 is the most TSON-like of the three IDLs examined — in architecture, not just in type inventory. Its founding design decision (1984!) is the separation of abstract syntax from transfer syntax: one type system, many encoding rules, each specified independently against the same model. That is exactly TSON’s part 1 / part 2 / part 3 structure. The consequence is that ASN.1’s constructs are already encoding-independent, so almost nothing in this mapping is contaminated by wire concerns — the opposite of protobuf, where wire pragmatics leak upward (three int32 spellings), and of JSON Schema, where the instance model is the wire format. The mapping difficulties that do exist are in ASN.1’s late-1980s/90s power features: extensibility markers, constraint algebra, and the information object system.

TL;DR

  • The core inventory maps exceptionally well: ASN.1 INTEGER is arbitrary-precision (exact match to TSON integer — the first source language examined where this is true), NULL is a first-class single-value type (the third independent corroboration of the planned null atom), SEQUENCE is a closed record, CHOICE is a label-discriminated, exactly-one sum — TSON’s REQUIRED field group, member for member, completing the pattern started by protobuf’s oneof (which was the at-most-one OPTIONAL group).
  • ASN.1 subtype constraints are TSON refinements, nearly term-for-term: value ranges → min/max, SIZE → length/size facets, permitted alphabets and PATTERN → pattern refinement (with an XSD-lineage regex dialect that is friendlier to I-Regexp than either ECMA-262 or RE2), and WITH COMPONENTS inner subtyping → record refinement ^ { … }. The constraint EXCEPT operator is negation and is declined, consistent with prior verdicts.
  • X.681/X.682 information object classes with table constraints are ASN.1’s discriminator — a value in one component (typically an OID or integer) selecting the type of an open-type component via an object set. This is precisely the envelope pattern + discriminator token designed in the JSON Schema work, and object sets give the deferred sealed feature its most concrete consumer yet: a closed object set is a permits list; an extensible one ({...}) is the open case.
  • X.683 parameterized types map to TSON templates — the first source language with real generics, exercising a TSON capability JSON Schema and protobuf left idle. Monomorphization handles it by construction.
  • The systematic tension is extensibility markers (...) — ASN.1’s forward-compatibility machinery — versus TSON closure. Same resolution as proto3 open enums: convert closed (stricter), flag every extension marker in the report as a forward-compatibility divergence.

A. Type-by-type mapping

Legend: ✅ clean · ⬆ strengthens · ◑ transform/decision · ✗ drop-with-report · Ⓐ annotation-only.

Basic types

ASN.1TSON
BOOLEANboolean
INTEGERintegerexact — both unbounded, arbitrary precision. Named numbers (status INTEGER { idle(0), busy(1) }) → the type stays integer; names are Ⓐ (they are labels, not an enumeration — the type admits all integers)
INTEGER (0..255) etc.integer ^ { min: 0, max: 255 }✅ constraints → refinements, §B
ENUMERATED!enum [...] of identifiers◑ numbers Ⓐ; extensibility marker → §C
REALfloat64 or number◑ ASN.1 REAL admits base-2 and base-10 values plus special values; a policy decision (mirror of the protojson float-specials item) — recommend number for base-10-constrained REALs, float64 otherwise, flag per type
NULLplanned null atom✅ third corroboration — ASN.1 treats the null value as a real type with one value, exactly the planned construct
BIT STRINGbinary type + SIZE → length facet◑ named bits (flags BIT STRING { a(0), b(1) }) are a bitset idiom — lower to the binary type with names Ⓐ, or to set<token> when used purely as a flag set (converter flag; the set form is more honest abstractly, the binary form matches encodings)
OCTET STRINGbinary type
OBJECT IDENTIFIER / RELATIVE-OIDpattern-refined text (dotted form), or a candidate core oid type◑ OIDs are load-bearing in every real ASN.1 corpus (X.509, LDAP, SNMP) and are the discriminator values of §D — a core oid type earns its place the same way uuid did
OID-IRIuri-adjacent refined text

Character strings — the restricted-string family

UTF8Stringtext ✅. Everything else in the family is text plus a refinement, which is a satisfying collapse: ASN.1 needed a dozen string types because it predates Unicode consolidation; TSON expresses each as text ^ { pattern }:

ASN.1TSON refinement
IA5StringASCII-range pattern
PrintableStringthe X.680 printable alphabet as a class pattern
NumericString[0-9 ]* pattern
VisibleStringvisible-ASCII pattern
BMPString / UniversalStringtext (+ plane restriction if worth enforcing)
TeletexString / GeneralString / GraphicStringtext + report◑ legacy repertoires not worth modeling; flag

The mapping is ⬆ in spirit: what ASN.1 encodes as nominal string types, TSON expresses as one type with checkable constraints — and permitted-alphabet constraints (§B) compose onto the same mechanism instead of multiplying types.

Time types

ASN.1TSON
DATE, TIME-OF-DAY, DATE-TIME, DURATION (X.680 2008+)date, time, datetime, duration✅ the modern time group matches core one-for-one — ISO 8601 on both sides
GeneralizedTimedatetime◑ spelling differences (local-time forms, fractional rules) — canonical-strict profile validates the DER-restricted form, which is UTC-only and maps cleanly
UTCTimepattern-refined text or datetime with a two-digit-year caveat◑ two-digit years; X.509 still carries these — flag, validate the RFC 5280 interpretation
TIME (general)◑ reportthe fully general X.680 TIME type is a small language of its own; convert the constrained subsets, report the rest

Constructed types

ASN.1TSON
SEQUENCE { ... }closed record✅ ordered, named, fixed components — the match is exact modulo extensibility (§C)
SET { ... }closed record + @unordered_components✅ at the abstract level SET differs from SEQUENCE only in component order significance, which is an encoding concern — the record is the same
SEQUENCE OF T[T]
SET OF T[T] + @unorderedsubtle: SET OF is an unordered bag — duplicates allowed — so TSON set<T> (unique) is wrong; set<T> is only correct when a SET OF carries a uniqueness constraint or the corpus semantics imply one. A multiset nuance worth one sentence in part 3
OPTIONAL? (OPTIONAL)
DEFAULT v~ v (REQUIRED_DEFAULT)✅ injection semantics match (DER even requires omitting default-valued components — the encoder-side mirror of decode-time injection)
COMPONENTS OF Tinline expansion of T’s fields◑ mechanical splice; note it is not composition (no IS-A implied)
CHOICE { a A, b B }REQUIRED field group ( a: A | b: B )✅ label-discriminated, exactly-one — completes the sum taxonomy: OpenAPI discriminator → choice + token (value-discriminated); protobuf oneof → OPTIONAL group (label, at-most-one); ASN.1 CHOICE → REQUIRED group (label, exactly-one). All three land on native constructs with no new machinery
Selection type (x < SomeChoice)resolve to the named alternative’s type◑ mechanical
ANY (deprecated) / open typevalue/unknown, or extern/discriminated when governed by a table constraint◑ §D
EMBEDDED PDV, EXTERNAL, CHARACTER STRING✗ report✗ presentation-layer relics; vanishingly rare outside OSI-era protocols
Tags ([0], [APPLICATION 3], IMPLICIT/EXPLICIT, AUTOMATIC)@tag { class, number, mode }Ⓐ the protobuf-field-number analogue: encoding identity, preserved for provenance and any future encoding part, invisible to validation
Value assignments (maxSize INTEGER ::= 100)schema-level constants; used in refinements✅ where TSON’s declaration model permits; otherwise inline the value with Ⓐ provenance

B. Constraints: ASN.1’s subtype system is TSON’s refinement system

This is the deepest affinity of the mapping. ASN.1 is the only source language examined whose constraint model, like TSON’s, is subtyping by refinement — a constrained type is a subtype of its parent, and constraints compose. The mapping is nearly a transliteration:

ASN.1 constraintTSON
Single value (5)= 5 / single-member refinement
Value range (1..10), (MIN..10), open endpoints (1<..<10)^ { min/max/exclusive_* }
SIZE (1..8) on strings/collectionsmin_length/max_length / [T; 1..8] / map size facets
Permitted alphabet (FROM ("A".."Z"))pattern refinement
PATTERN constraintpattern (I-Regexp)friendliest regex story yet: X.680’s PATTERN regexes are XSD-flavored, and I-Regexp is defined as a subset of XSD regular expressions — so the translatable class is large and the dialect distance small; the anchoring and feature-classification rules from the regex policy still apply
Contained subtype (INCLUDES T)reference the refined type / compose refinements
Constraint UNION (|)◑ a union of refinements is a choice of refined types when disjoint; report when it degenerates to arbitrary predicate union
Constraint INTERSECTION (^)stacked refinements
EXCEPT✗ negation — declined, same verdict and reasoning as JSON Schema not
WITH COMPONENTS { ..., field (constraint), other ABSENT }record refinement T ^ { field: <refined>, … } with presence pinning◑ the value-constraint parts map to refinement cleanly; ABSENT/PRESENT pins map to field-state tightening; the alternative-correlating uses (constraining a CHOICE alternative based on sibling values) are the dependent-field pattern — lift to a sum of refined records where mechanical, report where not
Extensible constraint (1..10, ...)convert the root (1..10), flag the marker◑ §C
User-defined constraints (CONSTRAINED BY)✗ report✗ the CEL analogue — arbitrary out-of-band predicates

C. Extensibility markers — the systematic tension

The ... marker (in SEQUENCE, SET, CHOICE, ENUMERATED, and constraints, optionally with version brackets [[ ]]) is ASN.1’s forward-compatibility contract: old decoders must accept and skip unknown extension additions. It is the one construct that appears everywhere in modern ASN.1 (mandated by many standards bodies) and conflicts directly with TSON closure.

The resolution follows the proto3 open-enum precedent exactly, now generalized: convert closed, flag every marker. A converted SEQUENCE with ... becomes a closed record validating the components known at conversion time — stricter than the source contract, correct for a strictness-first tool, and a forward-compatibility regression the report must state per site: a document produced against a newer version of this module will fail here, where an ASN.1 decoder would have skipped the additions. Version brackets convert to their fields with @version_bracket(n) provenance Ⓐ, useful for diffing converted schemas across module revisions. An extensible CHOICE additionally means the field group’s alternative set may grow — same flag, applied to the group.

The alternative — modeling extension roots as a rest field — is tempting and wrong here: ASN.1 extension additions are typed once known and positional/tagged, not an arbitrary string-keyed tail; a rest field would accept garbage the source never would. Closure-plus-flag is both stricter and more honest. (If a live-migration mode ever matters, the principled version is re-conversion against the newer module plus the schema-diff tooling from open gap #6 — not schema-level openness.)

D. X.681/X.682/X.683: the information object system — ASN.1’s discriminator, and sealed’s best customer

The information object system is where ASN.1 encodes what OpenAPI does with discriminator and what protobuf approximates with Any — and it does so with the most machinery and the most precision of the three. The canonical shape (X.509’s AlgorithmIdentifier, every LDAP extension, every PKCS structure):

ALGORITHM ::= CLASS {
  &id   OBJECT IDENTIFIER UNIQUE,
  &Type OPTIONAL
} WITH SYNTAX { OID &id [PARMS &Type] }

SupportedAlgorithms ALGORITHM ::= { sha256WithRSA | ecdsaWithSHA256, ... }

AlgorithmIdentifier ::= SEQUENCE {
  algorithm   ALGORITHM.&id   ({SupportedAlgorithms}),
  parameters  ALGORITHM.&Type ({SupportedAlgorithms}{@algorithm}) OPTIONAL
}

Decompose it and every part lands on machinery already designed:

  • The relational table constraint {@algorithm} — “the type of parameters is determined by the value of algorithm” — is the envelope pattern: a sum of records, each pinning the discriminator component to one object’s &id value and typing the payload component with that object’s &Type. Convert each information object to an envelope variant (Sha256RsaAlg => { algorithm: = <oid>, parameters: NULL }, EcdsaAlg => { algorithm: = <oid>, parameters: EcdsaParams }), the SEQUENCE to a choice over them, and the discriminator component to the discriminator token (#9). UNIQUE on &id is precisely the pairwise-distinct-values rule the token enforces — ASN.1 wrote TSON’s resolution check into its class definition thirty years early.
  • The object set is the variant universe, and its extensibility answers the sealed question concretely: a closed object set ({ a | b }, no marker) is a permits list — the deferred sealed (#10) with its first native-source consumer; an extensible set ({ a | b, ... }) is the open case — convert the known objects, flag the marker per §C, and note that this corpus evidence (how often real object sets are closed vs extensible) is exactly the data the sealed deferral said to collect.
  • An open type governed by no table constraint degrades to value/extern — the Any verdict from the protobuf report, unchanged.
  • X.683 parameterized types (SIGNED { ToBeSigned } ::= SEQUENCE { toBeSigned ToBeSigned, algorithm AlgorithmIdentifier, signature BIT STRING }) map to TSON templates directly — the first source language exercising them. Monomorphization at resolution handles every concrete application; parameterized value sets and class parameters convert where they reduce to type/value substitution, report where they don’t.

The satisfying summary: the JSON Schema work built the value-discriminated machinery (choice + token + envelopes), the protobuf work confirmed the label-discriminated machinery (field groups), and ASN.1 — the language that formalized the discriminated-open-type problem first — needs both at once and nothing more. The information object system, ASN.1’s most feared feature, converts onto the existing design with sealed as the only missing piece, and even that only for closed object sets.

E. Closing the gap — what ASN.1 asks of TSON

  1. Null atom (#4) — third corroboration; nothing new, but ASN.1 NULL-typed components (ubiquitous as “parameters NULL” in AlgorithmIdentifier) make it load-bearing for real corpora, not just nullable-union sugar.
  2. oid core type (Tier 1–2): pattern-refinable dotted-integer text with defined canonical form. Earns its place like uuid did; doubles as the natural discriminator value type for §D envelopes and for a future extern-addressing story.
  3. REAL policy (Tier 2): folds into the float-specials decision already queued from protojson — one core decision serves both.
  4. Multiset note (documentation, not construct): SET OFset<T>; [T] @unordered is the honest lowering. Recommend against adding a bag type — annotation suffices, and the uniqueness-constrained cases map to set<T> already.
  5. @tag, @version_bracket, named-number/named-bit annotations (Tier 1): provenance vocabulary, same family as @field_number.
  6. sealed (#10) gains its evidence source: closed object sets. The recommendation stands — still deferred — but the corpus study in Stage 5 of the JSON Schema plan should include ASN.1 object sets, which will likely show more demand for sealing than OpenAPI does.
  7. Nothing else. No negation (EXCEPT declined), no conditionals (CONSTRAINED BY declined), no rest field (extensibility resolved by closure-plus-flag), no new sum machinery. Templates, refinements, field groups, and the discriminator design absorb the rest of X.680–X.683 as specified.

Recommendations

  • Sequence third, after protobuf: it reuses the report format, the regex policy (in its easiest form yet), the open-vs-closed flagging pattern, and the envelope/discriminator machinery — while adding the templates and refinement-composition paths as the genuinely new converter code.
  • Pick a pilot corpus with teeth: RFC 5280 (X.509) exercises SEQUENCE, CHOICE, OPTIONAL/DEFAULT, extensibility, UTCTime/GeneralizedTime, and the information object pattern in one battle-tested module set, and “validate certificates more strictly than the ASN.1 tooling you’re replacing” is a legible pitch to the security community — the audience most likely to value TSON’s decidability story.
  • Treat JER (X.697) as the protojson analogue when encodings return to scope: it is the specified JSON transfer syntax, and the §A/§B mapping was written to be compatible with validating JER documents once its spellings (BIT STRING, OID, time formats) are pinned in a canonical-strict profile.
  • Feed §D’s object-set census into the sealed decision — this is the concrete Stage 5 input the deferral asked for.

Caveats

  • ASN.1 has accumulated four decades of revisions; this report targets current X.680 (2021-ish era) syntax. Legacy modules (1988 syntax, macro notation, ANY DEFINED BY) need a normalization front-end, the analogue of the OpenAPI 3.0 and proto2 passes — and macros in particular may resist mechanical conversion (report-heavy).
  • The general TIME type, EMBEDDED PDV/EXTERNAL, user-defined constraints, and EXCEPT are declined or reported; corpora from OSI-era protocols will see fatter reports than modern PKI/telecom modules.
  • WITH COMPONENTS alternative-correlation and parameterized value sets convert case-by-case; the honest posture is a classifier (mechanical / lift-to-sum / report), mirroring the regex classifier.
  • Extensibility-marker flags will be numerous in standards-mandated modules; the report format should aggregate them (per-module summary) or they’ll drown the signal.
  • TSON references are to the 2026 Revision 32 working draft plus planned additions from the prior reports; the ASN.1 PATTERN-regex lineage claim (XSD-flavored, hence I-Regexp-friendly) should be verified against the exact X.680 clause before part 3 cites it normatively.