synta 0.2.5

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
/**
 * Integration test: Error Handling
 *
 * Tests that error conditions are properly detected and reported
 */

#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_invalid_tag(void) {
    TEST("Invalid tag detection");

    // Invalid tag: 0xFF (reserved)
    const uint8_t data[] = {0xFF, 0x01, 0x00};

    SyntaDecoder* decoder = synta_decoder_new(data, sizeof(data), SyntaEncoding_Der);
    assert(decoder != NULL);

    bool value = false;
    SyntaErrorCode err = synta_decode_boolean(decoder, &value);
    assert(err != SyntaErrorCode_Success);
    // Test passes if we get any error (don't check specific error code)

    synta_decoder_free(decoder);

    END_TEST();
}

void test_truncated_data(void) {
    TEST("Truncated data detection");

    // Incomplete INTEGER: tag=0x02, length=0x05, but only 2 bytes of data
    const uint8_t data[] = {0x02, 0x05, 0x01, 0x02};

    SyntaDecoder* decoder = synta_decoder_new(data, sizeof(data), SyntaEncoding_Der);
    assert(decoder != NULL);

    SyntaInteger* integer = NULL;
    SyntaErrorCode err = synta_decode_integer(decoder, &integer);
    assert(err != SyntaErrorCode_Success);
    assert(err == SyntaErrorCode_UnexpectedEof || err == SyntaErrorCode_InvalidLength);

    synta_decoder_free(decoder);

    END_TEST();
}

void test_null_pointer_handling(void) {
    TEST("NULL pointer handling");

    // Test decoder functions with NULL pointers
    // NULL data with len=0 is allowed (creates empty decoder)
    SyntaDecoder* empty_decoder = synta_decoder_new(NULL, 0, SyntaEncoding_Der);
    assert(empty_decoder != NULL);
    synta_decoder_free(empty_decoder);

    // NULL data with non-zero len should fail
    assert(synta_decoder_new(NULL, 10, SyntaEncoding_Der) == NULL);

    const uint8_t data[] = {0x02, 0x01, 0x2A};
    SyntaDecoder* decoder = synta_decoder_new(data, sizeof(data), SyntaEncoding_Der);
    assert(decoder != NULL);

    // NULL output pointer should return error
    SyntaErrorCode err = synta_decode_integer(decoder, NULL);
    assert(err == SyntaErrorCode_NullPointer);

    synta_decoder_free(decoder);

    // Test encoder functions with NULL
    SyntaEncoder* non_null_encoder = synta_encoder_new(SyntaEncoding_Der);
    assert(non_null_encoder != NULL);
    synta_encoder_free(non_null_encoder);

    END_TEST();
}

void test_empty_data(void) {
    TEST("Empty data handling");

    const uint8_t data[] = {};

    SyntaDecoder* decoder = synta_decoder_new(data, 0, SyntaEncoding_Der);
    assert(decoder != NULL);

    assert(synta_decoder_at_end(decoder) == true);
    assert(synta_decoder_remaining(decoder) == 0);

    bool value = false;
    SyntaErrorCode err = synta_decode_boolean(decoder, &value);
    assert(err != SyntaErrorCode_Success);

    synta_decoder_free(decoder);

    END_TEST();
}

void test_error_message_retrieval(void) {
    TEST("Error message retrieval");

    // Clear any previous errors
    synta_clear_last_error();

    // Trigger an error
    const uint8_t data[] = {0x02, 0x05, 0x01};  // Truncated INTEGER
    SyntaDecoder* decoder = synta_decoder_new(data, sizeof(data), SyntaEncoding_Der);
    assert(decoder != NULL);

    SyntaInteger* integer = NULL;
    SyntaErrorCode err = synta_decode_integer(decoder, &integer);
    assert(err != SyntaErrorCode_Success);

    // Get error message
    const char* msg = synta_get_last_error_message();
    assert(msg != NULL);
    assert(strlen(msg) > 0);

    // Get static error message
    const char* static_msg = synta_error_message(err);
    assert(static_msg != NULL);
    assert(strlen(static_msg) > 0);

    synta_decoder_free(decoder);

    // Clear and verify
    synta_clear_last_error();
    msg = synta_get_last_error_message();
    assert(msg == NULL || strlen(msg) == 0);

    END_TEST();
}

