synta 0.2.5

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# Ownership and Lifetimes


Understanding memory ownership and lifetime management in the Synta C API is
essential for writing correct, leak-free programs.

## Key Principles

1. **Explicit ownership** — It is always clear who owns what.
2. **Free what you create** — If a function returns a pointer, you must free it
   with the corresponding `_free` function.
3. **Do not free borrowed data** — Some functions return borrowed references.
4. **One free per allocation** — Never double-free.

## Ownership Categories

### Library-Owned (You Must Free)

These functions allocate memory that the caller must free:

| Function | Free With |
|---|---|
| `synta_decoder_new()` | `synta_decoder_free()` |
| `synta_decoder_new_with_config()` | `synta_decoder_free()` |
| `synta_decoder_enter_sequence()` | `synta_decoder_free()` |
| `synta_decoder_enter_set()` | `synta_decoder_free()` |
| `synta_decoder_enter_constructed()` | `synta_decoder_free()` |
| `synta_encoder_new()` | `synta_encoder_free()` or `synta_encoder_finish()` |
| `synta_decode_integer()` | `synta_integer_free()` |
| `synta_decode_octet_string()` | `synta_octet_string_free()` |
| `synta_decode_object_identifier()` | `synta_oid_free()` |
| `synta_integer_new_*()` | `synta_integer_free()` |
| `synta_octet_string_new()` | `synta_octet_string_free()` |
| `synta_oid_new()` | `synta_oid_free()` |
| `synta_oid_from_string()` | `synta_oid_free()` |
| `synta_encoder_finish()` (output) | `synta_byte_array_free()` |
| `synta_byte_array_alloc()` | `synta_byte_array_free()` |
| `synta_byte_array_clone()` | `synta_byte_array_free()` |
| `synta_certificate_parse_der()` | `synta_certificate_free()` |
| `synta_crl_parse_der()` / `_pem()` | `synta_crl_free()` |
| `synta_csr_parse_der()` / `_pem()` | `synta_csr_free()` |
| `synta_ocsp_parse_der()` | `synta_ocsp_free()` |
| `synta_pem_to_der()` | `synta_der_list_free()` |
| `synta_cms_content_info_parse_der()` | `synta_cms_content_info_free()` |

### Caller-Owned (You Provide Storage)

These functions write into caller-provided storage:

```c
/* Caller provides storage; no allocation */
bool value;
synta_decode_boolean(decoder, &value);

SyntaByteArray output;
synta_decode_utf8_string(decoder, &output);
/* Free only if output.owned != 0 */

SyntaTag tag;
synta_decoder_peek_tag(decoder, &tag);
/* No cleanup needed; SyntaTag is a plain struct */
```

### Borrowed References (Do Not Free)

These functions return pointers to data owned by another object:

```c
/* Borrowed from SyntaOctetString — do NOT free */
const uint8_t *data = synta_octet_string_data(octet_string);

/* Borrowed from SyntaObjectIdentifier — do NOT free */
const uint32_t *components;
size_t len;
synta_oid_components(oid, &components, &len);

/* Static string — do NOT free */
const char *msg = synta_error_message(err);
```

Certificate getter functions return borrowed `SyntaByteArray` values
(`owned == 0`) pointing directly into the original DER buffer:

```c
SyntaByteArray subject = {0};
synta_certificate_get_subject_der(cert, &subject);
/* subject.owned == 0: valid while cert is alive; do NOT free */
```

### Consumed Objects

Some functions consume their argument:

```c
SyntaEncoder *encoder = synta_encoder_new(SyntaEncoding_Der);
synta_encode_integer_i64(encoder, 42);

SyntaByteArray output = {0};
synta_encoder_finish(encoder, &output); /* encoder is CONSUMED */

/* DO NOT use encoder after this point */
/* DO NOT call synta_encoder_free(encoder) */

synta_byte_array_free(&output);
```

## SyntaByteArray: owned Flag

`SyntaByteArray.owned` is a `uint32_t`:

- `0` = borrowed; do not free.
- Non-zero = owned allocation; caller must call `synta_byte_array_free()`.

Always check `owned` before freeing:

```c
SyntaByteArray array = {0};
synta_decode_utf8_string(decoder, &array);
if (array.owned != 0) {
    synta_byte_array_free(&array);
}
```

`synta_byte_array_free()` already checks `owned` internally, so calling it on
a borrowed array is safe (it does nothing).  However, explicitly checking
`owned` makes the intent clear in application code.

## Common Pitfalls

### Double Free

