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
201pub 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 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
319pub 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 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
343pub 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 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 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}