pbrs 0.2.0

Pure-Rust Protocol Buffers kernel with the Google protobuf v4 application API
Documentation
#![allow(
    clippy::unimplemented,
    clippy::unwrap_used,
    clippy::expect_used,
    reason = "null MiniTable/MessagePtr is a rust_out kernel invariant, not a user Result"
)]

use std::fmt::Debug;

pub type MiniTableEnumPtr = *const ();

#[derive(Clone, Copy, Debug)]
pub struct MiniTableEnumInitPtr(pub MiniTableEnumPtr);
unsafe impl Send for MiniTableEnumInitPtr {}
unsafe impl Sync for MiniTableEnumInitPtr {}

impl MiniTableEnumInitPtr {
    pub const fn dangling() -> Self {
        Self(std::ptr::null())
    }
}

#[derive(Clone, Copy, Debug)]
pub struct MiniTableInitPtr(pub MiniTablePtr);
unsafe impl Send for MiniTableInitPtr {}
unsafe impl Sync for MiniTableInitPtr {}

#[derive(Clone, Copy, Debug)]
pub struct MiniTablePtr(pub *const MiniTable);
unsafe impl Send for MiniTablePtr {}
unsafe impl Sync for MiniTablePtr {}

impl MiniTablePtr {
    pub const fn dangling() -> Self {
        Self(std::ptr::null())
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FieldType {
    Double,
    Float,
    Int32,
    Int64,
    UInt32,
    UInt64,
    SInt32,
    SInt64,
    Fixed32,
    Fixed64,
    SFixed32,
    SFixed64,
    Bool,
    String,
    Bytes,
    Message,
    Group,
    Enum,
}

#[derive(Clone, Copy, Debug)]
pub struct MiniField {
    pub number: u32,
    pub ty: FieldType,
    pub repeated: bool,
    pub packed: bool,
    pub proto3_singular: bool,
    pub required: bool,
    pub is_map: bool,
    pub sub: MiniTablePtr,
    pub oneof_group: u32,
}

#[derive(Debug)]
pub struct MiniTable {
    pub fields: Vec<MiniField>,
    pub is_map: bool,
    pub enforce_utf8: bool,
}

pub mod __unstable {
    pub struct DescriptorInfo {
        pub descriptor: &'static [u8],
        pub deps: &'static [&'static DescriptorInfo],
    }
}

const FROM92: [i8; 95] = [
    0, 1, -1, 2, 3, 4, 5, -1, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
    24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
    48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
    71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
];

fn from92(ch: u8) -> i8 {
    if !(b' '..=b'~').contains(&ch) {
        return -1;
    }
    FROM92[(ch - b' ') as usize]
}

fn encoded_type(ty: i8) -> FieldType {
    match ty {
        0 => FieldType::Double,
        1 => FieldType::Float,
        2 => FieldType::Fixed32,
        3 => FieldType::Fixed64,
        4 => FieldType::SFixed32,
        5 => FieldType::SFixed64,
        6 => FieldType::Int32,
        7 => FieldType::UInt32,
        8 => FieldType::SInt32,
        9 => FieldType::Int64,
        10 => FieldType::UInt64,
        11 => FieldType::SInt64,
        12 => FieldType::Enum,
        13 => FieldType::Bool,
        14 => FieldType::Bytes,
        15 => FieldType::String,
        16 => FieldType::Group,
        17 => FieldType::Message,
        18 => FieldType::Enum,
        _ => FieldType::Int32,
    }
}

fn decode_base92_varint(s: &[u8], i: &mut usize, first: u8, min: u8, max: u8) -> u32 {
    let mut val = 0u32;
    let mut shift = 0u32;
    let bits = {
        let span = (from92(max) - from92(min)) as u32;
        32 - span.leading_zeros()
    };
    let mut ch = first;
    loop {
        let bits_val = (from92(ch) - from92(min)) as u32;
        val |= bits_val << shift;
        if *i >= s.len() || s[*i] < min || s[*i] > max {
            return val;
        }
        ch = s[*i];
        *i += 1;
        shift += bits;
        if shift >= 32 {
            return val;
        }
    }
}

pub unsafe fn build_mini_table(mini_descriptor: &'static str) -> MiniTablePtr {
    let mt = decode_mini_table(mini_descriptor.as_bytes());
    MiniTablePtr(Box::into_raw(Box::new(mt)))
}

pub unsafe fn build_enum_mini_table(_mini_descriptor: &'static str) -> MiniTableEnumPtr {
    std::ptr::null()
}

pub unsafe fn link_mini_table(
    mini_table: MiniTablePtr,
    submessages: &[MiniTablePtr],
    _subenums: &[MiniTableEnumPtr],
) {
    if mini_table.0.is_null() {
        return;
    }
    let mt = unsafe { &mut *mini_table.0.cast_mut() };
    let mut si = 0usize;
    for f in &mut mt.fields {
        if (f.ty == FieldType::Message || f.ty == FieldType::Group || f.is_map)
            && si < submessages.len()
        {
            f.sub = submessages[si];
            si += 1;
        }
    }
    for f in &mut mt.fields {
        if !f.sub.0.is_null() {
            if let Some(sub) = unsafe { f.sub.0.as_ref() } {
                if sub.is_map {
                    f.is_map = true;
                }
            }
        }
    }
}

fn decode_mini_table(bytes: &[u8]) -> MiniTable {
    if bytes.is_empty() {
        return MiniTable {
            fields: Vec::new(),
            is_map: false,
            enforce_utf8: false,
        };
    }
    let mut i = 0usize;
    let ver = bytes[i];
    i += 1;
    let is_map = ver == b'%';
    let mut fields = Vec::new();
    let mut last_num = 0u32;
    let mut enforce_utf8 = false;
    while i < bytes.len() {
        let ch = bytes[i];
        i += 1;
        if ch <= b'I' {
            last_num += 1;
            let mut tyv = from92(ch);
            let mut repeated = false;
            if tyv >= 20 {
                tyv -= 20;
                repeated = true;
            }
            let ty = encoded_type(tyv);
            let packable = !matches!(
                ty,
                FieldType::String | FieldType::Bytes | FieldType::Message | FieldType::Group
            );
            fields.push(MiniField {
                number: last_num,
                ty,
                repeated,
                packed: repeated && packable,
                proto3_singular: false,
                required: false,
                is_map: false,
                sub: MiniTablePtr::dangling(),
                oneof_group: 0,
            });
        } else if (b'L'..=b'[').contains(&ch) {
            let modv = decode_base92_varint(bytes, &mut i, ch, b'L', b'[');
            if let Some(f) = fields.last_mut() {
                if modv & 1 != 0 {
                    f.packed = !f.packed;
                }
                if modv & 2 != 0 {
                    f.required = true;
                }
                if modv & 4 != 0 {
                    f.proto3_singular = true;
                }
                if modv & 8 != 0 {
                    f.proto3_singular = !f.proto3_singular;
                }
            } else if modv & 1 != 0 {
                enforce_utf8 = true;
            }
        } else if (b'_'..=b'~').contains(&ch) && ch != b'~' {
            let skip = decode_base92_varint(bytes, &mut i, ch, b'_', b'~');
            last_num = last_num.saturating_add(skip).saturating_sub(1);
        } else if ch == b'^' {
            let mut group = 1u32;
            while i < bytes.len() {
                let och = bytes[i];
                i += 1;
                if och == b'~' {
                    group += 1;
                    continue;
                }
                if och == b'|' {
                    continue;
                }
                let num = decode_base92_varint(bytes, &mut i, och, b' ', b'b');
                if let Some(f) = fields.iter_mut().find(|f| f.number == num) {
                    f.oneof_group = group;
                }
            }
            break;
        }
    }
    if is_map {
        for f in &mut fields {
            f.is_map = false;
        }
    }
    MiniTable {
        fields,
        is_map,
        enforce_utf8,
    }
}

impl MiniTable {
    pub(crate) fn field_by_number(&self, n: u32) -> Option<(usize, MiniField)> {
        self.fields
            .iter()
            .copied()
            .enumerate()
            .find(|(_, f)| f.number == n)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn address_mini_table_one_string() {
        let mt = decode_mini_table(b"$M1P");
        assert_eq!(mt.fields.len(), 1);
        assert_eq!(mt.fields[0].number, 1);
        assert_eq!(mt.fields[0].ty, FieldType::String);
    }
}