lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

persisted_value.rs (2765B)


      1 //! Bounded projections for untrusted SQLite TEXT and BLOB values.
      2 
      3 use sqlx::{Row, sqlite::SqliteRow};
      4 
      5 pub(crate) const MAX_IDENTIFIER_UTF8_BYTES: usize = 128;
      6 pub(crate) const MAX_INTEGRITY_RESULT_BYTES: usize = 64;
      7 pub(crate) const INTEGRITY_CHECK_SQL: &str = "PRAGMA integrity_check(1)";
      8 
      9 pub(crate) fn bounded_bytes<'row>(
     10     row: &'row SqliteRow,
     11     type_ok_column: &str,
     12     length_column: &str,
     13     prefix_column: &str,
     14     minimum: usize,
     15     maximum: usize,
     16 ) -> Option<&'row [u8]> {
     17     let type_ok = row.try_get::<i64, _>(type_ok_column).ok()? == 1;
     18     let length = row.try_get::<Option<i64>, _>(length_column).ok()??;
     19     let length = usize::try_from(length).ok()?;
     20     let prefix = row.try_get::<Option<&'row [u8]>, _>(prefix_column).ok()??;
     21     bounded_projection(type_ok, length, prefix, minimum, maximum)
     22 }
     23 
     24 pub(crate) fn bounded_utf8<'row>(
     25     row: &'row SqliteRow,
     26     type_ok_column: &str,
     27     length_column: &str,
     28     prefix_column: &str,
     29     minimum: usize,
     30     maximum: usize,
     31 ) -> Option<&'row str> {
     32     core::str::from_utf8(bounded_bytes(
     33         row,
     34         type_ok_column,
     35         length_column,
     36         prefix_column,
     37         minimum,
     38         maximum,
     39     )?)
     40     .ok()
     41 }
     42 
     43 pub(crate) fn bounded_integrity_bytes(row: &SqliteRow) -> Option<&[u8]> {
     44     let value = row.try_get::<&[u8], _>(0).ok()?;
     45     crate::all_constraints([!value.is_empty(), value.len() <= MAX_INTEGRITY_RESULT_BYTES])
     46         .then_some(value)
     47 }
     48 
     49 pub(crate) fn integrity_result_failed(row: &SqliteRow) -> Option<bool> {
     50     let value = row.try_get::<&[u8], _>(0).ok()?;
     51     (!value.is_empty()).then_some(value != b"ok")
     52 }
     53 
     54 fn bounded_projection(
     55     type_ok: bool,
     56     length: usize,
     57     prefix: &[u8],
     58     minimum: usize,
     59     maximum: usize,
     60 ) -> Option<&[u8]> {
     61     crate::all_constraints([
     62         type_ok,
     63         minimum <= maximum,
     64         length >= minimum,
     65         length <= maximum,
     66         prefix.len() == length,
     67     ])
     68     .then_some(prefix)
     69 }
     70 
     71 #[cfg(test)]
     72 mod tests {
     73     use super::*;
     74 
     75     #[test]
     76     fn bounded_projection_rejects_every_independent_drift() {
     77         assert_eq!(
     78             bounded_projection(true, 2, b"ok", 1, 64),
     79             Some(b"ok".as_slice())
     80         );
     81         assert!(bounded_projection(false, 2, b"ok", 1, 64).is_none());
     82         assert!(bounded_projection(true, 2, b"ok", 65, 64).is_none());
     83         assert!(bounded_projection(true, 0, b"", 1, 64).is_none());
     84         assert!(bounded_projection(true, 65, &[b'x'; 65], 1, 64).is_none());
     85         assert!(bounded_projection(true, 2, b"x", 1, 64).is_none());
     86     }
     87 
     88     #[test]
     89     fn integrity_query_and_borrowed_byte_cap_are_exact() {
     90         assert_eq!(INTEGRITY_CHECK_SQL, "PRAGMA integrity_check(1)");
     91         assert_eq!(MAX_INTEGRITY_RESULT_BYTES, 64);
     92     }
     93 }