msnp11-sdk 0.12.0

An MSNP11 client SDK
Documentation
use crate::errors::p2p_error::P2pError;
use crate::switchboard_server::p2p::binary_header::BinaryHeader;
use base64::{Engine as _, engine::general_purpose::STANDARD};
use core::str;
use deku::{DekuContainerRead, DekuContainerWrite};
use rand::Rng;
use rand::rng;
use std::error::Error;
use std::io::Cursor;

pub struct DisplayPictureSession {
    session_id: u32,
    base_identifier: u32,
    branch: String,
    call_id: String,
}

impl DisplayPictureSession {
    pub fn new() -> Self {
        Self {
            session_id: 0,
            base_identifier: rng().next_u32(),
            branch: "".to_string(),
            call_id: "".to_string(),
        }
    }

    pub fn new_from_invite(invite: &Vec<u8>) -> Result<Self, Box<dyn Error>> {
        let mut invite = unsafe { str::from_utf8_unchecked(invite.as_slice()) }.split("\r\n");

        let Some(branch) =
            invite.find(|parameter| parameter.starts_with("Via: MSNSLP/1.0/TLP ;branch={"))
        else {
            return Err(P2pError::P2pInvite.into());
        };

        let branch = branch
            .replace("Via: MSNSLP/1.0/TLP ;branch={", "")
            .replace("}", "");

        let Some(call_id) = invite.find(|parameter| parameter.starts_with("Call-ID: {")) else {
            return Err(P2pError::P2pInvite.into());
        };

        let call_id = call_id.replace("Call-ID: {", "").replace("}", "");

        let Some(session_id) = invite.find(|parameter| parameter.starts_with("SessionID: ")) else {
            return Err(P2pError::P2pInvite.into());
        };

        let session_id = session_id.replace("SessionID: ", "").parse::<u32>()?;

        Ok(Self {
            session_id,
            base_identifier: rng().next_u32(),
            branch,
            call_id,
        })
    }

    pub fn invite(&mut self, to: &str, from: &str, msn_object: &str) -> Result<Vec<u8>, P2pError> {
        let branch = guid_create::GUID::rand().to_string();
        let call_id = guid_create::GUID::rand().to_string();
        let session_id = rng().next_u32();

        let mut body = "EUF-GUID: {A4268EEC-FEC5-49E5-95C3-F126696BDBF6}\r\n".to_string();
        body.push_str(format!("SessionID: {session_id}\r\n").as_str());
        body.push_str("AppID: 1\r\n");
        body.push_str(
            format!(
                "Context: {}\r\n\r\n\0",
                STANDARD.encode((msn_object.to_owned() + "\0").as_bytes())
            )
            .as_str(),
        );

        let mut headers = format!("INVITE MSNMSGR:{to} MSNSLP/1.0\r\n");
        headers.push_str(format!("To: <msnmsgr:{to}>\r\n").as_str());
        headers.push_str(format!("From: <msnmsgr:{from}>\r\n").as_str());
        headers.push_str(format!("Via: MSNSLP/1.0/TLP ;branch={{{branch}}}\r\n").as_str());
        headers.push_str("CSeq: 0\r\n");
        headers.push_str(format!("Call-ID: {{{call_id}}}\r\n").as_str());
        headers.push_str("Max-Forwards: 0\r\n");
        headers.push_str("Content-Type: application/x-msnmsgr-sessionreqbody\r\n");
        headers.push_str(format!("Content-Length: {}\r\n\r\n", body.len()).as_str());

        let message = format!("{headers}{body}");
        let mut invite = BinaryHeader {
            session_id: 0,
            identifier: self.base_identifier,
            data_offset: 0,
            total_data_size: message.len() as u64,
            length: message.len() as u32,
            flag: 0x00,
            ack_identifier: self.base_identifier + 1,
            ack_unique_id: 0,
            ack_data_size: 0,
        }
        .to_bytes()
        .or(Err(P2pError::BinaryHeaderReadingError))?;

        self.branch = branch;
        self.call_id = call_id;

        invite.extend_from_slice(message.as_bytes());
        invite.extend_from_slice(&[0; 4]);
        Ok(invite)
    }

    pub fn acknowledge(payload: &[u8]) -> Result<Vec<u8>, P2pError> {
        let binary_header = payload
            .get(..48)
            .ok_or(P2pError::BinaryHeaderReadingError)?;

        let mut cursor = Cursor::new(binary_header);
        let (_, binary_header) = BinaryHeader::from_reader((&mut cursor, 0))
            .or(Err(P2pError::BinaryHeaderReadingError))?;

        let mut ack_header = BinaryHeader {
            session_id: binary_header.session_id,
            identifier: !binary_header.identifier,
            data_offset: 0,
            total_data_size: binary_header.total_data_size,
            length: 0,
            flag: 0x02,
            ack_identifier: binary_header.identifier + 1,
            ack_unique_id: binary_header.ack_unique_id,
            ack_data_size: binary_header.ack_data_size,
        }
        .to_bytes()
        .or(Err(P2pError::BinaryHeaderReadingError))?;

        ack_header.extend_from_slice(&[0; 4]);
        Ok(ack_header)
    }