```c
/* WRONG */
SyntaInteger *integer = synta_integer_new_i64(42);
synta_integer_free(integer);
synta_integer_free(integer); /* CRASH: double free */

/* RIGHT */
SyntaInteger *integer = synta_integer_new_i64(42);
synta_integer_free(integer);
integer = NULL; /* prevent accidental reuse */
```

### Use After Free

```c
/* WRONG */
SyntaDecoder *decoder = synta_decoder_new(data, len, SyntaEncoding_Der);
synta_decoder_free(decoder);
synta_decode_integer(decoder, &integer); /* CRASH: use after free */

/* RIGHT */
SyntaDecoder *decoder = synta_decoder_new(data, len, SyntaEncoding_Der);
synta_decode_integer(decoder, &integer); /* use first */
synta_decoder_free(decoder);              /* then free */
```

### Memory Leak on Error Path

```c
/* WRONG: leaks decoder on error */
SyntaDecoder *decoder = synta_decoder_new(data, len, SyntaEncoding_Der);
SyntaInteger *integer;
if (synta_decode_integer(decoder, &integer) != SyntaErrorCode_Success) {
    return ERROR; /* LEAK: decoder not freed */
}
synta_decoder_free(decoder);

/* RIGHT */
SyntaDecoder *decoder = synta_decoder_new(data, len, SyntaEncoding_Der);
SyntaInteger *integer;
if (synta_decode_integer(decoder, &integer) != SyntaErrorCode_Success) {
    synta_decoder_free(decoder); /* clean up before returning */
    return ERROR;
}
synta_decoder_free(decoder);
```

### Freeing Borrowed Data

```c
/* WRONG */
const uint8_t *data = synta_octet_string_data(octet_string);
free(data); /* CRASH: borrowed data */

/* RIGHT */
const uint8_t *data = synta_octet_string_data(octet_string);
memcpy(buffer, data, synta_octet_string_len(octet_string)); /* just copy it */
```

### Using a Consumed Encoder

```c
/* WRONG */
SyntaEncoder *encoder = synta_encoder_new(SyntaEncoding_Der);
SyntaByteArray output;
synta_encoder_finish(encoder, &output); /* encoder consumed */
synta_encode_boolean(encoder, true);    /* CRASH: encoder was consumed */

/* RIGHT */
SyntaEncoder *encoder = synta_encoder_new(SyntaEncoding_Der);
synta_encode_boolean(encoder, true);    /* encode first */
SyntaByteArray output;
synta_encoder_finish(encoder, &output); /* then finish */
```

## RAII Wrappers for C++

C++ users can use `std::unique_ptr` with custom deleters for automatic cleanup:

```cpp
#include <synta.h>
#include <memory>

struct DecoderDeleter {
    void operator()(SyntaDecoder *p) const { synta_decoder_free(p); }
};
struct IntegerDeleter {
    void operator()(SyntaInteger *p) const { synta_integer_free(p); }
};

using DecoderPtr = std::unique_ptr<SyntaDecoder, DecoderDeleter>;
using IntegerPtr = std::unique_ptr<SyntaInteger, IntegerDeleter>;

void decode_example(const uint8_t *data, size_t len) {
    DecoderPtr decoder(synta_decoder_new(data, len, SyntaEncoding_Der));
    if (!decoder) throw std::runtime_error("Failed to create decoder");

    SyntaInteger *raw_integer = nullptr;
    if (synta_decode_integer(decoder.get(), &raw_integer) != SyntaErrorCode_Success)
        throw std::runtime_error("Failed to decode integer");
    IntegerPtr integer(raw_integer);

    int64_t value;
    if (synta_integer_to_i64(integer.get(), &value) != SyntaErrorCode_Success)
        throw std::runtime_error("Integer overflow");

    std::cout << "Value: " << value << "\n";
    /* All resources automatically cleaned up by destructors */
}
```

## Debugging

### Valgrind

```bash
valgrind --leak-check=full --show-leak-kinds=all ./your_program
```

Expected output for correct code:

```
HEAP SUMMARY:
    in use at exit: 0 bytes in 0 blocks
All heap blocks were freed -- no leaks are possible
```

### AddressSanitizer

```bash
gcc -fsanitize=address -fno-omit-frame-pointer -g myapp.c -lcsynta -o myapp
./myapp
```

### Debug Checklist

1. Check all `_new()` and `parse_*()` functions have corresponding `_free()` calls.
2. Verify error paths also clean up.
3. Confirm encoder is not used after `synta_encoder_finish()`.
4. Check that borrowed pointers (`owned == 0`) are not freed.
5. Verify `SyntaByteArray.owned` flag is checked before freeing.
6. Ensure no double-frees.
7. Check that all child decoders from `enter_*` are freed.