Exercício: Análise de Protobuf

Neste exercício, você construirá um analisador (parser) para a codificação binária de protobuf. Não se preocupe, é mais simples do que parece! Isso ilustra um padrão de análise comum, passando slices de dados. Os próprios dados subjacentes nunca são copiados.

Analisar (parse) completamente uma mensagem protobuf requer conhecer os tipos dos campos, indexados por seus números de campo. Isso é normalmente fornecido em um arquivo proto. Neste exercício, codificaremos essas informações em declarações match em funções que são chamadas para cada campo.

Usaremos o seguinte proto:

message PhoneNumber {
  optional string number = 1;
  optional string type = 2;
}

message Person {
  optional string name = 1;
  optional int32 id = 2;
  repeated PhoneNumber phones = 3;
}

Uma mensagem proto é codificada como uma série de campos, um após o outro. Cada um é implementado como uma “tag” seguida pelo valor. A tag contém um número de campo (por exemplo, 2 para o campo id de uma mensagem Person) e um tipo de fio (wire type) definindo como a carga útil deve ser determinada a partir do fluxo (stream) de bytes.

Números inteiros, incluindo a tag, são representados com uma codificação de comprimento variável chamada VARINT. Felizmente, parse_varint é definido para você abaixo. O código fornecido também define callbacks para lidar com campos Person e PhoneNumber, e para analisar uma mensagem em uma série de chamadas para esses callbacks.

O que resta para você é implementar a função parse_field e o trait ProtoMessage para Person e PhoneNumber.

use std::convert::TryFrom;
use thiserror::Error;

#[derive(Debug, Error)]
enum Error {
    #[error("Varint inválido")]
    InvalidVarint,
    #[error("Wire-type inválido")]
    InvalidWireType,
    #[error("EOF inesperado")]
    UnexpectedEOF,
    #[error("Comprimento inválido")]
    InvalidSize(#[from] std::num::TryFromIntError),
    #[error("Wire-type inesperado")]
    UnexpectedWireType,
    #[error("String inválida (não UTF-8)")]
    InvalidString,
}

/// Um wire type como visto no wire.
enum WireType {
    /// O Varint WireType indica que o valor é um único VARINT.
    Varint,
    //I64,  -- não é necessário para este exercício
    /// O Len WireType indica que o valor é um comprimento representado como um
    /// VARINT seguido exatamente por esse número de bytes.
    Len,
    /// O I32 WireType indica que o valor é precisamente 4 bytes em
    /// ordem little-endian contendo um inteiro de 32 bits com sinal.
    I32,
}

#[derive(Debug)]
/// O valor de um campo, digitado com base no wire type.
enum FieldValue<'a> {
    Varint(u64),
    //I64(i64),  -- não é necessário para este exercício
    Len(&'a [u8]),
    I32(i32),
}

#[derive(Debug)]
/// Um campo, contendo o número do campo e seu valor.
struct Field<'a> {
    field_num: u64,
    value: FieldValue<'a>,
}

trait ProtoMessage<'a>: Default + 'a {
    fn add_field(&mut self, field: Field<'a>) -> Result<(), Error>;
}

impl TryFrom<u64> for WireType {
    type Error = Error;

    fn try_from(value: u64) -> Result<WireType, Error> {
        Ok(match value {
            0 => WireType::Varint,
            //1 => WireType::I64,  -- não é necessário para este exercício
            2 => WireType::Len,
            5 => WireType::I32,
            _ => return Err(Error::InvalidWireType),
        })
    }
}

impl<'a> FieldValue<'a> {
    fn as_string(&self) -> Result<&'a str, Error> {
        let FieldValue::Len(data) = self else {
            return Err(Error::UnexpectedWireType);
        };
        std::str::from_utf8(data).map_err(|_| Error::InvalidString)
    }

    fn as_bytes(&self) -> Result<&'a [u8], Error> {
        let FieldValue::Len(data) = self else {
            return Err(Error::UnexpectedWireType);
        };
        Ok(data)
    }

    fn as_u64(&self) -> Result<u64, Error> {
        let FieldValue::Varint(value) = self else {
            return Err(Error::UnexpectedWireType);
        };
        Ok(*value)
    }
}

/// Analise (_parse_) um VARINT, retornando o valor analisado e os bytes restantes.
fn parse_varint(data: &[u8]) -> Result<(u64, &[u8]), Error> {
    for i in 0..7 {
        let Some(b) = data.get(i) else {
            return Err(Error::InvalidVarint);
        };
        if b & 0x80 == 0 {
            // Este é o último byte do VARINT, então converta-o para
            // um u64 e retorne-o.
            let mut value = 0u64;
            for b in data[..=i].iter().rev() {
                value = (value << 7) | (b & 0x7f) as u64;
            }
            return Ok((value, &data[i + 1..]));
        }
    }

    // Mais de 7 bytes é inválido.
    Err(Error::InvalidVarint)
}

/// Converta uma tag em um número de campo e um WireType.
fn unpack_tag(tag: u64) -> Result<(u64, WireType), Error> {
    let field_num = tag >> 3;
    let wire_type = WireType::try_from(tag & 0x7)?;
    Ok((field_num, wire_type))
}


/// Analise (_parse_) um campo, retornando os bytes restantes
fn parse_field(data: &[u8]) -> Result<(Field, &[u8]), Error> {
    let (tag, remainder) = parse_varint(data)?;
    let (field_num, wire_type) = unpack_tag(tag)?;
    let (fieldvalue, remainder) = match wire_type {
        _ => todo!("Com base no wire type, construa um Field, consumindo quantos bytes forem necessários.")
    };
    todo!("Retorne o campo e quaisquer bytes não consumidos.")
}

/// Analise (_parse_) uma mensagem nos dados fornecidos, chamando `T::add_field` para cada campo na
/// mensagem.
///
/// Todo o input é consumido.
fn parse_message<'a, T: ProtoMessage<'a>>(mut data: &'a [u8]) -> Result<T, Error> {
    let mut result = T::default();
    while !data.is_empty() {
        let parsed = parse_field(data)?;
        result.add_field(parsed.0)?;
        data = parsed.1;
    }
    Ok(result)
}

#[derive(Debug, Default)]
struct PhoneNumber<'a> {
    number: &'a str,
    type_: &'a str,
}

#[derive(Debug, Default)]
struct Person<'a> {
    name: &'a str,
    id: u64,
    phone: Vec<PhoneNumber<'a>>,
}

// TODO: Implemente ProtoMessage para Person e PhoneNumber.

fn main() {
    let person: Person = parse_message(&[
        0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a,
        0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35,
        0x2d, 0x31, 0x32, 0x31, 0x32, 0x12, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x1a,
        0x18, 0x0a, 0x0e, 0x2b, 0x31, 0x38, 0x30, 0x30, 0x2d, 0x38, 0x36, 0x37,
        0x2d, 0x35, 0x33, 0x30, 0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c,
        0x65,
    ])
    .unwrap();
    println!("{:#?}", person);
}