Skip to main content

ccf_app/
lib.rs

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3
4//! Experimental Rust API for native CCF applications.
5//!
6//! A registration function installs endpoint handlers on a [`Registry`], and
7//! [`export_app!`] exports it to CCF. Each handler call receives a
8//! [`ReadOnlyContext`] or [`WriteContext`], which gives access to the request,
9//! the response and raw-byte KV maps in the call's transaction. See
10//! `samples/apps/basic_rust` in the CCF repository for a complete application.
11//!
12//! Handlers may run concurrently, and may be re-executed after a transaction
13//! conflict (see [`RetrySafeHandler`]). KV writes are applied only if the
14//! response status is 2xx. A handler returns an [`EndpointError`] to send an
15//! error response, and `?` on a [`BridgeResult`] sends HTTP 500. Panics are
16//! caught and also sent as HTTP 500, which requires `panic = "unwind"`.
17
18#![deny(missing_docs, rustdoc::broken_intra_doc_links)]
19
20#[cfg(not(panic = "unwind"))]
21compile_error!(
22    "ccf-app requires panic = \"unwind\" because its C ABI catches panics at the boundary"
23);
24
25use std::cell::Cell;
26use std::ffi::c_void;
27use std::marker::PhantomData;
28use std::panic::{AssertUnwindSafe, catch_unwind};
29use std::ptr::NonNull;
30use std::slice;
31use std::sync::Once;
32
33#[doc(hidden)]
34pub const ABI_VERSION: u32 = 1;
35
36#[doc(hidden)]
37#[repr(C)]
38pub struct RawRegistry {
39    _private: [u8; 0],
40}
41
42#[doc(hidden)]
43#[repr(C)]
44pub struct RawEndpointContext {
45    _private: [u8; 0],
46}
47
48#[repr(C)]
49#[derive(Clone, Copy)]
50struct RawSlice {
51    data: *const u8,
52    len: usize,
53}
54
55#[repr(i32)]
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57enum RawResult {
58    Ok = 0,
59    NotFound = 1,
60    InvalidArgument = 2,
61    ReadOnly = 3,
62    InternalError = 4,
63}
64
65#[repr(i32)]
66#[derive(Clone, Copy)]
67enum RawAuth {
68    None = 0,
69    UserCert = 1,
70}
71
72type RawHandler = unsafe extern "C" fn(*mut c_void, *mut RawEndpointContext) -> i32;
73type RawDrop = unsafe extern "C" fn(*mut c_void);
74
75#[cfg(not(test))]
76mod ffi {
77    use super::*;
78
79    unsafe extern "C" {
80        pub fn ccf_rust_get_abi_version() -> u32;
81        pub fn ccf_rust_register_endpoint(
82            registry: *mut RawRegistry,
83            path: RawSlice,
84            method: RawSlice,
85            auth: RawAuth,
86            read_only: i32,
87            callback: RawHandler,
88            drop: RawDrop,
89            user_data: *mut c_void,
90        ) -> i32;
91        pub fn ccf_rust_request_body(ctx: *mut RawEndpointContext, body: *mut RawSlice) -> i32;
92        pub fn ccf_rust_request_query(ctx: *mut RawEndpointContext, query: *mut RawSlice) -> i32;
93        pub fn ccf_rust_request_path_param(
94            ctx: *mut RawEndpointContext,
95            name: RawSlice,
96            value: *mut RawSlice,
97        ) -> i32;
98        pub fn ccf_rust_request_header(
99            ctx: *mut RawEndpointContext,
100            name: RawSlice,
101            value: *mut RawSlice,
102        ) -> i32;
103        pub fn ccf_rust_response_status(ctx: *mut RawEndpointContext, status: u16) -> i32;
104        pub fn ccf_rust_response_header(
105            ctx: *mut RawEndpointContext,
106            name: RawSlice,
107            value: RawSlice,
108        ) -> i32;
109        pub fn ccf_rust_response_body(ctx: *mut RawEndpointContext, body: RawSlice) -> i32;
110        pub fn ccf_rust_response_error(
111            ctx: *mut RawEndpointContext,
112            status: u16,
113            code: RawSlice,
114            message: RawSlice,
115        ) -> i32;
116        pub fn ccf_rust_kv_get(
117            ctx: *mut RawEndpointContext,
118            map_name: RawSlice,
119            key: RawSlice,
120            value: *mut RawSlice,
121        ) -> i32;
122        pub fn ccf_rust_kv_has(
123            ctx: *mut RawEndpointContext,
124            map_name: RawSlice,
125            key: RawSlice,
126            present: *mut i32,
127        ) -> i32;
128        pub fn ccf_rust_kv_put(
129            ctx: *mut RawEndpointContext,
130            map_name: RawSlice,
131            key: RawSlice,
132            value: RawSlice,
133        ) -> i32;
134        pub fn ccf_rust_kv_remove(
135            ctx: *mut RawEndpointContext,
136            map_name: RawSlice,
137            key: RawSlice,
138        ) -> i32;
139    }
140}
141
142#[cfg(test)]
143mod ffi {
144    use super::*;
145
146    pub unsafe extern "C" fn ccf_rust_get_abi_version() -> u32 {
147        ABI_VERSION
148    }
149
150    pub unsafe extern "C" fn ccf_rust_register_endpoint(
151        _registry: *mut RawRegistry,
152        _path: RawSlice,
153        _method: RawSlice,
154        _auth: RawAuth,
155        _read_only: i32,
156        _callback: RawHandler,
157        _drop: RawDrop,
158        _user_data: *mut c_void,
159    ) -> i32 {
160        RawResult::InternalError as i32
161    }
162
163    macro_rules! failing_ffi {
164        ($name:ident($($arg:ident: $ty:ty),*)) => {
165            pub unsafe extern "C" fn $name($($arg: $ty),*) -> i32 {
166                $(let _ = $arg;)*
167                RawResult::InternalError as i32
168            }
169        };
170    }
171
172    failing_ffi!(ccf_rust_request_body(ctx: *mut RawEndpointContext, body: *mut RawSlice));
173    failing_ffi!(ccf_rust_request_query(ctx: *mut RawEndpointContext, query: *mut RawSlice));
174    failing_ffi!(ccf_rust_request_path_param(ctx: *mut RawEndpointContext, name: RawSlice, value: *mut RawSlice));
175    failing_ffi!(ccf_rust_request_header(ctx: *mut RawEndpointContext, name: RawSlice, value: *mut RawSlice));
176    failing_ffi!(ccf_rust_response_status(ctx: *mut RawEndpointContext, status: u16));
177    failing_ffi!(ccf_rust_response_header(ctx: *mut RawEndpointContext, name: RawSlice, value: RawSlice));
178    failing_ffi!(ccf_rust_response_body(ctx: *mut RawEndpointContext, body: RawSlice));
179    failing_ffi!(ccf_rust_response_error(ctx: *mut RawEndpointContext, status: u16, code: RawSlice, message: RawSlice));
180    failing_ffi!(ccf_rust_kv_get(ctx: *mut RawEndpointContext, map_name: RawSlice, key: RawSlice, value: *mut RawSlice));
181    failing_ffi!(ccf_rust_kv_has(ctx: *mut RawEndpointContext, map_name: RawSlice, key: RawSlice, present: *mut i32));
182    failing_ffi!(ccf_rust_kv_put(ctx: *mut RawEndpointContext, map_name: RawSlice, key: RawSlice, value: RawSlice));
183    failing_ffi!(ccf_rust_kv_remove(ctx: *mut RawEndpointContext, map_name: RawSlice, key: RawSlice));
184}
185
186fn raw_slice(value: &[u8]) -> RawSlice {
187    RawSlice {
188        data: value.as_ptr(),
189        len: value.len(),
190    }
191}
192
193fn raw_str(value: &str) -> RawSlice {
194    raw_slice(value.as_bytes())
195}
196
197fn decode_result(result: i32) -> Result<(), BridgeError> {
198    match result {
199        value if value == RawResult::Ok as i32 => Ok(()),
200        value if value == RawResult::NotFound as i32 => Err(BridgeError::NotFound),
201        value if value == RawResult::InvalidArgument as i32 => Err(BridgeError::InvalidArgument),
202        value if value == RawResult::ReadOnly as i32 => Err(BridgeError::ReadOnly),
203        _ => Err(BridgeError::Internal),
204    }
205}
206
207unsafe fn borrowed_slice<'a>(value: RawSlice) -> &'a [u8] {
208    if value.len == 0 {
209        &[]
210    } else {
211        // SAFETY: The C++ bridge guarantees that successful output slices are
212        // valid until the next bridge call on this callback context.
213        unsafe { slice::from_raw_parts(value.data, value.len) }
214    }
215}
216
217/// Failure of an SDK call into CCF.
218///
219/// Conversion to [`EndpointError`], as by `?` in a handler, always gives
220/// HTTP 500.
221#[derive(Clone, Copy, Debug, Eq, PartialEq)]
222pub enum BridgeError {
223    /// A value was not found. Lookups report this as `None` or `false` instead.
224    NotFound,
225    /// CCF rejected an argument, such as an empty map name, an unknown status
226    /// or an invalid header.
227    InvalidArgument,
228    /// A write was attempted in a read-only transaction.
229    ReadOnly,
230    /// CCF failed the operation, or returned invalid UTF-8 where a string was
231    /// expected.
232    Internal,
233    /// The SDK and CCF were built with different ABI versions.
234    AbiMismatch,
235}
236
237/// Result of an SDK call into CCF.
238pub type BridgeResult<T> = Result<T, BridgeError>;
239
240/// Authentication policy for an endpoint.
241///
242/// Handlers cannot access the authenticated identity.
243#[derive(Clone, Copy, Debug)]
244pub enum Auth {
245    /// No authentication.
246    None,
247    /// Require a client certificate that is registered as a CCF user and within
248    /// its validity period.
249    UserCert,
250}
251
252impl Auth {
253    fn raw(self) -> RawAuth {
254        match self {
255            Self::None => RawAuth::None,
256            Self::UserCert => RawAuth::UserCert,
257        }
258    }
259}
260
261/// Error response returned by a handler.
262///
263/// CCF sends an OData JSON error with this code and message, which are visible
264/// to the client and may be empty. A status that is not a known HTTP status of
265/// 400 or above is sent as HTTP 500.
266///
267/// ```
268/// use ccf_app::EndpointError;
269///
270/// let error = EndpointError::new(404, "ResourceNotFound", "No such key");
271/// assert_eq!(error.status, 404);
272/// ```
273#[derive(Clone, Debug)]
274pub struct EndpointError {
275    /// HTTP status.
276    pub status: u16,
277    /// Error code, such as `"ResourceNotFound"`.
278    pub code: String,
279    /// Error message.
280    pub message: String,
281}
282
283impl EndpointError {
284    /// Create an error. The status is only checked when the response is sent.
285    pub fn new(status: u16, code: impl Into<String>, message: impl Into<String>) -> Self {
286        Self {
287            status,
288            code: code.into(),
289            message: message.into(),
290        }
291    }
292
293    /// Create an HTTP 500 error with code `"InternalError"`.
294    pub fn internal(message: impl Into<String>) -> Self {
295        Self::new(500, "InternalError", message)
296    }
297}
298
299/// Convert to an HTTP 500 `InternalError`, so that `?` can be used in handlers.
300///
301/// ```
302/// use ccf_app::{BridgeError, EndpointError};
303///
304/// let error = EndpointError::from(BridgeError::InvalidArgument);
305/// assert_eq!(error.status, 500);
306/// assert_eq!(error.message, "CCF bridge error: InvalidArgument");
307/// ```
308impl From<BridgeError> for EndpointError {
309    fn from(error: BridgeError) -> Self {
310        Self::internal(format!("CCF bridge error: {error:?}"))
311    }
312}
313
314/// Result of a handler: `Ok` keeps the response set by the handler, and `Err`
315/// replaces it with an error response.
316pub type EndpointResult = Result<(), EndpointError>;
317
318/// Optional conversion between application values and KV bytes.
319///
320/// Maps never apply a codec implicitly. The encoding is part of the
321/// application's ledger format.
322///
323/// ```
324/// use ccf_app::Codec;
325///
326/// struct BigEndianU32;
327///
328/// impl Codec<u32> for BigEndianU32 {
329///     type Error = std::array::TryFromSliceError;
330///
331///     fn encode(value: &u32) -> Result<Vec<u8>, Self::Error> {
332///         Ok(value.to_be_bytes().to_vec())
333///     }
334///
335///     fn decode(bytes: &[u8]) -> Result<u32, Self::Error> {
336///         Ok(u32::from_be_bytes(bytes.try_into()?))
337///     }
338/// }
339///
340/// let bytes = BigEndianU32::encode(&42).unwrap();
341/// assert_eq!(BigEndianU32::decode(&bytes).unwrap(), 42);
342/// assert!(BigEndianU32::decode(&[42]).is_err());
343/// ```
344pub trait Codec<T> {
345    /// Encoding or decoding error.
346    type Error;
347
348    /// Encode a value as bytes.
349    fn encode(value: &T) -> Result<Vec<u8>, Self::Error>;
350    /// Decode a value from bytes.
351    fn decode(value: &[u8]) -> Result<T, Self::Error>;
352}
353
354struct Context<'a> {
355    raw: NonNull<RawEndpointContext>,
356    _lifetime: PhantomData<&'a mut RawEndpointContext>,
357}
358
359impl Context<'_> {
360    fn body(&self) -> BridgeResult<&[u8]> {
361        let mut value = RawSlice {
362            data: std::ptr::null(),
363            len: 0,
364        };
365        // SAFETY: raw is valid for the handler callback and value is writable.
366        decode_result(unsafe { ffi::ccf_rust_request_body(self.raw.as_ptr(), &mut value) })?;
367        // SAFETY: The returned body is owned by the request and outlives self.
368        Ok(unsafe { borrowed_slice(value) })
369    }
370
371    fn query(&self) -> BridgeResult<&str> {
372        let mut value = RawSlice {
373            data: std::ptr::null(),
374            len: 0,
375        };
376        // SAFETY: raw is valid for the handler callback and value is writable.
377        decode_result(unsafe { ffi::ccf_rust_request_query(self.raw.as_ptr(), &mut value) })?;
378        // SAFETY: The returned query is owned by the request and outlives self.
379        let bytes = unsafe { borrowed_slice(value) };
380        std::str::from_utf8(bytes).map_err(|_| BridgeError::Internal)
381    }
382
383    fn copied_optional(
384        &mut self,
385        name: &str,
386        get: unsafe extern "C" fn(*mut RawEndpointContext, RawSlice, *mut RawSlice) -> i32,
387    ) -> BridgeResult<Option<Vec<u8>>> {
388        let mut value = RawSlice {
389            data: std::ptr::null(),
390            len: 0,
391        };
392        // SAFETY: raw is valid for the callback and all pointers remain valid
393        // for this call.
394        match decode_result(unsafe { get(self.raw.as_ptr(), raw_str(name), &mut value) }) {
395            Ok(()) => {
396                // SAFETY: The bridge returned a valid scratch slice.
397                Ok(Some(unsafe { borrowed_slice(value) }.to_vec()))
398            }
399            Err(BridgeError::NotFound) => Ok(None),
400            Err(error) => Err(error),
401        }
402    }
403
404    fn path_param(&mut self, name: &str) -> BridgeResult<Option<String>> {
405        self.copied_optional(name, ffi::ccf_rust_request_path_param)?
406            .map(|value| String::from_utf8(value).map_err(|_| BridgeError::Internal))
407            .transpose()
408    }
409
410    fn header(&mut self, name: &str) -> BridgeResult<Option<Vec<u8>>> {
411        self.copied_optional(name, ffi::ccf_rust_request_header)
412    }
413
414    fn set_status(&mut self, status: u16) -> BridgeResult<()> {
415        // SAFETY: raw is valid for the callback.
416        decode_result(unsafe { ffi::ccf_rust_response_status(self.raw.as_ptr(), status) })
417    }
418
419    fn set_header(&mut self, name: &str, value: &str) -> BridgeResult<()> {
420        // SAFETY: raw and both strings are valid for this call.
421        decode_result(unsafe {
422            ffi::ccf_rust_response_header(self.raw.as_ptr(), raw_str(name), raw_str(value))
423        })
424    }
425
426    fn set_body(&mut self, body: &[u8]) -> BridgeResult<()> {
427        // SAFETY: raw and body are valid for this call.
428        decode_result(unsafe { ffi::ccf_rust_response_body(self.raw.as_ptr(), raw_slice(body)) })
429    }
430
431    fn set_error(&mut self, error: &EndpointError) -> BridgeResult<()> {
432        // SAFETY: raw and all strings are valid for this call.
433        decode_result(unsafe {
434            ffi::ccf_rust_response_error(
435                self.raw.as_ptr(),
436                error.status,
437                raw_str(&error.code),
438                raw_str(&error.message),
439            )
440        })
441    }
442
443    fn get(&mut self, map_name: &str, key: &[u8]) -> BridgeResult<Option<Vec<u8>>> {
444        let mut value = RawSlice {
445            data: std::ptr::null(),
446            len: 0,
447        };
448        // SAFETY: raw and input buffers are valid for this call.
449        match decode_result(unsafe {
450            ffi::ccf_rust_kv_get(
451                self.raw.as_ptr(),
452                raw_str(map_name),
453                raw_slice(key),
454                &mut value,
455            )
456        }) {
457            Ok(()) => {
458                // SAFETY: The bridge returned a valid scratch slice.
459                Ok(Some(unsafe { borrowed_slice(value) }.to_vec()))
460            }
461            Err(BridgeError::NotFound) => Ok(None),
462            Err(error) => Err(error),
463        }
464    }
465
466    fn has(&mut self, map_name: &str, key: &[u8]) -> BridgeResult<bool> {
467        let mut present = 0;
468        // SAFETY: raw and input buffers are valid for this call.
469        decode_result(unsafe {
470            ffi::ccf_rust_kv_has(
471                self.raw.as_ptr(),
472                raw_str(map_name),
473                raw_slice(key),
474                &mut present,
475            )
476        })?;
477        Ok(present != 0)
478    }
479
480    fn put(&mut self, map_name: &str, key: &[u8], value: &[u8]) -> BridgeResult<()> {
481        // SAFETY: raw and input buffers are valid for this call.
482        decode_result(unsafe {
483            ffi::ccf_rust_kv_put(
484                self.raw.as_ptr(),
485                raw_str(map_name),
486                raw_slice(key),
487                raw_slice(value),
488            )
489        })
490    }
491
492    fn remove(&mut self, map_name: &str, key: &[u8]) -> BridgeResult<()> {
493        // SAFETY: raw and input buffers are valid for this call.
494        decode_result(unsafe {
495            ffi::ccf_rust_kv_remove(self.raw.as_ptr(), raw_str(map_name), raw_slice(key))
496        })
497    }
498}
499
500/// Request, response and read-only KV access for one call of a
501/// [`Registry::read_only`] handler.
502///
503/// Maps cannot be written:
504///
505/// ```compile_fail,E0599
506/// use ccf_app::{EndpointResult, ReadOnlyContext};
507///
508/// fn write_in_read_only_handler(context: &mut ReadOnlyContext<'_>) -> EndpointResult {
509///     context.map("records").put(b"key", b"value")?;
510///     Ok(())
511/// }
512/// ```
513pub struct ReadOnlyContext<'a>(Context<'a>);
514
515impl<'ctx> ReadOnlyContext<'ctx> {
516    /// Borrow the raw request body. Copy it before calling a `&mut self` method
517    /// if it is still needed.
518    pub fn body(&self) -> BridgeResult<&[u8]> {
519        self.0.body()
520    }
521
522    /// Borrow the raw query string, without the leading `?` and not
523    /// percent-decoded.
524    pub fn query(&self) -> BridgeResult<&str> {
525        self.0.query()
526    }
527
528    /// Copy the decoded path parameter `name`, such as `"key"` for
529    /// `/records/{key}`, or `None` if it is absent.
530    pub fn path_param(&mut self, name: &str) -> BridgeResult<Option<String>> {
531        self.0.path_param(name)
532    }
533
534    /// Copy the raw value of the request header `name`, which must be
535    /// lowercase, or `None` if it is absent.
536    pub fn header(&mut self, name: &str) -> BridgeResult<Option<Vec<u8>>> {
537        self.0.header(name)
538    }
539
540    /// Set the response status. Unknown statuses are rejected with
541    /// [`BridgeError::InvalidArgument`].
542    pub fn set_status(&mut self, status: u16) -> BridgeResult<()> {
543        self.0.set_status(status)
544    }
545
546    /// Set a response header. Invalid names and values, including any
547    /// containing CR or LF, are rejected with [`BridgeError::InvalidArgument`].
548    pub fn set_header(&mut self, name: &str, value: &str) -> BridgeResult<()> {
549        self.0.set_header(name, value)
550    }
551
552    /// Set the raw response body.
553    pub fn set_body(&mut self, body: &[u8]) -> BridgeResult<()> {
554        self.0.set_body(body)
555    }
556
557    /// Access the map `name` in this call's transaction.
558    pub fn map<'a>(&'a mut self, name: &'a str) -> ReadOnlyMap<'a, 'ctx> {
559        ReadOnlyMap {
560            context: &mut self.0,
561            name,
562        }
563    }
564}
565
566/// Request, response and read-write KV access for one call of a
567/// [`Registry::read_write`] handler.
568///
569/// Borrowed request data must be copied before calling a `&mut self` method,
570/// and cannot outlive the context:
571///
572/// ```compile_fail,E0502
573/// use ccf_app::{EndpointResult, WriteContext};
574///
575/// fn echo(context: &mut WriteContext<'_>) -> EndpointResult {
576///     let body = context.body()?;
577///     context.set_body(body)?;
578///     Ok(())
579/// }
580/// ```
581///
582/// ```compile_fail
583/// use ccf_app::WriteContext;
584///
585/// fn retain_body(context: &WriteContext<'_>) -> &'static [u8] {
586///     context.body().unwrap()
587/// }
588/// ```
589pub struct WriteContext<'a>(Context<'a>);
590
591impl<'ctx> WriteContext<'ctx> {
592    /// Borrow the raw request body. Copy it before calling a `&mut self` method
593    /// if it is still needed.
594    pub fn body(&self) -> BridgeResult<&[u8]> {
595        self.0.body()
596    }
597
598    /// Borrow the raw query string, without the leading `?` and not
599    /// percent-decoded.
600    pub fn query(&self) -> BridgeResult<&str> {
601        self.0.query()
602    }
603
604    /// Copy the decoded path parameter `name`, such as `"key"` for
605    /// `/records/{key}`, or `None` if it is absent.
606    pub fn path_param(&mut self, name: &str) -> BridgeResult<Option<String>> {
607        self.0.path_param(name)
608    }
609
610    /// Copy the raw value of the request header `name`, which must be
611    /// lowercase, or `None` if it is absent.
612    pub fn header(&mut self, name: &str) -> BridgeResult<Option<Vec<u8>>> {
613        self.0.header(name)
614    }
615
616    /// Set the response status. Unknown statuses are rejected with
617    /// [`BridgeError::InvalidArgument`].
618    pub fn set_status(&mut self, status: u16) -> BridgeResult<()> {
619        self.0.set_status(status)
620    }
621
622    /// Set a response header. Invalid names and values, including any
623    /// containing CR or LF, are rejected with [`BridgeError::InvalidArgument`].
624    pub fn set_header(&mut self, name: &str, value: &str) -> BridgeResult<()> {
625        self.0.set_header(name, value)
626    }
627
628    /// Set the raw response body.
629    pub fn set_body(&mut self, body: &[u8]) -> BridgeResult<()> {
630        self.0.set_body(body)
631    }
632
633    /// Access the map `name` in this call's transaction.
634    pub fn map<'a>(&'a mut self, name: &'a str) -> Map<'a, 'ctx> {
635        Map {
636            context: &mut self.0,
637            name,
638        }
639    }
640}
641
642/// Read-only access to a raw-byte KV map, from [`ReadOnlyContext::map`].
643pub struct ReadOnlyMap<'a, 'ctx> {
644    context: &'a mut Context<'ctx>,
645    name: &'a str,
646}
647
648impl ReadOnlyMap<'_, '_> {
649    /// Copy the value of `key`, or `None` if it is absent.
650    pub fn get(&mut self, key: &[u8]) -> BridgeResult<Option<Vec<u8>>> {
651        self.context.get(self.name, key)
652    }
653
654    /// Check whether `key` is present.
655    pub fn has(&mut self, key: &[u8]) -> BridgeResult<bool> {
656        self.context.has(self.name, key)
657    }
658}
659
660/// Read-write access to a raw-byte KV map, from [`WriteContext::map`].
661///
662/// Writes are visible to later reads in the same transaction, and are applied
663/// only if the response status is 2xx.
664pub struct Map<'a, 'ctx> {
665    context: &'a mut Context<'ctx>,
666    name: &'a str,
667}
668
669impl Map<'_, '_> {
670    /// Copy the value of `key`, or `None` if it is absent.
671    pub fn get(&mut self, key: &[u8]) -> BridgeResult<Option<Vec<u8>>> {
672        self.context.get(self.name, key)
673    }
674
675    /// Check whether `key` is present.
676    pub fn has(&mut self, key: &[u8]) -> BridgeResult<bool> {
677        self.context.has(self.name, key)
678    }
679
680    /// Write `value` to `key`.
681    pub fn put(&mut self, key: &[u8], value: &[u8]) -> BridgeResult<()> {
682        self.context.put(self.name, key, value)
683    }
684
685    /// Remove `key`, if it is present.
686    pub fn remove(&mut self, key: &[u8]) -> BridgeResult<()> {
687        self.context.remove(self.name, key)
688    }
689}
690
691/// Marker trait for endpoint handlers, which CCF may re-execute.
692///
693/// Handlers may run concurrently, and CCF may discard a transaction and
694/// re-execute its handler after a conflict. The transaction's KV changes and
695/// response are discarded, so any other side effects must be safe to repeat.
696/// This trait is implemented for all `Send + Sync` types, so the compiler
697/// cannot check this.
698pub trait RetrySafeHandler: Send + Sync {}
699
700impl<T> RetrySafeHandler for T where T: Send + Sync {}
701
702trait ReadHandler:
703    for<'a> Fn(&mut ReadOnlyContext<'a>) -> EndpointResult + RetrySafeHandler + 'static
704{
705}
706
707impl<T> ReadHandler for T where
708    T: for<'a> Fn(&mut ReadOnlyContext<'a>) -> EndpointResult + RetrySafeHandler + 'static
709{
710}
711
712trait WriteHandler:
713    for<'a> Fn(&mut WriteContext<'a>) -> EndpointResult + RetrySafeHandler + 'static
714{
715}
716
717impl<T> WriteHandler for T where
718    T: for<'a> Fn(&mut WriteContext<'a>) -> EndpointResult + RetrySafeHandler + 'static
719{
720}
721
722enum Handler {
723    Read(Box<dyn ReadHandler>),
724    Write(Box<dyn WriteHandler>),
725}
726
727thread_local! {
728    static IN_APP_CALLBACK: Cell<bool> = const { Cell::new(false) };
729}
730
731fn in_app_callback() -> bool {
732    // The hook may run while this thread's locals are being destroyed.
733    IN_APP_CALLBACK.try_with(Cell::get).unwrap_or(false)
734}
735
736/// Runs application code, catching any panic at the ABI boundary.
737fn catch_app_panic<R>(f: impl FnOnce() -> R) -> std::thread::Result<R> {
738    struct Restore(bool);
739
740    impl Drop for Restore {
741        fn drop(&mut self) {
742            IN_APP_CALLBACK.set(self.0);
743        }
744    }
745
746    let _restore = Restore(IN_APP_CALLBACK.replace(true));
747    catch_unwind(AssertUnwindSafe(f))
748}
749
750/// Stops panics raised by application callbacks from being reported.
751///
752/// Rust's default hook writes panic messages, which may contain request or KV
753/// data, to the node's stderr, which is visible to the host. These panics are
754/// contained by `catch_app_panic`, and handler panics are reported to the
755/// caller as HTTP 500 errors. Other panics, including those raised by CCF's own
756/// Rust code, which may share this process-wide hook, are passed to the
757/// previously installed hook.
758fn install_panic_hook() {
759    static INSTALL: Once = Once::new();
760    INSTALL.call_once(|| {
761        let previous = std::panic::take_hook();
762        std::panic::set_hook(Box::new(move |info| {
763            if !in_app_callback() {
764                previous(info);
765            }
766        }));
767    });
768}
769
770unsafe extern "C" fn invoke_handler(
771    user_data: *mut c_void,
772    raw_context: *mut RawEndpointContext,
773) -> i32 {
774    if user_data.is_null() || raw_context.is_null() {
775        return RawResult::InvalidArgument as i32;
776    }
777
778    // SAFETY: The registry owns this Handler until it invokes drop_handler.
779    let handler = unsafe { &*(user_data.cast::<Handler>()) };
780    // SAFETY: The null guard above validated raw_context.
781    let raw = unsafe { NonNull::new_unchecked(raw_context) };
782
783    let result = catch_app_panic(|| match handler {
784        Handler::Read(handler) => handler(&mut ReadOnlyContext(Context {
785            raw,
786            _lifetime: PhantomData,
787        })),
788        Handler::Write(handler) => handler(&mut WriteContext(Context {
789            raw,
790            _lifetime: PhantomData,
791        })),
792    });
793
794    let endpoint_error = match result {
795        Ok(Ok(())) => return RawResult::Ok as i32,
796        Ok(Err(error)) => error,
797        Err(_) => EndpointError::internal("Rust endpoint panicked"),
798    };
799
800    let mut context = Context {
801        raw,
802        _lifetime: PhantomData,
803    };
804    match context.set_error(&endpoint_error) {
805        Ok(()) => RawResult::Ok as i32,
806        Err(_) => RawResult::InternalError as i32,
807    }
808}
809
810unsafe extern "C" fn drop_handler(user_data: *mut c_void) {
811    if !user_data.is_null() {
812        let _ = catch_app_panic(|| {
813            // SAFETY: The pointer was created by Box::into_raw during endpoint
814            // registration and is dropped exactly once by the C++ registry.
815            drop(unsafe { Box::from_raw(user_data.cast::<Handler>()) });
816        });
817    }
818}
819
820/// Endpoint registry, passed to the function exported with [`export_app!`].
821pub struct Registry {
822    raw: NonNull<RawRegistry>,
823}
824
825impl Registry {
826    #[doc(hidden)]
827    /// # Safety
828    ///
829    /// `raw` must point to the live C++ registry passed to
830    /// `ccf_rust_app_register` and may not outlive that call.
831    pub unsafe fn from_raw(raw: *mut RawRegistry) -> BridgeResult<Self> {
832        if unsafe { ffi::ccf_rust_get_abi_version() } != ABI_VERSION {
833            return Err(BridgeError::AbiMismatch);
834        }
835        NonNull::new(raw)
836            .map(|raw| Self { raw })
837            .ok_or(BridgeError::InvalidArgument)
838    }
839
840    /// Register a handler with read-only KV access.
841    ///
842    /// `path` excludes the `/app` prefix and may contain parameters, such as
843    /// `/records/{key}`. `method` is an HTTP method, such as `"GET"`. Returns
844    /// [`BridgeError::InvalidArgument`] if either is empty, or
845    /// [`BridgeError::Internal`] if CCF rejects the endpoint.
846    pub fn read_only<F>(
847        &mut self,
848        path: &str,
849        method: &str,
850        auth: Auth,
851        handler: F,
852    ) -> BridgeResult<()>
853    where
854        F: for<'a> Fn(&mut ReadOnlyContext<'a>) -> EndpointResult + RetrySafeHandler + 'static,
855    {
856        self.register(path, method, auth, Handler::Read(Box::new(handler)))
857    }
858
859    /// Register a handler with read-write KV access.
860    ///
861    /// Arguments and errors are as for [`read_only`](Self::read_only).
862    pub fn read_write<F>(
863        &mut self,
864        path: &str,
865        method: &str,
866        auth: Auth,
867        handler: F,
868    ) -> BridgeResult<()>
869    where
870        F: for<'a> Fn(&mut WriteContext<'a>) -> EndpointResult + RetrySafeHandler + 'static,
871    {
872        self.register(path, method, auth, Handler::Write(Box::new(handler)))
873    }
874
875    fn register(
876        &mut self,
877        path: &str,
878        method: &str,
879        auth: Auth,
880        handler: Handler,
881    ) -> BridgeResult<()> {
882        let read_only = matches!(handler, Handler::Read(_)) as i32;
883        let user_data = Box::into_raw(Box::new(handler)).cast::<c_void>();
884        // SAFETY: All inputs are valid for this call. Ownership of user_data is
885        // transferred only when registration succeeds.
886        let result = unsafe {
887            ffi::ccf_rust_register_endpoint(
888                self.raw.as_ptr(),
889                raw_str(path),
890                raw_str(method),
891                auth.raw(),
892                read_only,
893                invoke_handler,
894                drop_handler,
895                user_data,
896            )
897        };
898        if let Err(error) = decode_result(result) {
899            // SAFETY: Registration failed, so C++ did not retain user_data.
900            unsafe { drop_handler(user_data) };
901            return Err(error);
902        }
903        Ok(())
904    }
905}
906
907/// Implements the `ccf_rust_app_register` function exported by `export_app!`.
908///
909/// # Safety
910///
911/// `raw_registry` must be the registry passed by CCF to
912/// `ccf_rust_app_register`.
913#[doc(hidden)]
914pub unsafe fn register_app<F>(raw_registry: *mut RawRegistry, register: F) -> i32
915where
916    F: FnOnce(&mut Registry) -> BridgeResult<()>,
917{
918    install_panic_hook();
919    let result = catch_app_panic(|| {
920        // SAFETY: The caller passes the live registry for this call.
921        let mut registry = unsafe { Registry::from_raw(raw_registry) }?;
922        register(&mut registry)
923    });
924    match result {
925        Ok(Ok(())) => RawResult::Ok as i32,
926        _ => RawResult::InternalError as i32,
927    }
928}
929
930/// Export `register`, a `fn(&mut Registry) -> BridgeResult<()>`, as the
931/// application's entry point.
932///
933/// Invoke once in the application crate, as `ccf_app::export_app!(register);`.
934/// If `register` returns an error or panics, CCF fails to initialize the
935/// application. The macro also keeps panic messages from application code,
936/// which may contain confidential data, out of node output.
937#[macro_export]
938macro_rules! export_app {
939    ($register:path) => {
940        #[doc(hidden)]
941        #[unsafe(no_mangle)]
942        pub extern "C" fn ccf_rust_app_abi_version() -> u32 {
943            $crate::ABI_VERSION
944        }
945
946        #[doc(hidden)]
947        #[unsafe(no_mangle)]
948        pub unsafe extern "C" fn ccf_rust_app_register(
949            raw_registry: *mut $crate::RawRegistry,
950        ) -> i32 {
951            // SAFETY: The C++ bridge passes a live registry for this call.
952            unsafe { $crate::register_app(raw_registry, $register) }
953        }
954    };
955}
956
957#[cfg(test)]
958mod tests {
959    use super::*;
960
961    #[test]
962    fn maps_raw_result_codes() {
963        assert_eq!(decode_result(0), Ok(()));
964        assert_eq!(decode_result(1), Err(BridgeError::NotFound));
965        assert_eq!(decode_result(2), Err(BridgeError::InvalidArgument));
966        assert_eq!(decode_result(3), Err(BridgeError::ReadOnly));
967        assert_eq!(decode_result(99), Err(BridgeError::Internal));
968    }
969
970    #[test]
971    fn rejects_null_registry() {
972        // SAFETY: This intentionally exercises null validation.
973        assert!(matches!(
974            unsafe { Registry::from_raw(std::ptr::null_mut()) },
975            Err(BridgeError::InvalidArgument)
976        ));
977    }
978
979    #[test]
980    fn preserves_error_status_for_bridge_validation() {
981        assert_eq!(EndpointError::new(432, "Error", "message").status, 432);
982    }
983
984    #[test]
985    fn panicking_handler_returns_internal_error() {
986        let handler = Box::new(Handler::Write(Box::new(|_| panic!("test panic"))));
987        let user_data = Box::into_raw(handler).cast::<c_void>();
988        let raw_context = NonNull::<RawEndpointContext>::dangling().as_ptr();
989        // SAFETY: In the test-only FFI stubs, the context pointer is never
990        // dereferenced. This exercises the panic trampoline without invoking
991        // any real C++ bridge logic.
992        let result = unsafe { invoke_handler(user_data, raw_context) };
993        assert_eq!(result, RawResult::InternalError as i32);
994        // SAFETY: The test retains ownership of the handler.
995        unsafe { drop_handler(user_data) };
996    }
997
998    #[test]
999    fn restores_app_callback_marker() {
1000        assert!(!in_app_callback());
1001        let nested = catch_app_panic(|| {
1002            assert!(catch_app_panic(|| ()).is_ok());
1003            in_app_callback()
1004        });
1005        assert_eq!(nested.ok(), Some(true));
1006        assert!(catch_app_panic::<()>(|| panic!("test panic")).is_err());
1007        assert!(!in_app_callback());
1008    }
1009
1010    #[test]
1011    fn panic_hook_forwards_only_panics_outside_app_callbacks() {
1012        thread_local! {
1013            static REPORTS: Cell<usize> = const { Cell::new(0) };
1014        }
1015        let default_hook = std::panic::take_hook();
1016        std::panic::set_hook(Box::new(move |info| {
1017            REPORTS.set(REPORTS.get() + 1);
1018            default_hook(info);
1019        }));
1020        install_panic_hook();
1021
1022        assert!(catch_app_panic::<()>(|| panic!("test panic")).is_err());
1023        assert_eq!(REPORTS.get(), 0);
1024        assert!(catch_unwind(|| panic!("test panic")).is_err());
1025        assert_eq!(REPORTS.get(), 1);
1026    }
1027}