weechat_relay_rs/basic_types.rs
1use std::fmt::{Display, Formatter};
2
3/// A
4/// [Pointer](https://weechat.org/files/doc/devel/weechat_relay_protocol.en.html#object_pointer)
5/// as provided by the WeeChat relay.
6///
7/// Note that while these pointers are probably parsed into an actual machine pointer type on the
8/// relay side, the protocol treats them as strings with particular constraints. This means that
9/// there are possible cases where a relay and a client may have a different interpretation of
10/// things like whether two pointers are equal (e.g., leading zeros), or a pointer is valid at all
11/// (e.g., Pointers longer than the relay machine's pointer width). Because the representation is
12/// only specified at the protocol level, we strongly advise to only use pointers as opaque handles
13/// provided by the relay.
14// pointers are always represented as ASCII hex strings in the protocol
15// e.g., command string "0xf00" == 3"f00" binary message
16#[derive(Debug, PartialEq, Eq, Hash, Clone)]
17pub struct Pointer(Vec<u8>);
18
19/// Errors when attempting to construct a pointer.
20#[derive(Debug)]
21pub enum PointerError {
22 /// The pointer string contains non-ASCII characters.
23 NonAscii,
24 /// The pointer string is valid UTF-8, but contains non-hexadecimal characters.
25 NonHex,
26 /// The pointer string is too long to fit in a pointer (as of version 3.7 of the WeeChat Relay
27 /// protocol, 255 bytes).
28 TooLong,
29}
30
31impl Pointer {
32 pub fn new(ascii_bytes: Vec<u8>) -> Result<Pointer, PointerError> {
33 let ascii_bytes = if ascii_bytes.starts_with(b"0x") {
34 ascii_bytes[2..].to_vec()
35 } else {
36 ascii_bytes
37 };
38 if ascii_bytes.len() > 255 {
39 return Err(PointerError::TooLong);
40 }
41 let pointer_str = std::str::from_utf8(&ascii_bytes).or(Err(PointerError::NonAscii))?;
42 if pointer_str.chars().all(|c| char::is_ascii_hexdigit(&c)) {
43 Ok(Pointer(ascii_bytes))
44 } else {
45 Err(PointerError::NonHex)
46 }
47 }
48
49 // NULL is explicitly defined as having "a length of 1 with value 0",
50 // with the demonstration showing that 0 to be ASCII 0, i.e. 0x30
51 /// Construct a `NULL` pointer.
52 ///
53 /// The `NULL` pointer is always an invalid identifier, using it in commands will yield empty
54 /// responses.
55 pub fn null() -> Pointer {
56 Pointer(vec![b'0'])
57 }
58
59 /// Whether this pointer is a `NULL` pointer.
60 ///
61 /// The `NULL` pointer is always an invalid identifier, using it in commands will yield empty
62 /// responses.
63 pub fn is_null(&self) -> bool {
64 self.0.len() == 1 && self.0[0] == b'0'
65 }
66}
67
68impl Display for Pointer {
69 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
70 let pointer_str = std::str::from_utf8(&self.0).expect("Invalid pointer: non-UTF-8");
71 write!(f, "0x{}", pointer_str)
72 }
73}
74
75/// A compression algorithm, or lack thereof.
76#[derive(Debug, PartialEq, Eq)]
77pub enum Compression {
78 Off,
79 Zlib,
80 Zstd,
81}
82
83impl Compression {
84 pub fn to_str(&self) -> &'static str {
85 match self {
86 Compression::Off => "off",
87 Compression::Zlib => "zlib",
88 Compression::Zstd => "zstd",
89 }
90 }
91
92 /// Parse the given `&str` into a Compression setting.
93 ///
94 /// The given string must be one of the strings listed in the Weechat Relay
95 /// [handshake docs](https://weechat.org/files/doc/devel/weechat_relay_protocol.en.html#command_handshake),
96 /// else this will return `None`.
97 pub fn parse(compression: &str) -> Option<Self> {
98 match compression {
99 "off" => Some(Compression::Off),
100 "zlib" => Some(Compression::Zlib),
101 "zstd" => Some(Compression::Zstd),
102 _ => None,
103 }
104 }
105}
106
107/// A password hash algorithm.
108#[derive(Debug, PartialEq, Eq)]
109pub enum PasswordHashAlgo {
110 Plain,
111 Sha256,
112 Sha512,
113 Pbkdf2Sha256,
114 Pbkdf2Sha512,
115}
116
117impl PasswordHashAlgo {
118 pub fn to_str(&self) -> &'static str {
119 match self {
120 PasswordHashAlgo::Plain => "plain",
121 PasswordHashAlgo::Sha256 => "sha256",
122 PasswordHashAlgo::Sha512 => "sha512",
123 PasswordHashAlgo::Pbkdf2Sha256 => "pbkdf2+sha256",
124 PasswordHashAlgo::Pbkdf2Sha512 => "pbkdf2+sha512",
125 }
126 }
127
128 /// Parse the given `&str` into a PasswordHashAlgo.
129 ///
130 /// The given string must be one of the strings listed in the Weechat Relay
131 /// [handshake docs](https://weechat.org/files/doc/devel/weechat_relay_protocol.en.html#command_handshake),
132 /// else this will return `None`.
133 pub fn parse(algo: &str) -> Option<PasswordHashAlgo> {
134 match algo {
135 "plain" => Some(PasswordHashAlgo::Plain),
136 "sha256" => Some(PasswordHashAlgo::Sha256),
137 "sha512" => Some(PasswordHashAlgo::Sha512),
138 "pbkdf2+sha256" => Some(PasswordHashAlgo::Pbkdf2Sha256),
139 "pbkdf2+sha512" => Some(PasswordHashAlgo::Pbkdf2Sha512),
140 _ => None,
141 }
142 }
143}