use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct PeerId(
#[serde(
deserialize_with = "deserialize_20_bytes",
serialize_with = "serialize_20_bytes"
)]
pub [u8; 20],
);
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct InfoHash(
#[serde(
deserialize_with = "deserialize_20_bytes",
serialize_with = "serialize_20_bytes"
)]
pub [u8; 20],
);
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct OfferId(
#[serde(
deserialize_with = "deserialize_20_bytes",
serialize_with = "serialize_20_bytes"
)]
pub [u8; 20],
);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AnnounceAction {
Announce,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ScrapeAction {
Scrape,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RtcOfferType {
Offer,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RtcAnswerType {
Answer,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RtcOffer {
#[serde(rename = "type")]
pub t: RtcOfferType,
pub sdp: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RtcAnswer {
#[serde(rename = "type")]
pub t: RtcAnswerType,
pub sdp: String,
}
fn serialize_20_bytes<S>(data: &[u8; 20], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut str_buffer = [0u8; 40];
let mut offset = 0;
for byte in data {
offset += char::from(*byte)
.encode_utf8(&mut str_buffer[offset..])
.len();
}
let text = ::std::str::from_utf8(&str_buffer[..offset]).unwrap();
serializer.serialize_str(text)
}
struct TwentyByteVisitor;
impl<'de> Visitor<'de> for TwentyByteVisitor {
type Value = [u8; 20];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("string consisting of 20 bytes")
}
#[inline]
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
let mut arr = [0u8; 20];
let mut char_iter = value.chars();
for a in arr.iter_mut() {
if let Some(c) = char_iter.next() {
if c as u32 > 255 {
return Err(E::custom(format!(
"character not in single byte range: {:#?}",
c
)));
}
*a = c as u8;
} else {
return Err(E::custom(format!("not 20 bytes: {:#?}", value)));
}
}
Ok(arr)
}
}
#[inline]
fn deserialize_20_bytes<'de, D>(deserializer: D) -> Result<[u8; 20], D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(TwentyByteVisitor)
}
#[cfg(test)]
mod tests {
use quickcheck_macros::quickcheck;
use crate::common::InfoHash;
fn info_hash_from_bytes(bytes: &[u8]) -> InfoHash {
let mut arr = [0u8; 20];
assert!(bytes.len() == 20);
arr.copy_from_slice(bytes);
InfoHash(arr)
}
#[test]
fn test_deserialize_20_bytes() {
unsafe {
let mut input = r#""aaaabbbbccccddddeeee""#.to_string();
let expected = info_hash_from_bytes(b"aaaabbbbccccddddeeee");
let observed: InfoHash = ::simd_json::serde::from_str(&mut input).unwrap();
assert_eq!(observed, expected);
}
unsafe {
let mut input = r#""aaaabbbbccccddddeee""#.to_string();
let res_info_hash: Result<InfoHash, _> = ::simd_json::serde::from_str(&mut input);
assert!(res_info_hash.is_err());
}
unsafe {
let mut input = r#""aaaabbbbccccddddeeeš""#.to_string();
let res_info_hash: Result<InfoHash, _> = ::simd_json::serde::from_str(&mut input);
assert!(res_info_hash.is_err());
}
}
#[test]
fn test_serde_20_bytes() {
let info_hash = info_hash_from_bytes(b"aaaabbbbccccddddeeee");
let info_hash_2 = unsafe {
let mut out = ::simd_json::serde::to_string(&info_hash).unwrap();
::simd_json::serde::from_str(&mut out).unwrap()
};
assert_eq!(info_hash, info_hash_2);
}
#[quickcheck]
fn quickcheck_serde_20_bytes(info_hash: InfoHash) -> bool {
unsafe {
let mut out = ::simd_json::serde::to_string(&info_hash).unwrap();
let info_hash_2 = ::simd_json::serde::from_str(&mut out).unwrap();
info_hash == info_hash_2
}
}
}