C API Overview
Matchy provides a stable C API for integration with C, C++, and other languages that support C FFI.
See First Database with C for a tutorial.
Design Principles
The C API follows these principles:
- Opaque handles: All Rust types are wrapped in opaque pointers
- Integer error codes: Functions return
int
status codes - No panics: All panics are caught at the FFI boundary
- Memory safety: Clear ownership semantics for all pointers
- ABI stability: Uses
#[repr(C)]
andextern "C"
Header File
#include <matchy.h>
The header is auto-generated by cbindgen
during release builds:
cargo build --release
# Generates include/matchy.h
Core Types
Opaque Handles
typedef struct matchy_database matchy_database;
typedef struct matchy_builder matchy_builder;
typedef struct matchy_result matchy_result;
These are opaque pointers - never dereference them directly.
Error Codes
typedef int matchy_error_t;
#define MATCHY_OK 0
#define MATCHY_ERROR_INVALID_PARAM 1
#define MATCHY_ERROR_FILE_NOT_FOUND 2
#define MATCHY_ERROR_INVALID_FORMAT 3
#define MATCHY_ERROR_CORRUPT_DATA 4
#define MATCHY_ERROR_PATTERN_ERROR 5
#define MATCHY_ERROR_BUILD_FAILED 6
#define MATCHY_ERROR_UNKNOWN 99
Result Types
typedef enum {
MATCHY_RESULT_IP = 1,
MATCHY_RESULT_PATTERN = 2,
MATCHY_RESULT_EXACT_STRING = 3,
} matchy_result_type;
Function Groups
The C API is organized into these groups:
Database Operations
matchy_open()
- Open database (default settings)matchy_open_with_options()
- Open database with custom optionsmatchy_init_open_options()
- Initialize option structurematchy_open_trusted()
- Open database (skip validation)matchy_close()
- Close databasematchy_query()
- Query databasematchy_get_stats()
- Get database statisticsmatchy_clear_cache()
- Clear query cache
Builder Operations
matchy_builder_new()
- Create buildermatchy_builder_add_ip()
- Add IP entrymatchy_builder_add_pattern()
- Add pattern entrymatchy_builder_add_exact()
- Add exact string entrymatchy_builder_build()
- Build databasematchy_builder_free()
- Free builder
Result Operations
matchy_result_type()
- Get result typematchy_result_ip_prefix_len()
- Get IP prefix lengthmatchy_result_pattern_count()
- Get pattern countmatchy_result_free()
- Free result
Error Handling Pattern
All functions return error codes:
matchy_database *db = NULL;
matchy_error_t err = matchy_open("database.mxy", &db);
if (err != MATCHY_OK) {
fprintf(stderr, "Error opening database: %d\n", err);
return 1;
}
// Use db...
matchy_close(db);
Memory Management
Ownership Rules
- Caller owns input strings - You must keep them valid during the call
- Callee owns output handles - Free them with the appropriate
_free()
function - Results must be freed - Always call
matchy_result_free()
Example
// You own this string
const char *path = "database.mxy";
// Matchy owns this handle after successful open
matchy_database *db = NULL;
if (matchy_open(path, &db) == MATCHY_OK) {
// Use db...
// Matchy owns this result
matchy_result *result = NULL;
if (matchy_lookup(db, "192.0.2.1", &result) == MATCHY_OK) {
if (result != NULL) {
// Use result...
// You must free the result
matchy_result_free(result);
}
}
// You must close the database
matchy_close(db);
}
Thread Safety
- Database handles (
matchy_database
) are thread-safe for reading - Builder handles (
matchy_builder
) are NOT thread-safe - Result handles (
matchy_result
) should not be shared
Multiple threads can safely call matchy_lookup()
on the same database:
// Thread 1
matchy_result *r1 = NULL;
matchy_lookup(db, "query1", &r1);
// Thread 2 (safe!)
matchy_result *r2 = NULL;
matchy_lookup(db, "query2", &r2);
Opening with Cache Options
Basic Opening (Default Cache)
// Opens with default cache (10,000 entries)
matchy_t *db = matchy_open("database.mxy");
if (db == NULL) {
fprintf(stderr, "Failed to open database\n");
return 1;
}
Custom Cache Configuration
// Initialize options structure
matchy_open_options_t opts;
matchy_init_open_options(&opts);
// Configure cache and validation
opts.cache_capacity = 100000; // Large cache for high repetition
opts.trusted = 1; // Skip validation (faster)
matchy_t *db = matchy_open_with_options("threats.mxy", &opts);
if (db == NULL) {
fprintf(stderr, "Failed to open database\n");
return 1;
}
No Cache
matchy_open_options_t opts;
matchy_init_open_options(&opts);
opts.cache_capacity = 0; // Disable cache
matchy_t *db = matchy_open_with_options("database.mxy", &opts);
Get Statistics
matchy_stats_t stats;
matchy_get_stats(db, &stats);
printf("Total queries: %llu\n", stats.total_queries);
printf("Queries with match: %llu\n", stats.queries_with_match);
printf("IP queries: %llu\n", stats.ip_queries);
printf("String queries: %llu\n", stats.string_queries);
// Calculate rates
double cache_hit_rate = 0.0;
if (stats.cache_hits + stats.cache_misses > 0) {
cache_hit_rate = (double)stats.cache_hits /
(stats.cache_hits + stats.cache_misses);
}
double match_rate = 0.0;
if (stats.total_queries > 0) {
match_rate = (double)stats.queries_with_match / stats.total_queries;
}
printf("Cache hit rate: %.1f%%\n", cache_hit_rate * 100.0);
printf("Match rate: %.1f%%\n", match_rate * 100.0);
matchy_stats_t Structure
typedef struct {
uint64_t total_queries;
uint64_t queries_with_match;
uint64_t queries_without_match;
uint64_t cache_hits;
uint64_t cache_misses;
uint64_t ip_queries;
uint64_t string_queries;
} matchy_stats_t;
Clear Cache
// Do some queries (fills cache)
matchy_result_t result = matchy_query(db, "example.com");
matchy_free_result(&result);
// Clear cache to force fresh lookups
matchy_clear_cache(db);
Complete Example
#include <matchy.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
matchy_error_t err;
// Build database
matchy_builder *builder = matchy_builder_new();
if (!builder) {
fprintf(stderr, "Failed to create builder\n");
return 1;
}
err = matchy_builder_add_ip(builder, "192.0.2.1/32", NULL);
if (err != MATCHY_OK) {
fprintf(stderr, "Failed to add IP: %d\n", err);
matchy_builder_free(builder);
return 1;
}
err = matchy_builder_add_pattern(builder, "*.example.com", NULL);
if (err != MATCHY_OK) {
fprintf(stderr, "Failed to add pattern: %d\n", err);
matchy_builder_free(builder);
return 1;
}
// Build to file
err = matchy_builder_build(builder, "database.mxy");
matchy_builder_free(builder);
if (err != MATCHY_OK) {
fprintf(stderr, "Failed to build: %d\n", err);
return 1;
}
// Open database
matchy_database *db = NULL;
err = matchy_open("database.mxy", &db);
if (err != MATCHY_OK) {
fprintf(stderr, "Failed to open: %d\n", err);
return 1;
}
// Query
const char *queries[] = {
"192.0.2.1",
"test.example.com",
"notfound.com",
};
for (int i = 0; i < 3; i++) {
matchy_result *result = NULL;
err = matchy_lookup(db, queries[i], &result);
if (err != MATCHY_OK) {
fprintf(stderr, "Lookup error for '%s': %d\n", queries[i], err);
continue;
}
if (result == NULL) {
printf("%s: Not found\n", queries[i]);
} else {
matchy_result_type type = matchy_result_type(result);
printf("%s: Found (type %d)\n", queries[i], type);
matchy_result_free(result);
}
}
matchy_close(db);
return 0;
}
Compilation
GCC/Clang
gcc -o myapp myapp.c \
-I./include \
-L./target/release \
-lmatchy
Setting Library Path
# Linux
export LD_LIBRARY_PATH=./target/release:$LD_LIBRARY_PATH
# macOS
export DYLD_LIBRARY_PATH=./target/release:$DYLD_LIBRARY_PATH
Static Linking
# For static linking on Linux, you may need system libraries:
gcc -o myapp myapp.c \
-I./include \
./target/release/libmatchy.a \
-lpthread -ldl -lm
# On macOS, static linking usually just needs:
gcc -o myapp myapp.c \
-I./include \
./target/release/libmatchy.a
Best Practices
1. Always Check Return Values
if (matchy_open(path, &db) != MATCHY_OK) {
// Handle error
}
2. Initialize Pointers to NULL
matchy_database *db = NULL; // Good
matchy_open(path, &db);
3. Free Resources in Reverse Order
matchy_result *result = NULL;
matchy_database *db = NULL;
matchy_open("db.mxy", &db);
matchy_lookup(db, "query", &result);
// Free in reverse order
matchy_result_free(result);
matchy_close(db);
4. Use Guards for Cleanup
matchy_database *db = NULL;
matchy_error_t err = matchy_open(path, &db);
if (err != MATCHY_OK) goto cleanup;
// ... use db ...
cleanup:
if (db) matchy_close(db);
return err;
Debugging
Valgrind
Check for memory leaks:
valgrind --leak-check=full --show-leak-kinds=all ./myapp
AddressSanitizer
Compile with sanitizer:
gcc -fsanitize=address -g -o myapp myapp.c -lmatchy
./myapp
See Also
- C Database Functions - Database operations
- C Builder Functions - Builder operations
- C Result Functions - Result operations
- First C Tutorial