CJ_Clippy 694dc89f03 git subrepo clone https://github.com/greatest-ape/aquatic ./apps/aquatic
subrepo:
  subdir:   "apps/aquatic"
  merged:   "34b45e92"
upstream:
  origin:   "https://github.com/greatest-ape/aquatic"
  branch:   "master"
  commit:   "34b45e92"
git-subrepo:
  version:  "0.4.9"
  origin:   "???"
  commit:   "???"
2025-02-21 19:47:41 -08:00

224 lines
6.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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],
);
/// Serializes to and deserializes from "announce"
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AnnounceAction {
Announce,
}
/// Serializes to and deserializes from "scrape"
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ScrapeAction {
Scrape,
}
/// Serializes to and deserializes from "offer"
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RtcOfferType {
Offer,
}
/// Serializes to and deserializes from "answer"
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RtcAnswerType {
Answer,
}
/// Nested structure with SDP offer from https://www.npmjs.com/package/simple-peer
///
/// Created using https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RtcOffer {
/// Always "offer"
#[serde(rename = "type")]
pub t: RtcOfferType,
pub sdp: String,
}
/// Nested structure with SDP answer from https://www.npmjs.com/package/simple-peer
///
/// Created using https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createAnswer
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RtcAnswer {
/// Always "answer"
#[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,
{
// Length of 40 is enough since each char created from a byte will
// utf-8-encode to max 2 bytes
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,
{
// Value is encoded in nodejs reference client something as follows:
// ```
// var infoHash = 'abcd..'; // 40 hexadecimals
// Buffer.from(infoHash, 'hex').toString('binary');
// ```
// As I understand it:
// - the code above produces a UTF16 string of 20 chars, each having
// only the "low byte" set (e.g., numeric value ranges from 0-255)
// - serde_json decodes this to string of 20 chars (tested), each in
// the aforementioned range (tested), so the bytes can be extracted
// by casting each char to u8.
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
}
}
}