|
1 | 1 | # eNot — Encoding Notations |
2 | 2 |
|
3 | | -[](https://github.com/flexca/eNot/actions/workflows/maven-test.yml) |
4 | | -[](LICENSE) |
5 | | -[](https://openjdk.org/projects/jdk/17/) |
| 3 | +[](https://github.com/flexca/eNot/actions/workflows/continuous-integration.yml) |
| 4 | +[](https://central.sonatype.com/artifact/io.github.flexca/enot-core) |
| 5 | +[](LICENSE) |
| 6 | +[](https://openjdk.org) |
6 | 7 |
|
7 | | -**eNot** (**Encoding Notations**) is a Java templating engine that turns plain **JSON or YAML** templates into binary-encoded data — primarily **ASN.1 DER**, with **BER-TLV** support demonstrating the design is not tied to a single format. |
| 8 | +**eNot** is a Java templating engine that transforms **JSON/YAML** structures into binary-encoded data (**ASN.1 DER**, **BER-TLV**, and more). By separating structure from values, eNot eliminates the fragility of hand-written encoding and provides a robust, maintainable way to generate complex binary payloads. |
8 | 9 |
|
9 | | ---- |
| 10 | +### Quick Start |
| 11 | + |
| 12 | +#### 1. Add dependency |
10 | 13 |
|
11 | | -## The problem it solves |
| 14 | +Add the following dependency to your `pom.xml`: |
12 | 15 |
|
13 | | -Applications that produce structured binary data — PKI certificates, smart-card commands, device provisioning payloads, network protocol messages — typically choose between two bad options: |
| 16 | +```xml |
| 17 | +<dependency> |
| 18 | + <groupId>io.github.flexca</groupId> |
| 19 | + <artifactId>enot-core</artifactId> |
| 20 | + <version>1.0.0</version> |
| 21 | +</dependency> |
| 22 | +``` |
14 | 23 |
|
15 | | -- A **heavy framework** that owns the structure and leaves no room to deviate |
16 | | -- **Hand-written encoding code** that is fragile, hard to review, and impossible to reuse |
| 24 | +Or to `build.gradle`: |
17 | 25 |
|
18 | | -eNot takes a different approach: describe the binary structure as a plain text template, supply values at runtime, and let the engine handle the encoding. |
| 26 | +```groovy |
| 27 | +implementation 'io.github.flexca:enot-core:1.0.0' |
| 28 | +``` |
19 | 29 |
|
20 | | -**JSON template:** |
| 30 | +#### 2. Create a template |
| 31 | +eNot templates mirror your data structure. For example, to encode this **ASN.1 definition**: |
21 | 32 |
|
22 | | -```json |
23 | | -{ |
24 | | - "type": "asn.1", |
25 | | - "attributes": { "tag": "utf8_string" }, |
26 | | - "body": "${common_name}" |
| 33 | +```asn1 |
| 34 | +ExampleStructure ::= SET { |
| 35 | + data SEQUENCE { |
| 36 | + oid OBJECT IDENTIFIER, |
| 37 | + name UTF8String |
| 38 | + } |
27 | 39 | } |
28 | 40 | ``` |
29 | 41 |
|
30 | | -**Equivalent YAML template:** |
31 | | - |
| 42 | +You define the following **template.yaml**: |
32 | 43 | ```yaml |
33 | 44 | type: asn.1 |
34 | | -attributes: |
35 | | - tag: utf8_string |
36 | | -body: "${common_name}" |
| 45 | +attributes: { tag: set } |
| 46 | +body: |
| 47 | + type: asn.1 |
| 48 | + attributes: { tag: sequence } |
| 49 | + body: |
| 50 | + - type: asn.1 |
| 51 | + attributes: { tag: object_identifier } |
| 52 | + body: "${my_oid}" |
| 53 | + - type: asn.1 |
| 54 | + attributes: { tag: utf8_string } |
| 55 | + body: "${my_text}" |
37 | 56 | ``` |
| 57 | +*Check out the [eNot reference](docs/enot.md) for a deep dive into elements and scoping.* |
| 58 | +
|
38 | 59 |
|
39 | | -**Serialize it:** |
| 60 | +#### 3. Encode your data |
| 61 | +Supply runtime values via `SerializationContext` to generate the binary output: |
40 | 62 |
|
41 | 63 | ```java |
42 | | -Enot enot = new Enot.Builder() |
43 | | - .withRegistry(registry) |
44 | | - .withJsonObjectMapper(new ObjectMapper()) |
45 | | - .build(); |
46 | | - |
47 | | -List<byte[]> der = enot.serialize(templateJson, |
48 | | - new SerializationContext.Builder(objectMapper) |
49 | | - .withParam("common_name", "Alice") |
50 | | - .build()); |
| 64 | +import io.github.flexca.enot.core.exception.EnotException; |
| 65 | +import io.github.flexca.enot.core.registry.EnotRegistry; |
| 66 | +import io.github.flexca.enot.core.serializer.context.SerializationContext; |
| 67 | +import io.github.flexca.enot.core.types.asn1.Asn1TypeSpecification; |
| 68 | +import io.github.flexca.enot.core.types.system.SystemTypeSpecification; |
| 69 | +import tools.jackson.databind.ObjectMapper; |
| 70 | +import tools.jackson.dataformat.yaml.YAMLFactory; |
| 71 | +
|
| 72 | +import java.util.Base64; |
| 73 | +import java.util.List; |
| 74 | +
|
| 75 | +public class EnotQuickStart { |
| 76 | +
|
| 77 | + // YAML template: |
| 78 | + private static final String ENOT_TEMPLATE = """ |
| 79 | + type: asn.1 |
| 80 | + attributes: { tag: set } |
| 81 | + body: |
| 82 | + type: asn.1 |
| 83 | + attributes: { tag: sequence } |
| 84 | + body: |
| 85 | + - type: asn.1 |
| 86 | + attributes: { tag: object_identifier } |
| 87 | + body: "${my_oid}" |
| 88 | + - type: asn.1 |
| 89 | + attributes: { tag: utf8_string } |
| 90 | + body: "${my_text}" |
| 91 | + """; |
| 92 | +
|
| 93 | + public static void main(String[] args) { |
| 94 | +
|
| 95 | + // Jackson object mappers: |
| 96 | + ObjectMapper jsonObjectMapper = new ObjectMapper(); // Required if you are using JSON |
| 97 | + ObjectMapper yamlObjectMapper = new ObjectMapper(new YAMLFactory()); // Required if you are using YAML |
| 98 | +
|
| 99 | + // Registry allow to supply eNot type specification: |
| 100 | + EnotRegistry registry = new EnotRegistry.Builder() |
| 101 | + .withTypeSpecification(new SystemTypeSpecification()) // System elements - loops, conditions and other utilites |
| 102 | + .withTypeSpecification(new Asn1TypeSpecification()) // ASN.1 elements - require to serialize to ASN.1 DER |
| 103 | + .build(); |
| 104 | +
|
| 105 | + // Enot - facade for parsing, serialization, etc: |
| 106 | + Enot enot = new Enot.Builder() |
| 107 | + .withRegistry(registry) // Adding registry |
| 108 | + .withJsonObjectMapper(jsonObjectMapper) // Add JSON ObjectMapper if you are using JSON templates |
| 109 | + .withYamlObjectMapper(yamlObjectMapper) // Add YAML ObjectMapper if you are using YAML templates |
| 110 | + .build(); |
| 111 | +
|
| 112 | + // Serialization context - hold params to replace placeholders: |
| 113 | + SerializationContext serializationContext = new SerializationContext.Builder() |
| 114 | + .withParam("my_oid", "1.2.840.113549.1.1.1") // Value to replace ${my_oid} placeholder in template |
| 115 | + .withParam("my_text", "eNot") // Value to replace ${my_text} placeholder in template |
| 116 | + .build(); |
| 117 | +
|
| 118 | + try { |
| 119 | + // Serializing template with params to binary: |
| 120 | + List<byte[]> result = enot.serialize(ENOT_TEMPLATE, serializationContext); |
| 121 | + // Encoding result to Base64: |
| 122 | + String resultBase64Encoded = Base64.getEncoder().encodeToString(result.get(0)); |
| 123 | + // Printing Base64 encoded result: |
| 124 | + System.out.println("Result: " + resultBase64Encoded); |
| 125 | + } catch (EnotException e) { |
| 126 | + e.printStackTrace(); |
| 127 | + } |
| 128 | + } |
| 129 | +} |
51 | 130 | ``` |
52 | 131 |
|
53 | | -The engine resolves `${common_name}`, encodes the UTF-8 string as DER, and returns the bytes. No hard-coded structures. No framework lock-in. |
| 132 | +### Key Features |
54 | 133 |
|
55 | | ---- |
| 134 | +- **Human-readable templates** — eNot templates are plain JSON or YAML files that mirror the binary structure they produce. They are easy to read, review, and version-control. Because templates are data, not code, they can be updated and reloaded at runtime without rebuilding the application. See [eNot format reference](docs/enot.md). |
56 | 135 |
|
57 | | -## Key features |
| 136 | +- **ASN.1 DER encoding** — Full support for DER-encoded ASN.1 structures: sequences, sets, OIDs, strings, integers, time types, context tagging, and more. See [ASN.1 elements](docs/asn1/index.md). |
58 | 137 |
|
59 | | -| Feature | Description | |
60 | | -|---------|-------------| |
61 | | -| **JSON & YAML** | Both formats are supported out of the box — the engine detects which one you pass | |
62 | | -| **Loops** | Iterate a template body over a list of parameters — one encoded output per entry | |
63 | | -| **Conditions** | Encode a body only when an expression is true — cover multiple encoding variants in one template | |
64 | | -| **References** | Include one template inside another at parse time — compose large structures from small, testable pieces | |
65 | | -| **Expression engine** | Arithmetic, comparison, logical operators, and built-in functions for date and binary operations | |
66 | | -| **Extensible** | Any binary format plugs in via `EnotRegistry` — `asn.1` and `ber-tlv` are two separate examples | |
67 | | -| **Error reporting** | All parse errors are collected and reported together, not one at a time | |
| 138 | +- **BER-TLV support** — BER-TLV encoding is available as a plug-in module, demonstrating that the engine is not tied to ASN.1. See [ber-tlv module](ber-tlv/README.md). |
68 | 139 |
|
69 | | ---- |
| 140 | +- **Control logic** — [`loop`](docs/system/loop.md) and [`condition`](docs/system/condition.md) system elements make templates truly flexible: iterate over parameter lists, include elements conditionally, and handle variable-length structures without any code changes. |
70 | 141 |
|
71 | | -## Loops, conditions, and composition |
| 142 | +- **Composition** — Break large templates into smaller, named pieces and reuse them with the [`reference`](docs/system/reference.md) system element. Build complex structures from tested, independently maintained parts. |
72 | 143 |
|
73 | | -Binary structures are rarely flat. eNot handles this with built-in control-flow elements. |
| 144 | +- **Extensibility** — The type system is open: register your own element types alongside the built-in ones. See [Adding a new eNot element type](docs/add-new-element-type.md). |
74 | 145 |
|
75 | | -**Loop** — any repeated structure maps directly to a loop: |
| 146 | +- **Interactive web tool** — Try eNot instantly in the browser without writing any Java. The web tool ships as a Docker image with a side-by-side template/params editor, YAML/JSON switching, and one-click example generation. See [web-tool](web-tool/README.md). |
76 | 147 |
|
77 | | -```json |
78 | | -{ |
79 | | - "type": "system", |
80 | | - "attributes": { "kind": "loop", "items_name": "dns_name" }, |
81 | | - "body": { |
82 | | - "type": "asn.1", |
83 | | - "attributes": { "tag": "tagged_object", "implicit": 2 }, |
84 | | - "body": { |
85 | | - "type": "asn.1", |
86 | | - "attributes": { "tag": "ia5_string" }, |
87 | | - "body": "${value}" |
88 | | - } |
89 | | - } |
90 | | -} |
91 | | -``` |
| 148 | +- **Detailed documentation** — Every element type, attribute, and constraint is documented with examples. See [Documentation](docs/index.md). |
92 | 149 |
|
93 | | -**Condition** — a single template covering multiple encoding variants. This example follows RFC 5280, which requires dates before 2050 to use `UTCTime` and dates from 2050 onward to use `GeneralizedTime`: |
94 | | - |
95 | | -```json |
96 | | -[ |
97 | | - { |
98 | | - "type": "system", |
99 | | - "attributes": { "kind": "condition", "expression": "${expires_on} < '2050-01-01T00:00:00Z'" }, |
100 | | - "body": { "type": "asn.1", "attributes": { "tag": "utc_time" }, "body": "${expires_on}" } |
101 | | - }, |
102 | | - { |
103 | | - "type": "system", |
104 | | - "attributes": { "kind": "condition", "expression": "${expires_on} >= '2050-01-01T00:00:00Z'" }, |
105 | | - "body": { "type": "asn.1", "attributes": { "tag": "generalized_time" }, "body": "${expires_on}" } |
106 | | - } |
107 | | -] |
108 | | -``` |
109 | | - |
110 | | -**References** let one template include another by identifier at parse time, so large or complex structures can be assembled from smaller, independently testable pieces. |
| 150 | +> [!NOTE] |
| 151 | +> **eNot is designed for small, structured payloads** — certificate fields, extensions, smart-card commands, and similar structures that are kilobytes in size. The engine holds the entire structure in memory during serialization. It is not suitable for large binary payloads (hundreds of megabytes), and using it for such workloads — especially under concurrent load — may cause out-of-memory errors. |
111 | 152 |
|
112 | 153 | --- |
113 | 154 |
|
114 | | -## Why plain text templates? |
| 155 | +### Modules |
115 | 156 |
|
116 | | -- **Version-controlled** — templates are diff-able and reviewable in a pull request like any other source file |
117 | | -- **Structure mirrors output** — a `sequence` wrapping a `set` wrapping a `utf8_string` looks exactly like that in the template tree |
118 | | -- **Runtime values stay separate** — the template describes *shape*; the `SerializationContext` provides *values*; neither knows about the other |
119 | | -- **Format-agnostic core** — the same parser and serializer infrastructure drives both `asn.1` and `ber-tlv`; new formats plug in through `EnotRegistry` without touching the engine |
| 157 | +| Module | Artifact | Description | |
| 158 | +|--------|----------|-------------| |
| 159 | +| `core` | `enot-core` | Parser, serializer, expression engine, type registry, and all built-in element types (ASN.1 DER + system elements). The only dependency needed for most use cases. | |
| 160 | +| `ber-tlv` | `enot-ber-tlv` | BER-TLV encoding support. A plug-in module that demonstrates the extensible type system beyond ASN.1. | |
| 161 | +| `web-tool` | — | Browser-based interactive playground for evaluating eNot templates. Not a library dependency — run it via Docker or as a standalone JAR. | |
120 | 162 |
|
121 | 163 | --- |
122 | 164 |
|
123 | | -## Status |
| 165 | +### Contributing |
124 | 166 |
|
125 | | -> **eNot is not yet published to Maven Central.** Build locally first: |
126 | | -> ``` |
127 | | -> git clone https://github.com/flexca/eNot.git |
128 | | -> cd eNot |
129 | | -> mvn install |
130 | | -> ``` |
131 | | -> Then reference the snapshot version — see [Quick Start](docs/quick-start.md) for the full setup. |
| 167 | +Issues and pull requests are welcome. If you find a bug, have a feature request, or want to add support for a new encoding format, please open an issue on [GitHub](https://github.com/flexca/eNot/issues). |
132 | 168 |
|
133 | 169 | --- |
134 | 170 |
|
135 | | -## Modules |
| 171 | +### Acknowledgements |
136 | 172 |
|
137 | | -| Module | Description | |
138 | | -|--------|-------------| |
139 | | -| `core` | Parser, serializer, expression engine, type registry | |
140 | | -| `ber-tlv` | BER-TLV type extension — plug-in format example | |
141 | | -
|
142 | | ---- |
| 173 | +eNot is built on top of several excellent open-source libraries: |
143 | 174 |
|
144 | | -## Building |
145 | | -
|
146 | | -Requirements: **Java 17+**, **Maven 3.8+** |
147 | | -
|
148 | | -``` |
149 | | -mvn install # build everything and run all tests |
150 | | -mvn -pl core test # run only core module tests |
151 | | -``` |
152 | | -
|
153 | | ---- |
154 | | -
|
155 | | -## Documentation |
156 | | -
|
157 | | -| Document | Description | |
158 | | -|----------|-------------| |
159 | | -| [Quick Start](docs/quick-start.md) | Build the registry, write a template, serialize | |
160 | | -| [Format overview](docs/format/README.md) | Element structure, values, placeholders, scoping | |
161 | | -| [ASN.1 elements](docs/format/asn1.md) | All supported tags and accepted body types | |
162 | | -| [System elements](docs/format/system.md) | loop, condition, group, reference, bit_map, sha1, … | |
163 | | -| [Expression syntax](docs/format/expressions.md) | Operators, functions, type rules | |
| 175 | +- **[Bouncy Castle](https://www.bouncycastle.org/)** — ASN.1 DER encoding and cryptographic primitives. The core of what makes eNot's ASN.1 support possible. |
| 176 | +- **[Jackson](https://github.com/FasterXML/jackson)** — JSON and YAML parsing for template loading. |
| 177 | +- **[Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)** and **[Apache Commons Collections](https://commons.apache.org/proper/commons-collections/)** — utility support throughout the library. |
| 178 | +- **[Lombok](https://projectlombok.org/)** — boilerplate reduction in the Java source. |
164 | 179 |
|
165 | 180 | --- |
166 | 181 |
|
167 | | -## License |
168 | | -
|
169 | | -Apache License 2.0 — see [LICENSE](LICENSE). |
| 182 | +### License |
| 183 | +Apache License 2.0 — see [LICENSE](LICENSE). |
0 commit comments