void test_invalid_utf8(void) {
    TEST("Invalid UTF-8 detection");

    // Invalid UTF-8 sequence: 0xC0 0x80 (overlong encoding of NULL)
    const char invalid_utf8[] = {(char)0xC0, (char)0x80, 0x00};

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

    SyntaErrorCode err = synta_encode_utf8_string(encoder, invalid_utf8);
    // Should either reject or accept (implementation dependent)
    // Just verify we get a consistent result

    if (err == SyntaErrorCode_Success) {
        SyntaByteArray encoded = {0};
        synta_encoder_finish(encoder, &encoded);
        synta_byte_array_free(&encoded);
    } else {
        synta_encoder_free(encoder);
    }

    END_TEST();
}

void test_integer_overflow(void) {
    TEST("Integer overflow detection");

    // Create a very large integer from bytes
    const uint8_t large_bytes[] = {
        0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
    };

    SyntaInteger* integer = synta_integer_new_bytes(large_bytes, sizeof(large_bytes), false);
    assert(integer != NULL);

    // Try to convert to int64_t - should fail (overflow)
    int64_t value;
    SyntaErrorCode err = synta_integer_to_i64(integer, &value);
    assert(err == SyntaErrorCode_IntegerOverflow);

    synta_integer_free(integer);

    END_TEST();
}

void test_invalid_oid_string(void) {
    TEST("Invalid OID string detection");

    // Invalid OID strings
    const char* invalid_oids[] = {
        "",
        ".",
        "1",
        "1.",
        ".1.2",
        "1.2.abc",
        "1.2.-3",
        "3.0.1",  // First component must be 0, 1, or 2
        NULL
    };

    for (int i = 0; invalid_oids[i] != NULL; i++) {
        SyntaObjectIdentifier* oid = synta_oid_from_string(invalid_oids[i]);
        // Should return NULL or valid OID (some implementations may accept)
        if (oid != NULL) {
            synta_oid_free(oid);
        }
    }

    END_TEST();
}

void test_sequence_mismatch(void) {
    TEST("Sequence/tag mismatch detection");

    // Data is an INTEGER, but try to decode as SEQUENCE
    const uint8_t data[] = {0x02, 0x01, 0x2A};

    SyntaDecoder* decoder = synta_decoder_new(data, sizeof(data), SyntaEncoding_Der);
    assert(decoder != NULL);

    SyntaDecoder* seq_decoder = NULL;
    SyntaErrorCode err = synta_decoder_enter_sequence(decoder, &seq_decoder);
    assert(err != SyntaErrorCode_Success);
    // Test passes if we get any error (don't check specific error code)

    synta_decoder_free(decoder);

    END_TEST();
}

int main(void) {
    printf("=== Error Handling Integration Tests ===\n\n");

    test_invalid_tag();
    test_truncated_data();
    test_null_pointer_handling();
    test_empty_data();
    test_error_message_retrieval();
    test_invalid_utf8();
    test_integer_overflow();
    test_invalid_oid_string();
    test_sequence_mismatch();

    printf("\n=== Summary ===\n");
    printf("Passed: %d/%d\n", pass_count, test_count);

    /* Clear the thread-local error storage before exit.  Rust's thread-local
     * destructors fire via pthread_key destructors, which only run on
     * pthread_exit(), not on a plain return from main().  Without this call
     * the StoredError (synta::Error + optional CString) is still live when
     * Valgrind takes its end-of-process leak snapshot. */
    synta_clear_last_error();

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