openlogi-hid 0.8.5

HID++ device discovery for OpenLogi, wrapping the hidpp crate over async-hid.
Documentation
use std::collections::BTreeMap;
use std::sync::mpsc::Receiver;

use hidpp::channel::ChannelObservation;
use openlogi_device::backend::NodeInfo;

use super::{
    NativeRecording, RecordedChannel, RecordedChannelEvidence, RecordedChannelId,
    RecordedChannelOpenOutcome, RecordedRawWrite, RecordedRawWriteOutcome, RecordedRawWriter,
    RecordedRawWriterId, RecordedRawWriterOpenOutcome, RecordedRequest, RecordedRequestFact,
    RecordingSequence,
};

pub(super) enum RecorderCommand {
    ChannelStarted {
        sequence: RecordingSequence,
        id: RecordedChannelId,
        node: NodeInfo,
    },
    ChannelOutcome {
        sequence: RecordingSequence,
        id: RecordedChannelId,
        outcome: RecordedChannelOpenOutcome,
    },
    ChannelObservation {
        sequence: RecordingSequence,
        id: RecordedChannelId,
        observation: ChannelObservation,
    },
    ChannelClosed {
        sequence: RecordingSequence,
        id: RecordedChannelId,
    },
    RawWriterStarted {
        sequence: RecordingSequence,
        id: RecordedRawWriterId,
        node: NodeInfo,
    },
    RawWriterOutcome {
        sequence: RecordingSequence,
        id: RecordedRawWriterId,
        outcome: RecordedRawWriterOpenOutcome,
    },
    RawWrite {
        sequence: RecordingSequence,
        id: RecordedRawWriterId,
        report: Box<[u8]>,
        outcome: RecordedRawWriteOutcome,
    },
    RawWriterClosed {
        sequence: RecordingSequence,
        id: RecordedRawWriterId,
    },
    Finish,
}

#[derive(Default)]
pub(super) struct Accumulator {
    channels: BTreeMap<RecordedChannelId, ChannelBuilder>,
    raw_writers: BTreeMap<RecordedRawWriterId, RawWriterBuilder>,
}

impl Accumulator {
    pub(super) fn run(mut self, receiver: &Receiver<RecorderCommand>) -> NativeRecording {
        while let Ok(command) = receiver.recv() {
            match command {
                RecorderCommand::ChannelStarted { sequence, id, node } => {
                    self.channels
                        .insert(id, ChannelBuilder::new(sequence, node));
                }
                RecorderCommand::ChannelOutcome {
                    sequence,
                    id,
                    outcome,
                } => {
                    if let Some(channel) = self.channels.get_mut(&id) {
                        channel.open_outcome = Some((sequence, outcome));
                    }
                }
                RecorderCommand::ChannelObservation {
                    sequence,
                    id,
                    observation,
                } => {
                    if let Some(channel) = self.channels.get_mut(&id) {
                        channel.observe(sequence, observation);
                    }
                }
                RecorderCommand::ChannelClosed { sequence, id } => {
                    if let Some(channel) = self.channels.get_mut(&id) {
                        channel.closed = Some(sequence);
                    }
                }
                RecorderCommand::RawWriterStarted { sequence, id, node } => {
                    self.raw_writers
                        .insert(id, RawWriterBuilder::new(sequence, node));
                }
                RecorderCommand::RawWriterOutcome {
                    sequence,
                    id,
                    outcome,
                } => {
                    if let Some(writer) = self.raw_writers.get_mut(&id) {
                        writer.open_outcome = Some((sequence, outcome));
                    }
                }
                RecorderCommand::RawWrite {
                    sequence,
                    id,
                    report,
                    outcome,
                } => {
                    if let Some(writer) = self.raw_writers.get_mut(&id) {
                        writer.writes.push(RecordedRawWrite {
                            sequence,
                            report,
                            outcome,
                        });
                    }
                }
                RecorderCommand::RawWriterClosed { sequence, id } => {
                    if let Some(writer) = self.raw_writers.get_mut(&id) {
                        writer.closed = Some(sequence);
                    }
                }
                RecorderCommand::Finish => break,
            }
        }
        NativeRecording {
            channels: self
                .channels
                .into_iter()
                .map(|(id, channel)| channel.finish(id))
                .collect(),
            raw_writers: self
                .raw_writers
                .into_iter()
                .map(|(id, writer)| writer.finish(id))
                .collect(),
        }
    }
}

struct ChannelBuilder {
    started: RecordingSequence,
    node: NodeInfo,
    open_outcome: Option<(RecordingSequence, RecordedChannelOpenOutcome)>,
    requests: BTreeMap<u64, Vec<RecordedRequestFact>>,
    unassociated: Vec<RecordedChannelEvidence>,
    closed: Option<RecordingSequence>,
}

impl ChannelBuilder {
    fn new(started: RecordingSequence, node: NodeInfo) -> Self {
        Self {
            started,
            node,
            open_outcome: None,
            requests: BTreeMap::new(),
            unassociated: Vec::new(),
            closed: None,
        }
    }

    fn observe(&mut self, sequence: RecordingSequence, observation: ChannelObservation) {
        let associated = match observation {
            ChannelObservation::OutgoingReport {
                request_id: Some(request_id),
                report,
            } => Some((
                request_id,
                RecordedRequestFact::OutgoingReport { sequence, report },
            )),
            ChannelObservation::IncomingReport {
                request_id: Some(request_id),
                report,
            } => Some((
                request_id,
                RecordedRequestFact::IncomingReport { sequence, report },
            )),
            ChannelObservation::RequestOutcome {
                request_id,
                outcome,
            } => Some((
                request_id,
                RecordedRequestFact::Outcome { sequence, outcome },
            )),
            unassociated => {
                self.unassociated.push(RecordedChannelEvidence {
                    sequence,
                    observation: unassociated,
                });
                None
            }
        };
        if let Some((request_id, fact)) = associated {
            self.requests.entry(request_id).or_default().push(fact);
        }
    }

    fn finish(self, id: RecordedChannelId) -> RecordedChannel {
        let (open_outcome_at, open_outcome) = self
            .open_outcome
            .unwrap_or((self.started, RecordedChannelOpenOutcome::Cancelled));
        RecordedChannel {
            id,
            node: self.node,
            started_at: self.started,
            open_outcome,
            open_outcome_at,
            requests: self
                .requests
                .into_iter()
                .map(|(request_id, facts)| RecordedRequest { request_id, facts })
                .collect(),
            unassociated: self.unassociated,
            closed_at: self.closed,
        }
    }
}

struct RawWriterBuilder {
    started: RecordingSequence,
    node: NodeInfo,
    open_outcome: Option<(RecordingSequence, RecordedRawWriterOpenOutcome)>,
    writes: Vec<RecordedRawWrite>,
    closed: Option<RecordingSequence>,
}

impl RawWriterBuilder {
    fn new(started: RecordingSequence, node: NodeInfo) -> Self {
        Self {
            started,
            node,
            open_outcome: None,
            writes: Vec::new(),
            closed: None,
        }
    }

    fn finish(self, id: RecordedRawWriterId) -> RecordedRawWriter {
        let (open_outcome_at, open_outcome) = self
            .open_outcome
            .unwrap_or((self.started, RecordedRawWriterOpenOutcome::Cancelled));
        RecordedRawWriter {
            id,
            node: self.node,
            started_at: self.started,
            open_outcome,
            open_outcome_at,
            writes: self.writes,
            closed_at: self.closed,
        }
    }
}