Files
roowho2/src/proto/finger_protocol.rs
h7x4 6ca9e0ced1
Some checks failed
Build and test / check (push) Failing after 42s
Build and test / build (push) Failing after 1m38s
Build and test / test (push) Failing after 1m40s
Build and test / docs (push) Failing after 2m56s
A bunch of work on finger
2026-02-08 22:03:29 +09:00

159 lines
3.9 KiB
Rust

use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FingerRequest {
long: bool,
name: String,
}
impl FingerRequest {
pub fn new(long: bool, name: String) -> Self {
Self { long, name }
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut result = Vec::new();
if self.long {
result.extend(b"/W ");
}
result.extend(self.name.as_bytes());
result.extend(b"\r\n");
result
}
pub fn from_bytes(bytes: &[u8]) -> Self {
let (long, name) = if &bytes[..3] == b"/W " {
(true, &bytes[3..])
} else {
(false, bytes)
};
let name = match name.strip_suffix(b"\r\n") {
Some(new_name) => new_name,
None => name,
};
Self::new(long, String::from_utf8_lossy(name).to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FingerResponse(String);
impl FingerResponse {
pub fn new(content: String) -> Self {
Self(content)
}
pub fn get_inner(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn from_bytes(bytes: &[u8]) -> Self {
if bytes.is_empty() {
return Self(String::new());
}
fn normalize(c: u8) -> u8 {
if c == (b'\r' | 0x80) || c == (b'\n' | 0x80) {
c & 0x7f
} else {
c
}
}
let normalized: Vec<u8> = bytes
.iter()
.copied()
.map(normalize)
.chain(std::iter::once(normalize(*bytes.last().unwrap())))
.map_windows(|[a, b]| {
if *a == b'\r' && *b == b'\n' {
None
} else {
Some(*a)
}
})
.flatten()
.collect();
let result = String::from_utf8_lossy(&normalized).to_string();
Self(result)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.0.len() + 2);
for &b in self.0.as_bytes() {
if b == b'\n' {
out.extend_from_slice(b"\r\n");
} else {
out.push(b);
}
}
if !self.0.ends_with('\n') {
out.extend_from_slice(b"\r\n");
}
out
}
}
impl Default for FingerResponse {
fn default() -> Self {
Self(String::new())
}
}
impl From<String> for FingerResponse {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl From<&str> for FingerResponse {
fn from(s: &str) -> Self {
Self::new(s.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_finger_serialization_roundrip() {
let request = FingerRequest::new(true, "alice".to_string());
let bytes = request.to_bytes();
let deserialized = FingerRequest::from_bytes(&bytes);
assert_eq!(request, deserialized);
let request2 = FingerRequest::new(false, "bob".to_string());
let bytes2 = request2.to_bytes();
let deserialized2 = FingerRequest::from_bytes(&bytes2);
assert_eq!(request2, deserialized2);
let response = FingerResponse::new("Hello, World!\nThis is a test.\n".to_string());
let response_bytes = response.to_bytes();
let deserialized_response = FingerResponse::from_bytes(&response_bytes);
assert_eq!(response, deserialized_response);
let response2 = FingerResponse::new("Single line response\n".to_string());
let response_bytes2 = response2.to_bytes();
let deserialized_response2 = FingerResponse::from_bytes(&response_bytes2);
assert_eq!(response2, deserialized_response2);
}
}