Search code examples
tcprustirc

IRC server doesn't respond to Rust IRC Client identify requests


I'm working on an IRC bot using TcpStream from the standard library.

I'm able to read all the lines that come in, but the IRC server doesn't seem to respond to my identify requests. I thought I was sending the request too soon so I tried sleeping before sending the IDENT but that doesn't work. I write using both BufReader, BufWriter and calling read and write directly on the stream to no avail.

use std::net::TcpStream;
use std::io::{BufReader, BufWriter, BufRead, Write, Read};
use std::{thread, time};
struct Rusty {
    name: String,
    stream: TcpStream,
    reader: BufReader<TcpStream>,
    writer: BufWriter<TcpStream>,
}
impl Rusty {
    fn new(name: &str, address: &str) -> Rusty {
        let stream = TcpStream::connect(address).expect("Couldn't connect to server");
        let reader = BufReader::new(stream.try_clone().unwrap());
        let writer = BufWriter::new(stream.try_clone().unwrap());
        Rusty {
            name: String::from(name),
            reader: reader,
            writer: writer,
            stream: stream,
        }
    }
    fn write_line(&mut self, string: String) {
        let line = format!("{}\r\n", string);
        &self.writer.write(line.as_bytes());
    }
    fn identify(&mut self) {
        let nick = &self.name.clone();
        self.write_line(format!("USER {} {} {} : {}", nick, nick, nick, nick));
        self.write_line(format!("NICK {}", nick));
    }
    fn read_lines(&mut self) {
        let mut line = String::new();
        loop {
            self.reader.read_line(&mut line);
            println!("{}", line);
        }
    }
}
fn main() {
    let mut bot = Rusty::new("rustyrusty", "irc.rizon.net:6667");
    thread::sleep_ms(5000);
    bot.identify();
    bot.read_lines();
}

Solution

  • It's very important to read the documentation for the components we use when programming. For example, the docs for BufWriter states (emphasis mine):

    Wraps a writer and buffers its output.

    It can be excessively inefficient to work directly with something that implements Write. For example, every call to write on TcpStream results in a system call. A BufWriter keeps an in-memory buffer of data and writes it to an underlying writer in large, infrequent batches.

    The buffer will be written out when the writer is dropped.

    Said another way, the entire purpose of a buffered reader or writer is that read or write requests don't have a one-to-one mapping to the underlying stream.

    That means when you call write, you are only writing to the buffer. You also need to call flush if you need to ensure that the bytes are written to the underlying stream.


    Additionally, you should:

    1. Handle the errors that can arise from read, write, and flush.
    2. Re-familiarize yourself with what each function does. For example, read and write don't guarantee that they read or write as much data as you ask them to. They may perform a partial read or write, and it's up to you to handle that. That's why there are helper methods like read_to_end or write_all.
    3. Clear your String that you are reading into. Otherwise the output just repeats every time the loop cycles.
    4. Use write! instead of building up a string that is immediately thrown away.
    fn write_line(&mut self, string: &str) {
        write!(self.writer, "{}\r\n", string).unwrap();
        self.writer.flush().unwrap();
    }
    

    With these changes, I was able to get a PING message from the server.