diff --git a/Cargo.toml b/Cargo.toml index 0df4dc4..8e98fdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ serde_json = "^1" reqwest = { version = "^0.11", features = ["json"]} url = "^2.2" ureq = { version = "^2", features = ["json"], optional = true} +base64 = "^0.13" [dev-dependencies] tokio = { version = "1.4.0", features = ["macros"]} diff --git a/src/robocraft/cubes.rs b/src/robocraft/cubes.rs new file mode 100644 index 0000000..a6c3717 --- /dev/null +++ b/src/robocraft/cubes.rs @@ -0,0 +1,189 @@ +use base64::{decode_config_buf, STANDARD}; +use std::io::Read; + +// TODO(maybe) parse iteratively instead of one-shot + +#[derive(Clone)] +pub struct Cubes { + pub provided_len: u32, + cubes: Vec, +} + +impl Cubes { + pub fn parse(cube_data: &mut Vec, colour_data: &mut Vec) -> Result { + // read first 4 bytes (cube count) from both arrays and make sure they match + let mut cube_buf = [0; 4]; + let mut colour_buf = [0; 4]; + let mut cube_slice = cube_data.as_slice(); + let mut colour_slice = colour_data.as_slice(); + if let Ok(len) = cube_slice.read(&mut cube_buf) { + if len != 4 { + //println!("Failed reading cube_data len"); + return Err(()); + } + } else { + //println!("Failed to read cube_data"); + return Err(()); + } + if let Ok(len) = colour_slice.read(&mut colour_buf) { + if len != 4 { + //println!("Failed reading colour_data len"); + return Err(()); + } + } else { + //println!("Failed to read colour_data"); + return Err(()); + } + if !(cube_buf[0] == colour_buf[0] + && cube_buf[1] == colour_buf[1] + && cube_buf[2] == colour_buf[2] + && cube_buf[3] == colour_buf[3]) { + //println!("Values do not match"); + return Err(()); + } + let mut cube_i = 4; + let mut colour_i = 4; + let mut parsed_cubes = Vec::with_capacity(cube_data.len() / 8); + while cube_i < cube_data.len() && colour_i < colour_data.len() { + let mut new_cube = Cube::default(); + if let Ok(cube_add) = new_cube.parse_cube_data(&mut cube_slice) { + if let Ok(colour_add) = new_cube.parse_colour_data(&mut colour_slice) { + cube_i += cube_add; + colour_i += colour_add; + parsed_cubes.push(new_cube); + } else { + // colour_data read error + return Err(()); + } + } else { + // cube_data read error + return Err(()); + } + } + Ok(Self { + provided_len: u32::from_le_bytes(cube_buf), + cubes: parsed_cubes, + }) + } + + pub fn dump(&self) -> (Vec, Vec) { + let mut cube_buf = Vec::new(); + let mut colour_buf = Vec::new(); + cube_buf.extend(&self.provided_len.to_le_bytes()); + colour_buf.extend(&self.provided_len.to_le_bytes()); + for c in self.into_iter() { + cube_buf.extend(&c.dump_cube_data()); + colour_buf.extend(&c.dump_colour_data()); + } + (cube_buf, colour_buf) + } + + pub fn len(&self) -> usize { + self.cubes.len() + } +} + +impl<'a> std::iter::IntoIterator for &'a Cubes { + type Item = &'a Cube; + + type IntoIter = std::slice::Iter<'a, Cube>; + + fn into_iter(self) -> Self::IntoIter { + self.cubes.iter() + } +} + +#[derive(Copy, Clone)] +pub struct Cube { + pub id: u32, + pub x: u8, // left to right + pub y: u8, // bottom to top + pub z: u8, // back to front + pub orientation: u8, + pub colour: u8, +} + +impl Cube { + fn parse_cube_data(&mut self, reader: &mut &[u8]) -> Result { + let mut buf = [0; 4]; + // read cube id + if let Ok(len) = reader.read(&mut buf) { + if len != 4 { + return Err(()); + } + self.id = u32::from_le_bytes(buf); + } else { + return Err(()); + } + // read x, y, z, orientation + if let Ok(len) = reader.read(&mut buf) { + if len != 4 { + return Err(()); + } + self.x = buf[0]; + self.y = buf[1]; + self.z = buf[2]; + self.orientation = buf[3]; + } else { + return Err(()); + } + Ok(8) + } + + fn parse_colour_data(&mut self, reader: &mut &[u8]) -> Result { + let mut buf = [0; 4]; + if let Ok(len) = reader.read(&mut buf) { + if len != 4 { + return Err(()); + } + self.colour = buf[0]; + } else { + return Err(()); + } + Ok(4) + } + + pub fn dump_cube_data(&self) -> [u8; 8] { + let id_buf = self.id.to_le_bytes(); + [id_buf[0], id_buf[1], id_buf[2], id_buf[3], self.x, self.y, self.z, self.orientation] + } + + pub fn dump_colour_data(&self) -> [u8; 4] { + [self.colour, self.x, self.y, self.z] + } +} + +impl std::default::Default for Cube { + fn default() -> Self { + Self { + id: 0, + x: 0, + y: 0, + z: 0, + orientation: 0, + colour: 0, + } + } +} + +impl std::convert::From for Cubes { + fn from(other: crate::robocraft::FactoryRobotGetInfo) -> Self { + let mut cube_buf = Vec::new(); + let mut colour_buf = Vec::new(); + decode_config_buf(other.cube_data, STANDARD, &mut cube_buf).unwrap(); + decode_config_buf(other.colour_data, STANDARD, &mut colour_buf).unwrap(); + Self::parse(&mut cube_buf, &mut colour_buf).unwrap() + } +} + +impl std::convert::From> for Cubes { + fn from(other: crate::robocraft::FactoryInfo) -> Self { + Self::from(other.response) + } +} + +impl std::string::ToString for Cube { + fn to_string(&self) -> String { + format!("{{x: {}, y: {}, z: {}}} ({})", self.x, self.y, self.z, self.id) + } +} diff --git a/src/robocraft/mod.rs b/src/robocraft/mod.rs index ba0c20b..1e548bb 100644 --- a/src/robocraft/mod.rs +++ b/src/robocraft/mod.rs @@ -7,6 +7,9 @@ pub use self::factory_request_builder::{FactorySearchBuilder, FactoryMovementTyp #[cfg(feature = "simple")] pub(crate) use self::factory_json::{ListPayload}; +mod cubes; +pub use self::cubes::{Cube, Cubes}; + mod auth; pub use self::auth::{ITokenProvider, DefaultTokenProvider}; diff --git a/tests/robocraft_factory.rs b/tests/robocraft_factory.rs index 8a26b91..02f1c3e 100644 --- a/tests/robocraft_factory.rs +++ b/tests/robocraft_factory.rs @@ -1,4 +1,5 @@ use libfj::robocraft; +use std::convert::From; #[test] fn robocraft_factory_api_init() -> Result<(), ()> { @@ -80,7 +81,7 @@ async fn robocraft_factory_player_query() -> Result<(), ()> { #[tokio::test] async fn robocraft_factory_robot_query() -> Result<(), ()> { let api = robocraft::FactoryAPI::new(); - let result = api.get(6478345 /* featured robot id*/).await; + let result = api.get(6478345 /* featured robot id */).await; assert!(result.is_ok()); let bot_info = result.unwrap(); assert_ne!(bot_info.response.item_name, ""); @@ -89,3 +90,22 @@ async fn robocraft_factory_robot_query() -> Result<(), ()> { assert_ne!(bot_info.response.colour_data, ""); Ok(()) } + +#[tokio::test] +async fn robocraft_factory_robot_cubes() -> Result<(), ()> { + let api = robocraft::FactoryAPI::new(); + let result = api.get(6478345 /* featured robot id */).await; + assert!(result.is_ok()); + let bot_info = result.unwrap(); + let cubes = robocraft::Cubes::from(bot_info.clone()); + println!("cube count: {} or {}", cubes.provided_len, cubes.len()); + /*for c in cubes.into_iter() { + println!("Cube.to_string() -> `{}`", c.to_string()); + }*/ + let (cube_d, colour_d) = cubes.dump(); + let cube_str = base64::encode(&cube_d); + let colour_str = base64::encode(&colour_d); + assert_eq!(cube_str, bot_info.response.cube_data); + assert_eq!(colour_str, bot_info.response.colour_data); + Ok(()) +}