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, ffi::data::delta::OrderBookDeltaFfi, identifiers::InstrumentId,
23};
24
25/// Creates a new `OrderBookDeltas` instance from a `CVec` of `OrderBookDeltaFfi`.
26///
27/// The data is cloned into Rust-managed memory and remains owned by the caller.
28///
29/// Returns an owning pointer to the heap-allocated `OrderBookDeltas` which the caller
30/// must eventually pass to [`orderbook_deltas_drop`].
31///
32/// # Safety
33///
34/// `deltas` must describe initialized `OrderBookDeltaFfi` values that remain valid and immutable for
35/// the duration of this call. The caller remains responsible for deallocating its buffer.
36#[unsafe(no_mangle)]
37pub unsafe extern "C" fn orderbook_deltas_new(
38    instrument_id: InstrumentId,
39    deltas: &CVec,
40) -> *mut OrderBookDeltas {
41    let cloned_deltas = unsafe { deltas.as_slice::<OrderBookDeltaFfi>() }
42        .iter()
43        .copied()
44        .map(Into::into)
45        .collect();
46    Box::into_raw(Box::new(OrderBookDeltas::new(instrument_id, cloned_deltas)))
47}
48
49/// # Safety
50///
51/// `deltas` must be a live owning pointer returned by [`orderbook_deltas_new`],
52/// [`orderbook_deltas_clone`], or
53/// [`orderbook_to_snapshot_deltas`](crate::ffi::orderbook::book::orderbook_to_snapshot_deltas), and
54/// must not be used after this call.
55///
56/// # Panics
57///
58/// Panics if `deltas` is null.
59#[unsafe(no_mangle)]
60pub unsafe extern "C" fn orderbook_deltas_drop(deltas: *mut OrderBookDeltas) {
61    abort_on_panic(|| {
62        assert!(!deltas.is_null(), "`deltas` was NULL");
63        // SAFETY: Caller guarantees exclusive ownership of a live `Box` allocation
64        drop(unsafe { Box::from_raw(deltas) }); // Memory freed here
65    });
66}
67
68/// Returns an owning pointer to a deep copy of `deltas` which the caller must
69/// eventually pass to [`orderbook_deltas_drop`].
70#[unsafe(no_mangle)]
71pub extern "C" fn orderbook_deltas_clone(deltas: &OrderBookDeltas) -> *mut OrderBookDeltas {
72    Box::into_raw(Box::new(deltas.clone()))
73}
74
75#[unsafe(no_mangle)]
76pub extern "C" fn orderbook_deltas_instrument_id(deltas: &OrderBookDeltas) -> InstrumentId {
77    deltas.instrument_id
78}
79
80#[unsafe(no_mangle)]
81pub extern "C" fn orderbook_deltas_vec_deltas(deltas: &OrderBookDeltas) -> CVec {
82    deltas
83        .deltas
84        .iter()
85        .copied()
86        .map(OrderBookDeltaFfi::from)
87        .collect::<Vec<_>>()
88        .into()
89}
90
91/// Returns `1` if `deltas` has the `F_SNAPSHOT` record flag, `0` otherwise.
92#[unsafe(no_mangle)]
93pub extern "C" fn orderbook_deltas_is_snapshot(deltas: &OrderBookDeltas) -> u8 {
94    u8::from(deltas.is_snapshot())
95}
96
97#[unsafe(no_mangle)]
98pub extern "C" fn orderbook_deltas_flags(deltas: &OrderBookDeltas) -> u8 {
99    deltas.flags
100}
101
102#[unsafe(no_mangle)]
103pub extern "C" fn orderbook_deltas_sequence(deltas: &OrderBookDeltas) -> u64 {
104    deltas.sequence
105}
106
107#[unsafe(no_mangle)]
108pub extern "C" fn orderbook_deltas_ts_event(deltas: &OrderBookDeltas) -> UnixNanos {
109    deltas.ts_event
110}
111
112#[unsafe(no_mangle)]
113pub extern "C" fn orderbook_deltas_ts_init(deltas: &OrderBookDeltas) -> UnixNanos {
114    deltas.ts_init
115}
116
117/// Drops a `CVec` of `OrderBookDeltaFfi` values.
118///
119/// # Safety
120///
121/// `v` must uniquely own a valid `Vec<OrderBookDeltaFfi>` allocation transferred from Rust.
122#[unsafe(no_mangle)]
123pub unsafe extern "C" fn orderbook_deltas_vec_drop(v: CVec) {
124    let deltas = unsafe { v.into_vec::<OrderBookDeltaFfi>() };
125    drop(deltas); // Memory freed here
126}
127
128#[cfg(test)]
129mod tests {
130    use rstest::rstest;
131
132    use super::*;
133    use crate::{
134        data::stubs::stub_delta,
135        enums::{BookAction, RecordFlag},
136    };
137
138    #[rstest]
139    fn test_empty_delta_drop_returns_without_panic() {
140        unsafe { orderbook_deltas_vec_drop(CVec::empty()) };
141    }
142
143    #[rstest]
144    fn test_orderbook_deltas_new_clones_borrowed_buffer() {
145        let delta = stub_delta();
146        let mut caller_owned = vec![OrderBookDeltaFfi::from(delta)];
147        let cvec = CVec {
148            ptr: caller_owned.as_mut_ptr().cast(),
149            len: caller_owned.len(),
150            cap: caller_owned.capacity(),
151        };
152
153        let deltas_ptr = unsafe { orderbook_deltas_new(delta.instrument_id, &cvec) };
154
155        // SAFETY: `deltas_ptr` was just returned by `orderbook_deltas_new`
156        let deltas = unsafe { &*deltas_ptr };
157        assert_eq!(deltas.deltas, vec![delta]);
158        caller_owned[0].sequence += 1;
159        assert_ne!(deltas.deltas[0].sequence, caller_owned[0].sequence);
160
161        unsafe { orderbook_deltas_drop(deltas_ptr) };
162    }
163
164    #[rstest]
165    #[case::snapshot(
166        BookAction::Add,
167        RecordFlag::F_SNAPSHOT as u8,
168        1
169    )]
170    #[case::combined_flags(
171        BookAction::Add,
172        RecordFlag::F_SNAPSHOT as u8 | RecordFlag::F_LAST as u8,
173        1
174    )]
175    #[case::not_snapshot(BookAction::Add, RecordFlag::F_MBP as u8, 0)]
176    #[case::clear_without_snapshot(BookAction::Clear, 0, 0)]
177    fn test_orderbook_deltas_is_snapshot(
178        #[case] action: BookAction,
179        #[case] flags: u8,
180        #[case] expected: u8,
181    ) {
182        let mut delta = stub_delta();
183        delta.action = action;
184        delta.flags = flags;
185        let deltas = OrderBookDeltas::new(delta.instrument_id, vec![delta]);
186
187        assert_eq!(orderbook_deltas_is_snapshot(&deltas), expected);
188    }
189}