use crate::error::{Result, SqliteError};
use crate::header::TextEncoding;
use crate::varint;
#[derive(Debug, Clone, PartialEq)]
pub enum SqliteValue {
Null,
Integer(i64),
Real(f64),
Text(String),
Blob(Vec<u8>),
}
impl SqliteValue {
pub fn type_name(&self) -> &'static str {
match self {
SqliteValue::Null => "null",
SqliteValue::Integer(_) => "integer",
SqliteValue::Real(_) => "real",
SqliteValue::Text(_) => "text",
SqliteValue::Blob(_) => "blob",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SerialType {
Null,
Int(u8),
Float,
Zero,
One,
Blob(usize),
Text(usize),
}
impl SerialType {
fn from_code(code: u64) -> Result<SerialType> {
Ok(match code {
0 => SerialType::Null,
1 => SerialType::Int(1),
2 => SerialType::Int(2),
3 => SerialType::Int(3),
4 => SerialType::Int(4),
5 => SerialType::Int(6),
6 => SerialType::Int(8),
7 => SerialType::Float,
8 => SerialType::Zero,
9 => SerialType::One,
10 | 11 => {
return Err(SqliteError::malformed(
None,
format!("serial type {code} is reserved for internal use"),
))
}
even if even % 2 == 0 => {
let len = (even - 12) / 2;
SerialType::Blob(usize::try_from(len).map_err(|_| {
SqliteError::malformed(None, format!("blob of {len} bytes is impossibly large"))
})?)
}
odd => {
let len = (odd - 13) / 2;
SerialType::Text(usize::try_from(len).map_err(|_| {
SqliteError::malformed(None, format!("text of {len} bytes is impossibly large"))
})?)
}
})
}
fn width(self) -> usize {
match self {
SerialType::Null | SerialType::Zero | SerialType::One => 0,
SerialType::Int(n) => n as usize,
SerialType::Float => 8,
SerialType::Blob(n) | SerialType::Text(n) => n,
}
}
}
pub fn decode(payload: &[u8], encoding: TextEncoding) -> Result<Vec<SqliteValue>> {
let (header_len, varint_len) = varint::read_unsigned(payload)?;
let header_len = usize::try_from(header_len)
.map_err(|_| SqliteError::malformed(None, "record header length does not fit in memory"))?;
if header_len < varint_len || header_len > payload.len() {
return Err(SqliteError::malformed(
None,
format!(
"record header claims {header_len} bytes of a {} byte payload",
payload.len()
),
));
}
let mut types = Vec::new();
let mut at = varint_len;
while at < header_len {
let (code, len) = varint::read_unsigned(&payload[at..header_len])?;
types.push(SerialType::from_code(code)?);
at += len;
}
let mut values = Vec::with_capacity(types.len());
let mut at = header_len;
for ty in types {
let width = ty.width();
let end = at.checked_add(width).ok_or_else(|| {
SqliteError::malformed(None, "a record value overflows the payload length")
})?;
if end > payload.len() {
return Err(SqliteError::malformed(
None,
format!(
"a {width} byte value at offset {at} runs past the {} byte payload",
payload.len()
),
));
}
let bytes = &payload[at..end];
values.push(match ty {
SerialType::Null => SqliteValue::Null,
SerialType::Zero => SqliteValue::Integer(0),
SerialType::One => SqliteValue::Integer(1),
SerialType::Int(_) => SqliteValue::Integer(signed_be(bytes)),
SerialType::Float => {
let mut eight = [0u8; 8];
eight.copy_from_slice(bytes);
SqliteValue::Real(f64::from_bits(u64::from_be_bytes(eight)))
}
SerialType::Blob(_) => SqliteValue::Blob(bytes.to_vec()),
SerialType::Text(_) => SqliteValue::Text(text_from(bytes, encoding)?),
});
at = end;
}
Ok(values)
}
fn text_from(bytes: &[u8], encoding: TextEncoding) -> Result<String> {
match encoding {
TextEncoding::Utf8 => match std::str::from_utf8(bytes) {
Ok(text) => Ok(text.to_string()),
Err(e) => Err(SqliteError::malformed(
None,
format!("a text value is not valid utf-8: {e}"),
)),
},
TextEncoding::Utf16Le | TextEncoding::Utf16Be => {
if !bytes.len().is_multiple_of(2) {
return Err(SqliteError::malformed(
None,
format!(
"a utf-16 text value is {} bytes long, which is not a whole number of \
code units",
bytes.len()
),
));
}
let big_endian = encoding == TextEncoding::Utf16Be;
let units: Vec<u16> = bytes
.as_chunks::<2>()
.0
.iter()
.map(|pair| {
if big_endian {
u16::from_be_bytes([pair[0], pair[1]])
} else {
u16::from_le_bytes([pair[0], pair[1]])
}
})
.collect();
String::from_utf16(&units).map_err(|_| {
SqliteError::malformed(
None,
"a utf-16 text value holds an unpaired surrogate, so it is not text",
)
})
}
}
}
fn signed_be(bytes: &[u8]) -> i64 {
let mut value: u64 = 0;
for byte in bytes {
value = (value << 8) | *byte as u64;
}
let bits = 8 * bytes.len() as u32;
if bits < 64 && (value >> (bits - 1)) & 1 == 1 {
(value | (!0u64 << bits)) as i64
} else {
value as i64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sign_extension_covers_every_width() {
assert_eq!(signed_be(&[0x7f]), 127);
assert_eq!(signed_be(&[0x80]), -128);
assert_eq!(signed_be(&[0xff]), -1);
assert_eq!(signed_be(&[0x80, 0x00]), -32768);
assert_eq!(signed_be(&[0x80, 0x00, 0x00]), -8388608);
assert_eq!(signed_be(&[0x7f, 0xff, 0xff]), 8388607);
assert_eq!(signed_be(&[0x80, 0, 0, 0]), -2147483648);
assert_eq!(signed_be(&[0x80, 0, 0, 0, 0, 0]), -140737488355328);
assert_eq!(
signed_be(&[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff]),
140737488355327
);
assert_eq!(signed_be(&[0x80, 0, 0, 0, 0, 0, 0, 0]), i64::MIN);
assert_eq!(signed_be(&[0xff; 8]), -1);
}
#[test]
fn a_record_of_every_cheap_type() {
let payload = [6u8, 0, 8, 9, 1, 17, 0x2a, b'h', b'i'];
let values = decode(&payload, TextEncoding::Utf8).unwrap();
assert_eq!(
values,
vec![
SqliteValue::Null,
SqliteValue::Integer(0),
SqliteValue::Integer(1),
SqliteValue::Integer(42),
SqliteValue::Text("hi".to_string()),
]
);
}
#[test]
fn an_empty_record_has_no_values() {
assert_eq!(decode(&[1u8], TextEncoding::Utf8).unwrap(), vec![]);
}
#[test]
fn reserved_serial_types_are_refused() {
assert!(decode(&[2u8, 10], TextEncoding::Utf8).is_err());
assert!(decode(&[2u8, 11], TextEncoding::Utf8).is_err());
}
#[test]
fn a_header_longer_than_the_payload_is_refused() {
assert!(decode(&[99u8, 0], TextEncoding::Utf8).is_err());
assert!(decode(&[], TextEncoding::Utf8).is_err());
}
#[test]
fn a_value_running_past_the_payload_is_refused() {
assert!(decode(&[2u8, 21, b'a'], TextEncoding::Utf8).is_err());
}
#[test]
fn invalid_utf8_in_a_text_value_is_refused() {
assert!(decode(&[2u8, 15, 0xff, 0xfe], TextEncoding::Utf8).is_err());
}
#[test]
fn floats_round_trip_bit_for_bit() {
let mut payload = vec![2u8, 7];
payload.extend_from_slice(&(-2.25f64).to_be_bytes());
assert_eq!(
decode(&payload, TextEncoding::Utf8).unwrap(),
vec![SqliteValue::Real(-2.25)]
);
}
}