Skip to main content

nautilus_model/ffi/orderbook/
level.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::ffi::{abort_on_panic, cvec::CVec};
17
18use crate::{
19    data::order::BookOrder,
20    ffi::{data::order::BookOrderFfi, enums::OrderSideOptional},
21    orderbook::{BookLevel, BookPrice},
22    types::{Price, quantity::QuantityRaw},
23};
24
25#[unsafe(no_mangle)]
26#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
27/// # Safety
28///
29/// `orders` must uniquely own a valid `Vec<BookOrderFfi>` allocation transferred from Rust.
30///
31/// # Panics
32///
33/// Panics if `order_side` is `NoOrderSide`.
34///
35/// Returns an owning pointer to the heap-allocated `BookLevel` which the caller must
36/// eventually pass to [`level_drop`].
37pub unsafe extern "C" fn level_new(
38    order_side: OrderSideOptional,
39    price: Price,
40    orders: CVec,
41) -> *mut BookLevel {
42    let orders = unsafe { orders.into_vec::<BookOrderFfi>() }
43        .into_iter()
44        .map(Into::into)
45        .collect::<Vec<BookOrder>>();
46    let price = BookPrice {
47        value: price,
48        side: order_side
49            .as_option()
50            .expect("Order side must be Buy or Sell"),
51    };
52    let mut level = BookLevel::new(price);
53    level.add_bulk(&orders);
54    Box::into_raw(Box::new(level))
55}
56
57/// # Safety
58///
59/// `level` must be a live owning pointer returned by [`level_new`] or [`level_clone`],
60/// and must not be used after this call.
61///
62/// # Panics
63///
64/// Panics if `level` is null.
65#[unsafe(no_mangle)]
66pub unsafe extern "C" fn level_drop(level: *mut BookLevel) {
67    abort_on_panic(|| {
68        assert!(!level.is_null(), "`level` was NULL");
69        // SAFETY: Caller guarantees `level` was allocated by `level_new` or `level_clone`
70        drop(unsafe { Box::from_raw(level) }); // Memory freed here
71    });
72}
73
74/// Returns an owning pointer to a deep copy of `level` which the caller must
75/// eventually pass to [`level_drop`].
76#[unsafe(no_mangle)]
77pub extern "C" fn level_clone(level: &BookLevel) -> *mut BookLevel {
78    Box::into_raw(Box::new(level.clone()))
79}
80
81#[unsafe(no_mangle)]
82pub extern "C" fn level_side(level: &BookLevel) -> OrderSideOptional {
83    Some(level.price.side).into()
84}
85
86#[unsafe(no_mangle)]
87#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
88pub extern "C" fn level_price(level: &BookLevel) -> Price {
89    level.price.value
90}
91
92#[unsafe(no_mangle)]
93pub extern "C" fn level_orders(level: &BookLevel) -> CVec {
94    let orders_vec: Vec<BookOrderFfi> = level.orders.values().copied().map(Into::into).collect();
95    orders_vec.into()
96}
97
98#[unsafe(no_mangle)]
99pub extern "C" fn level_size(level: &BookLevel) -> f64 {
100    level.size()
101}
102
103#[unsafe(no_mangle)]
104pub extern "C" fn level_size_raw(level: &BookLevel) -> QuantityRaw {
105    level.size_raw()
106}
107
108#[unsafe(no_mangle)]
109pub extern "C" fn level_exposure(level: &BookLevel) -> f64 {
110    level.exposure()
111}
112
113/// Drops a `CVec` of owning `BookLevel` pointers, freeing each level.
114///
115/// # Safety
116///
117/// `v` must uniquely own a valid `Vec<*mut BookLevel>` allocation transferred from Rust,
118/// where each element is a live owning pointer.
119#[unsafe(no_mangle)]
120pub unsafe extern "C" fn vec_drop_book_levels(v: CVec) {
121    let levels = unsafe { v.into_vec::<*mut BookLevel>() };
122    for level in levels {
123        if !level.is_null() {
124            // SAFETY: Caller guarantees each element is a live owning pointer
125            drop(unsafe { Box::from_raw(level) }); // Memory freed here
126        }
127    }
128}
129
130/// Drops a `CVec` of `BookOrderFfi` values.
131///
132/// # Safety
133///
134/// `v` must uniquely own a valid `Vec<BookOrderFfi>` allocation transferred from Rust.
135#[unsafe(no_mangle)]
136pub unsafe extern "C" fn vec_drop_book_orders(v: CVec) {
137    let orders = unsafe { v.into_vec::<BookOrderFfi>() };
138    drop(orders); // Memory freed here
139}
140
141#[cfg(test)]
142mod tests {
143    use rstest::rstest;
144
145    use super::*;
146    use crate::data::stubs::stub_book_order;
147
148    #[rstest]
149    fn test_empty_typed_drops_return_without_panic() {
150        unsafe { vec_drop_book_levels(CVec::empty()) };
151        unsafe { vec_drop_book_orders(CVec::empty()) };
152    }
153
154    #[rstest]
155    fn test_level_new_preserves_valid_behavior() {
156        let order = stub_book_order();
157        let price = order.price;
158        let orders = vec![BookOrderFfi::from(order)];
159        let level_ptr = unsafe { level_new(order.side.into(), price, orders.into()) };
160
161        // SAFETY: `level_ptr` was just returned by `level_new`
162        let level = unsafe { &*level_ptr };
163        assert_eq!(level.price.value, price);
164        assert_eq!(level.len(), 1);
165        assert_eq!(level.first(), Some(&order));
166
167        unsafe { level_drop(level_ptr) };
168    }
169}