# ASN.1 DER

```c
#define FIO_DER
#include "fio-stl.h"
```

ASN.1 DER is the small binary grammar hiding inside X.509 certificates, keys, signatures, and TLS messages. This module parses and writes the pieces facil.io needs for that world.

The parser is non-allocating. Parsed elements point into the original DER buffer, so keep that buffer alive while you use the results. No buffer, no treasure map.

## Core Types

### `fio_der_element_s`

```c
typedef struct {
  const uint8_t *data;
  size_t len;
  uint8_t tag;
  uint8_t is_constructed;
  uint8_t tag_class;
  uint8_t tag_number;
} fio_der_element_s;
```

A parsed DER element. `data` points to the content bytes, after the tag and length fields. `len` is the content length. The remaining fields describe the tag byte.

### `fio_der_iterator_s`

```c
typedef struct {
  const uint8_t *pos;
  const uint8_t *end;
} fio_der_iterator_s;
```

Iterator state for walking a `SEQUENCE` or `SET` element.

### Tags and Classes

`fio_der_tag_e` defines the common universal tags, including `FIO_DER_INTEGER`, `FIO_DER_BIT_STRING`, `FIO_DER_OID`, `FIO_DER_SEQUENCE`, `FIO_DER_SET`, string types, `FIO_DER_UTC_TIME`, `FIO_DER_GENERALIZED_TIME`, and context wrappers `FIO_DER_CONTEXT_0` through `FIO_DER_CONTEXT_3`.

`fio_der_class_e` names the tag classes: universal, application, context-specific, and private.

## OID Values

OID constants live in the X.509 module (`FIO_X509_OID_*`, see the X.509
documentation). An OID value is a plain `fio_u128` where bytes 0-14 hold the
DER content bytes (zero-padded) and byte 15 holds the content length. The
length is folded in because `0x00` is a legal OID content byte (arc 0), so
padding alone could not distinguish `{..0B}` from `{..0B 00}` - different
OIDs. Oversized OIDs (content > 15 bytes) simply never match, which is safe:
AlgorithmIdentifier OIDs are attacker-controlled and RFC 5280 requires exact
match.

Build a value once per parsed element with `fio___der_oid_value` (internal),
then compare it against any number of constants with `fio___der_oid_eq`
(internal) - two u64-lane `==` comparisons, no memcmp, no call:

```c
fio_u128 oid = fio___der_oid_value(&elem); /* build ONCE per element */
if (fio___der_oid_eq(oid, FIO_X509_OID_COMMON_NAME)) { /* ... */ }
```

## Parsing

### `fio_der_parse`

```c
SFUNC const uint8_t *fio_der_parse(fio_der_element_s *elem,
                                    const uint8_t *data,
                                    size_t data_len);
```

Parses one DER element. On success, fills `elem` and returns a pointer to the next element. On error, returns `NULL`.

It rejects invalid DER lengths, truncated buffers, unsupported high-tag-number encodings, and indefinite lengths.

### `fio_der_element_total_len`

```c
FIO_IFUNC size_t fio_der_element_total_len(const fio_der_element_s *elem,
                                            const uint8_t *data);
```

Returns the full encoded length of an element: tag + length + content. `data` must be the original pointer used to parse the element.

## Type Parsers

### `fio_der_parse_integer`

```c
SFUNC int fio_der_parse_integer(const fio_der_element_s *elem,
                                 uint64_t *value);
```

Parses an ASN.1 `INTEGER`. For small integers, pass `value` and receive the 64-bit value. For large integers such as RSA moduli, pass `NULL` and use `elem->data` / `elem->len` directly. Leading zero bytes for positive integers are handled.

Returns `0` on success, `-1` on error.

### `fio_der_parse_bit_string`

```c
SFUNC int fio_der_parse_bit_string(const fio_der_element_s *elem,
                                    const uint8_t **bits,
                                    size_t *bit_len,
                                    uint8_t *unused_bits);
```

Parses an ASN.1 `BIT STRING`. `bits` points into the element data after the unused-bit count byte. `bit_len` is the byte length of the bit payload. `unused_bits` is the number of unused bits in the last byte.

### `fio_der_parse_oid`

```c
SFUNC int fio_der_parse_oid(const fio_der_element_s *elem,
                             char *buf,
                             size_t buf_len);
```

Converts an ASN.1 `OBJECT IDENTIFIER` into dotted text such as `1.2.840.113549.1.1.11`.

Returns the number of characters written, excluding the NUL byte, or `-1` on error.

### `fio_der_parse_time`

```c
SFUNC int fio_der_parse_time(const fio_der_element_s *elem,
                              int64_t *unix_time);
```

Parses `UTCTime` or `GeneralizedTime` into a Unix timestamp. Times must be UTC (`Z`). Fractional seconds are skipped when present.

### `fio_der_parse_string`

```c
FIO_IFUNC const char *fio_der_parse_string(const fio_der_element_s *elem,
                                            size_t *len);
```

Returns a pointer to the string bytes for supported ASN.1 string tags, with the byte length in `len`. This does not validate or transcode the text; it simply gives you the payload.

