synta 0.1.2

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
# C Code Generation

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [1. Quick Start]#1-quick-start
  - [Generate header and implementation]#generate-header-and-implementation
  - [Compile and link]#compile-and-link
- [2. Type Mapping]#2-type-mapping
  - [BIT STRING struct layout]#bit-string-struct-layout
  - [Named INTEGER values]#named-integer-values
  - [Constrained INTEGER]#constrained-integer
  - [OPTIONAL fields]#optional-fields
  - [Struct ordering]#struct-ordering
  - [Header includes]#header-includes
- [3. Generated Function Signatures]#3-generated-function-signatures
- [4. libcsynta API Used by Generated Code]#4-libcsynta-api-used-by-generated-code
  - [4.1 Decoder lifecycle]#41-decoder-lifecycle
  - [4.2 Decoder navigation]#42-decoder-navigation
  - [4.3 Primitive decode functions]#43-primitive-decode-functions
  - [4.4 String and time decode functions]#44-string-and-time-decode-functions
  - [4.5 Integer conversion and free]#45-integer-conversion-and-free
  - [4.6 OctetString accessors and free]#46-octetstring-accessors-and-free
  - [4.7 OID free]#47-oid-free
  - [4.8 Encoder lifecycle]#48-encoder-lifecycle
  - [4.9 Encoder navigation]#49-encoder-navigation
  - [4.10 Primitive encode functions]#410-primitive-encode-functions
  - [4.11 String and time encode functions]#411-string-and-time-encode-functions
  - [4.12 Tag and encoding types]#412-tag-and-encoding-types
- [5. Memory Contract]#5-memory-contract
  - [Ownership rules]#ownership-rules
  - [Partial decode failure]#partial-decode-failure
  - [`_free()` contract]#_free-contract
- [6. Error Handling]#6-error-handling
  - [SyntaErrorCode]#syntaerrorcode
  - [Error propagation]#error-propagation
- [7. OPTIONAL Fields]#7-optional-fields
  - [Struct layout]#struct-layout
  - [Decode]#decode
  - [Encode]#encode
- [8. Tagged Fields (IMPLICIT and EXPLICIT)]#8-tagged-fields-implicit-and-explicit
  - [IMPLICIT tags -- primitive fields]#implicit-tags-primitive-fields
  - [IMPLICIT tags -- structural types]#implicit-tags-structural-types
  - [EXPLICIT tags]#explicit-tags
  - [Current limitation]#current-limitation
- [9. CHOICE Dispatch]#9-choice-dispatch
  - [Struct layout]#struct-layout-1
  - [Decode]#decode-1
  - [Encode]#encode-1
- [10. Constraint Validation Options]#10-constraint-validation-options
  - [Default (no flags)]#default-no-flags
  - [--with-regex (POSIX regex)]#-with-regex-posix-regex
  - [--with-pcre (PCRE2)]#-with-pcre-pcre2
  - [--with-containing (CONTAINING constraint)]#-with-containing-containing-constraint
- [11. Arena / Bump Allocator Mode]#11-arena-bump-allocator-mode
  - [Arena type and helpers (generated in header)]#arena-type-and-helpers-generated-in-header
  - [Additional generated function]#additional-generated-function
  - [Usage pattern]#usage-pattern
