Skip to main content

nautilus_model/ffi/data/
deltas.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use nautilus_core::{
17    UnixNanos,
18    ffi::{abort_on_panic, cvec::CVec},
19};
20
21use crate::{
22    data::OrderBookDeltas, enums::BookAction, ffi::data::delta::OrderBookDeltaFfi,
23    identifiers::InstrumentId,
24};
25
26/// Creates a new `OrderBookDeltas` instance from a `CVec` of `OrderBookDeltaFfi`.
27///
28/// The data is cloned into Rust-managed memory and remains owned by the caller.
29///
30/// Returns an owning pointer to the heap-allocated `OrderBookDeltas` which the caller
31/// must eventually pass to [`orderbook_deltas_drop`].
32///
33/// # Safety
34///
35/// `deltas` must describe initialized `OrderBookDeltaFfi` values that remain valid and immutable for
36/// the duration of this call. The caller remains responsible for deallocating its buffer.
37#[unsafe(no_mangle)]
38pub unsafe extern "C" fn orderbook_deltas_new(
39    instrument_id: InstrumentId,
40    deltas: &CVec,
41) -> *mut OrderBookDeltas {
42    let cloned_deltas = unsafe { deltas.as_slice::<OrderBookDeltaFfi>() }
43        .iter()
44        .copied()
45        .map(Into::into)
46        .collect();
47    Box::into_raw(Box::new(OrderBookDeltas::new(instrument_id, cloned_deltas)))
48}
49
50/// # Safety
51///
52/// `deltas` must be a live owning pointer returned by [`orderbook_deltas_new`],
53/// [`orderbook_deltas_clone`], or
54/// [`orderbook_to_snapshot_deltas`](crate::ffi::orderbook::book::orderbook_to_snapshot_deltas), and
55/// must not be used after this call.
56///
57/// # Panics
58///
59/// Panics if `deltas` is null.
60#[unsafe(no_mangle)]
61pub unsafe extern "C" fn orderbook_deltas_drop(deltas: *mut OrderBookDeltas) {
62    abort_on_panic(|| {
63        assert!(!deltas.is_null(), "`deltas` was NULL");
64        // SAFETY: Caller guarantees exclusive ownership of a live `Box` allocation
65        drop(unsafe { Box::from_raw(deltas) }); // Memory freed here
66    });
67}
68
69/// Returns an owning pointer to a deep copy of `deltas` which the caller must
70/// eventually pass to [`orderbook_deltas_drop`].
71#[unsafe(no_mangle)]
72pub extern "C" fn orderbook_deltas_clone(deltas: &OrderBookDeltas) -> *mut OrderBookDeltas {
73    Box::into_raw(Box::new(deltas.clone()))
74}
75
76#[unsafe(no_mangle)]
77pub extern "C" fn orderbook_deltas_instrument_id(deltas: &OrderBookDeltas) -> InstrumentId {
78    deltas.instrument_id
79}
80
81#[unsafe(no_mangle)]
82pub extern "C" fn orderbook_deltas_vec_deltas(deltas: &OrderBookDeltas) -> CVec {
83    deltas
84        .deltas
85        .iter()
86        .copied()
87        .map(OrderBookDeltaFfi::from)
88        .collect::<Vec<_>>()
89        .into()
90}
91
92/// Returns `1` if the first delta is a `Clear` action (snapshot), `0` otherwise.
93///
94/// Returns `0` for empty delta vectors to avoid panicking on malformed FFI input.
95#[unsafe(no_mangle)]
96pub extern "C" fn orderbook_deltas_is_snapshot(deltas: &OrderBookDeltas) -> u8 {
97    deltas
98        .deltas
99        .first()
100        .map_or(0, |first| u8::from(first.action == BookAction::Clear))
101}
102
103#[unsafe(no_mangle)]
104pub extern "C" fn orderbook_deltas_flags(deltas: &OrderBookDeltas) -> u8 {
105    deltas.flags
106}
107
108#[unsafe(no_mangle)]
109pub extern "C" fn orderbook_deltas_sequence(deltas: &OrderBookDeltas) -> u64 {
110    deltas.sequence
111}
112
113#[unsafe(no_mangle)]
114pub extern "C" fn orderbook_deltas_ts_event(deltas: &OrderBookDeltas) -> UnixNanos {
115    deltas.ts_event
116}
117
118#[unsafe(no_mangle)]
119pub extern "C" fn orderbook_deltas_ts_init(deltas: &OrderBookDeltas) -> UnixNanos {
120    deltas.ts_init
121}
122
123/// Drops a `CVec` of `OrderBookDeltaFfi` values.
124///
125/// # Safety
126///
127/// `v` must uniquely own a valid `Vec<OrderBookDeltaFfi>` allocation transferred from Rust.
128#[unsafe(no_mangle)]
129pub unsafe extern "C" fn orderbook_deltas_vec_drop(v: CVec) {
130    let deltas = unsafe { v.into_vec::<OrderBookDeltaFfi>() };
131    drop(deltas); // Memory freed here
132}
133
134#[cfg(test)]
135mod tests {
136    use rstest::rstest;
137
138    use super::*;
139    use crate::data::stubs::stub_delta;
140
141    #[rstest]
142    fn test_empty_delta_drop_returns_without_panic() {
143        unsafe { orderbook_deltas_vec_drop(CVec::empty()) };
144    }
145
146    #[rstest]
147    fn test_orderbook_deltas_new_clones_borrowed_buffer() {
148        let delta = stub_delta();
149        let mut caller_owned = vec![OrderBookDeltaFfi::from(delta)];
150        let cvec = CVec {
151            ptr: caller_owned.as_mut_ptr().cast(),
152            len: caller_owned.len(),
153            cap: caller_owned.capacity(),
154        };
155
156        let deltas_ptr = unsafe { orderbook_deltas_new(delta.instrument_id, &cvec) };
157
158        // SAFETY: `deltas_ptr` was just returned by `orderbook_deltas_new`
159        let deltas = unsafe { &*deltas_ptr };
160        assert_eq!(deltas.deltas, vec![delta]);
161        caller_owned[0].sequence += 1;
162        assert_ne!(deltas.deltas[0].sequence, caller_owned[0].sequence);
163
164        unsafe { orderbook_deltas_drop(deltas_ptr) };
165    }
166}