1use std::cmp::Ordering;
2
3use radixdb_plugin_abi::{
4 RadixAbiCodecFnV1, RadixAbiCompareFnV1, RadixAbiEqualFnV1, RadixAbiHashFnV1, RadixAbiParseFnV1,
5 RadixAbiTypeRefV1, RadixAbiValueV1, RADIX_BUILTIN_BOOLEAN, RADIX_BUILTIN_BYTES,
6 RADIX_BUILTIN_FLOAT, RADIX_BUILTIN_INTEGER, RADIX_BUILTIN_TEXT, RADIX_TYPE_REF_EXTERNAL,
7 RADIX_VALUE_FLAG_NULL,
8};
9
10use crate::{CodecReader, CodecWriter, HashSink, PluginError, PluginResult};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct BoundedBytes<const MAX: usize>(Vec<u8>);
14
15impl<const MAX: usize> BoundedBytes<MAX> {
16 pub fn new(bytes: impl Into<Vec<u8>>) -> PluginResult<Self> {
17 let bytes = bytes.into();
18 if bytes.len() > MAX {
19 return Err(PluginError::limit_exceeded(
20 "byte value exceeds its declared bound",
21 ));
22 }
23 Ok(Self(bytes))
24 }
25
26 pub fn as_slice(&self) -> &[u8] {
27 &self.0
28 }
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct BoundedText<const MAX: usize>(String);
33
34impl<const MAX: usize> BoundedText<MAX> {
35 pub fn new(text: impl Into<String>) -> PluginResult<Self> {
36 let text = text.into();
37 if text.len() > MAX {
38 return Err(PluginError::limit_exceeded(
39 "text value exceeds its declared bound",
40 ));
41 }
42 Ok(Self(text))
43 }
44
45 pub fn as_str(&self) -> &str {
46 &self.0
47 }
48}
49
50pub trait RadixType: Sized + Send + Sync + 'static {
51 const LOCAL_ID: &'static str;
52 const DISPLAY_NAME: &'static str;
53 const CODEC_VERSION: u32;
54 const SEMANTIC_REVISION: u32;
55 const STORAGE_KIND: u16;
56 const FIXED_BYTES: u32;
57 const MAX_BYTES: u32;
58 const CAPABILITIES: u64;
59 const CODEC_FINGERPRINT: [u8; 32];
60
61 #[doc(hidden)]
62 const ABI_ENCODE: Option<RadixAbiCodecFnV1>;
63 #[doc(hidden)]
64 const ABI_DECODE: Option<RadixAbiParseFnV1>;
65 #[doc(hidden)]
66 const ABI_EQUALITY: Option<RadixAbiEqualFnV1>;
67 #[doc(hidden)]
68 const ABI_HASH: Option<RadixAbiHashFnV1>;
69 #[doc(hidden)]
70 const ABI_ORDERING: Option<RadixAbiCompareFnV1>;
71
72 fn encode(&self, output: &mut CodecWriter) -> PluginResult<()>;
73 fn decode(input: &mut CodecReader<'_>) -> PluginResult<Self>;
74 fn test_corpus() -> Vec<Self>;
75
76 fn semantic_equal(_left: &Self, _right: &Self) -> Option<bool> {
77 None
78 }
79
80 fn semantic_hash(_value: &Self, _sink: &mut HashSink<'_>) -> Option<PluginResult<()>> {
81 None
82 }
83
84 fn semantic_compare(_left: &Self, _right: &Self) -> Option<Ordering> {
85 None
86 }
87}
88
89pub trait ValueType: Sized {
90 const TYPE_REF: RadixAbiTypeRefV1;
91 const MAX_OUTPUT_BYTES: u32;
92
93 #[doc(hidden)]
94 fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self>;
95 #[doc(hidden)]
96 fn encode_abi(&self) -> PluginResult<Vec<u8>>;
97
98 #[doc(hidden)]
99 fn is_null(&self) -> bool {
100 false
101 }
102}
103
104#[doc(hidden)]
105pub struct AbiValue<'a> {
106 raw: &'a RadixAbiValueV1,
107 bytes: &'a [u8],
108}
109
110impl<'a> AbiValue<'a> {
111 pub(crate) unsafe fn new(raw: &'a RadixAbiValueV1) -> PluginResult<Self> {
112 if raw.reserved != 0 || raw.flags & !RADIX_VALUE_FLAG_NULL != 0 {
113 return Err(PluginError::invalid_input("invalid ABI value flags"));
114 }
115 let bytes = if raw.borrowed_bytes.len == 0 {
116 &[]
117 } else {
118 if raw.borrowed_bytes.ptr.is_null() || raw.borrowed_bytes.reserved != 0 {
119 return Err(PluginError::invalid_input("invalid ABI value slice"));
120 }
121 unsafe {
124 std::slice::from_raw_parts(raw.borrowed_bytes.ptr, raw.borrowed_bytes.len as usize)
125 }
126 };
127 Ok(Self { raw, bytes })
128 }
129
130 pub fn is_null(&self) -> bool {
131 self.raw.flags & RADIX_VALUE_FLAG_NULL != 0
132 }
133
134 fn expect_type(&self, expected: RadixAbiTypeRefV1) -> PluginResult<()> {
135 if self.raw.type_ref != expected {
136 return Err(PluginError::invalid_input("ABI value type mismatch"));
137 }
138 Ok(())
139 }
140}
141
142macro_rules! integer_value {
143 ($type:ty) => {
144 impl ValueType for $type {
145 const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_INTEGER);
146 const MAX_OUTPUT_BYTES: u32 = 8;
147
148 fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
149 value.expect_type(Self::TYPE_REF)?;
150 if value.is_null() {
151 return Err(PluginError::invalid_input("unexpected NULL argument"));
152 }
153 let raw = i64::from_le_bytes(value.raw.inline_bytes[..8].try_into().unwrap());
154 <$type>::try_from(raw)
155 .map_err(|_| PluginError::invalid_input("integer argument is out of range"))
156 }
157
158 fn encode_abi(&self) -> PluginResult<Vec<u8>> {
159 let value = i64::try_from(*self)
160 .map_err(|_| PluginError::domain("integer result is out of ABI range"))?;
161 Ok(value.to_le_bytes().to_vec())
162 }
163 }
164 };
165}
166
167integer_value!(i8);
168integer_value!(i16);
169integer_value!(i32);
170integer_value!(i64);
171integer_value!(u8);
172integer_value!(u16);
173integer_value!(u32);
174
175impl ValueType for u64 {
176 const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_INTEGER);
177 const MAX_OUTPUT_BYTES: u32 = 8;
178
179 fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
180 value.expect_type(Self::TYPE_REF)?;
181 if value.is_null() {
182 return Err(PluginError::invalid_input("unexpected NULL argument"));
183 }
184 let raw = i64::from_le_bytes(value.raw.inline_bytes[..8].try_into().unwrap());
185 Self::try_from(raw).map_err(|_| PluginError::invalid_input("negative integer for u64"))
186 }
187
188 fn encode_abi(&self) -> PluginResult<Vec<u8>> {
189 let value = i64::try_from(*self)
190 .map_err(|_| PluginError::domain("u64 result exceeds signed ABI integer"))?;
191 Ok(value.to_le_bytes().to_vec())
192 }
193}
194
195impl ValueType for f64 {
196 const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_FLOAT);
197 const MAX_OUTPUT_BYTES: u32 = 8;
198
199 fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
200 value.expect_type(Self::TYPE_REF)?;
201 if value.is_null() {
202 return Err(PluginError::invalid_input("unexpected NULL argument"));
203 }
204 Ok(Self::from_bits(u64::from_le_bytes(
205 value.raw.inline_bytes[..8].try_into().unwrap(),
206 )))
207 }
208
209 fn encode_abi(&self) -> PluginResult<Vec<u8>> {
210 Ok(self.to_bits().to_le_bytes().to_vec())
211 }
212}
213
214impl ValueType for f32 {
215 const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_FLOAT);
216 const MAX_OUTPUT_BYTES: u32 = 8;
217
218 fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
219 Ok(f64::decode_abi(value)? as f32)
220 }
221
222 fn encode_abi(&self) -> PluginResult<Vec<u8>> {
223 (*self as f64).encode_abi()
224 }
225}
226
227impl ValueType for bool {
228 const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_BOOLEAN);
229 const MAX_OUTPUT_BYTES: u32 = 1;
230
231 fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
232 value.expect_type(Self::TYPE_REF)?;
233 if value.is_null() {
234 return Err(PluginError::invalid_input("unexpected NULL argument"));
235 }
236 match value.raw.inline_bytes[0] {
237 0 => Ok(false),
238 1 => Ok(true),
239 _ => Err(PluginError::invalid_input("invalid ABI boolean")),
240 }
241 }
242
243 fn encode_abi(&self) -> PluginResult<Vec<u8>> {
244 Ok(vec![u8::from(*self)])
245 }
246}
247
248impl<const MAX: usize> ValueType for BoundedBytes<MAX> {
249 const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_BYTES);
250 const MAX_OUTPUT_BYTES: u32 = if MAX > u32::MAX as usize {
251 u32::MAX
252 } else {
253 MAX as u32
254 };
255
256 fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
257 value.expect_type(Self::TYPE_REF)?;
258 if value.is_null() {
259 return Err(PluginError::invalid_input("unexpected NULL argument"));
260 }
261 Self::new(value.bytes)
262 }
263
264 fn encode_abi(&self) -> PluginResult<Vec<u8>> {
265 Ok(self.0.clone())
266 }
267}
268
269impl<const MAX: usize> ValueType for BoundedText<MAX> {
270 const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_TEXT);
271 const MAX_OUTPUT_BYTES: u32 = if MAX > u32::MAX as usize {
272 u32::MAX
273 } else {
274 MAX as u32
275 };
276
277 fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
278 value.expect_type(Self::TYPE_REF)?;
279 if value.is_null() {
280 return Err(PluginError::invalid_input("unexpected NULL argument"));
281 }
282 let text = std::str::from_utf8(value.bytes)
283 .map_err(|_| PluginError::invalid_input("text argument is not UTF-8"))?;
284 Self::new(text)
285 }
286
287 fn encode_abi(&self) -> PluginResult<Vec<u8>> {
288 Ok(self.0.as_bytes().to_vec())
289 }
290}
291
292impl<T: RadixType> ValueType for T {
293 const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1 {
294 kind: RADIX_TYPE_REF_EXTERNAL,
295 builtin_tag: 0,
296 codec_version: T::CODEC_VERSION,
297 object_id: [0; 16],
298 };
299 const MAX_OUTPUT_BYTES: u32 = T::MAX_BYTES;
300
301 fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
302 if value.raw.type_ref.kind != RADIX_TYPE_REF_EXTERNAL
303 || value.raw.type_ref.codec_version != T::CODEC_VERSION
304 || value.is_null()
305 {
306 return Err(PluginError::invalid_input(
307 "external ABI value type or codec mismatch",
308 ));
309 }
310 if T::STORAGE_KIND == radixdb_plugin_abi::RADIX_EXTERNAL_STORAGE_FIXED
311 && value.bytes.len() != T::FIXED_BYTES as usize
312 {
313 return Err(PluginError::invalid_input(
314 "fixed external value has wrong encoded width",
315 ));
316 }
317 let mut reader = CodecReader::new(value.bytes);
318 let decoded = T::decode(&mut reader)?;
319 reader.finish()?;
320 Ok(decoded)
321 }
322
323 fn encode_abi(&self) -> PluginResult<Vec<u8>> {
324 let mut output = CodecWriter::new(T::MAX_BYTES as usize);
325 self.encode(&mut output)?;
326 let bytes = output.into_bytes();
327 if T::STORAGE_KIND == radixdb_plugin_abi::RADIX_EXTERNAL_STORAGE_FIXED
328 && bytes.len() != T::FIXED_BYTES as usize
329 {
330 return Err(PluginError::internal(
331 "fixed external codec emitted the wrong width",
332 ));
333 }
334 Ok(bytes)
335 }
336}
337
338impl<T: ValueType> ValueType for Option<T> {
339 const TYPE_REF: RadixAbiTypeRefV1 = T::TYPE_REF;
340 const MAX_OUTPUT_BYTES: u32 = T::MAX_OUTPUT_BYTES;
341
342 fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
343 if value.is_null() {
344 Ok(None)
345 } else {
346 T::decode_abi(value).map(Some)
347 }
348 }
349
350 fn encode_abi(&self) -> PluginResult<Vec<u8>> {
351 match self {
352 Some(value) => value.encode_abi(),
353 None => Ok(Vec::new()),
354 }
355 }
356
357 fn is_null(&self) -> bool {
358 self.is_none()
359 }
360}