- [12. Helper Functions (--with-helpers)]#12-helper-functions-with-helpers
  - [_init](#_init)
  - [_validate](#_validate)
  - [_print](#_print)
- [13. Build System Integration](#13-build-system-integration)
  - [--cmake](#-cmake)
  - [--meson](#-meson)
- [14. Multi-Module Projects](#14-multi-module-projects)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

This document covers how synta-codegen generates C headers and implementation
files from ASN.1 schemas, and documents the exact libcsynta API that the
generated code depends on at link time.

---

## 1. Quick Start

### Generate header and implementation

```sh
# Generate C header to stdout
synta-codegen --lang c schema.asn1

# Generate header to a file
synta-codegen --lang c -o types.h schema.asn1

# Generate implementation file (references types.h via --impl)
synta-codegen --lang c --impl types.h -o types.c schema.asn1

# Specify a custom path for synta.h inside the generated header
synta-codegen --lang c --header-path /usr/local/include/synta.h -o types.h schema.asn1
```

### Compile and link

The generated `.c` file depends on libcsynta. A minimal compile-and-link
sequence using pkg-config looks like:

```sh
cc -c types.c -o types.o $(pkg-config --cflags csynta)
cc myapp.c types.o -o myapp $(pkg-config --libs csynta)
```

Without pkg-config, pass the include and library directories manually:

```sh
cc -I/path/to/synta/include -c types.c -o types.o
cc myapp.c types.o -o myapp -L/path/to/synta/lib -lcsynta
```

The generated code requires a C99-capable compiler (`-std=c99` or later).

---

## 2. Type Mapping

The table below shows how each ASN.1 type maps to a C type in the generated
header.

| ASN.1 type                              | C type                        | Notes                                      |
|-----------------------------------------|-------------------------------|--------------------------------------------|
| INTEGER                                 | `int64_t`                     | `stdint.h`; see Named Numbers below        |
| INTEGER (with named numbers)            | `enum TypeNameValue`          | enum constants generated alongside struct  |
| INTEGER (constrained)                   | `typedef struct { uintN_t value; }` | smallest `uintN_t`/`intN_t`; see Constrained INTEGER below |
| BOOLEAN                                 | `bool`                        | `stdbool.h`                                |
| OCTET STRING                            | `SyntaOctetString*`           | heap-allocated                             |
| BIT STRING                              | `SyntaBitString`              | struct by value; see below                 |
| OBJECT IDENTIFIER                       | `SyntaObjectIdentifier*`      | heap-allocated                             |
| NULL                                    | (no field)                    | skipped entirely in struct                 |
| REAL                                    | `double`                      |                                            |
| UTF8String                              | `SyntaOctetString*`           | raw bytes, heap-allocated                  |
| PrintableString                         | `SyntaOctetString*`           | raw bytes, heap-allocated                  |
| IA5String                               | `SyntaOctetString*`           | raw bytes, heap-allocated                  |
| UTCTime                                 | `SyntaOctetString*`           | raw bytes, heap-allocated                  |
| GeneralizedTime                         | `SyntaOctetString*`           | raw bytes, heap-allocated                  |
| SEQUENCE                                | `struct TypeName { ... }`     |                                            |
| SET                                     | `struct TypeName { ... }`     |                                            |
| SEQUENCE OF ElementType                 | `struct TypeName { ElementType *items; size_t count; }` | |
| SET OF ElementType                      | `struct TypeName { ElementType *items; size_t count; }` | |
| CHOICE                                  | `struct TypeName { enum TypeNameTag tag; union { ... } value; }` | |
| TypeRef                                 | referenced type by value      |                                            |
| ANY / ANY DEFINED BY                    | `SyntaOctetString*`           | heap-allocated raw DER bytes               |

### BIT STRING struct layout

`SyntaBitString` is defined in `synta.h` as:

```c
typedef struct {
    uint8_t  *data;
    size_t    len;
    uint8_t   unused_bits;
} SyntaBitString;
```

`data` points into libcsynta-managed memory. Do not free `data` directly; call
the relevant `_free()` function for the enclosing struct.

### Named INTEGER values

When an INTEGER type carries named numbers, synta-codegen generates an enum or
a set of `#define` constants alongside the struct:

```c
/* ASN.1: Version ::= INTEGER { v1(0), v2(1), v3(2) } */
typedef enum {
    Version_v1 = 0,
    Version_v2 = 1,
    Version_v3 = 2,
} VersionValue;
```

The struct field type is still `int64_t`; the enum serves as a companion
constant set.

### Constrained INTEGER

When an INTEGER type carries a value-range constraint (e.g. `INTEGER (0..100)`),
synta-codegen generates a `typedef struct` newtype whose `value` field uses the
smallest C integer type that covers the declared range:

- Lower bound ≥ 0 → unsigned: `uint8_t` (≤255), `uint16_t` (≤65535),
  `uint32_t` (≤4294967295), `uint64_t`.
- Lower bound < 0 → signed: `int8_t`, `int16_t`, `int32_t`, `int64_t`.
- Unconstrained bounds (`MIN`/`MAX`, named values) → `int64_t`.

For example, `Percentage ::= INTEGER (0..100)` produces:

```c
/* INTEGER (0..100) */
typedef struct { uint8_t value; } Percentage;

static inline bool percentage_new(uint8_t v, Percentage* out) {
    if (!(v <= 100LL)) return false;
    out->value = v;
    return true;
}
static inline Percentage percentage_new_unchecked(uint8_t v) {
    Percentage out; out.value = v; return out;
}
static inline uint8_t percentage_get(const Percentage* self) { return self->value; }
```

Note that for unsigned storage types the lower-bound check (e.g. `v >= 0`) is
omitted from the generated validation expression because it is always true and
would trigger compiler warnings.

### OPTIONAL fields

Each OPTIONAL member `fieldname` in a SEQUENCE or SET gains a companion
boolean immediately before it:

```c
struct MySequence {
    bool has_optional_field;
    SyntaOctetString *optional_field;
};
```

See Section 7 for decode and encode patterns.

### Struct ordering

All generated struct types appear in topological order: leaf types (those
referenced by others) are emitted first. Every struct type also receives a
forward declaration before the definitions block, so pointer types are always
valid.

### Header includes

Every generated header begins with:

```c
#include <stdint.h>
#include <stdbool.h>
#include "synta.h"   /* or the path given by --header-path */
```

---

## 3. Generated Function Signatures

For each type `TypeName` the code generator emits at minimum three functions:

```c
/* 'TypeName' is a placeholder; the generator emits one set of functions per
   schema type, e.g. MyRecord_decode(...), MyRecord_encode(...), MyRecord_free(). */
/* Decode from DER/BER into an already-allocated struct */
SyntaErrorCode TypeName_decode(SyntaDecoder *decoder, TypeName *out);

/* Encode a struct to DER/BER via an existing encoder */
SyntaErrorCode TypeName_encode(SyntaEncoder *encoder, const TypeName *value);

/* Release all heap resources owned by the struct.
   Does NOT free the struct itself. */
void TypeName_free(TypeName *value);
```

With `--with-helpers` three additional static inline helpers appear in the
header (see Section 12).

With `--arena` an additional decode variant is generated (see Section 11).

---

## 4. libcsynta API Used by Generated Code

The following functions are called directly by generated decode, encode, and
free implementations. Any program that links against generated code must also
link against libcsynta, which provides all of these symbols.

Functions are grouped by category.

### 4.1 Decoder lifecycle

```c
/* Create a decoder over a DER or BER byte slice.
   The slice must remain valid for the lifetime of the decoder. */
SyntaDecoder *synta_decoder_new(const uint8_t *data, size_t len,
                                SyntaEncoding   encoding);

/* Release a decoder and all child decoders it created. */
void synta_decoder_free(SyntaDecoder *decoder);
```

### 4.2 Decoder navigation

```c
/* Enter a SEQUENCE element, creating a child decoder scoped to its contents. */
SyntaErrorCode synta_decoder_enter_sequence(SyntaDecoder  *parent,
                                            SyntaDecoder **out);

/* Enter a SET element. */
SyntaErrorCode synta_decoder_enter_set(SyntaDecoder  *parent,
                                       SyntaDecoder **out);

/* Enter an explicitly or implicitly tagged constructed element by tag.
   Used for IMPLICIT structural tags and EXPLICIT tags. */
SyntaErrorCode synta_decoder_enter_constructed(SyntaDecoder  *parent,
                                               SyntaTag       tag,
                                               SyntaDecoder **out);

/* Return true when the decoder has consumed all elements in its scope.
   Used to stop iterating SEQUENCE OF / SET OF content. */
bool synta_decoder_at_end(SyntaDecoder *decoder);

/* Peek at the next tag without consuming the element.
   Used to probe OPTIONAL fields and CHOICE variants. */
SyntaErrorCode synta_decoder_peek_tag(SyntaDecoder *decoder, SyntaTag *out);
```

### 4.3 Primitive decode functions

```c
/* Decode a BER/DER INTEGER into a heap-allocated SyntaInteger. */
enum SyntaErrorCode synta_decode_integer(SyntaDecoder  *decoder,
                                         SyntaInteger **out);

/* Decode a BOOLEAN. */
enum SyntaErrorCode synta_decode_boolean(SyntaDecoder *decoder, bool *out);

/* Decode an OCTET STRING into a heap-allocated SyntaOctetString. */
enum SyntaErrorCode synta_decode_octet_string(SyntaDecoder      *decoder,
                                              SyntaOctetString **out);

/* Decode a BIT STRING. out receives the raw bytes; bits_unused receives
   the number of unused padding bits in the last byte. */
enum SyntaErrorCode synta_decode_bit_string(SyntaDecoder   *decoder,
                                            SyntaByteArray *out,
                                            uint8_t        *bits_unused);

/* Decode an OBJECT IDENTIFIER into a heap-allocated SyntaObjectIdentifier. */
enum SyntaErrorCode synta_decode_oid(SyntaDecoder           *decoder,
                                     SyntaObjectIdentifier **out);

/* Decode a NULL element (consumes the TLV, no output value). */
enum SyntaErrorCode synta_decode_null(SyntaDecoder *decoder);

/* Decode a REAL element. */
enum SyntaErrorCode synta_decode_real(SyntaDecoder *decoder, double *out);
```

### 4.4 String and time decode functions

Each function allocates a heap `SyntaOctetString*` containing the raw bytes of
the string or time value.

```c
SyntaErrorCode synta_decode_utf8_string_os(SyntaDecoder     *decoder,
                                           SyntaOctetString **out);

SyntaErrorCode synta_decode_printable_string_os(SyntaDecoder     *decoder,
                                                SyntaOctetString **out);

SyntaErrorCode synta_decode_ia5_string_os(SyntaDecoder     *decoder,
                                          SyntaOctetString **out);

SyntaErrorCode synta_decode_utctime_bytes(SyntaDecoder     *decoder,
                                          SyntaOctetString **out);

SyntaErrorCode synta_decode_generalizedtime_bytes(SyntaDecoder     *decoder,
                                                   SyntaOctetString **out);
```

### 4.5 Integer conversion and free

```c
/* Convert a SyntaInteger to a C int64_t.
   Returns SyntaErrorCode_InvalidEncoding if the value overflows int64_t. */
SyntaErrorCode synta_integer_to_i64(SyntaInteger *integer, int64_t *out);

/* Free a heap-allocated SyntaInteger. */
void synta_integer_free(SyntaInteger *integer);

/* Allocate a new SyntaInteger from an int64_t value.
   Used by generated encode implementations. */
SyntaInteger *synta_integer_new_i64(int64_t value);
```

### 4.6 OctetString accessors and free

```c
/* Return a pointer to the raw byte data. Valid until synta_octet_string_free(). */
const uint8_t *synta_octet_string_data(const SyntaOctetString *os);

/* Return the number of bytes. */
size_t synta_octet_string_len(const SyntaOctetString *os);

/* Free a heap-allocated SyntaOctetString. */
void synta_octet_string_free(SyntaOctetString *os);
```

### 4.7 OID free

```c
/* Free a heap-allocated SyntaObjectIdentifier. */
void synta_oid_free(SyntaObjectIdentifier *oid);
```

### 4.8 Encoder lifecycle

```c
/* Create an encoder targeting DER or BER output. */
SyntaEncoder *synta_encoder_new(SyntaEncoding encoding);

/* Release an encoder and all sub-encoders it created. */
void synta_encoder_free(SyntaEncoder *encoder);

/* Obtain a pointer to the encoded bytes and their length.
   Valid until synta_encoder_free() is called. */
const uint8_t *synta_encoder_get_bytes(SyntaEncoder *encoder, size_t *len);
```

### 4.9 Encoder navigation

```c
/* Begin a SEQUENCE element, returning a child encoder for its contents. */
SyntaErrorCode synta_encoder_start_sequence(SyntaEncoder  *parent,
                                            SyntaEncoder **out);

/* Begin a SET element. */
SyntaErrorCode synta_encoder_start_set(SyntaEncoder  *parent,
                                       SyntaEncoder **out);

/* Begin an explicitly or implicitly tagged constructed element. */
SyntaErrorCode synta_encoder_start_constructed(SyntaEncoder  *parent,
                                               SyntaTag       tag,
                                               SyntaEncoder **out);

/* Finish a constructed element started by start_sequence, start_set, or
   start_constructed. Must be called exactly once per start call. */
SyntaErrorCode synta_encoder_end_constructed(SyntaEncoder *encoder);
```

### 4.10 Primitive encode functions

```c
SyntaErrorCode synta_encode_integer(SyntaEncoder       *encoder,
                                    const SyntaInteger *integer);

SyntaErrorCode synta_encode_boolean(SyntaEncoder *encoder, bool value);

SyntaErrorCode synta_encode_octet_string(SyntaEncoder  *encoder,
                                         const uint8_t *data,
                                         size_t         len);

SyntaErrorCode synta_encode_bit_string(SyntaEncoder  *encoder,
                                       const uint8_t *data,
                                       size_t         len,
                                       uint8_t        unused_bits);

SyntaErrorCode synta_encode_object_identifier(SyntaEncoder              *encoder,
                                              const SyntaObjectIdentifier *oid);

SyntaErrorCode synta_encode_null(SyntaEncoder *encoder);

SyntaErrorCode synta_encode_real(SyntaEncoder *encoder, double value);
```

### 4.11 String and time encode functions

```c
SyntaErrorCode synta_encode_utf8_string_bytes(SyntaEncoder  *encoder,
                                              const uint8_t *data,
                                              size_t         len);

SyntaErrorCode synta_encode_printable_string_bytes(SyntaEncoder  *encoder,
                                                   const uint8_t *data,
                                                   size_t         len);

SyntaErrorCode synta_encode_ia5_string_bytes(SyntaEncoder  *encoder,
                                             const uint8_t *data,
                                             size_t         len);
```

Additional string and time encode variants (UTCTime, GeneralizedTime) follow
the same signature pattern.

### 4.12 Tag and encoding types

```c
typedef enum SyntaTagClass {
    SyntaTagClass_Universal       = 0,
    SyntaTagClass_Application     = 1,
    SyntaTagClass_ContextSpecific = 2,
    SyntaTagClass_Private         = 3,
} SyntaTagClass;

typedef struct SyntaTag {
    SyntaTagClass class_;
    bool          constructed;
    uint32_t      number;
} SyntaTag;

typedef enum SyntaEncoding {
    SyntaEncoding_Der = 0,
    SyntaEncoding_Ber = 1,
} SyntaEncoding;
```

---

## 5. Memory Contract

### Ownership rules

- `_decode()` allocates heap memory for `SyntaOctetString*`,
  `SyntaInteger*`, and `SyntaObjectIdentifier*` fields. The caller owns
  those allocations through the struct.
- `SyntaBitString` is decoded into a caller-provided struct; its `data`
  pointer points into libcsynta-managed memory and must not be freed
  directly.
- SEQUENCE OF and SET OF arrays (`items` pointer) are allocated by
  `_decode()` with `malloc`. The caller owns this allocation.
- The struct itself is always caller-owned. `_decode()` expects a pointer
  to an already-allocated (or stack-allocated) struct and fills it in.

### Partial decode failure

If `_decode()` returns a non-success error code partway through a SEQUENCE,
fields that were successfully decoded before the failure are left in the
struct. They are NOT automatically freed. The caller must call `_free()` on
the partially-filled struct to release any already-allocated fields.

Recommended pattern:

```c
/* 'MyType' is a placeholder; replace with your generated type: MyType_decode(...) */
MyType result = {0};
SyntaErrorCode err = MyType_decode(decoder, &result);
if (err != SyntaErrorCode_Success) {
    MyType_free(&result);  /* safe even if result is partially filled */
    return err;
}
/* ... use result ... */
MyType_free(&result);
```

### `_free()` contract

- `_free()` is safe to call on a zero-initialized struct. It checks each
  pointer field for NULL before freeing.
- `_free()` does NOT free the struct itself. The caller is responsible for
  the struct's storage (stack, heap, or embedded in a larger struct).
- For SEQUENCE OF / SET OF: if `items` is non-NULL, `_free()` frees each
  element within the array (calling the element type's own free logic), then
  calls `free(items)` and sets `count` to 0.
- Calling `_free()` more than once on the same struct is undefined behavior.
  Zero the struct after freeing if re-use is required.

---

## 6. Error Handling

### SyntaErrorCode

All decode and encode functions return `SyntaErrorCode`. The relevant values
are:

```c
typedef enum SyntaErrorCode {
    SyntaErrorCode_Success             = 0,
    SyntaErrorCode_InvalidTag          = 1,
    SyntaErrorCode_InvalidLength       = 2,
    SyntaErrorCode_UnexpectedEof       = 3,
    SyntaErrorCode_InvalidEncoding     = 4,
    /* ... additional codes defined in synta.h ... */
} SyntaErrorCode;
```

### Error propagation

Generated decode functions propagate errors from every libcsynta call
immediately, using a pattern equivalent to:

```c
/* 'FieldType' is a placeholder; replace with your schema type: FieldType_decode(...) */
SyntaErrorCode err;
err = synta_decoder_enter_sequence(decoder, &seq);
if (err != SyntaErrorCode_Success) return err;

err = FieldType_decode(seq, &out->field);
if (err != SyntaErrorCode_Success) {
    synta_decoder_free(seq);
    return err;
}
```

Because partial allocations are not freed automatically (see Section 5), the
caller must check every return value and call `_free()` on the output struct
before propagating a failure.

---

## 7. OPTIONAL Fields

### Struct layout

For each OPTIONAL field `fieldname` of type `FieldType`, the generated struct
contains:

```c
bool       has_fieldname;
FieldType  fieldname;
```

The `has_fieldname` bool appears immediately before its associated field.

### Decode

The generated decode implementation uses `synta_decoder_peek_tag()` to inspect
the next tag. If the tag matches the expected tag for `fieldname`, the field is
decoded and `has_fieldname` is set to `true`. If it does not match, the field
is left at its zero-initialized value and `has_fieldname` remains `false`.

```c
/* 'FieldType' is a placeholder; replace with your schema type: FieldType_decode(...) */
SyntaTag peeked;
err = synta_decoder_peek_tag(seq, &peeked);
if (err == SyntaErrorCode_Success &&
    peeked.class_ == SyntaTagClass_ContextSpecific &&
    peeked.number == 0) {
    err = FieldType_decode(seq, &out->fieldname);
    if (err != SyntaErrorCode_Success) return err;
    out->has_fieldname = true;
}
```

### Encode

The generated encode implementation checks `has_fieldname` before encoding:

```c
/* 'FieldType' is a placeholder; replace with your schema type: FieldType_encode(...) */
if (value->has_fieldname) {
    err = FieldType_encode(encoder, &value->fieldname);
    if (err != SyntaErrorCode_Success) return err;
}
```

---

## 8. Tagged Fields (IMPLICIT and EXPLICIT)

### IMPLICIT tags -- primitive fields

For a primitive field with an IMPLICIT context tag, the generated code calls
the primitive decode/encode function directly. The tag on the wire is replaced
by the context tag; no separate container is created.

### IMPLICIT tags -- structural types

For fields whose underlying type is a SEQUENCE, SET, SEQUENCE OF, SET OF, or a
TypeRef that resolves to one of these, the generated code uses
`synta_decoder_enter_constructed()` (or `synta_encoder_start_constructed()`)
to enter the tagged wrapper, then decodes or encodes the inner fields directly:

```c
/* 'parent' is the outer SyntaDecoder; replace with your variable: TypeName_decode(...) */
/* Decode: IMPLICIT [0] SEQUENCE */
SyntaTag impl_tag = { SyntaTagClass_ContextSpecific, true, 0 };
SyntaDecoder *inner;
err = synta_decoder_enter_constructed(parent, impl_tag, &inner);
if (err != SyntaErrorCode_Success) return err;
/* decode fields of the inner SEQUENCE using 'inner' ... */
synta_decoder_free(inner);
```

```c
/* 'parent' is the outer SyntaEncoder; replace with your variable: TypeName_encode(...) */
/* Encode: IMPLICIT [0] SEQUENCE */
SyntaTag impl_tag = { SyntaTagClass_ContextSpecific, true, 0 };
SyntaEncoder *inner;
err = synta_encoder_start_constructed(parent, impl_tag, &inner);
if (err != SyntaErrorCode_Success) return err;
/* encode fields of the inner SEQUENCE using 'inner' ... */
err = synta_encoder_end_constructed(inner);
```

### EXPLICIT tags

For both primitive and TypeRef inner types, the generated code wraps the inner
decode or encode call in an outer constructed element carrying the explicit tag:

```c
/* 'parent'/'SomeType' are placeholders; replace with your variables: SomeType_decode(...) */
/* Decode: EXPLICIT [2] SomeType */
SyntaTag expl_tag = { SyntaTagClass_ContextSpecific, true, 2 };
SyntaDecoder *tagged;
err = synta_decoder_enter_constructed(parent, expl_tag, &tagged);
if (err != SyntaErrorCode_Success) return err;
err = SomeType_decode(tagged, &out->field);
synta_decoder_free(tagged);
if (err != SyntaErrorCode_Success) return err;
```

```c
/* 'parent'/'SomeType' are placeholders; replace with your variables: SomeType_encode(...) */
/* Encode: EXPLICIT [2] SomeType */
SyntaTag expl_tag = { SyntaTagClass_ContextSpecific, true, 2 };
SyntaEncoder *tagged;
err = synta_encoder_start_constructed(parent, expl_tag, &tagged);
if (err != SyntaErrorCode_Success) return err;
err = SomeType_encode(tagged, &value->field);
if (err != SyntaErrorCode_Success) return err;
err = synta_encoder_end_constructed(tagged);
```

### Current limitation

Anonymous inline SEQUENCE, SET, or CHOICE types nested directly inside an
EXPLICIT tag -- without going through a named TypeRef -- are not supported by
the code generator. Define a named type and reference it instead.

---

## 9. CHOICE Dispatch

### Struct layout

A CHOICE type generates:

```c
typedef enum {
    TypeName_TAG_Variant1,
    TypeName_TAG_Variant2,
    /* ... */
} TypeNameTag;

typedef struct {
    TypeNameTag tag;
    union {
        Variant1Type variant1;
        Variant2Type variant2;
        /* ... */
    } value;
} TypeName;
```

### Decode

The generated decode function calls `synta_decoder_peek_tag()` on the decoder
(without entering any container) and dispatches to the variant's decode
function based on the tag class and number:

```c
SyntaTag peeked;
err = synta_decoder_peek_tag(decoder, &peeked);
if (err != SyntaErrorCode_Success) return err;

if (peeked.class_ == SyntaTagClass_Universal && peeked.number == 16) {
    out->tag = TypeName_TAG_Variant1;
    return Variant1Type_decode(decoder, &out->value.variant1);
} else if (...) {
    ...
}
return SyntaErrorCode_InvalidTag;
```

### Encode

The generated encode function switches on `value->tag`:

```c
/* 'TypeName', 'Variant1Type' etc. are schema type placeholders: TypeName_encode(...) */
switch (value->tag) {
case TypeName_TAG_Variant1:
    return Variant1Type_encode(encoder, &value->value.variant1);
case TypeName_TAG_Variant2:
    return Variant2Type_encode(encoder, &value->value.variant2);
default:
    return SyntaErrorCode_InvalidTag;
}
```

---

## 10. Constraint Validation Options

synta-codegen can optionally generate runtime constraint checks in the `.c`
implementation file.

### Default (no flags)

Value range constraints and size constraints are emitted as comments only. No
runtime validation code is generated. PATTERN and CONTAINING constraints are
also comment-only.

### --with-regex (POSIX regex)

For fields with a PATTERN constraint, the generated code uses POSIX
`regcomp()`/`regexec()` (from `<regex.h>`):

- The pattern is compiled once (static variable, compiled on first call).
- Each decode call runs `regexec()` against the decoded string bytes.
- A failed match returns an error.

Link requirement: on most platforms `<regex.h>` is part of libc. No extra
library is needed.

### --with-pcre (PCRE2)

For PATTERN constraints, uses PCRE2 (`<pcre2.h>`):

- The pattern is compiled once with `pcre2_compile()`.
- Each decode call runs `pcre2_match()`.

Link requirement: `-lpcre2-8` (or the appropriate PCRE2 variant).

### --with-containing (CONTAINING constraint)

For fields carrying a CONTAINING constraint (indicating that the raw bytes must
be valid encodings of another type), the generated code allocates a scratch
decoder, decodes the inner type, and frees it:

```c
/* 'InnerType' is the CONTAINING constraint type placeholder: InnerType_decode(...) */
SyntaDecoder *scratch = synta_decoder_new(
    synta_octet_string_data(out->field),
    synta_octet_string_len(out->field),
    SyntaEncoding_Der);
if (!scratch) return SyntaErrorCode_OutOfMemory;
InnerType tmp = {0};
err = InnerType_decode(scratch, &tmp);
InnerType_free(&tmp);
synta_decoder_free(scratch);
if (err != SyntaErrorCode_Success) return err;
```

Without `--with-containing` the check is a comment only.

---

## 11. Arena / Bump Allocator Mode

With `--arena`, the generated header includes an arena type and three inline
helpers, and each type gains an additional `_decode_arena()` function.

### Arena type and helpers (generated in header)

```c
/* Generated by synta-codegen --arena; SYNTA_ARENA_CAPACITY is set in the generated header.
   Arena usage: synta_arena_init(...) */
typedef struct {
    void  *_ptrs[SYNTA_ARENA_CAPACITY];
    void (*_fns[SYNTA_ARENA_CAPACITY])(void *);
    size_t _n;
} SyntaArena;

static inline void synta_arena_init(SyntaArena *arena);

/* Internal -- called by generated _decode_arena() implementations. */
static inline void _synta_arena_track(SyntaArena *arena,
                                      void       *ptr,
                                      void      (*fn)(void *));

/* Free every allocation tracked by the arena. */
static inline void synta_arena_free_all(SyntaArena *arena);
```

### Additional generated function

```c
/* 'TypeName' is a placeholder; generated per schema type: TypeName_decode_arena(...) */
SyntaErrorCode TypeName_decode_arena(SyntaDecoder *decoder,
                                     SyntaArena   *arena,
                                     TypeName     *out);
```

`_decode_arena()` behaves like `_decode()` but registers every heap allocation
(OctetString, Integer, OID, items arrays) with the arena instead of leaving
them for the caller to track individually.

### Usage pattern

```c
/* 'TypeName' is a placeholder; replace with your schema type: TypeName_decode_arena(...) */
SyntaArena arena;
synta_arena_init(&arena);

TypeName result = {0};
SyntaErrorCode err = TypeName_decode_arena(decoder, &arena, &result);
if (err != SyntaErrorCode_Success) {
    synta_arena_free_all(&arena);
    return err;
}

/* use result ... */

synta_arena_free_all(&arena); /* releases all allocations at once */
```

There is no need to call `TypeName_free()` when using arena mode; `synta_arena_free_all()`
releases everything tracked by `_decode_arena()`.

---

## 12. Helper Functions (--with-helpers)

With `--with-helpers`, three additional static inline functions are generated
in the header for each type.

### _init

Zero-initializes every field in the struct to 0, NULL, or false. Equivalent to
`memset(out, 0, sizeof(*out))` but expressed field-by-field for clarity.

```c
/* Generated into the header with --with-helpers: TypeName_init(...) */
static inline void TypeName_init(TypeName *out);
```

### _validate

Checks that all required pointer fields are non-NULL, and that a CHOICE struct
has a recognized tag value. Returns `SyntaErrorCode_Success` or
`SyntaErrorCode_NullPointer` / `SyntaErrorCode_InvalidTag`.

```c
/* Generated into the header with --with-helpers: TypeName_validate(...) */
static inline SyntaErrorCode TypeName_validate(const TypeName *value);
```

### _print

Writes a human-readable debug representation to a `FILE*` stream. Handles NULL
input gracefully (prints `"(null)"`). Intended for debugging, not for
production output.

```c
/* Generated into the header with --with-helpers: TypeName_print(...) */
static inline void TypeName_print(const TypeName *value, FILE *stream);
```

`_print` requires `<stdio.h>` to be included before the generated header, or
the generated header includes it automatically when `--with-helpers` is active.

---

## 13. Build System Integration

### --cmake

Generates a `CMakeLists.txt` targeting CMake 3.10 or later with C99 standard.

Key properties of the generated file:

- Defines a `Synta::Synta` IMPORTED target that wraps libcsynta. The path is
  taken from `--synta-root` if provided, or from a `SYNTA_ROOT` CMake cache
  variable at configure time.
- Generates one `add_library()` call per schema module, in topological order.
- Sets platform-specific link libraries automatically:
  - Linux / BSD: `pthread`, `dl`, `m`
  - Apple: `pthread`
  - Windows: `ws2_32`, `userenv`, `bcrypt`
- Default library type is STATIC. Pass `--shared` to switch to SHARED.

Example generated CMakeLists excerpt:

```cmake
cmake_minimum_required(VERSION 3.10)
project(MySchema C)
set(CMAKE_C_STANDARD 99)

# ... Synta::Synta imported target setup ...

add_library(my_schema STATIC types.c)
target_link_libraries(my_schema PUBLIC Synta::Synta)
```

### --meson

Generates a `meson.build` file with `c_std=c99` and `warning_level=2`.

Key properties:

- If `--synta-root` is provided, locates libcsynta with
  `cc.find_library('csynta', dirs: synta_root)`.
- Otherwise, uses `dependency('synta')` to locate it via pkg-config or the
  Meson wrap system.
- Generates one `library()` or `shared_library()` call per module.
- Generates `declare_dependency()` entries for inter-module dependencies.

---

## 14. Multi-Module Projects

For schemas that import types from other schemas, pass `--output-dir` with
`--emit both` to generate all modules into a single directory at once:

```sh
synta-codegen --lang c --emit both --output-dir ./generated/ \
    base.asn1 extension.asn1
```

Each module produces a `<stem>.h` and `<stem>.c` pair in topological order.
Import references resolve to the corresponding module header.

Use `--check-imports` to validate that all inter-module imports resolve
correctly without generating any output:

```sh
synta-codegen --check-imports base.asn1 extension.asn1
```

When combining `--cmake` or `--meson` with `--output-dir`, the build file and
all C sources (headers and implementations) are generated together in a single
invocation, covering all modules in dependency order:

```sh
synta-codegen --cmake --output-dir ./generated/ base.asn1 extension.asn1
synta-codegen --meson --output-dir ./generated/ base.asn1 extension.asn1
```