### `fio_der_parse_boolean`

```c
FIO_IFUNC int fio_der_parse_boolean(const fio_der_element_s *elem,
                                     int *value);
```

Parses a DER boolean and writes `0` or non-zero to `value`.

## Walking Sequences and Sets

### `fio_der_iterator_init`

```c
FIO_IFUNC void fio_der_iterator_init(fio_der_iterator_s *it,
                                      const fio_der_element_s *sequence);
```

Initializes an iterator over the content bytes of a parsed `SEQUENCE` or `SET`.

### `fio_der_iterator_next`

```c
SFUNC int fio_der_iterator_next(fio_der_iterator_s *it,
                                 fio_der_element_s *elem);
```

Parses the next child element and advances the iterator. Returns `0` when an element was read, `-1` at end or on parse error.

### `fio_der_iterator_has_next`

```c
FIO_IFUNC int fio_der_iterator_has_next(const fio_der_iterator_s *it);
```

Returns `1` when the iterator still has bytes to parse, otherwise `0`.

## Tag Helpers

```c
FIO_IFUNC int fio_der_is_tag(const fio_der_element_s *elem, uint8_t tag);
FIO_IFUNC int fio_der_is_context_tag(const fio_der_element_s *elem,
                                      uint8_t tag_num);
FIO_IFUNC uint8_t fio_der_tag_number(const fio_der_element_s *elem);
```

Use these to keep parser code readable:

- `fio_der_is_tag` checks a universal tag such as `FIO_DER_INTEGER`.
- `fio_der_is_context_tag` checks `[0]`, `[1]`, and friends.
- `fio_der_tag_number` returns the decoded tag number.

## Encoding

All encoder functions return the number of bytes written or needed. Pass `NULL` as `buf` to calculate the encoded length before writing.

### Basic Encoders

```c
SFUNC size_t fio_der_encode_length(uint8_t *buf, size_t len);
SFUNC size_t fio_der_encode_integer(uint8_t *buf,
                                     const uint8_t *data,
                                     size_t data_len);
SFUNC size_t fio_der_encode_integer_small(uint8_t *buf, uint64_t value);
SFUNC size_t fio_der_encode_null(uint8_t *buf);
SFUNC size_t fio_der_encode_boolean(uint8_t *buf, int value);
```

These write DER length fields, positive integers, `NULL`, and booleans. `fio_der_encode_integer` expects big-endian integer bytes and adds the leading zero byte when DER needs one to keep the integer positive.

OIDs are encoded with the internal `fio___der_encode_oid(buf, fio_u128 oid)`,
which TLV-wraps an OID value (see OID Values above) without any dot-string
parsing.

### String and Byte Encoders

```c
SFUNC size_t fio_der_encode_utf8_string(uint8_t *buf,
                                         const char *str,
                                         size_t str_len);
SFUNC size_t fio_der_encode_printable_string(uint8_t *buf,
                                              const char *str,
                                              size_t str_len);
SFUNC size_t fio_der_encode_bit_string(uint8_t *buf,
                                        const uint8_t *bits,
                                        size_t bit_len,
                                        uint8_t unused_bits);
SFUNC size_t fio_der_encode_octet_string(uint8_t *buf,
                                          const uint8_t *data,
                                          size_t data_len);
```

These write the tag, DER length, and payload for common primitive values.

### Header Encoders

```c
SFUNC size_t fio_der_encode_sequence_header(uint8_t *buf, size_t content_len);
SFUNC size_t fio_der_encode_set_header(uint8_t *buf, size_t content_len);
SFUNC size_t fio_der_encode_context_header(uint8_t *buf,
                                            uint8_t tag_num,
                                            size_t content_len,
                                            int constructed);
```

These write only the wrapper tag and length. Write the content bytes immediately after the returned header length.

### Time Encoders

```c
SFUNC size_t fio_der_encode_utc_time(uint8_t *buf, int64_t unix_time);
SFUNC size_t fio_der_encode_generalized_time(uint8_t *buf, int64_t unix_time);
```

These encode Unix timestamps as DER `UTCTime` or `GeneralizedTime`.

## Example

```c
#define FIO_DER
#include "fio-stl.h"

void scan_sequence(const uint8_t *der, size_t der_len) {
  fio_der_element_s seq;
  if (!fio_der_parse(&seq, der, der_len))
    return;
  if (!fio_der_is_tag(&seq, FIO_DER_SEQUENCE))
    return;

  fio_der_iterator_s it;
  fio_der_iterator_init(&it, &seq);

  fio_der_element_s elem;
  while (fio_der_iterator_next(&it, &elem) == 0) {
    if (fio_der_is_tag(&elem, FIO_DER_OID)) {
      fio_u128 oid = fio___der_oid_value(&elem);
      /* compare with FIO_X509_OID_* constants via fio___der_oid_eq, or */
      char dot[128];
      if (fio_der_parse_oid(&elem, dot, sizeof(dot)) > 0) {
        /* dotted string available for diagnostics */
      }
    }
  }
}
```

------------------------------------------------------------
