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}