Skip to main content

radixdb_plugin_abi/
validate.rs

1use core::{mem, slice, str};
2
3use crate::*;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum RadixAbiValidationError {
7    UnsupportedMajor,
8    UnsupportedMinor,
9    StructTooSmall,
10    UnknownFlags,
11    NonZeroReserved,
12    NullPointer,
13    MisalignedPointer,
14    LimitExceeded,
15    InvalidBoolean,
16    InvalidTypeReference,
17    InvalidValue,
18    InvalidBatchShape,
19    InvalidStatus,
20    InvalidUtf8,
21    MissingCallback,
22    InvalidDescriptor,
23}
24
25pub type RadixAbiValidationResult<T = ()> = Result<T, RadixAbiValidationError>;
26
27pub const fn negotiate_minor(
28    host_min: u16,
29    host_max: u16,
30    plugin_min: u16,
31    plugin_max: u16,
32) -> RadixAbiValidationResult<u16> {
33    if host_min > host_max || plugin_min > plugin_max {
34        return Err(RadixAbiValidationError::UnsupportedMinor);
35    }
36    let selected = if host_max < plugin_max {
37        host_max
38    } else {
39        plugin_max
40    };
41    if selected < host_min || selected < plugin_min {
42        Err(RadixAbiValidationError::UnsupportedMinor)
43    } else {
44        Ok(selected)
45    }
46}
47
48pub const fn validate_header(
49    header: &RadixAbiHeaderV1,
50    mandatory_size: u32,
51    allowed_flags: u64,
52) -> RadixAbiValidationResult {
53    if header.abi_major != RADIX_ABI_MAJOR {
54        return Err(RadixAbiValidationError::UnsupportedMajor);
55    }
56    if header.abi_minor > RADIX_ABI_MINOR {
57        return Err(RadixAbiValidationError::UnsupportedMinor);
58    }
59    if header.struct_size < mandatory_size {
60        return Err(RadixAbiValidationError::StructTooSmall);
61    }
62    if header.flags & !allowed_flags != 0 {
63        return Err(RadixAbiValidationError::UnknownFlags);
64    }
65    Ok(())
66}
67
68pub fn validate_slice(
69    value: RadixAbiSliceV1,
70    maximum: u32,
71    alignment: usize,
72) -> RadixAbiValidationResult {
73    validate_pointer(value.ptr, value.len, maximum, alignment)?;
74    if value.reserved != 0 {
75        return Err(RadixAbiValidationError::NonZeroReserved);
76    }
77    Ok(())
78}
79
80pub fn validate_u32_slice(value: RadixAbiU32SliceV1, maximum: u32) -> RadixAbiValidationResult {
81    validate_pointer(
82        value.ptr.cast::<u8>(),
83        value.len,
84        maximum,
85        mem::align_of::<u32>(),
86    )?;
87    if value.reserved != 0 {
88        return Err(RadixAbiValidationError::NonZeroReserved);
89    }
90    Ok(())
91}
92
93fn validate_pointer<T>(
94    pointer: *const T,
95    length: u32,
96    maximum: u32,
97    alignment: usize,
98) -> RadixAbiValidationResult {
99    if length > maximum {
100        return Err(RadixAbiValidationError::LimitExceeded);
101    }
102    if length != 0 && pointer.is_null() {
103        return Err(RadixAbiValidationError::NullPointer);
104    }
105    if !pointer.is_null() && alignment > 1 && !pointer.addr().is_multiple_of(alignment) {
106        return Err(RadixAbiValidationError::MisalignedPointer);
107    }
108    Ok(())
109}
110
111pub fn validate_type_ref(value: &RadixAbiTypeRefV1) -> RadixAbiValidationResult {
112    match value.kind {
113        RADIX_TYPE_REF_BUILTIN
114            if (RADIX_BUILTIN_TYPE_MIN..=RADIX_BUILTIN_TYPE_MAX).contains(&value.builtin_tag)
115                && value.codec_version == 0
116                && value.object_id == [0; 16] =>
117        {
118            Ok(())
119        }
120        RADIX_TYPE_REF_EXTERNAL
121            if value.builtin_tag == 0 && value.codec_version != 0 && value.object_id != [0; 16] =>
122        {
123            Ok(())
124        }
125        _ => Err(RadixAbiValidationError::InvalidTypeReference),
126    }
127}
128
129pub fn validate_optional_type_ref(value: &RadixAbiTypeRefV1) -> RadixAbiValidationResult {
130    if *value == RadixAbiTypeRefV1::ABSENT {
131        Ok(())
132    } else {
133        validate_type_ref(value)
134    }
135}
136
137pub fn validate_value(value: &RadixAbiValueV1) -> RadixAbiValidationResult {
138    validate_type_ref(&value.type_ref)?;
139    if value.flags & !RADIX_VALUE_FLAG_ALL != 0 || value.reserved != 0 {
140        return Err(RadixAbiValidationError::InvalidValue);
141    }
142    let is_null = value.flags & RADIX_VALUE_FLAG_NULL != 0;
143    if is_null {
144        if value.inline_bytes != [0; 16]
145            || !value.borrowed_bytes.ptr.is_null()
146            || value.borrowed_bytes.len != 0
147            || value.borrowed_bytes.reserved != 0
148        {
149            return Err(RadixAbiValidationError::InvalidValue);
150        }
151        return Ok(());
152    }
153    match value.type_ref.kind {
154        RADIX_TYPE_REF_BUILTIN => match value.type_ref.builtin_tag {
155            RADIX_BUILTIN_INTEGER | RADIX_BUILTIN_FLOAT | RADIX_BUILTIN_TIMESTAMP => {
156                if value.inline_bytes[8..] != [0; 8] || !is_empty_slice(value.borrowed_bytes) {
157                    return Err(RadixAbiValidationError::InvalidValue);
158                }
159            }
160            RADIX_BUILTIN_BOOLEAN => {
161                if value.inline_bytes[0] > 1
162                    || value.inline_bytes[1..] != [0; 15]
163                    || !is_empty_slice(value.borrowed_bytes)
164                {
165                    return Err(RadixAbiValidationError::InvalidValue);
166                }
167            }
168            RADIX_BUILTIN_UUID => {
169                if !is_empty_slice(value.borrowed_bytes) {
170                    return Err(RadixAbiValidationError::InvalidValue);
171                }
172            }
173            RADIX_BUILTIN_DATE => {
174                if value.inline_bytes[4..] != [0; 12] || !is_empty_slice(value.borrowed_bytes) {
175                    return Err(RadixAbiValidationError::InvalidValue);
176                }
177            }
178            RADIX_BUILTIN_TEXT
179            | RADIX_BUILTIN_JSON
180            | RADIX_BUILTIN_VECTOR
181            | RADIX_BUILTIN_DECIMAL
182            | RADIX_BUILTIN_BYTES => {
183                if value.inline_bytes != [0; 16] {
184                    return Err(RadixAbiValidationError::InvalidValue);
185                }
186                validate_slice(value.borrowed_bytes, RADIX_MAX_EXTERNAL_VALUE_BYTES, 1)?;
187            }
188            _ => return Err(RadixAbiValidationError::InvalidValue),
189        },
190        RADIX_TYPE_REF_EXTERNAL => {
191            if value.inline_bytes != [0; 16] {
192                return Err(RadixAbiValidationError::InvalidValue);
193            }
194            validate_slice(value.borrowed_bytes, RADIX_MAX_EXTERNAL_VALUE_BYTES, 1)?;
195        }
196        _ => return Err(RadixAbiValidationError::InvalidValue),
197    }
198    Ok(())
199}
200
201/// Validate the contents of a structurally valid borrowed scalar payload.
202///
203/// # Safety
204///
205/// Every non-null `borrowed_bytes.ptr` must point to `borrowed_bytes.len`
206/// initialized bytes and remain alive for this call. The bytes are never
207/// retained.
208pub unsafe fn validate_value_contents(value: &RadixAbiValueV1) -> RadixAbiValidationResult {
209    validate_value(value)?;
210    if value.flags & RADIX_VALUE_FLAG_NULL != 0 || value.type_ref.kind == RADIX_TYPE_REF_EXTERNAL {
211        return Ok(());
212    }
213    let bytes = if value.borrowed_bytes.len == 0 {
214        &[]
215    } else {
216        // SAFETY: guaranteed by this function's caller contract after the
217        // structural pointer/length validation above.
218        unsafe {
219            slice::from_raw_parts(value.borrowed_bytes.ptr, value.borrowed_bytes.len as usize)
220        }
221    };
222    match value.type_ref.builtin_tag {
223        RADIX_BUILTIN_TEXT | RADIX_BUILTIN_JSON => {
224            str::from_utf8(bytes).map_err(|_| RadixAbiValidationError::InvalidUtf8)?;
225        }
226        RADIX_BUILTIN_VECTOR if !bytes.len().is_multiple_of(mem::size_of::<f32>()) => {
227            return Err(RadixAbiValidationError::InvalidValue);
228        }
229        RADIX_BUILTIN_DECIMAL if bytes.len() != 18 => {
230            return Err(RadixAbiValidationError::InvalidValue);
231        }
232        _ => {}
233    }
234    Ok(())
235}
236
237pub fn validate_batch_view(value: &RadixAbiBatchViewV1) -> RadixAbiValidationResult {
238    validate_header(
239        &value.header,
240        mem::size_of::<RadixAbiBatchViewV1>() as u32,
241        0,
242    )?;
243    if value.row_count == u32::MAX {
244        return Err(RadixAbiValidationError::LimitExceeded);
245    }
246    validate_descriptor_table(value.columns, value.column_count)
247        .map_err(|_| RadixAbiValidationError::InvalidBatchShape)
248}
249
250pub fn validate_column_view(value: &RadixAbiColumnViewV1) -> RadixAbiValidationResult {
251    validate_header(
252        &value.header,
253        mem::size_of::<RadixAbiColumnViewV1>() as u32,
254        0,
255    )?;
256    validate_type_ref(&value.type_ref)?;
257    if value.row_count == u32::MAX {
258        return Err(RadixAbiValidationError::LimitExceeded);
259    }
260    if value.reserved_u16 != 0 {
261        return Err(RadixAbiValidationError::NonZeroReserved);
262    }
263    let null_bytes = value
264        .row_count
265        .checked_add(7)
266        .ok_or(RadixAbiValidationError::LimitExceeded)?
267        / 8;
268    if value.null_bitmap.len != 0 && value.null_bitmap.len != null_bytes {
269        return Err(RadixAbiValidationError::InvalidBatchShape);
270    }
271    validate_slice(value.null_bitmap, null_bytes, 1)?;
272    validate_slice(value.data, u32::MAX, 1)?;
273    match value.layout {
274        RADIX_COLUMN_LAYOUT_FIXED => {
275            if value.element_width == 0
276                || value.stride < u32::from(value.element_width)
277                || value.alignment == 0
278                || !value.alignment.is_power_of_two()
279                || !is_empty_u32_slice(value.offsets)
280            {
281                return Err(RadixAbiValidationError::InvalidBatchShape);
282            }
283            let required = value
284                .row_count
285                .checked_mul(value.stride)
286                .ok_or(RadixAbiValidationError::LimitExceeded)?;
287            if value.data.len != required {
288                return Err(RadixAbiValidationError::InvalidBatchShape);
289            }
290            validate_slice(value.data, u32::MAX, usize::from(value.alignment))?;
291            validate_builtin_fixed_layout(value)?;
292        }
293        RADIX_COLUMN_LAYOUT_VARIABLE => {
294            if value.element_width != 0
295                || value.stride != 0
296                || value.alignment != 1
297                || value.offsets.len != value.row_count + 1
298            {
299                return Err(RadixAbiValidationError::InvalidBatchShape);
300            }
301            validate_u32_slice(value.offsets, value.row_count + 1)?;
302            if value.type_ref.kind == RADIX_TYPE_REF_BUILTIN
303                && !matches!(
304                    value.type_ref.builtin_tag,
305                    RADIX_BUILTIN_TEXT
306                        | RADIX_BUILTIN_JSON
307                        | RADIX_BUILTIN_VECTOR
308                        | RADIX_BUILTIN_BYTES
309                )
310            {
311                return Err(RadixAbiValidationError::InvalidBatchShape);
312            }
313        }
314        _ => return Err(RadixAbiValidationError::InvalidBatchShape),
315    }
316    Ok(())
317}
318
319/// Validate offset contents after the host has established that the borrowed
320/// `u32` range is readable for the duration of this call.
321///
322/// # Safety
323///
324/// `value.offsets.ptr` must point to `value.offsets.len` initialized `u32`
325/// values and remain alive for the call. This function never retains it.
326pub unsafe fn validate_column_offsets(value: &RadixAbiColumnViewV1) -> RadixAbiValidationResult {
327    validate_column_view(value)?;
328    if value.layout != RADIX_COLUMN_LAYOUT_VARIABLE {
329        return Ok(());
330    }
331    // SAFETY: guaranteed by this function's caller contract after structural
332    // pointer/length/alignment validation above.
333    let offsets = unsafe { slice::from_raw_parts(value.offsets.ptr, value.offsets.len as usize) };
334    if offsets.first().copied() != Some(0)
335        || offsets.last().copied() != Some(value.data.len)
336        || offsets.windows(2).any(|window| window[0] > window[1])
337    {
338        return Err(RadixAbiValidationError::InvalidBatchShape);
339    }
340    Ok(())
341}
342
343/// Validate every column after the host has established that the descriptor
344/// table and all borrowed column buffers are readable for this call.
345///
346/// # Safety
347///
348/// `value.columns` must point to `value.column_count` initialized column
349/// descriptors. Every pointer reachable from them must satisfy the safety
350/// contract of [`validate_column_offsets`]. Nothing is retained.
351pub unsafe fn validate_batch_columns(value: &RadixAbiBatchViewV1) -> RadixAbiValidationResult {
352    validate_batch_view(value)?;
353    let columns = if value.column_count == 0 {
354        &[]
355    } else {
356        // SAFETY: guaranteed by this function's caller contract after the
357        // structural pointer/length/alignment validation above.
358        unsafe { slice::from_raw_parts(value.columns, value.column_count as usize) }
359    };
360    for column in columns {
361        if column.row_count != value.row_count {
362            return Err(RadixAbiValidationError::InvalidBatchShape);
363        }
364        // SAFETY: inherited from this function's caller contract.
365        unsafe { validate_column_offsets(column)? };
366    }
367    Ok(())
368}
369
370pub fn validate_result_builder(value: &RadixAbiResultBuilderV1) -> RadixAbiValidationResult {
371    validate_header(
372        &value.header,
373        mem::size_of::<RadixAbiResultBuilderV1>() as u32,
374        0,
375    )?;
376    if value.handle == 0
377        || value.max_bytes == 0
378        || value.max_items == 0
379        || value.write.is_none()
380        || value.finish.is_none()
381    {
382        return Err(RadixAbiValidationError::MissingCallback);
383    }
384    Ok(())
385}
386
387pub fn validate_result_item(
388    item_flags: u32,
389    reserved: u32,
390    bytes: RadixAbiSliceV1,
391    maximum: u32,
392) -> RadixAbiValidationResult {
393    if item_flags & !RADIX_RESULT_ITEM_FLAG_ALL != 0 || reserved != 0 {
394        return Err(RadixAbiValidationError::UnknownFlags);
395    }
396    if item_flags & RADIX_RESULT_ITEM_FLAG_NULL != 0 {
397        if !is_empty_slice(bytes) {
398            return Err(RadixAbiValidationError::InvalidValue);
399        }
400        return Ok(());
401    }
402    validate_slice(bytes, maximum, 1)
403}
404
405pub fn validate_hash_sink(value: &RadixAbiHashSinkV1) -> RadixAbiValidationResult {
406    validate_header(
407        &value.header,
408        mem::size_of::<RadixAbiHashSinkV1>() as u32,
409        0,
410    )?;
411    if value.handle == 0 {
412        return Err(RadixAbiValidationError::InvalidDescriptor);
413    }
414    if value.max_components == 0
415        || value.max_components > RADIX_MAX_HASH_COMPONENTS
416        || value.max_bytes == 0
417        || value.max_bytes > RADIX_MAX_HASH_BYTES
418    {
419        return Err(RadixAbiValidationError::LimitExceeded);
420    }
421    if value.append.is_none() {
422        return Err(RadixAbiValidationError::MissingCallback);
423    }
424    Ok(())
425}
426
427pub fn validate_hash_component(
428    component_kind: u16,
429    reserved: u16,
430    bytes: RadixAbiSliceV1,
431    remaining_bytes: u32,
432) -> RadixAbiValidationResult {
433    if reserved != 0 {
434        return Err(RadixAbiValidationError::NonZeroReserved);
435    }
436    match component_kind {
437        RADIX_HASH_COMPONENT_BYTES | RADIX_HASH_COMPONENT_EXTERNAL => {
438            validate_slice(bytes, remaining_bytes, 1)
439        }
440        RADIX_HASH_COMPONENT_I64 | RADIX_HASH_COMPONENT_U64 | RADIX_HASH_COMPONENT_F64_BITS => {
441            validate_slice(bytes, remaining_bytes.min(8), 1)?;
442            if bytes.len == 8 {
443                Ok(())
444            } else {
445                Err(RadixAbiValidationError::InvalidValue)
446            }
447        }
448        _ => Err(RadixAbiValidationError::InvalidValue),
449    }
450}
451
452pub fn validate_diagnostic(value: &RadixAbiDiagnosticV1) -> RadixAbiValidationResult {
453    validate_header(
454        &value.header,
455        mem::size_of::<RadixAbiDiagnosticV1>() as u32,
456        0,
457    )?;
458    if !(RADIX_DIAGNOSTIC_INVALID_INPUT..=RADIX_DIAGNOSTIC_DEPENDENCY).contains(&value.category) {
459        return Err(RadixAbiValidationError::InvalidDescriptor);
460    }
461    validate_failure_status(value.status)?;
462    validate_slice(value.detail, RADIX_MAX_DIAGNOSTIC_BYTES, 1)?;
463    validate_slice(value.field, RADIX_MAX_LOCAL_ID_BYTES, 1)
464}
465
466pub fn validate_diagnostic_sink(value: &RadixAbiDiagnosticSinkV1) -> RadixAbiValidationResult {
467    validate_header(
468        &value.header,
469        mem::size_of::<RadixAbiDiagnosticSinkV1>() as u32,
470        0,
471    )?;
472    if value.handle == 0 {
473        return Err(RadixAbiValidationError::InvalidDescriptor);
474    }
475    if value.max_detail_bytes == 0 || value.max_detail_bytes > RADIX_MAX_DIAGNOSTIC_BYTES {
476        return Err(RadixAbiValidationError::LimitExceeded);
477    }
478    if value.reserved != 0 {
479        return Err(RadixAbiValidationError::NonZeroReserved);
480    }
481    if value.write.is_none() {
482        return Err(RadixAbiValidationError::MissingCallback);
483    }
484    Ok(())
485}
486
487pub fn validate_call_context(value: &RadixAbiCallContextV1) -> RadixAbiValidationResult {
488    validate_header(
489        &value.header,
490        mem::size_of::<RadixAbiCallContextV1>() as u32,
491        0,
492    )?;
493    if value.handle == 0 || value.deadline_unix_ns == 0 {
494        return Err(RadixAbiValidationError::InvalidDescriptor);
495    }
496    if value.max_output_bytes == 0 || value.max_work_units == 0 {
497        return Err(RadixAbiValidationError::LimitExceeded);
498    }
499    if value.check_cancelled.is_none() || value.charge_work.is_none() || value.diagnostics.is_null()
500    {
501        return Err(RadixAbiValidationError::MissingCallback);
502    }
503    Ok(())
504}
505
506pub fn validate_host_api(value: &RadixHostApiV1) -> RadixAbiValidationResult {
507    validate_header(&value.header, mem::size_of::<RadixHostApiV1>() as u32, 0)?;
508    if value.handle == 0
509        || value.max_external_value_bytes == 0
510        || value.max_external_value_bytes > RADIX_MAX_EXTERNAL_VALUE_BYTES
511        || value.max_batch_rows == 0
512        || value.max_batch_rows == u32::MAX
513        || value.max_planner_spans == 0
514        || value.max_planner_spans > RADIX_MAX_PLANNER_SPANS
515        || value.reserved != 0
516        || value.log.is_none()
517    {
518        return Err(RadixAbiValidationError::InvalidDescriptor);
519    }
520    Ok(())
521}
522
523pub fn validate_log_record(
524    level: u16,
525    reserved: u16,
526    message: RadixAbiStringV1,
527) -> RadixAbiValidationResult {
528    if reserved != 0 {
529        return Err(RadixAbiValidationError::NonZeroReserved);
530    }
531    if !matches!(
532        level,
533        RADIX_LOG_INFO | RADIX_LOG_WARN | RADIX_LOG_ERROR | RADIX_LOG_DEBUG
534    ) {
535        return Err(RadixAbiValidationError::InvalidValue);
536    }
537    validate_slice(message, RADIX_MAX_DIAGNOSTIC_BYTES, 1)
538}
539
540pub fn validate_package_descriptor_shallow(
541    value: &RadixPluginDescriptorV1,
542) -> RadixAbiValidationResult {
543    validate_header(
544        &value.header,
545        mem::size_of::<RadixPluginDescriptorV1>() as u32,
546        RADIX_PACKAGE_CAP_ALL,
547    )?;
548    if value.package_id == [0; 16]
549        || value.descriptor_fingerprint == [0; 32]
550        || value.abi_min_minor > value.abi_max_minor
551        || value.abi_min_minor > RADIX_ABI_MINOR
552        || value.reserved != 0
553        || value.reserved_types != 0
554        || value.reserved_functions != 0
555        || value.reserved_operators != 0
556        || value.reserved_operator_classes != 0
557        || value.reserved_planner_support != 0
558        || (value.header.flags & RADIX_PACKAGE_CAP_BATCH_FUNCTIONS != 0
559            && value.function_count == 0)
560    {
561        return Err(RadixAbiValidationError::InvalidDescriptor);
562    }
563    validate_slice(value.package_name, RADIX_MAX_LOCAL_ID_BYTES, 1)?;
564    validate_slice(value.package_version, 64, 1)?;
565    if value.package_name.len == 0 || value.package_version.len == 0 {
566        return Err(RadixAbiValidationError::InvalidDescriptor);
567    }
568    validate_descriptor_table(value.types, value.type_count)?;
569    validate_descriptor_table(value.functions, value.function_count)?;
570    validate_descriptor_table(value.operators, value.operator_count)?;
571    validate_descriptor_table(value.operator_classes, value.operator_class_count)?;
572    validate_descriptor_table(value.planner_support, value.planner_support_count)?;
573    validate_capability_count(
574        value.header.flags,
575        RADIX_PACKAGE_CAP_EXTERNAL_TYPES,
576        value.type_count,
577    )?;
578    validate_capability_count(
579        value.header.flags,
580        RADIX_PACKAGE_CAP_SCALAR_FUNCTIONS,
581        value.function_count,
582    )?;
583    validate_capability_count(
584        value.header.flags,
585        RADIX_PACKAGE_CAP_OPERATORS,
586        value.operator_count,
587    )?;
588    validate_capability_count(
589        value.header.flags,
590        RADIX_PACKAGE_CAP_OPERATOR_CLASSES,
591        value.operator_class_count,
592    )?;
593    validate_capability_count(
594        value.header.flags,
595        RADIX_PACKAGE_CAP_PLANNER_SUPPORT,
596        value.planner_support_count,
597    )?;
598    Ok(())
599}
600
601pub fn validate_external_type_descriptor(
602    value: &RadixAbiExternalTypeDescriptorV1,
603) -> RadixAbiValidationResult {
604    validate_header(
605        &value.header,
606        mem::size_of::<RadixAbiExternalTypeDescriptorV1>() as u32,
607        0,
608    )?;
609    if value.object_id == [0; 16]
610        || value.codec_version == 0
611        || value.semantic_revision == 0
612        || value.max_bytes == 0
613        || value.max_bytes > RADIX_MAX_EXTERNAL_VALUE_BYTES
614        || value.reserved_u16 != 0
615        || value.reserved_u32 != 0
616        || value.capabilities & !RADIX_TYPE_CAP_ALL != 0
617        || value.codec_fingerprint == [0; 32]
618        || value.encode.is_none()
619        || value.decode.is_none()
620    {
621        return Err(RadixAbiValidationError::InvalidDescriptor);
622    }
623    validate_non_empty_string(value.local_id, RADIX_MAX_LOCAL_ID_BYTES)?;
624    validate_non_empty_string(value.display_name, RADIX_MAX_LOCAL_ID_BYTES)?;
625    match value.storage_kind {
626        RADIX_EXTERNAL_STORAGE_FIXED
627            if value.fixed_bytes != 0 && value.fixed_bytes == value.max_bytes => {}
628        RADIX_EXTERNAL_STORAGE_VARIABLE if value.fixed_bytes == 0 => {}
629        _ => return Err(RadixAbiValidationError::InvalidDescriptor),
630    }
631    validate_capability_callback(
632        value.capabilities,
633        RADIX_TYPE_CAP_EQUALITY,
634        value.equality.is_some(),
635    )?;
636    validate_capability_callback(
637        value.capabilities,
638        RADIX_TYPE_CAP_HASH,
639        value.hash.is_some(),
640    )?;
641    validate_capability_callback(
642        value.capabilities,
643        RADIX_TYPE_CAP_ORDERING,
644        value.ordering.is_some(),
645    )?;
646    validate_capability_callback(
647        value.capabilities,
648        RADIX_TYPE_CAP_TEXT_INPUT,
649        value.text_input.is_some(),
650    )?;
651    validate_capability_callback(
652        value.capabilities,
653        RADIX_TYPE_CAP_TEXT_OUTPUT,
654        value.text_output.is_some(),
655    )?;
656    validate_capability_callback(
657        value.capabilities,
658        RADIX_TYPE_CAP_BINARY_INPUT,
659        value.binary_input.is_some(),
660    )?;
661    validate_capability_callback(
662        value.capabilities,
663        RADIX_TYPE_CAP_BINARY_OUTPUT,
664        value.binary_output.is_some(),
665    )?;
666    if value.capabilities & RADIX_TYPE_CAP_HASH != 0
667        && value.capabilities & RADIX_TYPE_CAP_EQUALITY == 0
668    {
669        return Err(RadixAbiValidationError::InvalidDescriptor);
670    }
671    Ok(())
672}
673
674pub fn validate_scalar_function_descriptor(
675    value: &RadixAbiScalarFunctionDescriptorV1,
676) -> RadixAbiValidationResult {
677    validate_header(
678        &value.header,
679        mem::size_of::<RadixAbiScalarFunctionDescriptorV1>() as u32,
680        0,
681    )?;
682    if value.object_id == [0; 16]
683        || value.semantic_revision == 0
684        || value.argument_count > RADIX_MAX_FUNCTION_ARGUMENTS
685        || !matches!(
686            value.volatility,
687            RADIX_VOLATILITY_IMMUTABLE | RADIX_VOLATILITY_STABLE | RADIX_VOLATILITY_VOLATILE
688        )
689        || value.strict > 1
690        || value.parallel_safe > 1
691        || value.cancellation != RADIX_CANCELLATION_BOUNDED
692        || value.reserved_u16 != 0
693        || value.cost == 0
694        || value.cost > 1_000_000
695        || value.max_output_bytes == 0
696        || value.scalar.is_none()
697    {
698        return Err(RadixAbiValidationError::InvalidDescriptor);
699    }
700    validate_non_empty_string(value.local_id, RADIX_MAX_LOCAL_ID_BYTES)?;
701    validate_non_empty_string(value.display_name, RADIX_MAX_LOCAL_ID_BYTES)?;
702    validate_descriptor_table(value.arguments, value.argument_count)?;
703    validate_type_ref(&value.result)
704}
705
706pub fn validate_operator_descriptor(
707    value: &RadixAbiOperatorDescriptorV1,
708) -> RadixAbiValidationResult {
709    validate_header(
710        &value.header,
711        mem::size_of::<RadixAbiOperatorDescriptorV1>() as u32,
712        0,
713    )?;
714    if value.object_id == [0; 16]
715        || value.function_id == [0; 16]
716        || value.semantic_revision == 0
717        || value.reserved != 0
718    {
719        return Err(RadixAbiValidationError::InvalidDescriptor);
720    }
721    validate_non_empty_string(value.local_id, RADIX_MAX_LOCAL_ID_BYTES)?;
722    validate_non_empty_string(value.symbol, 3)?;
723    validate_optional_type_ref(&value.left)?;
724    validate_type_ref(&value.right)?;
725    validate_type_ref(&value.result)
726}
727
728pub fn validate_operator_class_descriptor(
729    value: &RadixAbiOperatorClassDescriptorV1,
730) -> RadixAbiValidationResult {
731    validate_header(
732        &value.header,
733        mem::size_of::<RadixAbiOperatorClassDescriptorV1>() as u32,
734        0,
735    )?;
736    if value.object_id == [0; 16]
737        || value.semantic_revision == 0
738        || !matches!(
739            value.access_method,
740            RADIX_ACCESS_METHOD_BTREE
741                | RADIX_ACCESS_METHOD_HASH
742                | RADIX_ACCESS_METHOD_BITMAP
743                | RADIX_ACCESS_METHOD_HNSW
744        )
745        || value.reserved_u16 != 0
746        || value.reserved_u32 != 0
747        || value.key_codec_revision == 0
748        || value.fingerprint == [0; 32]
749        || value.encode_key.is_none()
750        || value.strategy_count > 1024
751        || value.support_count > 1024
752    {
753        return Err(RadixAbiValidationError::InvalidDescriptor);
754    }
755    validate_non_empty_string(value.local_id, RADIX_MAX_LOCAL_ID_BYTES)?;
756    validate_type_ref(&value.input_type)?;
757    validate_type_ref(&value.key_type)?;
758    validate_descriptor_table(value.strategies, value.strategy_count)?;
759    validate_descriptor_table(value.supports, value.support_count)
760}
761
762pub fn validate_planner_support_descriptor(
763    value: &RadixAbiPlannerSupportDescriptorV1,
764) -> RadixAbiValidationResult {
765    validate_header(
766        &value.header,
767        mem::size_of::<RadixAbiPlannerSupportDescriptorV1>() as u32,
768        0,
769    )?;
770    if value.object_id == [0; 16]
771        || value.semantic_revision == 0
772        || value.max_spans == 0
773        || value.max_spans > RADIX_MAX_PLANNER_SPANS
774        || value.max_output_bytes == 0
775        || value.max_output_bytes > RADIX_MAX_EXTERNAL_VALUE_BYTES
776        || !matches!(
777            value.recheck_policy,
778            RADIX_RECHECK_EXACT | RADIX_RECHECK_ALWAYS
779        )
780        || value.reserved_u16 != 0
781        || (value.target_function_id == [0; 16] && value.target_operator_class_id == [0; 16])
782        || value.fingerprint == [0; 32]
783        || value.callback.is_none()
784    {
785        return Err(RadixAbiValidationError::InvalidDescriptor);
786    }
787    validate_non_empty_string(value.local_id, RADIX_MAX_LOCAL_ID_BYTES)
788}
789
790fn validate_non_empty_string(value: RadixAbiStringV1, maximum: u32) -> RadixAbiValidationResult {
791    validate_slice(value, maximum, 1)?;
792    if value.len == 0 {
793        return Err(RadixAbiValidationError::InvalidDescriptor);
794    }
795    Ok(())
796}
797
798fn validate_capability_callback(
799    capabilities: u64,
800    capability: u64,
801    callback_present: bool,
802) -> RadixAbiValidationResult {
803    if (capabilities & capability != 0) != callback_present {
804        Err(RadixAbiValidationError::MissingCallback)
805    } else {
806        Ok(())
807    }
808}
809
810fn validate_capability_count(
811    capabilities: u64,
812    capability: u64,
813    count: u32,
814) -> RadixAbiValidationResult {
815    if (capabilities & capability != 0) != (count != 0) {
816        Err(RadixAbiValidationError::InvalidDescriptor)
817    } else {
818        Ok(())
819    }
820}
821
822fn validate_descriptor_table<T>(pointer: *const T, count: u32) -> RadixAbiValidationResult {
823    validate_pointer(
824        pointer.cast::<u8>(),
825        count,
826        RADIX_MAX_DESCRIPTOR_ENTRIES,
827        mem::align_of::<T>(),
828    )
829}
830
831fn validate_failure_status(status: RadixAbiStatusV1) -> RadixAbiValidationResult {
832    if (RADIX_STATUS_INVALID_ARGUMENT..=RADIX_STATUS_DEPENDENCY_MISSING).contains(&status) {
833        Ok(())
834    } else {
835        Err(RadixAbiValidationError::InvalidStatus)
836    }
837}
838
839fn validate_builtin_fixed_layout(value: &RadixAbiColumnViewV1) -> RadixAbiValidationResult {
840    if value.type_ref.kind != RADIX_TYPE_REF_BUILTIN {
841        return Ok(());
842    }
843    let expected_width = match value.type_ref.builtin_tag {
844        RADIX_BUILTIN_INTEGER | RADIX_BUILTIN_FLOAT | RADIX_BUILTIN_TIMESTAMP => 8,
845        RADIX_BUILTIN_BOOLEAN => 1,
846        RADIX_BUILTIN_UUID => 16,
847        RADIX_BUILTIN_DECIMAL => 18,
848        RADIX_BUILTIN_DATE => 4,
849        RADIX_BUILTIN_TEXT | RADIX_BUILTIN_JSON | RADIX_BUILTIN_VECTOR | RADIX_BUILTIN_BYTES => {
850            return Err(RadixAbiValidationError::InvalidBatchShape)
851        }
852        _ => return Err(RadixAbiValidationError::InvalidBatchShape),
853    };
854    if value.element_width != expected_width {
855        return Err(RadixAbiValidationError::InvalidBatchShape);
856    }
857    Ok(())
858}
859
860fn is_empty_slice(value: RadixAbiSliceV1) -> bool {
861    value.ptr.is_null() && value.len == 0 && value.reserved == 0
862}
863
864fn is_empty_u32_slice(value: RadixAbiU32SliceV1) -> bool {
865    value.ptr.is_null() && value.len == 0 && value.reserved == 0
866}