sip_rld 0.3.1

Library for work with SIP protocol
Documentation
// TODO DOCUMENTATION

// TODO divide Message into traits

#[derive(Debug)]
pub enum MessageType {
    Request(RequestMethod),
    Response(String),
}

#[derive(Debug)]
pub enum RequestMethod {
    Register,
    Invite,
    ACK,
    Cancel,
    Bye,
    Options,
    Subscribe,
    Notify,
}

// TODO Remove pub's, add getters
#[derive(Debug)]
pub struct Message {
    pub mtype: MessageType, // Request/Response
    pub request_uri: String,

    // Mandatory for request headers:
    pub to: String,
    pub from: String,
    pub cseq: String,
    pub call_id: String,
    pub max_forwards: String,
    pub via: String,

    pub body: String,

    pub domain: String,
}

impl Message {
    pub fn new(mtype: MessageType, domain: String) -> Message {
        Message {
            mtype: mtype,
            request_uri: String::new(),
            to: String::from("\r\n"),
            from: String::from("\r\n"),
            cseq: String::from("\r\n"),
            call_id: String::from("\r\n"),
            max_forwards: String::from("\r\n"),
            via: String::from("\r\n"),
            body: String::new(),
            domain: domain,
        }
    }

    fn get_method_name(&mut self) -> Result<String, &'static str> {
        match &self.mtype {
            MessageType::Request(method) => {
                let method_str = match method {
                    RequestMethod::ACK => "ACK",
                    RequestMethod::Bye => "BYE",
                    RequestMethod::Cancel => "CANCEL",
                    RequestMethod::Invite => "INVITE",
                    RequestMethod::Options => "OPTIONS",
                    RequestMethod::Register => "REGISTER",
                    RequestMethod::Subscribe => "SUBSCRIBE",
                    RequestMethod::Notify => "NOTIFY",
                };
                Ok(method_str.to_string())
            },
            MessageType::Response(_) => {
                Err("Incorrect message type.")
            }
        }
    }

    fn get_request_method_from_name(name: &str) -> Result<RequestMethod, &'static str> {
        let method = match name {
            "ACK" => RequestMethod::ACK,
            "BYE" => RequestMethod::Bye,
            "CANCEL" => RequestMethod::Cancel,
            "INVITE" => RequestMethod::Invite,
            "OPTIONS" => RequestMethod::Options,
            "REGISTER" => RequestMethod::Register,
            "SUBSCRIBE" => RequestMethod::Subscribe,
            "NOTIFY" => RequestMethod::Notify,
            _ => return Err("Unknown method name.")
        };
        Ok(method)
    }

    // TODO change implementation: make struct for each header:
    // for each implement value, build string to avoid this spaghetti
    // also, change branch
    pub fn via(&mut self, proto: String, host: String, port: String) -> &mut Message {
        self.via = format!("Via: SIP/2.0/{} {}:{};branch=z9hG4bK\r\n", proto, host, port);
        self
    }

    pub fn to(&mut self, display_name: Option<String>, ext: String) -> &mut Message {
        match display_name {
            None => self.to = format!("To: sip:{}@{}\r\n", ext, self.domain),
            Some(name) => self.to = format!("To: {} <sip:{}@{}>\r\n", name, ext, self.domain)
        };
        self
    }

    pub fn get_to(&self) -> String {
        self.to.chars().skip_while(|c| c != &':').skip(1).skip_while(|c| c != &':').skip(1).take_while(|c| c != &'@').collect()
    }

    pub fn from(&mut self, display_name: Option<String>, ext: String) -> &mut Message {
        match display_name {
            None => self.from = format!("From: sip:{}@{}\r\n", ext, self.domain),
            Some(name) => self.from = format!("From: {} <sip:{}@{}>\r\n", name, ext, self.domain)
        };
        self
    }

    pub fn get_from(&self) -> String {
        self.from.chars().skip_while(|c| c != &':').skip(1).skip_while(|c| c != &':').skip(1).take_while(|c| c != &'@').collect()
    }

    pub fn call_id(&mut self, call_id: String) -> &mut Message {
        self.call_id = format!("Call-ID: {}\r\n", call_id);
        self
    }

    pub fn cseq(&mut self, number: String) -> &mut Message {
        self.cseq = format!("CSeq: {} {}\r\n", number, self.get_method_name().unwrap());
        self
    }

    pub fn max_forwards(&mut self, number: String) -> &mut Message {
        self.max_forwards = format!("Max-Forwards: {}\r\n", number);
        self
    }

    pub fn request_uri(&mut self, uri: String) -> &mut Message {
        self.request_uri = uri;
        self
    }

    pub fn build_message(&mut self) -> String {
        let start_line = match &self.mtype {
            MessageType::Request(_) => {
                let method_name = self.get_method_name().unwrap();
                format!("{} sip:{}@{} SIP/2.0\r\n", method_name, self.request_uri, self.domain)
            },
            MessageType::Response(response) => {
                format!("SIP/2.0 {}\r\n", response)
            }
        };
        let content_length = match &self.body.is_empty() {
            true => String::from("Content-Length: 0\r\n\r\n"),
            false => format!("Content-Length: {}\r\n\r\n", &self.body.len()),
        };
        format!("{}{}{}{}{}{}{}{}{}", start_line, self.via, self.to, self.from, self.call_id, self.cseq, self.max_forwards, content_length, self.body)
    }

    pub fn parse(msg: &str) -> Message {
        let msg_split = msg.split("\r\n").collect::<Vec<_>>();

        let msg_head = msg_split[0].split(" ").collect::<Vec<_>>();

        let message_type = match msg_head[0].starts_with("SIP") {
            true => MessageType::Response(format!("{} {}", msg_head[0], msg_head[1])),
            false => MessageType::Request(Message::get_request_method_from_name(msg_head[0]).unwrap())
        };

        let mut message = Message::new(message_type, String::new());

        match message.mtype {
            MessageType::Request(_) => message.request_uri = msg_head[1].chars().skip_while(|c| c != &':').skip(1).take_while(|c| c != &'@').collect(),
            _ => ()
        };

        // Need to refactor
        for i in 1..msg_split.len() {
            if msg_split[i].starts_with("Via:") {
                message.via = String::from(msg_split[i]);
            } else if msg_split[i].starts_with("To:") {
                message.to = String::from(msg_split[i]);
            } else if msg_split[i].starts_with("From:") {
                message.from = String::from(msg_split[i]);
            } else if msg_split[i].starts_with("Call-ID:") {
                message.call_id = String::from(msg_split[i]);
            } else if msg_split[i].starts_with("CSeq:") {
                message.cseq = String::from(msg_split[i]);
            } else if msg_split[i].starts_with("Max-Forwards:") {
                message.max_forwards = String::from(msg_split[i]);
            } else if msg_split[i].starts_with("Content-Length:") {
                // do smth
            } else { // body
                message.body = String::from(msg_split[i]);
            }
        }
        message
    }
}

