diff --git a/agent/lib/src/external_api/mod.rs b/agent/lib/src/external_api/mod.rs index 908db95..e3a6fa2 100644 --- a/agent/lib/src/external_api/mod.rs +++ b/agent/lib/src/external_api/mod.rs @@ -1,2 +1,3 @@ pub mod model; +pub mod comms; pub mod service; diff --git a/agent/lib/src/external_api/model.rs b/agent/lib/src/external_api/model.rs index ac7d033..b78410c 100644 --- a/agent/lib/src/external_api/model.rs +++ b/agent/lib/src/external_api/model.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] pub struct FileModel { pub filename: String, pub content: String, @@ -12,14 +12,14 @@ pub struct CodeEntry { pub script: Vec, // All commands to execute at startup } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] pub enum Type { Status, Request, Response, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] pub enum Code { Run, Ok, @@ -40,6 +40,12 @@ impl StatusMessage { } } +impl Default for StatusMessage { + fn default() -> Self { + Self::new() + } +} + #[derive(Deserialize, Serialize, Debug)] pub struct ResponseStep { pub command: String, @@ -96,7 +102,7 @@ impl ResponseMessage { } } -#[derive(Deserialize, Serialize, Debug, Clone)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] pub struct RequestStep { pub command: String, pub enable_output: bool, diff --git a/agent/lib/src/external_api/service.rs b/agent/lib/src/external_api/service.rs index d76e46f..eadeb09 100644 --- a/agent/lib/src/external_api/service.rs +++ b/agent/lib/src/external_api/service.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Result}; -use log::{error, info}; +use log::{error, info, debug, trace}; use super::model::{RequestMessage, ResponseMessage, StatusMessage}; +use super::comms::{Message, MESSAGE_SIZE_NB_BYTES}; pub struct ExternalApi { serial_path: String, @@ -25,20 +26,62 @@ impl ExternalApi { .map_err(|e| anyhow!("Failed to open serial port: {}", e))?; // Create a buffer to hold the data + let mut data_size: usize = 0; + let mut size_buffer: [u8; MESSAGE_SIZE_NB_BYTES] = [0; MESSAGE_SIZE_NB_BYTES]; + let mut got_size = false; + let mut buf = [0; 128]; + let mut bytes_read: usize = 0; // Create the final vector to hold the data let mut data_received: Vec = Vec::new(); - let mut find_delimiter = false; + //we read the buffer and retrieve the first 8 bytes which are the size of the message + while !got_size { + match serial.read(&mut size_buffer) { + Ok(t) => { + if t == 0 { + break; + } + + bytes_read += t; + data_received.extend_from_slice(&size_buffer[..t]); + + trace!("Received {} bytes", t); + trace!("Size buffer: {:?}", data_received.clone()); + + if bytes_read >= MESSAGE_SIZE_NB_BYTES { + got_size = true; + + let size_string = String::from_utf8( + data_received.clone() + ).map_err( + |e| anyhow!("Failed to get message size as string: {}", e) + )?; + + trace!("Size string: {}", size_string); + + data_size = size_string.parse::().map_err( + |e| anyhow!("Failed to parse length of message: {}", e) + )?; + + data_received.drain(..MESSAGE_SIZE_NB_BYTES); + } + } + Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => (), + Err(e) => error!("{:?}", e), + } + } + + bytes_read = 0; - while !find_delimiter { + while bytes_read < data_size { match serial.read(&mut buf) { Ok(t) => { if t > 0 { - info!("Buffer received {:?}", &buf[..t]); - find_delimiter = - self.append_data_before_delimiter(&buf, &mut data_received)?; + bytes_read += t; + data_received.extend_from_slice(&buf[..t]); + debug!("Received {} bytes", t); } } Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => (), @@ -46,10 +89,13 @@ impl ExternalApi { } } - info!("Final received data: {:?}", data_received); + debug!("Final received data: {:?}", data_received); + debug!("Total bytes read {:?}", bytes_read); let code_entry = self.parse_json_payload(&data_received)?; + info!("Code entry: {:?}", code_entry); + // Flush the serial port serial .flush() @@ -58,29 +104,6 @@ impl ExternalApi { Ok(code_entry) } - pub fn append_data_before_delimiter( - &mut self, - buf: &[u8], - data_received: &mut Vec, - ) -> Result { - // find char 1c (record separator) in the buffer - if let Some(i) = buf.iter().position(|&r| r == 0x1c) { - // Split the buffer at the position of the record separator - let (data_to_add, _) = buf.split_at(i); - - // Add the data to the data vector - data_received.extend_from_slice(data_to_add); - - info!("Delimiter found at index: {}", i); - - Ok(true) - } else { - // Add the data to the data vector - data_received.extend_from_slice(&buf[..buf.len()]); - Ok(false) - } - } - pub fn parse_json_payload(&mut self, data: &[u8]) -> Result { // Convert the data vector to a codeEntry struct let request_message: RequestMessage = serde_json::from_slice(data) @@ -100,7 +123,8 @@ impl ExternalApi { } pub fn send_response_message(&mut self, response_message: ResponseMessage) -> Result<()> { - let code_json = serde_json::to_string(&response_message).unwrap(); + let code_json = serde_json::to_string(&response_message) + .map_err(|e| anyhow!("Failed to stringify response message : {}", e))?; // Write the JSON to the serial port self.write_to_serial(&code_json)?; @@ -119,7 +143,8 @@ impl ExternalApi { .map_err(|e| anyhow!("Failed to open serial port: {}", e))?; // Conver the string to a byte array - let buf = data.as_bytes(); + let message = Message::new(data.to_string()).to_bytes(); + let buf = message.as_slice(); // Write the byte array to the serial port serial @@ -139,41 +164,76 @@ mod tests { use anyhow::Result; + use crate::external_api::model::{Code, FileModel, RequestStep, Type}; + use super::ExternalApi; #[test] fn test_parse_json_payload() -> Result<()> { - // let mut internal_api = ExternalApi::new("".to_string(), 0); + let mut internal_api = ExternalApi::new("".to_string(), 0); // Data vector with the following JSON payload: // { - // "files": [ + // "type": "Request", + // "code": "Run", + // "data": { + // "id": "4bf68974-c315-4c41-aee2-3dc2920e76e9", + // "files": [ + // { + // "filename": "src/index.js", + // "content": "console.log('Hello World!');" + // } + // ], + // "steps": [ // { - // "filename": "test.py", - // "content": "print('Hello World')" + // "command": "node src/index.js", + // "enable_output": true // } - // ], - // "script": [ - // "python3 test.py" - // ] - // } + // ] + // } + // } let data = [ - 123, 10, 32, 32, 34, 102, 105, 108, 101, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 123, - 10, 32, 32, 32, 32, 32, 32, 34, 102, 105, 108, 101, 110, 97, 109, 101, 34, 58, 32, 34, - 116, 101, 115, 116, 46, 112, 121, 34, 44, 10, 32, 32, 32, 32, 32, 32, 34, 99, 111, 110, - 116, 101, 110, 116, 34, 58, 32, 34, 112, 114, 105, 110, 116, 40, 39, 72, 101, 108, 108, - 111, 32, 87, 111, 114, 108, 100, 39, 41, 34, 10, 32, 32, 32, 32, 125, 10, 32, 32, 93, - 44, 10, 32, 32, 34, 115, 99, 114, 105, 112, 116, 34, 58, 32, 91, 10, 32, 32, 32, 32, - 34, 112, 121, 116, 104, 111, 110, 51, 32, 116, 101, 115, 116, 46, 112, 121, 34, 10, 32, - 32, 93, 10, 32, 32, 10, 125, + 123, 10, 32, 32, 34, 116, 121, 112, 101, 34, 58, 32, 34, 82, 101, 113, 117, 101, 115, + 116, 34, 44, 10, 32, 32, 34, 99, 111, 100, 101, 34, 58, 32, 34, 82, 117, 110, 34, 44, + 10, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 105, 100, + 34, 58, 32, 34, 52, 98, 102, 54, 56, 57, 55, 52, 45, 99, 51, 49, 53, 45, 52, 99, 52, + 49, 45, 97, 101, 101, 50, 45, 51, 100, 99, 50, 57, 50, 48, 101, 55, 54, 101, 57, 34, + 44, 10, 32, 32, 32, 32, 34, 102, 105, 108, 101, 115, 34, 58, 32, 91, 10, 32, 32, 32, + 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 102, 105, 108, 101, 110, 97, + 109, 101, 34, 58, 32, 34, 115, 114, 99, 47, 105, 110, 100, 101, 120, 46, 106, 115, 34, + 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 99, 111, 110, 116, 101, 110, 116, 34, 58, + 32, 34, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 40, 39, 72, 101, 108, 108, + 111, 32, 87, 111, 114, 108, 100, 33, 39, 41, 59, 34, 10, 32, 32, 32, 32, 32, 32, 125, + 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 115, 116, 101, 112, 115, 34, 58, + 32, 91, 10, 32, 32, 32, 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 99, + 111, 109, 109, 97, 110, 100, 34, 58, 32, 34, 110, 111, 100, 101, 32, 115, 114, 99, 47, + 105, 110, 100, 101, 120, 46, 106, 115, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, + 101, 110, 97, 98, 108, 101, 95, 111, 117, 116, 112, 117, 116, 34, 58, 32, 116, 114, + 117, 101, 10, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 93, 10, 32, 32, 125, 10, + 125, ]; - // let code_entry = internal_api.parse_json_payload(&data)?; - // assert_eq!(code_entry.files[0].filename, "test.py"); - // assert_eq!(code_entry.files[0].content, "print('Hello World')"); - // assert_eq!(code_entry.script[0], "python3 test.py"); - assert!(true); + let request_message = internal_api.parse_json_payload(&data)?; + + let files = vec![FileModel { + filename: "src/index.js".to_string(), + content: "console.log('Hello World!');".to_string(), + }]; + + let steps = vec![RequestStep { + command: "node src/index.js".to_string(), + enable_output: true, + }]; + + assert_eq!(request_message.r#type, Type::Request); + assert_eq!(request_message.code, Code::Run); + assert_eq!( + request_message.data.id, + "4bf68974-c315-4c41-aee2-3dc2920e76e9" + ); + assert_eq!(request_message.data.files[0], files[0]); + assert_eq!(request_message.data.steps[0], steps[0]); Ok(()) } @@ -199,80 +259,4 @@ mod tests { Ok(()) } - - #[test] - fn test_data_cut_before_delimiter() -> Result<()> { - let mut internal_api = ExternalApi::new("".to_string(), 0); - - let data = [97, 98, 99, 28, 1, 2, 3, 4, 5, 6, 7]; - let mut data_received: Vec = Vec::new(); - - let find_demiliter = - internal_api.append_data_before_delimiter(&data, &mut data_received)?; - - assert!(find_demiliter); - assert_eq!(data_received, [97, 98, 99]); - - Ok(()) - } - - #[test] - fn test_data_transferred_without_delimiter() -> Result<()> { - let mut internal_api = ExternalApi::new("".to_string(), 0); - - let data = [97, 98, 99, 1, 2, 3, 4, 5, 6, 7]; - let mut data_received: Vec = Vec::new(); - - let find_demiliter = - internal_api.append_data_before_delimiter(&data, &mut data_received)?; - - assert!(!find_demiliter); - assert_eq!(data_received, [97, 98, 99, 1, 2, 3, 4, 5, 6, 7]); - - Ok(()) - } - - #[test] - fn test_data_transferred_multiple_time() -> Result<()> { - let mut internal_api = ExternalApi::new("".to_string(), 0); - - let data = [97, 98, 99]; - let data2 = [1, 2, 3, 4, 5, 6, 7]; - let mut data_received: Vec = Vec::new(); - - let find_demiliter = - internal_api.append_data_before_delimiter(&data, &mut data_received)?; - let find_demiliter2 = - internal_api.append_data_before_delimiter(&data2, &mut data_received)?; - - assert!(!find_demiliter); - assert!(!find_demiliter2); - assert_eq!(data_received, [97, 98, 99, 1, 2, 3, 4, 5, 6, 7]); - - Ok(()) - } - - #[test] - fn test_data_transferred_with_delimiter() -> Result<()> { - let mut internal_api = ExternalApi::new("".to_string(), 0); - - let data = [97, 98, 99]; - let data2 = [1, 2, 3, 4, 5, 6, 7]; - let data3 = [8, 9, 10, 28, 11, 12, 13]; - let mut data_received: Vec = Vec::new(); - - let find_demiliter = - internal_api.append_data_before_delimiter(&data, &mut data_received)?; - let find_demiliter2 = - internal_api.append_data_before_delimiter(&data2, &mut data_received)?; - let find_demiliter3 = - internal_api.append_data_before_delimiter(&data3, &mut data_received)?; - - assert!(!find_demiliter); - assert!(!find_demiliter2); - assert!(find_demiliter3); - assert_eq!(data_received, [97, 98, 99, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - - Ok(()) - } } diff --git a/agent/lib/src/internal_api/service.rs b/agent/lib/src/internal_api/service.rs index 21b1bcb..662d04a 100644 --- a/agent/lib/src/internal_api/service.rs +++ b/agent/lib/src/internal_api/service.rs @@ -163,7 +163,7 @@ impl InternalApi { #[cfg(test)] mod tests { use super::*; - use crate::external_api::model::{CodeEntry, FileModel, RequestData, RequestStep}; + use crate::external_api::model::{FileModel, RequestData, RequestStep}; use std::fs::File; use std::io::Read; @@ -246,7 +246,7 @@ mod tests { //Check that the file contains the specified content let mut file = File::open(&path).unwrap(); let mut buffer = [0; 12]; - file.read(&mut buffer[..]).unwrap(); + file.read_exact(&mut buffer[..]).unwrap(); // Convert buffer to string let content = String::from_utf8(buffer.to_vec()).unwrap();