synta 0.1.6

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
/**
 * Integration test: Memory Management
 *
 * Tests for memory leaks and proper cleanup
 * Run with Valgrind to verify no leaks
 */

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include "../../include/synta.h"

static int test_count = 0;
static int pass_count = 0;

#define TEST(name) \
    do { \
        test_count++; \
        printf("Test %d: %s ... ", test_count, name);
#define END_TEST() \
        pass_count++; \
        printf("PASS\n"); \
    } while(0)

void test_decoder_cleanup(void) {
    TEST("Decoder cleanup");

    const uint8_t data[] = {0x02, 0x01, 0x2A};

    for (int i = 0; i < 1000; i++) {
        SyntaDecoder* decoder = synta_decoder_new(data, sizeof(data), SyntaEncoding_Der);
        assert(decoder != NULL);
        synta_decoder_free(decoder);
    }

    END_TEST();
}

void test_encoder_cleanup(void) {
    TEST("Encoder cleanup");

    for (int i = 0; i < 1000; i++) {
        SyntaEncoder* encoder = synta_encoder_new(SyntaEncoding_Der);
        assert(encoder != NULL);

        assert(synta_encode_integer_i64(encoder, 42) == SyntaErrorCode_Success);

        SyntaByteArray output = {0};
        assert(synta_encoder_finish(encoder, &output) == SyntaErrorCode_Success);
        synta_byte_array_free(&output);
    }

    END_TEST();
}

void test_integer_cleanup(void) {
    TEST("Integer cleanup");

    for (int i = 0; i < 1000; i++) {
        SyntaInteger* integer = synta_integer_new_i64(123456789);
        assert(integer != NULL);
        synta_integer_free(integer);
    }

    END_TEST();
}

void test_oid_cleanup(void) {
    TEST("OID cleanup");

    for (int i = 0; i < 1000; i++) {
        SyntaObjectIdentifier* oid = synta_oid_from_string("1.2.840.113549.1.1.1");
        assert(oid != NULL);
        synta_oid_free(oid);
    }

    END_TEST();
}

void test_octet_string_cleanup(void) {
    TEST("OctetString cleanup");

    const uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05};

    for (int i = 0; i < 1000; i++) {
        SyntaOctetString* os = synta_octet_string_new(data, sizeof(data));
        assert(os != NULL);
        synta_octet_string_free(os);
    }

    END_TEST();
}

void test_byte_array_cleanup(void) {
    TEST("ByteArray cleanup");

    const uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05};

    for (int i = 0; i < 1000; i++) {
        SyntaByteArray array = {0};
        array.data = data;
        array.len = sizeof(data);
        array.owned = false;

        SyntaByteArray cloned = synta_byte_array_clone(&array);
        assert(cloned.data != NULL);
        assert(cloned.len == array.len);
        assert(cloned.owned == true);

        synta_byte_array_free(&cloned);
    }

    END_TEST();
}

void test_nested_sequence_cleanup(void) {
    TEST("Nested sequence cleanup");

    for (int i = 0; i < 100; i++) {
        // Encode nested structure
        SyntaEncoder* encoder = synta_encoder_new(SyntaEncoding_Der);
        assert(encoder != NULL);

        SyntaEncoder* outer_seq = NULL;
        assert(synta_encoder_start_sequence(encoder, &outer_seq) == SyntaErrorCode_Success);

        SyntaEncoder* inner_seq = NULL;
        assert(synta_encoder_start_sequence(outer_seq, &inner_seq) == SyntaErrorCode_Success);

        assert(synta_encode_integer_i64(inner_seq, 42) == SyntaErrorCode_Success);
        assert(synta_encoder_end_constructed(inner_seq) == SyntaErrorCode_Success);

        assert(synta_encode_boolean(outer_seq, true) == SyntaErrorCode_Success);
        assert(synta_encoder_end_constructed(outer_seq) == SyntaErrorCode_Success);

        SyntaByteArray output = {0};
        assert(synta_encoder_finish(encoder, &output) == SyntaErrorCode_Success);

        // Decode nested structure
        SyntaDecoder* decoder = synta_decoder_new(output.data, output.len, SyntaEncoding_Der);
        assert(decoder != NULL);

        SyntaDecoder* outer_dec = NULL;
        assert(synta_decoder_enter_sequence(decoder, &outer_dec) == SyntaErrorCode_Success);

        SyntaDecoder* inner_dec = NULL;
        assert(synta_decoder_enter_sequence(outer_dec, &inner_dec) == SyntaErrorCode_Success);

        SyntaInteger* integer = NULL;
        assert(synta_decode_integer(inner_dec, &integer) == SyntaErrorCode_Success);
        synta_integer_free(integer);

        synta_decoder_free(inner_dec);

        bool value = false;
        assert(synta_decode_boolean(outer_dec, &value) == SyntaErrorCode_Success);

        synta_decoder_free(outer_dec);
        synta_decoder_free(decoder);
        synta_byte_array_free(&output);
    }

    END_TEST();
}

