Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

README.md

thetadatadx-ffi

C FFI layer for thetadatadx — exposes the Rust SDK as extern "C" functions.

Compiled as both cdylib (shared library) and staticlib (archive). Consumed by the C++ (RAII) and TypeScript/Node.js (napi-rs) SDKs, and available to any third-party C/C++/Go/etc. consumer that wants to roll their own wrapper against the tdx_* symbols.

Surface coverage: the FFI layer exposes all three ThetaData surfaces — MDDS (historical), FPSS (streaming), and FLATFILES (whole-universe daily blobs). The flat-files entry points are tdx_flatfile_request_decoded (pull + decode into an opaque row-list), tdx_flatfile_rows_to_arrow_ipc (serialise to Arrow IPC bytes), tdx_flatfile_request_to_path (raw vendor bytes straight to disk), and the matching _rowlist_free / _bytes_free cleanup helpers.

Building

cargo build --release -p thetadatadx-ffi

Produces:

  • target/release/libthetadatadx_ffi.so (Linux)
  • target/release/libthetadatadx_ffi.dylib (macOS)
  • target/release/libthetadatadx_ffi.a (static, all platforms)

API Surface

Handle types (opaque pointers)

Handle Create Free
TdxCredentials tdx_credentials_new, tdx_credentials_from_file tdx_credentials_free
TdxConfig tdx_config_production, tdx_config_dev tdx_config_free
TdxClient tdx_client_connect tdx_client_free
TdxUnified tdx_unified_connect tdx_unified_free
TdxFpssHandle tdx_fpss_connect tdx_fpss_free

Historical (via TdxClient or TdxUnified)

Every historical endpoint is available as tdx_stock_*, tdx_option_*, tdx_index_*, tdx_calendar_*, tdx_interest_rate_* functions. Each takes a *const TdxClient handle and returns a typed #[repr(C)] struct array (e.g. TdxEodTickArray, TdxOhlcTickArray). Callers must free with the corresponding tdx_*_array_free function. List endpoints return TdxStringArray (freed with tdx_string_array_free).

tdx_unified_historical() returns a borrowed *const TdxClient from a unified handle - same session, no double auth.

Streaming (via TdxUnified)

Function Description
tdx_unified_set_callback Register the push-mode user callback on the unified handle. Mutually exclusive with tdx_unified_start_streaming_iter.
tdx_unified_subscribe Polymorphic subscribe — takes TdxSubscriptionRequest (per-contract or full-stream)
tdx_unified_unsubscribe Polymorphic unsubscribe — takes TdxSubscriptionRequest
tdx_unified_is_streaming Check if FPSS connection is live
tdx_unified_active_subscriptions List active subscriptions (typed TdxSubscriptionArray)
tdx_unified_await_drain Block until the previous session's consumer has finished firing the callback (drain barrier)
tdx_unified_reconnect Reconnect FPSS, drain the previous generation, and re-subscribe everything that was active
tdx_unified_stop_streaming Stop streaming, historical stays alive
tdx_unified_free Free the unified handle

Pull-iter delivery (sibling of tdx_unified_set_callback)

Function Description
tdx_unified_start_streaming_iter Start FPSS in pull-iter mode; returns an opaque TdxFpssEventIterator*. Mutually exclusive with tdx_unified_set_callback.
tdx_fpss_event_iter_next Pop next event with timeout (0=poll, positive ms=block-with-deadline). Returns 0=event filled / 1=timeout / -1=terminal end-of-stream.
tdx_fpss_event_iter_close Mark iterator closed; subsequent _next returns -1 once queue drains.
tdx_fpss_event_iter_free Free the iterator handle.

Streaming (via TdxFpssHandle, standalone)

Function Description
tdx_fpss_connect Connect standalone FPSS client
tdx_fpss_set_callback Register the push-mode user callback
tdx_fpss_subscribe Polymorphic subscribe — takes TdxSubscriptionRequest
tdx_fpss_unsubscribe Polymorphic unsubscribe — takes TdxSubscriptionRequest
tdx_fpss_is_authenticated Check if FPSS is authenticated
tdx_fpss_active_subscriptions List active subscriptions (typed TdxSubscriptionArray)
tdx_fpss_dropped_events Cumulative count of events the TLS reader could not publish into the Disruptor ring
tdx_fpss_await_drain Block until the previous session's consumer has finished firing the callback
tdx_fpss_reconnect Reconnect FPSS, drain the previous generation, and re-subscribe everything that was active
tdx_fpss_shutdown Shut down FPSS client
tdx_fpss_free Free the FPSS handle

Error handling

All functions that can fail return null on error. Call tdx_last_error() to get the error message (valid until the next FFI call on the same thread).

Memory model

  • Opaque handles are heap-allocated via Box::into_raw, freed via Box::from_raw in the corresponding *_free function.
  • Data endpoints return typed #[repr(C)] struct arrays (e.g. TdxEodTickArray { data, len }) - free with the corresponding tdx_*_array_free function.
  • List endpoints return TdxStringArray - free with tdx_string_array_free.
  • tdx_fpss_active_subscriptions returns *mut TdxSubscriptionArray - free with tdx_subscription_array_free.
  • tdx_last_error() returns a borrowed pointer - do NOT free it.
  • tdx_unified_historical() returns a borrowed pointer - do NOT free it.

Safety

  • All functions check for null handles before dereferencing.
  • Mutex locks use poison recovery (unwrap_or_else(|e| e.into_inner())).
  • TdxClient is #[repr(transparent)] over MddsClient for safe pointer casting.

Panic boundary

Every extern "C" function (145 fns — 84 in ffi/src/lib.rs plus 61 generator-emitted in ffi/src/endpoint_with_options.rs) is wrapped in the ffi_boundary! macro, which std::panic::catch_unwind(AssertUnwindSafe(|| { ... }))s the body. Rust panics no longer cross the C ABI — the payload is downcast to String, routed through tracing::error! on target thetadatadx::ffi::panic, stored in the thread-local LAST_ERROR slot accessed by tdx_last_error(), and the function returns the caller-declared default (ptr::null_mut() / -1 / 0 / sentinel-empty-array). Before this wrapper every panic on Rust 1.81+ aborted the host process; pre-1.81 it was undefined behaviour.

Regression tests live at ffi/tests/panic_boundary.rs; the tdx_test_panic_{str,string} symbols used for testing are gated behind a testing-panic-boundary cargo feature so the production cdylib never ships them.