Skip to main content

sails_rs/gstd/
mod.rs

1#[doc(hidden)]
2#[cfg(feature = "ethexe")]
3pub use ethexe::{EthEvent, EthEventExpo};
4#[doc(hidden)]
5pub use events::{EventEmitter, SailsEvent};
6#[cfg(not(feature = "ethexe"))]
7#[doc(hidden)]
8pub use gstd::handle_signal;
9#[doc(hidden)]
10pub use gstd::{async_init, async_main, handle_reply_with_hook, message_loop};
11pub use gstd::{debug, exec, msg};
12use sails_idl_meta::{InterfaceId, MethodMeta};
13#[doc(hidden)]
14pub use sails_macros::{event, export, program, service};
15pub use syscalls::Syscall;
16
17/// Maximum payload size for structured panic.
18///
19/// If payload exceeds this limit, standard text panic is used.
20pub const MAX_PANIC_PAYLOAD_SIZE: usize = 1024;
21
22use crate::{
23    errors::{Error, Result, RtlError},
24    meta::SailsMessageHeader,
25    prelude::{any::TypeId, *},
26    utils::MaybeUninitBufferWriter,
27};
28use gcore::stack_buffer;
29
30#[cfg(feature = "ethexe")]
31mod ethexe;
32mod events;
33mod macros;
34pub mod services;
35mod syscalls;
36
37pub struct CommandReply<T>(T, ValueUnit);
38
39impl<T> CommandReply<T> {
40    pub fn new(result: T) -> Self {
41        Self(result, 0)
42    }
43
44    pub fn with_value(self, value: ValueUnit) -> Self {
45        Self(self.0, value)
46    }
47
48    pub fn to_tuple(self) -> (T, ValueUnit) {
49        (self.0, self.1)
50    }
51}
52
53impl<T> From<T> for CommandReply<T> {
54    fn from(result: T) -> Self {
55        Self(result, 0)
56    }
57}
58
59impl<T> From<(T, ValueUnit)> for CommandReply<T> {
60    fn from(value: (T, ValueUnit)) -> Self {
61        Self(value.0, value.1)
62    }
63}
64
65pub fn unknown_input_panic(message: &str, input: &[u8]) -> ! {
66    let mut __input = input;
67    match String::decode(&mut __input) {
68        Ok(s) => panic!("{}: {}", message, s),
69        Err(_) => panic!("{}: {}", message, HexSlice(input)),
70    }
71}
72
73pub struct HexSlice<T: AsRef<[u8]>>(pub T);
74
75impl<T: AsRef<[u8]>> core::fmt::Display for HexSlice<T> {
76    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77        let slice = self.0.as_ref();
78        let len = slice.len();
79        let precision = f.precision().unwrap_or(4);
80
81        f.write_str("0x")?;
82        if len <= precision * 2 {
83            for byte in slice {
84                write!(f, "{byte:02x}")?;
85            }
86        } else {
87            for byte in &slice[..precision] {
88                write!(f, "{byte:02x}")?;
89            }
90            f.write_str("..")?;
91            for byte in &slice[len - precision..] {
92                write!(f, "{byte:02x}")?;
93            }
94        }
95        Ok(())
96    }
97}
98
99impl<T: AsRef<[u8]>> core::fmt::Debug for HexSlice<T> {
100    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
101        core::fmt::Display::fmt(self, f)
102    }
103}
104
105/// Invocation parameter metadata for a generated service method.
106///
107/// This trait intentionally does not require `Params: Decode`. SCALE dispatch
108/// adds that bound at the call site through [`decode_invocation_params`], while
109/// ABI-only methods can still use the same metadata type without requiring
110/// SCALE codec support for their parameters.
111pub trait InvocationIo: MethodMeta {
112    type Params;
113}
114
115/// Decode the SCALE-encoded `Params` of an invocation, validating that the
116/// Sails message header's interface id and entry id match the target `I`.
117pub fn decode_invocation_params<I>(payload: impl AsRef<[u8]>) -> Result<I::Params>
118where
119    I: InvocationIo,
120    I::Params: Decode,
121{
122    let mut value = payload.as_ref();
123    let header: SailsMessageHeader = Decode::decode(&mut value).map_err(Error::Codec)?;
124    if header.interface_id() != I::INTERFACE_ID {
125        return Err(Error::Rtl(RtlError::InvocationPrefixMismatches));
126    }
127    if header.entry_id() != I::ENTRY_ID {
128        return Err(Error::Rtl(RtlError::InvocationPrefixMismatches));
129    }
130    let value: I::Params = Decode::decode(&mut value).map_err(Error::Codec)?;
131    Ok(value)
132}
133
134/// SCALE-encode a reply payload prefixed with the Sails header derived from `I`,
135/// passing the encoded bytes to the caller's closure.
136pub fn encode_invocation_payload<I, T, R>(value: &T, route_idx: u8, f: impl FnOnce(&[u8]) -> R) -> R
137where
138    I: InvocationIo,
139    T: Encode,
140{
141    encode_invocation_payload_with_id::<T, R>(I::INTERFACE_ID, I::ENTRY_ID, value, route_idx, f)
142}
143
144/// SCALE-encode a reply payload with explicit interface and entry ids.
145pub fn encode_invocation_payload_with_id<T, R>(
146    interface_id: InterfaceId,
147    entry_id: u16,
148    value: &T,
149    route_idx: u8,
150    f: impl FnOnce(&[u8]) -> R,
151) -> R
152where
153    T: Encode,
154{
155    let header = SailsMessageHeader::v1(interface_id, entry_id, route_idx);
156    let size = 16 + Encode::encoded_size(value);
157    stack_buffer::with_byte_buffer(size, |buffer| {
158        let mut buffer_writer = MaybeUninitBufferWriter::new(buffer);
159        Encode::encode_to(&header, &mut buffer_writer);
160        Encode::encode_to(value, &mut buffer_writer);
161        buffer_writer.with_buffer(f)
162    })
163}
164
165pub fn with_optimized_encode<T: Encode, R>(
166    value: &T,
167    // prefix: &[u8],
168    f: impl FnOnce(&[u8]) -> R,
169) -> R {
170    let size = Encode::encoded_size(value);
171    stack_buffer::with_byte_buffer(size, |buffer| {
172        let mut buffer_writer = MaybeUninitBufferWriter::new(buffer);
173        Encode::encode_to(value, &mut buffer_writer);
174        buffer_writer.with_buffer(f)
175    })
176}
177
178pub fn is_empty_tuple<T: 'static>() -> bool {
179    TypeId::of::<T>() == TypeId::of::<()>()
180}