void test_large_allocation(void) {
    TEST("Large data allocation");

    // Allocate and free large byte array
    size_t large_size = 1024 * 1024;  // 1 MB
    uint8_t* large_data = (uint8_t*)malloc(large_size);
    assert(large_data != NULL);
    memset(large_data, 0x42, large_size);

    SyntaEncoder* encoder = synta_encoder_new(SyntaEncoding_Der);
    assert(encoder != NULL);

    assert(synta_encode_octet_string(encoder, large_data, large_size) == SyntaErrorCode_Success);

    SyntaByteArray output = {0};
    assert(synta_encoder_finish(encoder, &output) == SyntaErrorCode_Success);

    // Decode it back
    SyntaDecoder* decoder = synta_decoder_new(output.data, output.len, SyntaEncoding_Der);
    assert(decoder != NULL);

    SyntaOctetString* os = NULL;
    assert(synta_decode_octet_string(decoder, &os) == SyntaErrorCode_Success);
    assert(synta_octet_string_len(os) == large_size);

    synta_octet_string_free(os);
    synta_decoder_free(decoder);
    synta_byte_array_free(&output);
    free(large_data);

    END_TEST();
}

void test_error_path_cleanup(void) {
    TEST("Error path cleanup");

    // Trigger errors and ensure cleanup happens properly
    const uint8_t invalid_data[] = {0x02, 0x05, 0x01};  // Truncated INTEGER

    for (int i = 0; i < 100; i++) {
        SyntaDecoder* decoder = synta_decoder_new(invalid_data, sizeof(invalid_data), SyntaEncoding_Der);
        assert(decoder != NULL);

        SyntaInteger* integer = NULL;
        SyntaErrorCode err = synta_decode_integer(decoder, &integer);
        assert(err != SyntaErrorCode_Success);
        assert(integer == NULL);  // Should not allocate on error

        synta_decoder_free(decoder);
    }

    END_TEST();
}

int main(void) {
    printf("=== Memory Management Integration Tests ===\n");
    printf("Run with: valgrind --leak-check=full ./test_memory\n\n");

    test_decoder_cleanup();
    test_encoder_cleanup();
    test_integer_cleanup();
    test_oid_cleanup();
    test_octet_string_cleanup();
    test_byte_array_cleanup();
    test_nested_sequence_cleanup();
    test_large_allocation();
    test_error_path_cleanup();

    printf("\n=== Summary ===\n");
    printf("Passed: %d/%d\n", pass_count, test_count);
    printf("\nCheck Valgrind output above for memory leaks.\n");

    /* Clear the thread-local error storage before exit.  test_error_path_cleanup
     * leaves LAST_ERROR populated with the last decode error.  Rust's
     * thread-local destructors fire via pthread_key and only run on
     * pthread_exit(), not on a plain return from main(), so the stored
     * synta::Error would appear as a leak in Valgrind without this call. */
    synta_clear_last_error();

    return (pass_count == test_count) ? 0 : 1;
}