// TODO more tests
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn check_method_name() {
        let mut message = Message::new(MessageType::Request(RequestMethod::Register), String::from("my.dom.ru"));
        assert_eq!(message.get_method_name().unwrap(), String::from("REGISTER"));
    }

    #[test]
    fn check_to() {
        let mut message = Message::new(MessageType::Request(RequestMethod::Register), String::from("my.dom.ru"));
        message.to(Some(String::from("name")), String::from("1175"));
        assert_eq!(message.get_to(), String::from("1175"));
    }

    #[test]
    fn check_from() {
        let mut message = Message::new(MessageType::Request(RequestMethod::Register), String::from("my.dom.ru"));
        message.from(Some(String::from("name")), String::from("1176"));
        assert_eq!(message.get_from(), String::from("1176"));
    }

    #[test]
    fn check_to_without_dname() {
        let mut message = Message::new(MessageType::Request(RequestMethod::Register), String::from("my.dom.ru"));
        message.to(None, String::from("1175"));
        assert_eq!(message.get_to(), String::from("1175"));
    }

    #[test]
    fn check_from_without_dname() {
        let mut message = Message::new(MessageType::Request(RequestMethod::Register), String::from("my.dom.ru"));
        message.from(None, String::from("1176"));
        assert_eq!(message.get_from(), String::from("1176"));
    }
}