    pub fn ok(&self, to: &str, from: &str) -> Result<Vec<u8>, Box<dyn Error>> {
        let mut body = "EUF-GUID: {A4268EEC-FEC5-49E5-95C3-F126696BDBF6}\r\n".to_string();
        body.push_str(format!("SessionID: {}\r\n\r\n\0", self.session_id).as_str());

        let mut headers = "MSNSLP/1.0 200 OK\r\n".to_string();
        headers.push_str(format!("To: <msnmsgr:{to}>\r\n").as_str());
        headers.push_str(format!("From: <msnmsgr:{from}>\r\n").as_str());
        headers.push_str(format!("Via: MSNSLP/1.0/TLP ;branch={{{}}}\r\n", self.branch).as_str());
        headers.push_str("CSeq: 1\r\n");
        headers.push_str(format!("Call-ID: {{{}}}\r\n", self.call_id).as_str());
        headers.push_str("Max-Forwards: 0\r\n");
        headers.push_str("Content-Type: application/x-msnmsgr-sessionreqbody\r\n");
        headers.push_str(format!("Content-Length: {}\r\n\r\n", body.len()).as_str());

        let message = format!("{headers}{body}");
        let mut ok = BinaryHeader {
            session_id: 0,
            identifier: self.base_identifier + 1,
            data_offset: 0,
            total_data_size: message.len() as u64,
            length: message.len() as u32,
            flag: 0x00,
            ack_identifier: self.base_identifier + 1,
            ack_unique_id: 0,
            ack_data_size: 0,
        }
        .to_bytes()?;

        ok.extend_from_slice(message.as_bytes());
        ok.extend_from_slice(&[0; 4]);
        Ok(ok)
    }

    pub fn decline(&self, to: &str, from: &str) -> Result<Vec<u8>, Box<dyn Error>> {
        let body = format!("SessionID: {}\r\n\r\n\0", self.session_id);

        let mut headers = "MSNSLP/1.0 603 Decline\r\n".to_string();
        headers.push_str(format!("To: <msnmsgr:{to}>\r\n").as_str());
        headers.push_str(format!("From: <msnmsgr:{from}>\r\n").as_str());
        headers.push_str(format!("Via: MSNSLP/1.0/TLP ;branch={{{}}}\r\n", self.branch).as_str());
        headers.push_str("CSeq: 1\r\n");
        headers.push_str(format!("Call-ID: {{{}}}\r\n", self.call_id).as_str());
        headers.push_str("Max-Forwards: 0\r\n");
        headers.push_str("Content-Type: application/x-msnmsgr-sessionreqbody\r\n");
        headers.push_str(format!("Content-Length: {}\r\n\r\n", body.len()).as_str());

        let message = format!("{headers}{body}");
        let mut decline = BinaryHeader {
            session_id: 0,
            identifier: self.base_identifier + 1,
            data_offset: 0,
            total_data_size: message.len() as u64,
            length: message.len() as u32,
            flag: 0x00,
            ack_identifier: self.base_identifier + 1,
            ack_unique_id: 0,
            ack_data_size: 0,
        }
        .to_bytes()?;

        decline.extend_from_slice(message.as_bytes());
        decline.extend_from_slice(&[0; 4]);
        Ok(decline)
    }

    pub fn data_preparation(&self) -> Result<Vec<u8>, Box<dyn Error>> {
        let message = &[0; 4];
        let mut data_preparation = BinaryHeader {
            session_id: self.session_id,
            identifier: self.base_identifier + 2,
            data_offset: 0,
            total_data_size: message.len() as u64,
            length: message.len() as u32,
            flag: 0x00,
            ack_identifier: self.base_identifier + 2,
            ack_unique_id: 0,
            ack_data_size: 0,
        }
        .to_bytes()?;

        data_preparation.extend_from_slice(message);
        data_preparation.extend_from_slice(&[0, 0, 0, 1]);
        Ok(data_preparation)
    }

    pub fn data(&self, data: &[u8]) -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
        let mut payloads: Vec<Vec<u8>> = Vec::new();
        let total_data_size = data.len() as u64;
        let mut data_offset = 0u64;

        let chunks = data.chunks(1202);
        for chunk in chunks {
            let mut data_message = BinaryHeader {
                session_id: self.session_id,
                identifier: self.base_identifier + 3,
                data_offset,
                total_data_size,
                length: chunk.len() as u32,
                flag: 0x20,
                ack_identifier: self.base_identifier + 3,
                ack_unique_id: 0,
                ack_data_size: 0,
            }
            .to_bytes()?;

            data_message.extend_from_slice(chunk);
            data_message.extend_from_slice(&[0, 0, 0, 1]);

            data_offset += chunk.len() as u64;
            payloads.push(data_message);
        }

        Ok(payloads)
    }

    pub fn bye(&self, to: &str, from: &str) -> Result<Vec<u8>, P2pError> {
        let body = "\r\n\0";

        let mut headers = format!("BYE MSNMSGR:{to} MSNSLP/1.0\r\n");
        headers.push_str(format!("To: <msnmsgr:{to}>\r\n").as_str());
        headers.push_str(format!("From: <msnmsgr:{from}>\r\n").as_str());
        headers.push_str(format!("Via: MSNSLP/1.0/TLP ;branch={{{}}}\r\n", self.branch).as_str());
        headers.push_str("CSeq: 0\r\n");
        headers.push_str(format!("Call-ID: {{{}}}\r\n", self.call_id).as_str());
        headers.push_str("Max-Forwards: 0\r\n");
        headers.push_str("Content-Type: application/x-msnmsgr-sessionreqbody\r\n");
        headers.push_str(format!("Content-Length: {}\r\n\r\n", body.len()).as_str());

        let message = format!("{headers}{body}");
        let mut bye = BinaryHeader {
            session_id: 0,
            identifier: self.base_identifier + 4,
            data_offset: 0,
            total_data_size: message.len() as u64,
            length: message.len() as u32,
            flag: 0x00,
            ack_identifier: self.base_identifier + 4,
            ack_unique_id: 0,
            ack_data_size: 0,
        }
        .to_bytes()
        .or(Err(P2pError::BinaryHeaderReadingError))?;

        bye.extend_from_slice(message.as_bytes());
        bye.extend_from_slice(&[0; 4]);
        Ok(bye)
    }
}