1use num_enum::{FromPrimitive, IntoPrimitive};
4use uom::si::f64::Time;
5use uom::si::time::second;
6
7use crate::error::KMError;
8
9const TRACE_RECORD_SIZE: usize = 5;
10const MAX_QUEUE_BYTES: usize = 200;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
14#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15#[repr(u8)]
16pub enum PdTypeCState {
17 Disabled = 0x00,
18 DelayUnattached = 0x01,
19 AttachedResistance = 0x02,
20 TryResistance = 0x03,
21 AttachedDebSource = 0x04,
22 UnattachedDebSource = 0x05,
23 AttachWaitDebSource = 0x06,
24 TryDebSource = 0x07,
25 AttachedSource = 0x08,
26 UnattachedSource = 0x09,
27 AttachWaitSource = 0x0a,
28 TryWaitSource = 0x0b,
29 TrySource = 0x0c,
30 DebugAccessorySource = 0x0d,
31 AttachedCable = 0x0e,
32 IllegalCable = 0x0f,
33 AttachWaitCable = 0x10,
34 AttachWaitMonitorDefective = 0x11,
35 AttachedLightningPlug = 0x12,
36 AttachWaitLightningPlug = 0x13,
37 AttachedDebSink = 0x14,
38 AttachWaitDebSink = 0x15,
39 TryWaitDebSink = 0x16,
40 AttachedSink = 0x17,
41 AttachWaitSink = 0x18,
42 TryWaitSink = 0x19,
43 TrySink = 0x1a,
44 DebugAccessorySink = 0x1b,
45 AttachedMonitor = 0x1c,
46 AttachWaitMonitor = 0x1d,
47 CableCross = 0x1e,
48 CablePlugShortCircuit = 0x1f,
49 ErrorRecovery = 0x20,
50 PoweredAccessory = 0x21,
51 UnsupportedAccessory = 0x22,
52 AudioAccessory = 0x23,
53 AttachWaitAccessory = 0x24,
54 #[num_enum(catch_all)]
55 Unknown(u8),
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
65#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
66#[repr(u8)]
67pub enum PdProtocolTraceEventKind {
68 Disabled = 0x00,
70 ReceivedMessage = 0x82,
72 ExtendedChunkRequest = 0x83,
74 #[num_enum(catch_all)]
75 Unknown(u8),
76}
77
78#[derive(Debug, Clone, Copy, PartialEq)]
80#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
81#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object))]
82pub struct PdTraceStateEvent {
83 pub state: PdTypeCState,
84 pub timestamp: Time,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq)]
90#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
91#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object))]
92pub struct PdTraceProtocolEvent {
93 pub kind: PdProtocolTraceEventKind,
95 pub timestamp: Time,
97}
98
99#[derive(Debug, Clone, PartialEq)]
101#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
102#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object))]
103pub struct PdTrace {
104 pub state_events: Vec<PdTraceStateEvent>,
106 pub protocol_events: Vec<PdTraceProtocolEvent>,
108}
109
110impl PdTrace {
111 pub fn from_bytes(bytes: &[u8]) -> Result<Self, KMError> {
113 let (state_records, offset) = parse_queue(bytes, 0, "state")?;
114 let (protocol_records, offset) = parse_queue(bytes, offset, "protocol")?;
115
116 if offset != bytes.len() {
117 return Err(KMError::InvalidPacket(format!(
118 "PD trace has {} trailing bytes",
119 bytes.len() - offset
120 )));
121 }
122
123 Ok(Self {
124 state_events: state_records
125 .into_iter()
126 .map(|record| PdTraceStateEvent {
127 state: PdTypeCState::from_primitive(record.code),
128 timestamp: record.timestamp,
129 })
130 .collect(),
131 protocol_events: protocol_records
132 .into_iter()
133 .map(|record| PdTraceProtocolEvent {
134 kind: PdProtocolTraceEventKind::from_primitive(record.code),
135 timestamp: record.timestamp,
136 })
137 .collect(),
138 })
139 }
140
141 pub fn to_bytes(&self) -> Result<Vec<u8>, KMError> {
143 let mut bytes = Vec::new();
144 append_queue(
145 &mut bytes,
146 self.state_events.iter().map(|event| TraceRecord {
147 code: event.state.into(),
148 timestamp: event.timestamp,
149 }),
150 "state",
151 )?;
152 append_queue(
153 &mut bytes,
154 self.protocol_events.iter().map(|event| TraceRecord {
155 code: event.kind.into(),
156 timestamp: event.timestamp,
157 }),
158 "protocol",
159 )?;
160 Ok(bytes)
161 }
162}
163
164pub(crate) fn payload_size(bytes: &[u8]) -> Result<usize, KMError> {
165 let state_size = queue_size(bytes, 0, "state")?;
166 let protocol_offset = 1 + state_size;
167 let protocol_size = queue_size(bytes, protocol_offset, "protocol")?;
168 Ok(protocol_offset + 1 + protocol_size)
169}
170
171fn queue_size(bytes: &[u8], offset: usize, name: &str) -> Result<usize, KMError> {
172 let size = usize::from(*bytes.get(offset).ok_or_else(|| {
173 KMError::InvalidPacket(format!(
174 "PD trace is missing the {name} queue length at offset {offset}"
175 ))
176 })?);
177
178 if size > MAX_QUEUE_BYTES {
179 return Err(KMError::InvalidPacket(format!(
180 "PD trace {name} queue is too large: {size} bytes, maximum is {MAX_QUEUE_BYTES}"
181 )));
182 }
183 if !size.is_multiple_of(TRACE_RECORD_SIZE) {
184 return Err(KMError::InvalidPacket(format!(
185 "PD trace {name} queue length must be a multiple of {TRACE_RECORD_SIZE}, got {size}"
186 )));
187 }
188 if bytes.len().saturating_sub(offset + 1) < size {
189 return Err(KMError::InvalidPacket(format!(
190 "PD trace {name} queue is truncated: expected {size} bytes, got {}",
191 bytes.len().saturating_sub(offset + 1)
192 )));
193 }
194
195 Ok(size)
196}
197
198#[derive(Clone, Copy)]
199struct TraceRecord {
200 code: u8,
201 timestamp: Time,
202}
203
204fn parse_queue(bytes: &[u8], offset: usize, name: &str) -> Result<(Vec<TraceRecord>, usize), KMError> {
205 let size = queue_size(bytes, offset, name)?;
206 let start = offset + 1;
207 let end = start + size;
208 let events = bytes[start..end]
209 .as_chunks::<TRACE_RECORD_SIZE>()
210 .0
211 .iter()
212 .map(|record| TraceRecord {
213 code: record[0],
214 timestamp: Time::new::<second>(f64::from(u32::from_le_bytes([
215 record[1], record[2], record[3], record[4],
216 ]))),
217 })
218 .collect();
219 Ok((events, end))
220}
221
222fn append_queue(
223 bytes: &mut Vec<u8>,
224 events: impl ExactSizeIterator<Item = TraceRecord>,
225 name: &str,
226) -> Result<(), KMError> {
227 let size = events
228 .len()
229 .checked_mul(TRACE_RECORD_SIZE)
230 .ok_or_else(|| KMError::InvalidPacket(format!("PD trace {name} queue length overflow")))?;
231 if size > MAX_QUEUE_BYTES {
232 return Err(KMError::InvalidPacket(format!(
233 "PD trace {name} queue is too large: {size} bytes, maximum is {MAX_QUEUE_BYTES}"
234 )));
235 }
236
237 bytes.push(size as u8);
238 for event in events {
239 bytes.push(event.code);
240 bytes.extend_from_slice(&(event.timestamp.get::<second>() as u32).to_le_bytes());
241 }
242 Ok(())
243}
244
245#[cfg(feature = "python")]
246#[pyo3::pymethods]
247impl PdTraceStateEvent {
248 #[getter]
249 fn state_code(&self) -> u8 {
250 self.state.into()
251 }
252
253 #[getter]
254 fn state_name(&self) -> String {
255 format!("{:?}", self.state)
256 }
257
258 #[getter]
259 fn timestamp_seconds(&self) -> f64 {
260 self.timestamp.get::<second>()
261 }
262}
263
264#[cfg(feature = "python")]
265#[pyo3::pymethods]
266impl PdTraceProtocolEvent {
267 #[getter]
268 fn code(&self) -> u8 {
269 self.kind.into()
270 }
271
272 #[getter]
273 fn event_name(&self) -> String {
274 format!("{:?}", self.kind)
275 }
276
277 #[getter]
278 fn timestamp_seconds(&self) -> f64 {
279 self.timestamp.get::<second>()
280 }
281}
282
283#[cfg(feature = "python")]
284#[pyo3::pymethods]
285impl PdTrace {
286 #[getter]
287 fn state_events(&self) -> Vec<PdTraceStateEvent> {
288 self.state_events.clone()
289 }
290
291 #[getter]
292 fn protocol_events(&self) -> Vec<PdTraceProtocolEvent> {
293 self.protocol_events.clone()
294 }
295}