use super::{ty, BlockError, BlockHeader, CmError, Cursor};
use crate::rpc::{Drep, Uuid};
pub mod cmd {
pub const PRM_END: u16 = 0x0001;
pub const APPLICATION_READY: u16 = 0x0002;
pub const RELEASE: u16 = 0x0004;
pub const DONE: u16 = 0x0008;
}
const BODY_LEN: usize = 26;
#[derive(Debug, Clone, PartialEq)]
pub struct ControlBlock {
pub block_type: u16,
pub ar_uuid: Uuid,
pub session_key: u16,
pub command: u16,
pub properties: u16,
}
impl ControlBlock {
pub fn parse(blocks: &[u8]) -> Result<ControlBlock, CmError> {
let (header, body) = BlockHeader::parse(blocks)?;
if !is_control_type(header.block_type) {
return Err(CmError::Block(BlockError::UnexpectedType {
expected: ty::IOD_CONTROL_REQ_PRM_END,
got: header.block_type,
}));
}
if body.len() != BODY_LEN {
return Err(CmError::Block(BlockError::Malformed(
"control block body length",
)));
}
let mut c = Cursor::new(body);
let _reserved_1 = c.u16()?;
let ar_uuid = c.uuid()?;
let session_key = c.u16()?;
let _reserved_2 = c.u16()?;
let command = c.u16()?;
let properties = c.u16()?;
Ok(ControlBlock {
block_type: header.block_type,
ar_uuid,
session_key,
command,
properties,
})
}
pub fn write(&self, out: &mut Vec<u8>) {
BlockHeader::write(out, self.block_type, BODY_LEN as u16);
out.extend_from_slice(&0u16.to_be_bytes()); self.ar_uuid.write(out, Drep::BIG);
out.extend_from_slice(&self.session_key.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes()); out.extend_from_slice(&self.command.to_be_bytes());
out.extend_from_slice(&self.properties.to_be_bytes());
}
}
fn is_control_type(t: u16) -> bool {
matches!(
t,
ty::IOD_CONTROL_REQ_PRM_END
| ty::IOD_CONTROL_RES_PRM_END
| ty::IOX_BLOCK_REQ_APP_READY
| ty::IOX_BLOCK_RES_APP_READY
| ty::RELEASE_BLOCK_REQ
| ty::RELEASE_BLOCK_RES
)
}
pub fn prm_end_done(req: &ControlBlock) -> ControlBlock {
ControlBlock {
block_type: ty::IOD_CONTROL_RES_PRM_END,
ar_uuid: req.ar_uuid,
session_key: req.session_key,
command: cmd::DONE,
properties: 0,
}
}
pub fn app_ready_req(ar_uuid: Uuid, session_key: u16) -> ControlBlock {
ControlBlock {
block_type: ty::IOX_BLOCK_REQ_APP_READY,
ar_uuid,
session_key,
command: cmd::APPLICATION_READY,
properties: 0,
}
}
pub fn release_done(req: &ControlBlock) -> ControlBlock {
ControlBlock {
block_type: ty::RELEASE_BLOCK_RES,
ar_uuid: req.ar_uuid,
session_key: req.session_key,
command: cmd::DONE,
properties: 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cm::block::ty;
use crate::testutil::golden;
const BLOCKS: usize = 142;
#[test]
fn parse_prm_end_and_answer_byte_exact() {
let req = ControlBlock::parse(&golden("prmend_req")[BLOCKS..]).unwrap();
assert_eq!(req.block_type, ty::IOD_CONTROL_REQ_PRM_END);
assert_eq!(
(req.session_key, req.command, req.properties),
(2, cmd::PRM_END, 0)
);
let mut out = Vec::new();
prm_end_done(&req).write(&mut out);
assert_eq!(out, &golden("prmend_res")[BLOCKS..]);
assert_eq!(out.len(), 32);
}
#[test]
fn app_ready_request_byte_exact() {
let req = ControlBlock::parse(&golden("prmend_req")[BLOCKS..]).unwrap();
let mut out = Vec::new();
app_ready_req(req.ar_uuid, req.session_key).write(&mut out);
assert_eq!(out, &golden("appready_req")[BLOCKS..]);
}
#[test]
fn parse_app_ready_response_from_cpu() {
let res = ControlBlock::parse(&golden("appready_res")[BLOCKS..]).unwrap();
assert_eq!(res.block_type, ty::IOX_BLOCK_RES_APP_READY);
assert_eq!(res.command, cmd::DONE);
assert_eq!(
res.ar_uuid.to_string(),
"e5e1aecc-b133-4b4d-b187-cc68b0211ed2"
);
}
#[test]
fn rejects_non_control_block() {
assert!(matches!(
ControlBlock::parse(&golden("write_req")[BLOCKS..]),
Err(CmError::Block(BlockError::UnexpectedType { .. }))
));
}
}