conclave_serialize/
lib.rs

1/*----------------------------------------------------------------------------------------------------------
2 *  Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/piot/conclave-serialize-rs
3 *  Licensed under the MIT License. See LICENSE in the project root for license information.
4 *--------------------------------------------------------------------------------------------------------*/
5//! The Conclave Protocol Serialization
6
7use std::io::Cursor;
8
9use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
10use conclave_room_serialize::{PingCommand, RoomInfoCommand};
11use conclave_types::{ClientNonce, GuiseUserSessionId, SessionId};
12
13use crate::ClientReceiveCommand::LoginResponseType;
14use crate::ServerReceiveCommand::{LoginRequestType, PingCommandType};
15
16/// Sent from Client to Server
17#[derive(Debug, PartialEq)]
18pub struct LoginRequest {
19    pub guise_user_session_id: GuiseUserSessionId,
20    pub nonce: ClientNonce,
21}
22
23impl LoginRequest {
24    pub fn to_octets(&self) -> Vec<u8> {
25        let mut writer = vec![];
26
27        writer
28            .write_u64::<BigEndian>(self.guise_user_session_id)
29            .unwrap();
30        writer.write_u64::<BigEndian>(self.nonce).unwrap();
31        writer
32    }
33
34    pub fn from_cursor(reader: &mut Cursor<&[u8]>) -> Self {
35        Self {
36            guise_user_session_id: reader.read_u64::<BigEndian>().unwrap(),
37            nonce: reader.read_u64::<BigEndian>().unwrap(),
38        }
39    }
40}
41
42#[derive(Debug)]
43pub enum ServerReceiveCommand {
44    LoginRequestType(LoginRequest),
45    PingCommandType(PingCommand),
46}
47
48impl ServerReceiveCommand {
49    pub fn to_octets(&self) -> Result<Vec<u8>, String> {
50        let command_type_id = match self {
51            LoginRequestType(_) => LOGIN_REQUEST_TYPE_ID,
52            _ => return Err(format!("unsupported command {:?}", self)),
53        };
54
55        let mut writer = vec![];
56
57        writer
58            .write_u8(command_type_id)
59            .expect("could not write command type id");
60
61        match self {
62            LoginRequestType(login_request) => {
63                writer.extend_from_slice(login_request.to_octets().as_slice())
64            }
65            PingCommandType(ping_command) => {
66                writer.extend_from_slice(ping_command.to_octets().as_slice())
67            }
68            // _ => return Err(format!("unknown command enum {:?}", self)),
69        }
70
71        Ok(writer)
72    }
73
74    pub fn from_octets(input: &[u8]) -> Result<ServerReceiveCommand, String> {
75        let reader = Cursor::new(input);
76        ServerReceiveCommand::from_cursor(reader)
77    }
78
79    pub fn from_cursor(mut reader: Cursor<&[u8]>) -> Result<ServerReceiveCommand, String> {
80        let command_type_id = reader.read_u8().unwrap();
81        match command_type_id {
82            LOGIN_REQUEST_TYPE_ID => Ok(LoginRequestType(LoginRequest::from_cursor(&mut reader))),
83            _ => Err(format!("unknown command 0x{:x}", command_type_id)),
84        }
85    }
86}
87
88/// Sent from Server to Client
89#[derive(Debug)]
90pub struct LoginResponse {
91    pub nonce: ClientNonce,
92    pub session_id: SessionId,
93}
94
95impl LoginResponse {
96    pub fn to_octets(&self) -> Vec<u8> {
97        let mut writer = vec![];
98
99        writer
100            .write_u64::<BigEndian>(self.nonce)
101            .unwrap();
102        writer.write_u64::<BigEndian>(self.session_id).unwrap();
103        writer
104    }
105
106    pub fn from_cursor(reader: &mut Cursor<&[u8]>) -> Self {
107        Self {
108            nonce: reader.read_u64::<BigEndian>().unwrap(),
109            session_id: reader.read_u64::<BigEndian>().unwrap(),
110        }
111    }
112}
113
114#[derive(Debug)]
115pub enum ClientReceiveCommand {
116    LoginResponseType(LoginResponse),
117    RoomInfoType(RoomInfoCommand),
118}
119
120pub const LOGIN_REQUEST_TYPE_ID: u8 = 0x02;
121pub const LOGIN_RESPONSE_TYPE_ID: u8 = 0x22;
122
123impl ClientReceiveCommand {
124    pub fn to_octets(&self) -> Result<Vec<u8>, String> {
125        let command_type_id = match self {
126            LoginResponseType(_) => LOGIN_RESPONSE_TYPE_ID,
127            _ => return Err(format!("unsupported command {:?}", self)),
128        };
129
130        let mut writer = vec![];
131
132        writer
133            .write_u8(command_type_id)
134            .expect("could not write command type id");
135
136        match self {
137            LoginResponseType(login_response) => {
138                writer.extend_from_slice(login_response.to_octets().as_slice())
139            }
140            _ => return Err(format!("unknown command enum {:?}", self)),
141        }
142
143        Ok(writer)
144    }
145
146    pub fn from_octets(input: &[u8]) -> Result<ClientReceiveCommand, String> {
147        let mut rdr = Cursor::new(input);
148        let command_type_id = rdr.read_u8().unwrap();
149        match command_type_id {
150            LOGIN_RESPONSE_TYPE_ID => Ok(LoginResponseType(LoginResponse::from_cursor(&mut rdr))),
151            _ => Err(format!("unknown command 0x{:x}", command_type_id)),
152        }
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use std::io::Cursor;
159
160    use crate::{ClientReceiveCommand, LOGIN_REQUEST_TYPE_ID, LOGIN_RESPONSE_TYPE_ID, LoginRequest, LoginResponseType, ServerReceiveCommand};
161    use crate::ServerReceiveCommand::LoginRequestType;
162
163    #[test]
164    fn check_login_request_serializer() {
165        let login_request = LoginRequest {
166            guise_user_session_id: 4214124,
167            nonce: 19,
168        };
169
170        let encoded = login_request.to_octets();
171        let mut receive_cursor = Cursor::new(encoded.as_slice());
172        let deserialized_ping_command = LoginRequest::from_cursor(&mut receive_cursor);
173
174        println!("before {:?}", &login_request);
175        println!("after {:?}", &deserialized_ping_command);
176        assert_eq!(login_request, deserialized_ping_command);
177    }
178
179    #[test]
180    fn check_server_receive_login_request() {
181        let octets = [
182            LOGIN_REQUEST_TYPE_ID,
183            0x00, // guise user session id
184            0x00,
185            0x00,
186            0x00,
187            0x00,
188            0x40,
189            0x4d,
190            0x6c,
191            0x00, // nonce
192            0x00,
193            0x00,
194            0x00,
195            0x00,
196            0x00,
197            0x00,
198            0x13,
199        ];
200
201        let message = &ServerReceiveCommand::from_octets(&octets).unwrap();
202
203        match message {
204            LoginRequestType(login_request) => {
205                println!("received {:?}", &login_request);
206                assert_eq!(login_request.guise_user_session_id, 4214124);
207                assert_eq!(login_request.nonce, 19);
208                let octets_after = message.to_octets().unwrap();
209                assert_eq!(octets, octets_after.as_slice());
210            }
211            _ => unreachable!("should be login request command"),
212        }
213    }
214
215    #[test]
216    fn check_client_receive_message() {
217        let octets = [
218            LOGIN_RESPONSE_TYPE_ID,
219            0x00, // Nonce
220            0x00,
221            0x00,
222            0x09,
223            0xC6,
224            0x36,
225            0xCD,
226            0x41,
227            0x00, // session id
228            0x00,
229            0x00,
230            0x00,
231            0x3B,
232            0x4B,
233            0xB9,
234            0x6A,
235        ];
236
237        let message = &ClientReceiveCommand::from_octets(&octets).unwrap();
238
239        match message {
240            LoginResponseType(login_response) => {
241                println!("received {:?}", &login_response);
242                assert_eq!(login_response.nonce, 41980185921);
243                assert_eq!(login_response.session_id, 994818410);
244                let octets_after = message.to_octets().unwrap();
245                assert_eq!(octets, octets_after.as_slice());
246            }
247            _ => unreachable!("should be room info command"),
248        }
249    }
250}