1pub mod ar;
6pub mod block;
7pub mod connect;
8pub mod control;
9pub mod model;
10pub mod records;
11pub mod status;
12pub mod write;
13
14pub use ar::{
15 AbortReason, Action, Ar, ArContext, ArState, Event, ACTIVITY_TIMEOUT_UNIT,
16 APP_READY_MAX_ATTEMPTS, APP_READY_TIMEOUT,
17};
18pub use block::{
19 ty, AlarmCrBlockReq, ArBlockReq, BlockHeader, Cursor, DataDescription, ExpectedApi,
20 ExpectedSubmodule, ExpectedSubmoduleBlockReq, IocrApi, IocrBlockReq, IocrObject,
21};
22pub use connect::{build_connect_res, validate, ArParams, ConnectReq, IocrParams};
23pub use control::{app_ready_req, cmd, prm_end_done, release_done, ControlBlock};
24pub use model::{DeviceModel, SlotModel, SubmoduleModel};
25pub use records::{build_read_res, read_record, write_im_record, ReadReq, RecordCtx};
26pub use status::{ConnectBlock, PnioStatus};
27pub use write::{build_write_res, Record, WriteReq, INDEX_MULTIPLE_WRITE};
28
29use crate::im::{Im0, ImStore, INDEX_IM1, INDEX_IM3};
30use crate::rpc::{
31 Drep, NdrRequest, NdrResponse, Opnum, PacketType, RpcError, RpcHeader, Uuid, FLAG1_IDEMPOTENT,
32 FLAG1_NO_FACK, PNIO_CONTROLLER_INTERFACE, PNIO_DEVICE_INTERFACE, PNIO_UDP_PORT,
33};
34use std::collections::VecDeque;
35use std::net::SocketAddr;
36use std::time::Instant;
37use thiserror::Error;
38
39#[derive(Debug, Error, PartialEq, Eq)]
41pub enum BlockError {
42 #[error("block buffer too short: need {need}, have {have}")]
44 TooShort {
45 need: usize,
47 have: usize,
49 },
50 #[error("unexpected block type: expected {expected:#06x}, got {got:#06x}")]
52 UnexpectedType {
53 expected: u16,
55 got: u16,
57 },
58 #[error("bad block version {0}.{1} (expected 1.0)")]
60 BadVersion(u8, u8),
61 #[error("bad block length: declared {declared}, available {available}")]
63 BadLength {
64 declared: u16,
66 available: usize,
68 },
69 #[error("malformed block: {0}")]
72 Malformed(&'static str),
73}
74
75#[derive(Debug, Error, PartialEq, Eq)]
77pub enum CmError {
78 #[error("block error: {0}")]
80 Block(#[from] BlockError),
81 #[error("connect rejected: {0:?}")]
83 Reject(PnioStatus),
84}
85
86pub const RPC_ARGS_MAX: u32 = 1340;
93
94const CACHE_CAPACITY: usize = 4;
96
97#[derive(Debug, Clone, PartialEq)]
99pub struct Outgoing {
100 pub bytes: Vec<u8>,
102 pub to: SocketAddr,
104}
105
106#[derive(Debug, Default, PartialEq)]
109pub struct CmOutput {
110 pub send: Vec<Outgoing>,
112 pub notify: Vec<(ArState, Option<AbortReason>)>,
115}
116
117struct RespondCtx {
120 object: Uuid,
121 interface: Uuid,
122 activity: Uuid,
123 seq_num: u32,
124 opnum: u16,
125 args_max: u32,
126 from: SocketAddr,
127}
128
129fn error_status(e: &CmError, default: PnioStatus) -> PnioStatus {
133 match e {
134 CmError::Reject(status) => *status,
135 CmError::Block(be) => {
136 log::warn!("PNIO block parse error: {be}");
137 default
138 }
139 }
140}
141
142fn respond(status: PnioStatus) -> Vec<Action> {
143 vec![Action::Respond {
144 status,
145 blocks: Vec::new(),
146 }]
147}
148
149fn respond_ok(blocks: Vec<u8>) -> Vec<Action> {
150 vec![Action::Respond {
151 status: PnioStatus::OK,
152 blocks,
153 }]
154}
155
156pub struct Cm {
163 ar: Ar,
164 model: DeviceModel,
167 im0: Im0,
170 im: ImStore,
173 activity_seed: Uuid,
175 call_seq: u32,
177 current_call_seq: Option<u32>,
179 controller_addr: Option<SocketAddr>,
182 cache: VecDeque<((Uuid, u32), Vec<u8>)>,
185}
186
187impl Cm {
188 pub fn new(model: DeviceModel, activity_seed: Uuid) -> Cm {
192 Cm {
193 model: model.clone(),
194 ar: Ar::new(model),
195 im0: Im0::default(),
196 im: ImStore::new(),
197 activity_seed,
198 call_seq: 0,
199 current_call_seq: None,
200 controller_addr: None,
201 cache: VecDeque::new(),
202 }
203 }
204
205 pub fn state(&self) -> ArState {
207 self.ar.state()
208 }
209
210 pub fn context(&self) -> Option<&ArContext> {
212 self.ar.context()
213 }
214
215 pub fn next_deadline(&self) -> Option<Instant> {
218 self.ar.next_deadline()
219 }
220
221 pub fn set_im(&mut self, im0: Im0, store: ImStore) {
225 self.im0 = im0;
226 self.im = store;
227 }
228
229 pub fn im_store(&self) -> &ImStore {
232 &self.im
233 }
234
235 pub fn handle_datagram(
241 &mut self,
242 buf: &[u8],
243 from: SocketAddr,
244 now: Instant,
245 ) -> Result<CmOutput, RpcError> {
246 let h = RpcHeader::parse(buf)?;
247 let body = &buf[RpcHeader::LEN..];
248 let mut output = CmOutput::default();
249
250 if h.ptype == PacketType::Response {
251 let (n, _blocks) = NdrResponse::parse(body, h.drep)?;
253 let actions = self.ar.on(
254 Event::AppReadyRsp {
255 status: PnioStatus(n.status),
256 },
257 now,
258 );
259 self.apply(actions, None, false, &mut output);
260 return Ok(output);
261 }
262 if h.ptype != PacketType::Request {
263 log::warn!(
264 "dropping DCE-RPC packet type {:?}, only Request/Response handled",
265 h.ptype
266 );
267 return Ok(output);
268 }
269
270 if h.interface != PNIO_DEVICE_INTERFACE {
271 return Err(RpcError::BadInterface(h.interface));
272 }
273
274 let cache_key = (h.activity, h.seq_num);
275 if let Some(bytes) = self.cache_lookup(&cache_key) {
276 log::debug!(
277 "cache hit for activity {} seq_num {}: resending cached response",
278 h.activity,
279 h.seq_num
280 );
281 output.send.push(Outgoing { bytes, to: from });
282 return Ok(output);
283 }
284
285 let (req, blocks) = NdrRequest::parse(body, h.drep)?;
286
287 let actions = match Opnum::from_u16(h.opnum) {
288 Some(Opnum::Connect) => {
289 let actions = match ConnectReq::parse(blocks) {
290 Ok(req) => self.ar.on(Event::ConnectReq(req), now),
291 Err(e) => respond(error_status(
292 &e,
293 PnioStatus::connect_reject(ConnectBlock::ArBlock, 0xfe),
294 )),
295 };
296 if actions.iter().any(|a| {
307 matches!(
308 a,
309 Action::Notify {
310 state: ArState::Connected,
311 ..
312 }
313 )
314 }) {
315 self.controller_addr = Some(SocketAddr::new(from.ip(), PNIO_UDP_PORT));
316 self.current_call_seq = None;
324 }
325 actions
326 }
327 Some(Opnum::Write) => match WriteReq::parse(blocks) {
328 Ok(req) => {
329 let records = req.records.clone();
334 let actions = self.ar.on(Event::WriteReq(req), now);
335 let accepted = actions
336 .iter()
337 .any(|a| matches!(a, Action::Respond { status, .. } if status.is_ok()));
338 if accepted {
339 for r in &records {
340 if (INDEX_IM1..=INDEX_IM3).contains(&r.index) {
341 let status = write_im_record(r, &self.model, &mut self.im);
342 if !status.is_ok() {
343 log::warn!(
344 "I&M write rejected: index {:#06x} slot {} \
345 subslot {} status {:#010x}",
346 r.index,
347 r.slot,
348 r.subslot,
349 status.to_u32()
350 );
351 }
352 }
353 }
354 }
355 actions
356 }
357 Err(e) => respond(error_status(&e, PnioStatus::write_index_unsupported())),
358 },
359 Some(Opnum::Control) => match ControlBlock::parse(blocks) {
360 Ok(cb) if cb.command == cmd::PRM_END => self.ar.on(Event::PrmEndReq(cb), now),
361 Ok(_) => respond(PnioStatus::control_wrong_state()),
362 Err(e) => respond(error_status(&e, PnioStatus::control_wrong_state())),
363 },
364 Some(Opnum::Release) => match ControlBlock::parse(blocks) {
365 Ok(cb) => self.ar.on(Event::ReleaseReq(cb), now),
366 Err(e) => respond(error_status(&e, PnioStatus::control_wrong_state())),
367 },
368 Some(Opnum::Read) => match ReadReq::parse(blocks) {
376 Ok(req) => self.handle_read(&req, true),
377 Err(e) => respond(error_status(&e, PnioStatus::read_index_unsupported())),
378 },
379 Some(Opnum::ReadImplicit) => match ReadReq::parse(blocks) {
380 Ok(req) => self.handle_read(&req, false),
381 Err(e) => respond(error_status(&e, PnioStatus::read_index_unsupported())),
382 },
383 None => respond(PnioStatus::service_unsupported()),
384 };
385
386 let ctx = RespondCtx {
387 object: h.object,
388 interface: h.interface,
389 activity: h.activity,
390 seq_num: h.seq_num,
391 opnum: h.opnum,
392 args_max: req.args_max,
393 from,
394 };
395 self.apply(actions, Some((ctx, cache_key)), true, &mut output);
396 Ok(output)
397 }
398
399 pub fn tick(&mut self, now: Instant) -> CmOutput {
402 let mut output = CmOutput::default();
403 let actions = self.ar.on(Event::Tick, now);
404 self.apply(actions, None, false, &mut output);
405 output
406 }
407
408 pub fn abort(&mut self, reason: AbortReason, now: Instant) -> CmOutput {
412 let mut output = CmOutput::default();
413 let actions = self.ar.on(Event::Abort(reason), now);
414 self.apply(actions, None, false, &mut output);
415 output
416 }
417
418 fn apply(
423 &mut self,
424 actions: Vec<Action>,
425 resp_ctx: Option<(RespondCtx, (Uuid, u32))>,
426 call_is_new: bool,
427 output: &mut CmOutput,
428 ) {
429 for action in actions {
430 match action {
431 Action::Respond { status, blocks } => match &resp_ctx {
432 Some((ctx, cache_key)) => {
433 let pdu = build_response_pdu(ctx, status, blocks);
434 self.cache_insert(*cache_key, pdu.clone());
435 output.send.push(Outgoing {
436 bytes: pdu,
437 to: ctx.from,
438 });
439 }
440 None => {
441 log::warn!("Respond action with no request context; dropping");
442 }
443 },
444 Action::CallController { blocks } => {
445 let to = self
446 .controller_addr
447 .expect("controller_addr set at Connect before any AR can reach PrmEnd");
448 let pdu = self.build_call_pdu(blocks, call_is_new);
449 output.send.push(Outgoing { bytes: pdu, to });
450 }
451 Action::Notify { state, reason } => output.notify.push((state, reason)),
452 }
453 }
454 }
455
456 fn build_call_pdu(&mut self, blocks: Vec<u8>, is_new: bool) -> Vec<u8> {
459 let object = self
460 .ar
461 .context()
462 .expect("ctx present for CallController action")
463 .params
464 .initiator_object_uuid;
465 let seq_num = if is_new {
466 let seq_num = self.call_seq;
467 self.call_seq += 1;
468 self.current_call_seq = Some(seq_num);
469 seq_num
470 } else {
471 self.current_call_seq
472 .expect("retry call with no prior new call")
473 };
474 let header = RpcHeader {
475 ptype: PacketType::Request,
476 flags1: FLAG1_IDEMPOTENT,
477 flags2: 0,
478 drep: Drep::BIG,
479 serial_hi: 0,
480 object,
481 interface: PNIO_CONTROLLER_INTERFACE,
482 activity: self.activity_seed,
483 server_boot: 0,
484 if_version: 1,
485 seq_num,
486 opnum: Opnum::Control.to_u16(),
487 ihint: 0xffff,
488 ahint: 0xffff,
489 frag_len: (NdrRequest::LEN + blocks.len()) as u16,
490 frag_num: 0,
491 auth_proto: 0,
492 serial_lo: 0,
493 };
494 let ndr = NdrRequest::for_blocks(RPC_ARGS_MAX, blocks.len() as u32);
495 let mut pdu = Vec::with_capacity(RpcHeader::LEN + NdrRequest::LEN + blocks.len());
496 header.write(&mut pdu);
497 ndr.write(&mut pdu, Drep::BIG);
498 pdu.extend_from_slice(&blocks);
499 pdu
500 }
501
502 fn handle_read(&self, req: &ReadReq, check_ar: bool) -> Vec<Action> {
505 if check_ar {
506 let matches_ar = match self.ar.context() {
507 Some(ctx) => ctx.params.ar_uuid == req.ar_uuid,
508 None => false,
509 };
510 if !matches_ar {
511 return respond(PnioStatus::read_wrong_ar());
512 }
513 }
514 let ctx = RecordCtx {
515 model: &self.model,
516 im0: &self.im0,
517 im: &self.im,
518 };
519 match read_record(req, &ctx) {
520 Some(data) => respond_ok(build_read_res(req, &data)),
521 None => respond(PnioStatus::read_index_unsupported()),
522 }
523 }
524
525 fn cache_lookup(&self, key: &(Uuid, u32)) -> Option<Vec<u8>> {
526 self.cache
527 .iter()
528 .find(|(k, _)| k == key)
529 .map(|(_, pdu)| pdu.clone())
530 }
531
532 fn cache_insert(&mut self, key: (Uuid, u32), pdu: Vec<u8>) {
533 if self.cache.len() >= CACHE_CAPACITY {
534 self.cache.pop_front();
535 }
536 self.cache.push_back((key, pdu));
537 }
538}
539
540fn build_response_pdu(ctx: &RespondCtx, status: PnioStatus, blocks: Vec<u8>) -> Vec<u8> {
544 let out_blocks = if status.is_ok() { blocks } else { Vec::new() };
545 let header = RpcHeader {
546 ptype: PacketType::Response,
547 flags1: FLAG1_IDEMPOTENT | FLAG1_NO_FACK,
548 flags2: 0,
549 drep: Drep::BIG,
550 serial_hi: 0,
551 object: ctx.object,
552 interface: ctx.interface,
553 activity: ctx.activity,
554 server_boot: 0,
555 if_version: 1,
556 seq_num: ctx.seq_num,
557 opnum: ctx.opnum,
558 ihint: 0xffff,
559 ahint: 0xffff,
560 frag_len: (NdrResponse::LEN + out_blocks.len()) as u16,
561 frag_num: 0,
562 auth_proto: 0,
563 serial_lo: 0,
564 };
565 let ndr = if status.is_ok() {
566 NdrResponse::ok(ctx.args_max, out_blocks.len() as u32)
567 } else {
568 NdrResponse::error(status.to_u32(), ctx.args_max)
569 };
570 let mut pdu = Vec::with_capacity(RpcHeader::LEN + NdrResponse::LEN + out_blocks.len());
571 header.write(&mut pdu);
572 ndr.write(&mut pdu, Drep::BIG);
573 pdu.extend_from_slice(&out_blocks);
574 pdu
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580 use crate::cm::model::DeviceModel;
581 use crate::eth::MacAddr;
582 use crate::im::SwRevision;
583 use crate::rpc::Uuid;
584 use crate::testutil::{golden, golden_alarm, RPC_OFF};
585 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
586 use std::time::Instant;
587
588 fn retag_ar_uuid(mut bytes: Vec<u8>, old: Uuid, new: Uuid) -> Vec<u8> {
592 let mut old_b = Vec::new();
593 old.write(&mut old_b, Drep::BIG);
594 let mut new_b = Vec::new();
595 new.write(&mut new_b, Drep::BIG);
596 let mut i = 0;
597 while i + 16 <= bytes.len() {
598 if bytes[i..i + 16] == old_b[..] {
599 bytes[i..i + 16].copy_from_slice(&new_b);
600 }
601 i += 1;
602 }
603 bytes
604 }
605
606 const MAC: MacAddr = MacAddr([0x8c, 0xf3, 0x19, 0xcd, 0x19, 0xf8]);
607 fn cpu() -> SocketAddr {
608 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(172, 16, 2, 100)), 54766)
609 }
610 fn cpu_cm() -> SocketAddr {
611 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(172, 16, 2, 100)), 34964)
612 }
613 fn cm() -> Cm {
614 Cm::new(
615 DeviceModel::pnet_sample(MAC),
616 Uuid::parse_str("14af198a-1234-1056-8079-8cf319cd19f8").unwrap(),
617 )
618 }
619 fn pdu(name: &str) -> Vec<u8> {
620 golden(name)[RPC_OFF..].to_vec()
621 }
622
623 #[test]
624 fn full_exchange_is_byte_exact_including_rpc_headers() {
625 let mut cm = cm();
626 let now = Instant::now();
627 let o = cm.handle_datagram(&pdu("connect_req"), cpu(), now).unwrap();
628 assert_eq!(o.send.len(), 1);
629 assert_eq!(o.send[0].bytes, pdu("connect_res"));
630 assert_eq!(o.send[0].to, cpu());
631 assert_eq!(o.notify, vec![(ArState::Connected, None)]);
632 let o = cm.handle_datagram(&pdu("write_req"), cpu(), now).unwrap();
633 assert_eq!(o.send[0].bytes, pdu("write_res"));
634 let o = cm.handle_datagram(&pdu("prmend_req"), cpu(), now).unwrap();
635 assert_eq!(o.send[0].bytes, pdu("prmend_res"));
636 assert_eq!(o.send[1].bytes, pdu("appready_req"));
637 assert_eq!(o.send[1].to, cpu_cm());
638 assert_eq!(cm.state(), ArState::AppReadySent);
639 let o = cm
640 .handle_datagram(&pdu("appready_res"), cpu_cm(), now)
641 .unwrap();
642 assert!(o.send.is_empty());
643 assert_eq!(o.notify, vec![(ArState::Data, None)]);
644 assert_eq!(cm.state(), ArState::Data);
645 }
646
647 #[test]
648 fn retransmitted_request_gets_cached_response() {
649 let mut cm = cm();
650 let now = Instant::now();
651 let first = cm.handle_datagram(&pdu("connect_req"), cpu(), now).unwrap();
652 let again = cm.handle_datagram(&pdu("connect_req"), cpu(), now).unwrap();
653 assert_eq!(again.send[0].bytes, first.send[0].bytes);
654 assert!(again.notify.is_empty());
655 }
656
657 #[test]
658 fn stray_connect_does_not_redirect_app_ready() {
659 let mut cm = cm();
660 let now = Instant::now();
661 let o = cm.handle_datagram(&pdu("connect_req"), cpu(), now).unwrap();
662 assert_eq!(o.notify, vec![(ArState::Connected, None)]);
663
664 let mut stray = pdu("connect_req");
670 stray[64] = 1; stray[108..124].copy_from_slice(&[0x11; 16]); stray[132..148].copy_from_slice(&[0x22; 16]); let stray_from = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(172, 16, 2, 77)), 40000);
674 let o = cm.handle_datagram(&stray, stray_from, now).unwrap();
675 let (n, _) =
676 crate::rpc::NdrResponse::parse(&o.send[0].bytes[80..], crate::rpc::Drep::BIG).unwrap();
677 assert_eq!(
678 PnioStatus(n.status),
679 PnioStatus::connect_ar_already_exists()
680 );
681 assert_eq!(cm.state(), ArState::Connected);
682
683 let o = cm.handle_datagram(&pdu("prmend_req"), cpu(), now).unwrap();
686 assert_eq!(o.send[1].bytes, pdu("appready_req"));
687 assert_eq!(o.send[1].to, cpu_cm());
688 }
689
690 #[test]
691 fn unsupported_opnum_gets_error_status_response() {
692 let mut cm = cm();
693 let mut read = pdu("prmend_req");
694 read[68] = 2; let o = cm.handle_datagram(&read, cpu(), Instant::now()).unwrap();
696 let h = crate::rpc::RpcHeader::parse(&o.send[0].bytes).unwrap();
697 assert_eq!(h.ptype, crate::rpc::PacketType::Response);
698 assert_eq!(h.opnum, 2);
699 let (n, blocks) =
700 crate::rpc::NdrResponse::parse(&o.send[0].bytes[80..], crate::rpc::Drep::BIG).unwrap();
701 assert_eq!(PnioStatus(n.status), PnioStatus::read_index_unsupported());
702 assert!(blocks.is_empty());
703 }
704
705 #[test]
706 fn unknown_opnum_gets_service_unsupported_status() {
707 let mut cm = cm();
708 let mut bad = pdu("prmend_req");
709 bad[68] = 99; let o = cm.handle_datagram(&bad, cpu(), Instant::now()).unwrap();
711 let (n, blocks) =
712 crate::rpc::NdrResponse::parse(&o.send[0].bytes[80..], crate::rpc::Drep::BIG).unwrap();
713 assert_eq!(PnioStatus(n.status), PnioStatus::service_unsupported());
714 assert!(blocks.is_empty());
715 }
716
717 #[test]
718 fn rejected_connect_has_error_status_and_no_blocks() {
719 let mut cm = Cm::new(
720 {
721 let mut m = DeviceModel::pnet_sample(MAC);
722 m.slots.pop();
723 m
724 },
725 Uuid::NIL,
726 );
727 let o = cm
728 .handle_datagram(&pdu("connect_req"), cpu(), Instant::now())
729 .unwrap();
730 let (n, blocks) =
731 crate::rpc::NdrResponse::parse(&o.send[0].bytes[80..], crate::rpc::Drep::BIG).unwrap();
732 assert_eq!(
733 PnioStatus(n.status),
734 PnioStatus::connect_reject(ConnectBlock::ExpectedSubmodule, 7)
735 );
736 assert!(blocks.is_empty());
737 assert_eq!(cm.state(), ArState::Idle);
738 }
739
740 #[test]
741 fn wrong_interface_and_garbage_are_errors_not_panics() {
742 let mut cm = cm();
743 let mut bad = pdu("connect_req");
744 bad[24] = 0xff;
745 assert!(matches!(
746 cm.handle_datagram(&bad, cpu(), Instant::now()),
747 Err(RpcError::BadInterface(_))
748 ));
749 assert!(matches!(
750 cm.handle_datagram(&[1, 2, 3], cpu(), Instant::now()),
751 Err(RpcError::TooShort { .. })
752 ));
753 }
754
755 #[test]
756 fn reconnect_updates_controller_addr() {
757 let mut cm = cm();
758 let now = Instant::now();
759 let o = cm.handle_datagram(&pdu("connect_req"), cpu(), now).unwrap();
760 assert_eq!(o.notify, vec![(ArState::Connected, None)]);
761 cm.handle_datagram(&pdu("write_req"), cpu(), now).unwrap();
762 cm.handle_datagram(&pdu("prmend_req"), cpu(), now).unwrap();
763 let o = cm
764 .handle_datagram(&pdu("appready_res"), cpu_cm(), now)
765 .unwrap();
766 assert_eq!(o.notify, vec![(ArState::Data, None)]);
767 assert_eq!(cm.state(), ArState::Data);
768
769 let mut reconnect = pdu("connect_req");
776 reconnect[64] = 3; reconnect[124..126].copy_from_slice(&5u16.to_be_bytes());
778 let reconnect_from = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(172, 16, 2, 100)), 54800);
779 let o = cm.handle_datagram(&reconnect, reconnect_from, now).unwrap();
780 assert_eq!(
781 o.notify,
782 vec![
783 (ArState::Idle, Some(AbortReason::ControllerReconnect)),
784 (ArState::Connected, None),
785 ]
786 );
787 assert_eq!(o.send.len(), 1);
788 let (n, _) =
789 crate::rpc::NdrResponse::parse(&o.send[0].bytes[80..], crate::rpc::Drep::BIG).unwrap();
790 assert!(PnioStatus(n.status).is_ok());
791
792 let mut prmend = pdu("prmend_req");
799 prmend[64] = 4; prmend[124..126].copy_from_slice(&5u16.to_be_bytes());
804 let o = cm.handle_datagram(&prmend, cpu(), now).unwrap();
805 assert_eq!(o.send[1].to, cpu_cm());
806 let mut expected = pdu("appready_req");
816 expected[124..126].copy_from_slice(&5u16.to_be_bytes());
817 assert_eq!(o.send[1].bytes[..64], expected[..64]);
818 assert_eq!(o.send[1].bytes[68..124], expected[68..124]);
819 assert_eq!(o.send[1].bytes[126..], expected[126..]);
820 assert_eq!(
821 u32::from_be_bytes(o.send[1].bytes[64..68].try_into().unwrap()),
822 1
823 );
824 }
825
826 #[test]
827 fn tick_resends_app_ready_to_controller() {
828 let mut cm = cm();
829 let now = Instant::now();
830 cm.handle_datagram(&pdu("connect_req"), cpu(), now).unwrap();
831 cm.handle_datagram(&pdu("prmend_req"), cpu(), now).unwrap();
832 let o =
833 cm.tick(now + crate::cm::ar::APP_READY_TIMEOUT + std::time::Duration::from_millis(1));
834 assert_eq!(o.send[0].bytes, pdu("appready_req"));
835 assert_eq!(o.send[0].to, cpu_cm());
836 }
837
838 fn pnet_im0() -> Im0 {
845 Im0 {
846 order_id: "12345 Abcdefghijk".into(),
847 serial_number: "007".into(),
848 hardware_revision: 3,
849 software_revision: SwRevision {
850 prefix: 'V',
851 functional: 0,
852 bug_fix: 2,
853 internal: 0,
854 },
855 revision_counter: 0,
856 profile_id: 0x1234,
857 profile_specific_type: 0x5678,
858 }
859 }
860
861 fn cm_in_data_with_the_alarm_capture_ar(now: Instant) -> Cm {
871 let mut cm = cm();
872 let old = Uuid::parse_str("e5e1aecc-b133-4b4d-b187-cc68b0211ed2").unwrap();
873 let new = Uuid::parse_str("ef796d60-ef2b-9946-b39e-8531f5b7f966").unwrap();
874 let retag = |name: &str| retag_ar_uuid(pdu(name), old, new);
875
876 cm.handle_datagram(&retag("connect_req"), cpu(), now)
877 .unwrap();
878 cm.handle_datagram(&retag("write_req"), cpu(), now).unwrap();
879 let o = cm
880 .handle_datagram(&retag("prmend_req"), cpu(), now)
881 .unwrap();
882 assert_eq!(o.send[1].to, cpu_cm());
883 let o = cm
884 .handle_datagram(&pdu("appready_res"), cpu_cm(), now)
885 .unwrap();
886 assert_eq!(o.notify, vec![(ArState::Data, None)]);
887 assert_eq!(cm.state(), ArState::Data);
888 cm.set_im(pnet_im0(), crate::im::ImStore::new());
889 cm
890 }
891
892 #[test]
893 fn read_response_matches_the_pnet_im0_golden_byte_exact() {
894 let now = Instant::now();
895 let mut cm = cm_in_data_with_the_alarm_capture_ar(now);
896 let o = cm
897 .handle_datagram(&golden_alarm("im0_read_req")[RPC_OFF..], cpu(), now)
898 .unwrap();
899 assert_eq!(o.send.len(), 1);
900 assert_eq!(o.send[0].bytes, golden_alarm("im0_read_res")[RPC_OFF..]);
901 assert_eq!(o.send[0].to, cpu());
902 }
903
904 #[test]
908 fn read_response_on_the_interface_submodule_matches_the_golden_byte_exact() {
909 let now = Instant::now();
910 let mut cm = cm_in_data_with_the_alarm_capture_ar(now);
911 let o = cm
912 .handle_datagram(&golden_alarm("im0_read_req_if")[RPC_OFF..], cpu(), now)
913 .unwrap();
914 assert_eq!(o.send.len(), 1);
915 assert_eq!(o.send[0].bytes, golden_alarm("im0_read_res_if")[RPC_OFF..]);
916 assert_eq!(o.send[0].to, cpu());
917 }
918
919 #[test]
920 fn read_with_a_foreign_ar_uuid_is_refused() {
921 let mut cm = cm();
922 let now = Instant::now();
923 cm.handle_datagram(&pdu("connect_req"), cpu(), now).unwrap();
924 cm.handle_datagram(&pdu("write_req"), cpu(), now).unwrap();
925 cm.handle_datagram(&pdu("prmend_req"), cpu(), now).unwrap();
926 cm.handle_datagram(&pdu("appready_res"), cpu_cm(), now)
927 .unwrap();
928 assert_eq!(cm.state(), ArState::Data);
929
930 let o = cm
933 .handle_datagram(&golden_alarm("im0_read_req")[RPC_OFF..], cpu(), now)
934 .unwrap();
935 let (n, blocks) =
936 crate::rpc::NdrResponse::parse(&o.send[0].bytes[80..], crate::rpc::Drep::BIG).unwrap();
937 assert_eq!(PnioStatus(n.status), PnioStatus::read_wrong_ar());
938 assert!(blocks.is_empty());
939 }
940
941 #[test]
942 fn read_implicit_answers_ok_from_idle_without_an_ar() {
943 let mut cm = cm();
944 assert_eq!(cm.state(), ArState::Idle);
945 let mut req = golden_alarm("im0_read_req")[RPC_OFF..].to_vec();
946 req[68] = 5; let o = cm.handle_datagram(&req, cpu(), Instant::now()).unwrap();
948 assert_eq!(o.send.len(), 1);
949 let h = crate::rpc::RpcHeader::parse(&o.send[0].bytes).unwrap();
950 assert_eq!(h.opnum, 5);
951 let (n, blocks) =
952 crate::rpc::NdrResponse::parse(&o.send[0].bytes[80..], crate::rpc::Drep::BIG).unwrap();
953 assert!(PnioStatus(n.status).is_ok());
954 assert!(!blocks.is_empty());
955 assert_eq!(cm.state(), ArState::Idle);
956 }
957}