irc_command_micro/
message.rs

1use std::fmt::{self};
2
3use crate::command::IRCCommand;
4use crate::error::{MessageError, MessageErrorDetails};
5
6#[derive(Debug)]
7pub struct Message {
8    pub source: Option<String>,
9    pub command: IRCCommand,
10    pub params: Vec<String>,
11}
12impl fmt::Display for Message {
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        write!(f, "{}", String::from_utf8_lossy(self.serialize().as_ref()))
15    }
16}
17
18static CR: u8 = 13;
19static LF: u8 = 10;
20
21impl Message {
22    /// Returns the serialize of this [`Message`].
23    ///
24    /// Will truncate output to 510 characters and then include CR (13) and LF (10) suffix as per RFC 2812
25    pub fn serialize(&self) -> Vec<u8> {
26        let mut ret_val: Vec<u8> = vec![];
27        if self.source.is_some() {
28            ret_val = [
29                ":".as_bytes().to_vec(),
30                self.source.as_ref().unwrap().as_bytes().to_vec(),
31                " ".as_bytes().to_vec(),
32            ]
33            .concat();
34        }
35
36        let mut params = self.params.join(" ");
37        if self.needs_colon() {
38            if self.params.len() > self.command.params_before_colon() {
39                let mut modified_params = self.params.clone();
40                modified_params[self.command.params_before_colon()] =
41                    format!(":{}", modified_params[self.command.params_before_colon()]);
42                params = modified_params.join(" ");
43            }
44        }
45
46        ret_val = [
47            ret_val,
48            self.command.command_text().as_bytes().to_vec(),
49            " ".as_bytes().to_vec(),
50            params.into(),
51        ]
52        .concat();
53
54        ret_val.truncate(510);
55
56        ret_val = [ret_val, vec![CR], vec![LF]].concat();
57
58        ret_val
59    }
60    /// From a u8 vector representing an IRC message will return a [`Message`], will not validate presence of CRLF but will strip them before processing. An optional client prefix can be provided to override or represent the source of the IRC message.
61    ///
62    /// # Errors
63    ///
64    /// This function will return an error if the provided message is not a valid IRC message as per RFC 2812, including presence of no data, invalid commands and missing mandatory parameters.
65    pub fn parse(
66        mut raw_input: Vec<u8>,
67        source_client: Option<String>,
68    ) -> Result<Self, MessageError> {
69        if raw_input.len() < 1 {
70            return Err(MessageError {
71                command: None,
72                detail: MessageErrorDetails::NoData,
73            });
74        }
75        if raw_input[raw_input.len() - 1] == LF {
76            raw_input.pop();
77        }
78        if raw_input[raw_input.len() - 1] == CR {
79            raw_input.pop();
80        }
81
82        raw_input = String::from_utf8_lossy(&raw_input)
83            .trim_end()
84            .trim_start()
85            .as_bytes()
86            .to_vec();
87
88        let split_message: Vec<Vec<u8>> = raw_input.into_iter().fold(Vec::new(), |mut acc, x| {
89            if x == 32 || acc.is_empty() {
90                acc.push(Vec::new());
91            }
92            if x != 32 {
93                acc.last_mut().unwrap().push(x);
94            }
95            acc
96        });
97
98        let mut source = source_client.clone();
99
100        let mut command_index = 0;
101        if split_message[0][0] == 58 {
102            command_index = 1;
103            let parsed_source = match Message::parse_prefix(split_message[0].clone()) {
104                Ok(x) => x,
105                Err(y) => return Err(y),
106            };
107            source = source_client.or_else(|| Some(parsed_source));
108        }
109
110        if split_message.len() < command_index + 1 {
111            return Err(MessageError {
112                command: None,
113                detail: MessageErrorDetails::NoCommand,
114            });
115        }
116
117        let irc_command = match Message::parse_command(split_message[command_index].clone()) {
118            Ok(x) => x,
119            Err(y) => return Err(y),
120        };
121
122        let params = match irc_command.parse_params(split_message[command_index + 1..].to_vec()) {
123            Ok(x) => x,
124            Err(y) => return Err(y),
125        };
126
127        Ok(Message {
128            source: source,
129            command: irc_command,
130            params: params
131                .iter()
132                .map(|p| String::from_utf8_lossy(p).into_owned())
133                .collect(),
134        })
135    }
136
137    fn parse_command(command_input: Vec<u8>) -> Result<IRCCommand, MessageError> {
138        let raw_command = match String::from_utf8(command_input) {
139            Ok(x) => x,
140            Err(_) => {
141                return Err(MessageError {
142                    command: None,
143                    detail: MessageErrorDetails::FailedCommandParse,
144                })
145            }
146        };
147
148        match IRCCommand::text_command(raw_command.as_str()) {
149            IRCCommand::NONE => {
150                return Err(MessageError {
151                    command: None,
152                    detail: MessageErrorDetails::InvalidCommand,
153                })
154            }
155            x => Ok(x),
156        }
157    }
158
159    fn parse_prefix(prefix_input: Vec<u8>) -> Result<String, MessageError> {
160        if prefix_input.len() < 2 {
161            return Err(MessageError {
162                command: None,
163                detail: MessageErrorDetails::NoClient,
164            });
165        }
166
167        if prefix_input[0] == 58 {
168            match String::from_utf8(prefix_input.clone().split_off(1)) {
169                Ok(x) => return Ok(x),
170                Err(_) => {
171                    return Err(MessageError {
172                        command: None,
173                        detail: MessageErrorDetails::FailedPrefixParse,
174                    });
175                }
176            }
177        } else {
178            return Err(MessageError {
179                command: None,
180                detail: MessageErrorDetails::NoPrefix,
181            });
182        }
183    }
184
185    fn needs_colon(&self) -> bool {
186        match self.command {
187            IRCCommand::USER => true,
188            IRCCommand::PRIVMSG => true,
189            IRCCommand::TOPIC => true,
190            _ => false,
191        }
192    }
193}
194
195#[test]
196fn test_valid_command() {
197    let message = "NAMES".as_bytes().to_vec();
198    let command = Message::parse_command(message);
199    assert_eq!(command, Ok(IRCCommand::NAMES));
200}
201
202#[test]
203fn test_invalid_command() {
204    let message = "NAMES2".as_bytes().to_vec();
205    let command = Message::parse_command(message);
206    assert!(command.is_err());
207}
208
209#[test]
210fn test_blank_command() {
211    let message = "".as_bytes().to_vec();
212    let command = Message::parse_command(message);
213    assert!(command.is_err());
214}
215
216#[test]
217fn test_valid_source() {
218    let message = ":TestClient".as_bytes().to_vec();
219    let source = Message::parse_prefix(message);
220    let source_string = match source {
221        Ok(x) => x,
222        Err(_) => "None".to_string(),
223    };
224    assert_eq!(source_string, "TestClient");
225}
226#[test]
227fn test_invalid_source() {
228    let message = ":".as_bytes().to_vec();
229    let source = Message::parse_prefix(message);
230    let source_string = match source {
231        Ok(x) => x,
232        Err(y) => y.detail.error_text().to_string(),
233    };
234    assert_eq!(source_string, "No client identifier");
235}
236#[test]
237fn test_empty_source() {
238    let message = Vec::new();
239    let source = Message::parse_prefix(message);
240    let source_string = match source {
241        Ok(x) => x,
242        Err(y) => y.detail.error_text().to_string(),
243    };
244    assert_eq!(source_string, "No client identifier");
245}
246
247#[test]
248fn test_format_source() {
249    let message = "TestClient".as_bytes().to_vec();
250    let source = Message::parse_prefix(message);
251    let source_string = match source {
252        Ok(x) => x,
253        Err(y) => y.detail.error_text().to_string(),
254    };
255    assert_eq!(source_string, "Missing : character");
256}
257
258#[test]
259fn test_serialize() {
260    let message = Message {
261        source: Some("Anon".to_string()),
262        command: IRCCommand::NICK,
263        params: vec!["nonA".to_string()],
264    };
265    let output = message.serialize();
266    println!("{:?}", output);
267    assert_eq!(output.len(), 17);
268    assert_eq!(output[output.len() - 3], 65);
269
270    let message = Message {
271        source: None,
272        command: IRCCommand::NICK,
273        params: vec!["nonA".to_string()],
274    };
275    let output = message.serialize();
276    println!("{:?}", output);
277    assert_eq!(output.len(), 11);
278    assert_eq!(output[output.len() - 3], 65);
279}
280
281#[test]
282fn test_user() {
283    let message = Message {
284        source: None,
285        command: IRCCommand::USER,
286        params: vec![
287            "Anon".to_string(),
288            "0".to_string(),
289            "*".to_string(),
290            "Anon".to_string(),
291        ],
292    };
293    println!("{:?}", message);
294    let output = message.serialize();
295    println!("{:?}", output);
296    assert_eq!(output.len(), 21);
297    assert_eq!(output[14], 58);
298}
299
300#[test]
301fn test_whitespace_suffix() {
302    let data = "      :Anon NICK Anon2       ".as_bytes().to_vec();
303    println!("{:?}", data);
304    let message = Message::parse(data, None).unwrap();
305    println!("{:?}", message);
306    assert_eq!(message.serialize().len(), 18)
307}
308
309#[test]
310fn test_privmsg() {
311    let message = Message {
312        source: None,
313        command: IRCCommand::PRIVMSG,
314        params: vec!["Dave".to_string(), "Hello Dave".to_string()],
315    };
316    println!("{:?}", message.serialize());
317    println!("{}", String::from_utf8_lossy(&message.serialize()));
318    assert_eq!(message.serialize().len(), 26);
319}
320
321#[test]
322fn test_topic() {
323    let message = Message {
324        source: None,
325        command: IRCCommand::TOPIC,
326        params: vec!["#Chat".to_string(), "Hello Dave".to_string()],
327    };
328    println!("{:?}", message.serialize());
329    println!("{}", String::from_utf8_lossy(&message.serialize()));
330    assert_eq!(message.serialize().len(), 25);
331
332    let message = Message {
333        source: None,
334        command: IRCCommand::TOPIC,
335        params: vec!["#Chat".to_string(), "".to_string()],
336    };
337    println!("{:?}", message.serialize());
338    println!("{}", String::from_utf8_lossy(&message.serialize()));
339    assert_eq!(message.serialize().len(), 15);
340
341    let message = Message {
342        source: None,
343        command: IRCCommand::TOPIC,
344        params: vec!["#Chat".to_string()],
345    };
346    println!("{:?}", message.serialize());
347    println!("{}", String::from_utf8_lossy(&message.serialize()));
348    assert_eq!(message.serialize().len(), 13);
349}