//! Binary encoding for protocol leaves backed by external types. use lb_codec::{BinaryDecode, BinaryEncode, DecodeError}; use lb_utils::bounded::UpperBoundedVec; use rusqlite::types::Value; use super::{CapturedFunction, MAX_PAYLOAD_BYTES, SqlParameter, SqlText}; // Stable wire tags for SQLite value variants. Existing values must never be // renumbered because inscriptions remain part of the channel history. const NULL: u8 = 0; const INTEGER: u8 = 1; const REAL: u8 = 2; const TEXT: u8 = 3; const BLOB: u8 = 4; // Stable wire tags for captured SQLite functions. New functions may be // appended, but existing values must keep their encoding. const RANDOM: u8 = 0; const RANDOM_BLOB: u8 = 1; const DATE: u8 = 2; const TIME: u8 = 3; const DATE_TIME: u8 = 4; const JULIAN_DAY: u8 = 5; const UNIX_EPOCH: u8 = 6; const STRFTIME: u8 = 7; const TIME_DIFF: u8 = 8; const CURRENT_DATE: u8 = 9; const CURRENT_TIME: u8 = 10; const CURRENT_TIMESTAMP: u8 = 11; type BoundedBytes = UpperBoundedVec; // Every variable-length field uses the same fixed-width prefix. Keep the two // leaf encoders in lockstep with the bounded collection decoder. const _: () = assert!(MAX_PAYLOAD_BYTES > u16::MAX as usize); const _: () = assert!(MAX_PAYLOAD_BYTES <= u32::MAX as usize); impl BinaryEncode for CapturedFunction { fn encoded_length(&self) -> usize { size_of::() } fn encode_into(&self, out: &mut Vec) { let discriminant = match self { Self::Random => RANDOM, Self::RandomBlob => RANDOM_BLOB, Self::Date => DATE, Self::Time => TIME, Self::DateTime => DATE_TIME, Self::JulianDay => JULIAN_DAY, Self::UnixEpoch => UNIX_EPOCH, Self::Strftime => STRFTIME, Self::TimeDiff => TIME_DIFF, Self::CurrentDate => CURRENT_DATE, Self::CurrentTime => CURRENT_TIME, Self::CurrentTimestamp => CURRENT_TIMESTAMP, }; discriminant.encode_into(out); } } impl BinaryDecode for CapturedFunction { type Context = (); fn decode<'input>( input: &'input [u8], (): &Self::Context, ) -> Result<(&'input [u8], Self), DecodeError> { let (input, discriminant) = ::decode(input, &())?; let function = match discriminant { RANDOM => Self::Random, RANDOM_BLOB => Self::RandomBlob, DATE => Self::Date, TIME => Self::Time, DATE_TIME => Self::DateTime, JULIAN_DAY => Self::JulianDay, UNIX_EPOCH => Self::UnixEpoch, STRFTIME => Self::Strftime, TIME_DIFF => Self::TimeDiff, CURRENT_DATE => Self::CurrentDate, CURRENT_TIME => Self::CurrentTime, CURRENT_TIMESTAMP => Self::CurrentTimestamp, _ => { return Err(DecodeError::unknown_discriminant::(u64::from( discriminant, ))); } }; Ok((input, function)) } } impl BinaryEncode for SqlText { fn encoded_length(&self) -> usize { size_of::() + self.as_str().len() } fn encode_into(&self, out: &mut Vec) { u32::try_from(self.as_str().len()) .expect("validated SQL length fits in u32") .encode_into(out); out.extend_from_slice(self.as_str().as_bytes()); } } impl BinaryDecode for SqlText { type Context = (); fn decode<'input>( input: &'input [u8], (): &Self::Context, ) -> Result<(&'input [u8], Self), DecodeError> { let (input, sql) = ::decode(input, &())?; let sql = String::from_utf8(sql.into_inner()) .map_err(|_| DecodeError::invalid_value::("statement SQL is not UTF-8"))?; let sql = Self::new(sql) .map_err(|_| DecodeError::invalid_value::("statement SQL is invalid"))?; Ok((input, sql)) } } impl BinaryEncode for SqlParameter { fn encoded_length(&self) -> usize { 1 + match &self.0 { Value::Null => 0, Value::Integer(_) | Value::Real(_) => size_of::(), Value::Text(value) => size_of::() + value.len(), Value::Blob(value) => size_of::() + value.len(), } } fn encode_into(&self, out: &mut Vec) { match &self.0 { Value::Null => NULL.encode_into(out), Value::Integer(value) => { INTEGER.encode_into(out); u64::from_le_bytes(value.to_le_bytes()).encode_into(out); } Value::Real(value) => { REAL.encode_into(out); value.to_bits().encode_into(out); } Value::Text(value) => { TEXT.encode_into(out); u32::try_from(value.len()) .expect("validated text length fits in u32") .encode_into(out); out.extend_from_slice(value.as_bytes()); } Value::Blob(value) => { BLOB.encode_into(out); u32::try_from(value.len()) .expect("validated blob length fits in u32") .encode_into(out); out.extend_from_slice(value); } } } } impl BinaryDecode for SqlParameter { type Context = (); fn decode<'input>( input: &'input [u8], (): &Self::Context, ) -> Result<(&'input [u8], Self), DecodeError> { let (input, tag) = ::decode(input, &())?; let (input, value) = match tag { NULL => (input, Value::Null), INTEGER => { let (input, value) = ::decode(input, &())?; let value = i64::from_le_bytes(value.to_le_bytes()); (input, Value::Integer(value)) } REAL => { let (input, bits) = ::decode(input, &())?; (input, Value::Real(f64::from_bits(bits))) } TEXT => { let (input, value) = ::decode(input, &())?; let value = String::from_utf8(value.into_inner()).map_err(|_| { DecodeError::invalid_value::("text parameter is not UTF-8") })?; (input, Value::Text(value)) } BLOB => { let (input, value) = ::decode(input, &())?; (input, Value::Blob(value.into_inner())) } _ => return Err(DecodeError::unknown_discriminant::(u64::from(tag))), }; let value = Self::try_from(value) .map_err(|_| DecodeError::invalid_value::("SQL parameter is invalid"))?; Ok((input, value)) } }