use serde_json::Value;
use std::collections::HashMap;
use crate::constants::*;
use crate::error::{NeutralIpcError, Result};
#[derive(Debug, Clone)]
pub(crate) struct NeutralIpcRecord;
impl NeutralIpcRecord {
pub(crate) fn decode_header(record_header: &[u8]) -> Result<HashMap<String, Value>> {
if record_header.len() != HEADER_LEN {
return Err(NeutralIpcError::InvalidHeaderLength);
}
let reserved = record_header[0];
let control = record_header[1];
let format1 = record_header[2];
let length1 = u32::from_be_bytes([
record_header[3], record_header[4], record_header[5], record_header[6]
]);
let format2 = record_header[7];
let length2 = u32::from_be_bytes([
record_header[8], record_header[9], record_header[10], record_header[11]
]);
let mut header = HashMap::new();
header.insert("reserved".to_string(), Value::Number(reserved.into()));
header.insert("control".to_string(), Value::Number(control.into()));
header.insert("format-1".to_string(), Value::Number(format1.into()));
header.insert("length-1".to_string(), Value::Number(length1.into()));
header.insert("format-2".to_string(), Value::Number(format2.into()));
header.insert("length-2".to_string(), Value::Number(length2.into()));
Ok(header)
}
pub(crate) fn encode_header(control: u8, format1: u8, length1: u32, format2: u8, length2: u32) -> Vec<u8> {
let mut header = Vec::with_capacity(HEADER_LEN);
header.push(RESERVED);
header.push(control);
header.push(format1);
header.extend_from_slice(&length1.to_be_bytes());
header.push(format2);
header.extend_from_slice(&length2.to_be_bytes());
header
}
pub(crate) fn encode_record(control: u8, format1: u8, content1: &[u8], format2: u8, content2: &[u8]) -> Vec<u8> {
let content1_bytes = content1;
let content2_bytes = content2;
let length1 = content1_bytes.len() as u32;
let length2 = content2_bytes.len() as u32;
let mut record = Self::encode_header(control, format1, length1, format2, length2);
record.extend_from_slice(content1_bytes);
record.extend_from_slice(content2_bytes);
record
}
pub(crate) fn decode_record(header: &[u8], content1: &str, content2: &str) -> Result<HashMap<String, Value>> {
let _header_map = Self::decode_header(header)?;
let mut record = HashMap::new();
record.insert("reserved".to_string(), Value::Number(RESERVED.into()));
record.insert("control".to_string(), Value::Number(header[1].into()));
record.insert("format-1".to_string(), Value::Number(header[2].into()));
record.insert("content-1".to_string(), Value::String(content1.to_string()));
record.insert("format-2".to_string(), Value::Number(header[7].into()));
record.insert("content-2".to_string(), Value::String(content2.to_string()));
Ok(record)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_encode_record_with_msgpack_content_sets_lengths_and_formats() {
let schema = json!({
"data": {
"text": "Hello",
"number": 123
}
});
let content1 = rmp_serde::to_vec(&schema).unwrap();
let content2 = b"Rust IPC client: {:;text:} {:;number:}";
let record = NeutralIpcRecord::encode_record(
CTRL_PARSE_TEMPLATE,
CONTENT_MSGPACK,
&content1,
CONTENT_TEXT,
content2,
);
assert_eq!(record.len(), HEADER_LEN + content1.len() + content2.len());
assert_eq!(record[0], RESERVED);
assert_eq!(record[1], CTRL_PARSE_TEMPLATE);
assert_eq!(record[2], CONTENT_MSGPACK);
assert_eq!(record[7], CONTENT_TEXT);
let len1 = u32::from_be_bytes([record[3], record[4], record[5], record[6]]) as usize;
let len2 = u32::from_be_bytes([record[8], record[9], record[10], record[11]]) as usize;
assert_eq!(len1, content1.len());
assert_eq!(len2, content2.len());
assert_eq!(&record[HEADER_LEN..HEADER_LEN + content1.len()], content1.as_slice());
assert_eq!(&record[HEADER_LEN + content1.len()..], content2);
}
#[test]
fn test_decode_header_reads_msgpack_format() {
let header = NeutralIpcRecord::encode_header(
CTRL_PARSE_TEMPLATE,
CONTENT_MSGPACK,
42,
CONTENT_TEXT,
8,
);
let decoded = NeutralIpcRecord::decode_header(&header).unwrap();
assert_eq!(decoded.get("format-1").and_then(|v| v.as_u64()), Some(CONTENT_MSGPACK as u64));
assert_eq!(decoded.get("length-1").and_then(|v| v.as_u64()), Some(42));
assert_eq!(decoded.get("format-2").and_then(|v| v.as_u64()), Some(CONTENT_TEXT as u64));
assert_eq!(decoded.get("length-2").and_then(|v| v.as_u64()), Some(8));
}
}