Skip to content

Commit 3397ce4

Browse files
committed
Docs for utils lib
1 parent 2e9d49c commit 3397ce4

File tree

2 files changed

+99
-36
lines changed

2 files changed

+99
-36
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,9 @@ specific subsystem.
5555
- [Fallback](./traces/fallback): fallback block authoring, no-safrole, no-work-reports
5656
- [Safrole](./traces/safrole): safrole block authoring, no-work-reports
5757
- [Work Reports L0](./traces/reports-l0): basic work reports, no-safrole
58+
59+
## Vectors Validation
60+
61+
Validation scripts are included to verify the JSON files against the expected
62+
ASN.1 syntax provided with the test vectors. These scripts currently rely on my
63+
[asn1tools](https://github.com/davxy/asn1tools) fork.

jam-types-asn/utils.py

Lines changed: 93 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,74 +4,131 @@
44
import asn1tools
55
import glob
66

7-
def get_schema_files(full = False):
7+
8+
def get_schema_files(full=False):
9+
"""Get the list of schema files for compilation.
10+
11+
Args:
12+
full: If True, use full-const.asn, otherwise use tiny-const.asn
13+
14+
Returns:
15+
List of absolute paths to schema files
16+
"""
817
script_dir = os.path.dirname(os.path.abspath(__file__))
9-
schema_files = [ os.path.join(script_dir, "jam-types.asn") ]
18+
schema_files = [os.path.join(script_dir, "jam-types.asn")]
19+
1020
if full:
11-
schema_files += [ os.path.join(script_dir, "full-const.asn") ]
21+
schema_files.append(os.path.join(script_dir, "full-const.asn"))
1222
else:
13-
schema_files += [ os.path.join(script_dir, "tiny-const.asn") ]
14-
return schema_files
23+
schema_files.append(os.path.join(script_dir, "tiny-const.asn"))
1524

25+
return schema_files
26+
1627

17-
# Tweaks:
18-
# - Support for user defined tweak callback. This is called first.
19-
# - JSON uses snake case, ASN.1 requires kebab case.
20-
# - JSON prefix octet strings with '0x', ASN doesn't like it.
21-
def make_asn1_parsable(json_str, json_tweaks_callback):
28+
def make_asn1_parsable(json_str, json_tweaks_callback=None):
29+
"""Transform JSON to be parsable by ASN.1 schema.
30+
31+
Transformations:
32+
- Apply user-defined tweak callback if provided
33+
- Convert snake_case to kebab-case (JSON uses snake case, ASN.1 requires kebab case)
34+
- Remove '0x' prefix from hex strings (ASN.1 doesn't like it)
35+
36+
Args:
37+
json_str: Original JSON string
38+
json_tweaks_callback: Optional callback to modify JSON object
39+
40+
Returns:
41+
Modified JSON string compatible with ASN.1
42+
"""
2243
if json_tweaks_callback is not None:
2344
json_obj = json.loads(json_str)
2445
json_obj = json_tweaks_callback(json_obj)
2546
json_str = json.dumps(json_obj, indent=4)
47+
48+
# Convert snake_case to kebab-case and remove hex prefixes
2649
json_str = json_str.replace('_', '-').replace('0x', '')
2750
return json_str
2851

2952

30-
def path_to_root_type(path):
31-
# Strip the directory path and extension
32-
name = os.path.splitext(os.path.basename(path))[0]
33-
# Remove "_X" if it ends with a number
34-
name = re.sub(r'_\d+$', '', name)
53+
def path_to_type_name(path):
54+
"""Convert file path to ASN.1 type name.
55+
56+
Converts filename to PascalCase for ASN.1 type detection.
57+
58+
Args:
59+
path: File path
60+
61+
Returns:
62+
PascalCase type name
63+
"""
64+
# Strip directory path and extension
65+
name = os.path.splitext(os.path.basename(path))[0]
66+
# Remove "_X" suffix if it ends with a number
67+
name = re.sub(r'_\d+$', '', name)
3568
# Convert kebab-case or snake_case to PascalCase
36-
name = re.sub(r'[-_](\w)', lambda m: m.group(1).upper(), name)
69+
name = re.sub(r'[-_](\w)', lambda m: m.group(1).upper(), name)
3770
# Capitalize the first character
38-
name = name[0].upper() + name[1:]
39-
return name
71+
name = name[0].upper() + name[1:]
72+
return name
4073

4174

42-
def validate(schema, json_file, json_tweaks_callback = None):
43-
print("* Validating: ", json_file)
75+
def validate(schema, json_file, json_tweaks_callback=None):
76+
"""Validate a JSON file against an ASN.1 schema.
77+
78+
The root type for decoding is determined as follows:
79+
- If the schema contains "TestCase", use that type
80+
- Otherwise, derive the type from the filename (e.g., "my_type.json" → "MyType")
4481
82+
Args:
83+
schema: Compiled ASN.1 schema
84+
json_file: Path to JSON file to validate
85+
json_tweaks_callback: Optional callback to modify JSON before validation
86+
"""
87+
# Determine root type used for decoding
4588
if "TestCase" in schema.types:
46-
root_type = "TestCase"
89+
root_type = "TestCase"
4790
else:
48-
# Auto-detect root type from schema
49-
root_type = path_to_root_type(json_file)
50-
51-
# Decode from json using the schema
52-
json_bytes = open(json_file, "rb").read()
91+
# Auto-detect root type from filename
92+
root_type = path_to_type_name(json_file)
93+
94+
# Read and prepare JSON
95+
with open(json_file, "rb") as f:
96+
json_bytes = f.read()
97+
5398
json_str_org = json_bytes.decode('utf-8')
5499
json_str_org = make_asn1_parsable(json_str_org, json_tweaks_callback)
55-
100+
101+
# Validate by round-trip encoding/decoding
56102
json_bytes = json_str_org.encode('utf-8')
57103
decoded = schema.decode(root_type, json_bytes, check_constraints=True)
58-
59-
# Encode to json using the schema
60104
encoded = schema.encode(root_type, decoded, check_constraints=True)
61-
# Original json uses snake case, asn1 requires kebab case
105+
106+
# Normalize for comparison
62107
json_str = encoded.decode('utf-8')
63108
json_obj = json.loads(json_str)
64-
# Strings are converted to arrays of characters,
65-
# map back to single string.
66-
json_str = json.dumps(json_obj, indent = 4)
109+
json_str = json.dumps(json_obj, indent=4)
67110

68-
assert (json_str.rstrip().lower() == json_str_org.rstrip().lower())
111+
# Verify round-trip consistency
112+
assert json_str.rstrip().lower() == json_str_org.rstrip().lower()
69113

70-
def validate_group(group_name, group_schema, spec_name, json_tweaks_callback = None):
114+
def validate_group(group_name, group_schema, spec_name, json_tweaks_callback=None):
115+
"""Validate a group of JSON files against an ASN.1 schema.
116+
117+
Args:
118+
group_name: Name of the validation group (for display)
119+
group_schema: ASN.1 schema file name (or None for base schema only)
120+
spec_name: Specification name ("tiny", "full", or "data")
121+
json_tweaks_callback: Optional callback to modify JSON before validation
122+
"""
71123
print(f"\n[Validating {group_name} ({spec_name})]")
124+
125+
# Build schema file list
72126
schema_files = get_schema_files(spec_name == "full")
73127
if group_schema is not None:
74-
schema_files += [group_schema]
128+
schema_files.append(group_schema)
129+
130+
# Compile schema and validate all JSON files
75131
schema = asn1tools.compile_files(schema_files, codec="jer")
76132
for json_file in glob.glob(f"{spec_name}/*.json"):
133+
print("* Validating:", json_file)
77134
validate(schema, json_file, json_tweaks_callback)

0 commit comments

Comments
 (0)