rust 反序列化二进制编码枚举

s4chpxco  于 2023-03-30  发布在  其他
关注(0)|答案(1)|浏览(159)

我接收校验和作为二进制协议的一部分,在Rust中以以下形式表示:

enum Crc {
   Crc16([u8; 16])
   Crc32([u8; 32])
   Crc64([u8; 64])
}

我接收到编码为字节数组的枚举变量,其前导判别式为u8,后跟校验和字节数组。(参见内部讨论)结合其它固定大小类型并且serde具有大小为32的limit for arrays,我想自己实现一个Deserializer。我知道我必须使用Deserializer::deserialize_tuple调用的序列访问器手动反序列化数组,但我如何处理不同的变体呢?

2w2cym1i

2w2cym1i1#

无需为此实现自定义Deserializer。只要提供自定义Deserialize实现来指示期望接收的数据,您仍然可以使用bincode' s Deserializer
从本质上讲,有两个独立的数据片段,你期望:变量判别式(作为字节)和校验和字节(大小不同,取决于变量)。您可以将其建模为2元组数据:(discriminant: u8, checksum_bytes: [u8; LEN])对于某个LEN,它是16、32或64。
我们可以分别对待每一部分:

变体

你是对的,默认情况下,bincode不能将变体判别式视为u8 s。但这并不意味着我们不能定义Deserialize实现来将它们视为u8 s。

enum Variant {
    Crc16,
    Crc32,
    Crc64,
}

impl<'de> Deserialize<'de> for Variant {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct VariantVisitor;

        impl<'de> Visitor<'de> for VariantVisitor {
            type Value = Variant;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a single descriminant byte, either a 0, 1, or 2")
            }

            fn visit_u8<E>(self, byte: u8) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                match byte {
                    0 => Ok(Variant::Crc16),
                    1 => Ok(Variant::Crc32),
                    2 => Ok(Variant::Crc64),
                    _ => Err(E::invalid_value(Unexpected::Unsigned(byte.into()), &self)),
                }
            }
        }

        deserializer.deserialize_u8(VariantVisitor)
    }
}

现在我们已经定义了自己的variant类型(与serde_derive派生的类型相反),它将一个字节反序列化为variant,我们可以将这个Variant类型视为2元组中的第一个类型。

校验和字节

正如你提到的,serde只提供长度为32的数组的实现。我们可以将这些实现用于16和32字节的数组,但我们需要定义自己的类型来反序列化64字节的数组:

struct Crc64([u8; 64]);

impl<'de> Deserialize<'de> for Crc64 {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct Crc64Visitor;

        impl<'de> Visitor<'de> for Crc64Visitor {
            type Value = Crc64;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("an array of length 64")
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: SeqAccess<'de>,
            {
                let mut value = [0; 64];
                for i in 0..64 {
                    value[i] = seq
                        .next_element()?
                        .ok_or(de::Error::invalid_length(i, &self))?;
                }
                Ok(Crc64(value))
            }
        }

        deserializer.deserialize_tuple(64, Crc64Visitor)
    }
}

这会将一个64字节的数组反序列化为一个Crc64struct。使用的方法与serde为长度为1-32的数组提供的方法非常相似。

把它放在一起

现在我们拥有所需的所有部件:我们可以反序列化变量discriminate,我们可以反序列化任何所需大小的字节数组。最后一步是告诉serde我们期望一个2元组,然后在访问者中反序列化该数据。

impl<'de> Deserialize<'de> for Crc {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct CrcVisitor;

        impl<'de> Visitor<'de> for CrcVisitor {
            type Value = Crc;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a single descriminant byte followed by a checksum byte array")
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: SeqAccess<'de>,
            {
                match seq
                    .next_element()?
                    .ok_or(de::Error::invalid_length(0, &self))?
                {
                    Variant::Crc16 => Ok(Crc::Crc16(
                        seq.next_element()?
                            .ok_or(de::Error::invalid_length(1, &self))?,
                    )),
                    Variant::Crc32 => Ok(Crc::Crc32(
                        seq.next_element()?
                            .ok_or(de::Error::invalid_length(1, &self))?,
                    )),
                    Variant::Crc64 => Ok(Crc::Crc64(
                        seq.next_element::<Crc64>()?
                            .ok_or(de::Error::invalid_length(1, &self))?
                            .0,
                    )),
                }
            }
        }

        deserializer.deserialize_tuple(2, CrcVisitor)
    }
}

这首先将变量反序列化为我们的Variant类型,然后,根据找到的判别式,将剩余的字节反序列化为正确的长度。请注意,我们必须使用Crc64类型来反序列化64字节数组。
完整的代码可以在playground上找到。请注意,您实际上无法在playground上使用bincode进行测试,因为bincode在那里不作为依赖项提供,但它应该可以在bincode版本1.3.3上正常工作,这是我测试的版本。

相关问题