icb/
lib.rs

1use crossbeam_channel::{unbounded, Receiver, Sender};
2use crossbeam_utils::thread;
3use std::collections::HashMap;
4use std::io::prelude::*;
5use std::io::ErrorKind;
6use std::net::{Shutdown, TcpStream};
7use std::time::Duration;
8
9#[macro_use]
10extern crate maplit;
11
12pub mod packets;
13mod util;
14use util::q;
15
16/// Messages the client needs to format/display to the user.
17/// First field is always the packet type (`T_*`), followed
18/// by a type-specific order.
19pub type Icbmsg = Vec<String>;
20
21/// Session parameters provided by client upon initialization.
22#[derive(Debug)]
23pub struct Config {
24    pub serverip: String,
25    pub nickname: String,
26    pub port: u16,
27    pub group: String,
28}
29
30/// Commands a `Client` can send to the `Server` through the `cmd` channels.
31#[derive(Debug, PartialEq)]
32pub enum Command {
33    /// Terminate the connection to the remote server. ICB doesn't have a way to
34    /// perform a clean disconnect other than shutting down the socket.
35    Bye,
36    /// Send a message to the group.
37    Open(String),
38    /// Send a personal message to a user.
39    /// First parameter is the username, second is the message text.
40    Personal(String, String),
41    /// Beep another user.
42    Beep(String),
43    /// Change nickname.
44    Name(String),
45}
46
47/// Representation of the client/user state.
48#[derive(Debug)]
49pub struct Client {
50    pub nickname: String,
51    pub cmd_s: Sender<Command>,
52    pub msg_r: Receiver<Icbmsg>,
53}
54
55/// Representation of the connection to the remote server.
56#[derive(Debug)]
57pub struct Server {
58    hostname: String,
59    port: u16,
60    sock: Option<TcpStream>,
61    cmd_r: Receiver<Command>,
62    msg_s: Sender<Icbmsg>,
63    nickname: String,
64    group: String,
65}
66
67impl Server {
68    fn new(
69        hostname: &str,
70        port: u16,
71        nickname: &str,
72        cmd_r: Receiver<Command>,
73        msg_s: Sender<Icbmsg>,
74        group: &str,
75    ) -> Server {
76        Server {
77            hostname: hostname.to_string(),
78            port,
79            cmd_r,
80            msg_s,
81            nickname: nickname.to_string(),
82            sock: None,
83            group: group.to_string(),
84        }
85    }
86
87    /// Read a buffer's worth of data from the TcpStream and dispatch it to the
88    /// correct parser.
89    /// If the caller expects a packet of certain type it is provided through `expected`.
90    fn read(&mut self, expected: Option<char>) -> Result<HashMap<&str, String>, std::io::Error> {
91        // Allocate a buffer large enough to hold two fully sized maximum ICB packets.
92        let mut buffer = [0; 512];
93
94        // Peek at the incoming data; some packets may show up as a single large buffer
95        // so we need to look at the size of the packet of the data we received.
96        // Then call read_exact() to read that many bytes, parse that data and send it
97        // up the stack.
98        // We know we won't be reading at the middle of an ICB packet because they are
99        // at most 255 bytes in size, our buffer is double that, and we will always start
100        // the connection with a valid packet. Therefore a full ICB packet will always
101        // fit the buffer wherever it's located.
102        let nbytes = self.sock.as_ref().unwrap().peek(&mut buffer)?;
103        if nbytes == 0 {
104            // Nothing to peek at.
105            return Ok(hashmap! {"type" => packets::T_INVALID.to_string()});
106        }
107
108        // Look for the beginning of the ICB packet. This is the first non-zero byte in the buffer.
109        let mut packet_len = 0;
110        for (i, byte) in buffer.iter().enumerate() {
111            // Skip over empty bytes; the first byte we encounter is the packet size.
112            if *byte != 0 {
113                q("Non-zero byte found with position and value", &(i, byte))?;
114                packet_len = *byte as usize;
115                break;
116            }
117        }
118
119        // XXX: We need to handle packets of 255 bytes too.
120        if packet_len == 0 {
121            // Still nothing worthwhile found -- bail out.
122            return Ok(hashmap! {"type" => packets::T_INVALID.to_string()});
123        }
124
125        // Allocate a new message vector the size of the packet plus the leading size byte
126        // (which gets stripped later).
127        let mut message = vec![0; packet_len + 1];
128
129        // Now read as much data from the socket as the server has indicated it has sent.
130        self.sock.as_ref().unwrap().read_exact(&mut message)?;
131
132        // Remove the packet size which is stored as packet_len already.
133        message.remove(0);
134
135        q("received message", &message)?;
136
137        let packet_type_byte = message[0] as char;
138
139        match expected {
140            Some(t) if (packet_type_byte == t) => {
141                // Caller was expecting a particular type, let's see if we have that.
142                q("OK! Received packet of expected type", &t)?;
143            }
144            Some(t) => {
145                q(
146                    "FAIL! Mismatch between expectation and result",
147                    &(t, packet_type_byte),
148                )?;
149                return Err(std::io::Error::new(
150                    ErrorKind::NotFound,
151                    "Packet type not found",
152                ));
153            }
154            _ => {
155                q("OK! Nothing was expected, just carry on", &())?;
156            }
157        }
158
159        q("Looking for a packet of type", &packet_type_byte)?;
160        for packet in &packets::PACKETS {
161            if packet.packet_type == packet_type_byte {
162                let data = (packet.parse)(message, packet_len);
163                q("data", &data)?;
164
165                return Ok(data);
166            }
167        }
168
169        Err(std::io::Error::new(
170            ErrorKind::InvalidData,
171            format!(
172                "Invalid data received from peer of type {}",
173                packet_type_byte
174            ),
175        ))
176    }
177
178    /// This is the "main event loop" of the library which starts by setting up the socket as
179    /// non-blocking before entering a loop where it looks for incoming commands on `msg_r`
180    /// which need to be dealt with. Secondly it looks for any ICB traffic that was received.
181    pub fn run(&mut self) {
182        // Up to this point blocking reads from the network were fine, now we're going to require
183        // non-blocking reads.
184        self.sock
185            .as_ref()
186            .unwrap()
187            .set_nonblocking(true)
188            .expect("set_nonblocking on socket failed");
189
190        // XXX: thread::scope() really needed here?
191        thread::scope(|s| {
192            s.spawn(|_| loop {
193                // Handle incoming commands sent by the client.
194                if let Ok(m) = self.cmd_r.try_recv() {
195                    match m {
196                        Command::Bye => {
197                            q("Terminating connection to remote host", &()).unwrap();
198                            self.sock
199                                .as_ref()
200                                .unwrap()
201                                .shutdown(Shutdown::Both)
202                                .unwrap();
203                            // XXX: Inform client the connection was closed
204                            break;
205                        }
206                        Command::Open(msg) => {
207                            q("Sending message to channel", &msg).unwrap();
208                            let packet = (packets::OPEN.create)(vec![msg.as_str()]);
209                            self.sock
210                                .as_ref()
211                                .unwrap()
212                                .write_all(&packet)
213                                .unwrap();
214                        }
215                        Command::Personal(recipient, msg) => {
216                            let packet = (packets::COMMAND.create)(vec![
217                                packets::CMD_MSG,
218                                format!("{} {}", recipient, msg).as_str(),
219                            ]);
220                            self.sock
221                                .as_ref()
222                                .unwrap()
223                                .write_all(&packet)
224                                .unwrap();
225                        }
226                        Command::Beep(recipient) => {
227                            let packet = (packets::COMMAND.create)(vec![
228                                packets::CMD_BEEP,
229                                recipient.as_str(),
230                            ]);
231                            self.sock
232                                .as_ref()
233                                .unwrap()
234                                .write_all(&packet)
235                                .unwrap();
236                        }
237                        Command::Name(newname) => {
238                            let packet = (packets::COMMAND.create)(vec![
239                                packets::CMD_NAME,
240                                newname.as_str(),
241                            ]);
242                            self.sock
243                                .as_ref()
244                                .unwrap()
245                                .write_all(&packet)
246                                .unwrap();
247                            self.nickname = newname;
248                        }
249                    }
250                }
251
252                // Handle incoming ICB packets, based on the type we'll determine
253                // how to handle them.
254                // For example T_OPEN and T_PERSONAL will be sent to the client.
255                if let Ok(v) = self.read(None) {
256                    if [packets::T_OPEN, packets::T_PERSONAL]
257                        .contains(&v["type"].chars().next().unwrap())
258                    {
259                        // Use an indirection to prevent mutably borrowing self.msg_s
260                        let msg = vec![
261                            v["type"].clone(),
262                            v["nickname"].clone(),
263                            v["message"].clone(),
264                        ];
265                        self.msg_s.send(msg).unwrap();
266                    } else if v["type"].chars().next().unwrap() == packets::T_STATUS {
267                        let msg = vec![
268                            v["type"].clone(),
269                            v["category"].clone(),
270                            v["message"].clone(),
271                        ];
272                        self.msg_s.send(msg).unwrap();
273                    } else if v["type"].chars().next().unwrap() == packets::T_BEEP {
274                        let msg = vec![v["type"].clone(), v["nickname"].clone()];
275                        self.msg_s.send(msg).unwrap();
276                    }
277                }
278
279                std::thread::sleep(Duration::from_millis(1));
280            });
281        })
282        .unwrap();
283    }
284
285    // Send a login packet with the 'login' command and a default group of '1'.
286    // Any other commands are currently not understood by the server implementation.
287    // Upon sending the login packet we expect an empty login response.
288    // At this point the client and server can start exchanging other types of packets.
289    fn login(&mut self) -> std::io::Result<()> {
290        let login_packet = (packets::LOGIN.create)(vec![
291            self.nickname.as_str(),
292            self.nickname.as_str(),
293            self.group.as_str(),
294            "login",
295        ]);
296
297        self.sock
298            .as_ref()
299            .unwrap()
300            .write_all(&login_packet)?;
301
302        if self.read(Some(packets::T_LOGIN)).is_err() {
303            panic!("Login failed.");
304        }
305
306        Ok(())
307    }
308
309    pub fn connect(&mut self) -> std::io::Result<()> {
310        // TcpStream::connect() returns a Result<TcpStream>; this we can
311        // handle with Ok() and Err(). self.sock is defined as an Option<TcpStream>,
312        // so we need to wrap the outcome of Ok() with Some() to convert it
313        // from a Result<> to an Option<>.
314        match TcpStream::connect(format!("{}:{}", &self.hostname, &self.port)) {
315            Ok(t) => self.sock = Some(t),
316            Err(e) => panic!(
317                "Could not connect to {}:{} - {}",
318                &self.hostname, &self.port, e
319            ),
320        }
321
322        // At this point we expect a protocol packet.
323        if let Ok(v) = self.read(Some(packets::T_PROTOCOL)) {
324            q("protocol packet data", &v)?;
325            q(
326                "connected to",
327                &(v.get("hostid").unwrap(), v.get("clientid").unwrap()),
328            )?;
329            let msg = vec![
330                v["type"].clone(),
331                v["hostid"].clone(),
332                v["clientid"].clone(),
333            ];
334            self.msg_s.send(msg).unwrap();
335        } else {
336            panic!("Expected a protocol packet, which didn't arrive.")
337        }
338
339        Ok(())
340    }
341}
342
343/// Entrypoint for this module; it sets up the `Client` and `Server` structs
344/// and establishes a connection to the configured server.
345pub fn init(config: Config) -> Result<(Client, Server), std::io::Error> {
346    let (msg_s, msg_r) = unbounded();
347    let (cmd_s, cmd_r) = unbounded();
348
349    let mut server = Server::new(
350        &config.serverip,
351        config.port,
352        &config.nickname,
353        cmd_r,
354        msg_s,
355        &config.group,
356    );
357    server.connect()?;
358    server.login()?;
359
360    let client = Client {
361        nickname: config.nickname,
362        cmd_s,
363        msg_r,
364    };
365
366    Ok((client, server))
367}