1use bytes::{BufMut, Bytes, BytesMut};
22
23pub const EVENT_LEN: usize = 4;
25
26pub const DEFAULT_PAYLOAD_TYPE: u8 = 101;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum Digit {
33 Number(u8),
35 Star,
37 Hash,
39 Letter(u8),
41}
42
43impl Digit {
44 #[must_use]
46 pub fn code(self) -> u8 {
47 match self {
48 Self::Number(n) => n.min(9),
49 Self::Star => 10,
50 Self::Hash => 11,
51 Self::Letter(l) => 12 + l.min(3),
52 }
53 }
54
55 #[must_use]
57 pub fn from_code(code: u8) -> Option<Self> {
58 match code {
59 0..=9 => Some(Self::Number(code)),
60 10 => Some(Self::Star),
61 11 => Some(Self::Hash),
62 12..=15 => Some(Self::Letter(code - 12)),
63 _ => None,
66 }
67 }
68
69 #[must_use]
71 pub fn from_char(c: char) -> Option<Self> {
72 match c {
73 '0'..='9' => u8::try_from(u32::from(c) - u32::from('0'))
74 .ok()
75 .map(Self::Number),
76 '*' => Some(Self::Star),
77 '#' => Some(Self::Hash),
78 'A'..='D' => u8::try_from(u32::from(c) - u32::from('A'))
79 .ok()
80 .map(Self::Letter),
81 'a'..='d' => u8::try_from(u32::from(c) - u32::from('a'))
82 .ok()
83 .map(Self::Letter),
84 _ => None,
85 }
86 }
87
88 #[must_use]
90 pub fn as_char(self) -> char {
91 match self {
92 Self::Number(n) => char::from(b'0' + n.min(9)),
93 Self::Star => '*',
94 Self::Hash => '#',
95 Self::Letter(l) => char::from(b'A' + l.min(3)),
96 }
97 }
98}
99
100impl std::fmt::Display for Digit {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 write!(f, "{}", self.as_char())
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct Event {
109 pub digit: Digit,
111 pub end: bool,
113 pub volume: u8,
115 pub duration: u16,
117}
118
119impl Event {
120 #[must_use]
122 pub fn new(digit: Digit, duration: u16) -> Self {
123 Self {
124 digit,
125 end: false,
126 volume: 10,
127 duration,
128 }
129 }
130
131 #[must_use]
133 pub fn encode(&self) -> Bytes {
134 let mut out = BytesMut::with_capacity(EVENT_LEN);
135 out.put_u8(self.digit.code());
136 out.put_u8((u8::from(self.end) << 7) | (self.volume & 0x3F));
139 out.put_u16(self.duration);
140 out.freeze()
141 }
142
143 #[must_use]
148 pub fn decode(payload: &[u8]) -> Option<Self> {
149 if payload.len() < EVENT_LEN {
150 return None;
151 }
152 let digit = Digit::from_code(*payload.first()?)?;
153 let second = *payload.get(1)?;
154 Some(Self {
155 digit,
156 end: second & 0x80 != 0,
157 volume: second & 0x3F,
158 duration: u16::from_be_bytes([*payload.get(2)?, *payload.get(3)?]),
159 })
160 }
161}
162
163pub const END_RETRANSMISSIONS: usize = 3;
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct TonePacket {
173 pub event: Event,
175 pub segment_offset: u32,
181}
182
183#[must_use]
191pub fn tone(digit: Digit, packets: usize, samples_per_packet: u16) -> Vec<TonePacket> {
192 let steps = packets.max(1);
193 let mut events = Vec::with_capacity(steps + END_RETRANSMISSIONS);
194 let mut segment_offset: u32 = 0;
195 let mut duration: u32 = 0;
196
197 for _ in 0..steps {
198 if duration + u32::from(samples_per_packet) > u32::from(u16::MAX) {
199 segment_offset += duration;
204 duration = 0;
205 }
206 duration += u32::from(samples_per_packet);
207 events.push(TonePacket {
208 event: Event::new(digit, u16::try_from(duration).unwrap_or(u16::MAX)),
209 segment_offset,
210 });
211 }
212
213 for _ in 0..END_RETRANSMISSIONS {
214 events.push(TonePacket {
215 event: Event {
216 digit,
217 end: true,
218 volume: 10,
219 duration: u16::try_from(duration).unwrap_or(u16::MAX),
220 },
221 segment_offset,
222 });
223 }
224 events
225}
226
227#[derive(Debug, Default)]
233pub struct Receiver {
234 current: Option<(u32, Digit)>,
236 reported: Option<u32>,
238}
239
240impl Receiver {
241 #[must_use]
243 pub fn new() -> Self {
244 Self::default()
245 }
246
247 pub fn push(&mut self, timestamp: u32, event: &Event) -> Option<Digit> {
252 if self.reported == Some(timestamp) {
255 return None;
256 }
257
258 match self.current {
259 Some((ts, _)) if ts == timestamp => {}
260 _ => self.current = Some((timestamp, event.digit)),
261 }
262
263 if event.end {
264 let digit = self.current.take().map(|(_, digit)| digit)?;
265 self.reported = Some(timestamp);
266 return Some(digit);
267 }
268 None
269 }
270
271 #[must_use]
273 pub fn in_progress(&self) -> Option<Digit> {
274 self.current.map(|(_, digit)| digit)
275 }
276}
277
278#[cfg(test)]
279#[allow(
280 clippy::unwrap_used,
281 clippy::expect_used,
282 clippy::panic,
283 clippy::indexing_slicing
284)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn every_digit_maps_to_its_rfc_event_code() {
290 for (c, code) in [
291 ('0', 0),
292 ('9', 9),
293 ('*', 10),
294 ('#', 11),
295 ('A', 12),
296 ('D', 15),
297 ] {
298 let digit = Digit::from_char(c).expect("a digit");
299 assert_eq!(digit.code(), code, "{c}");
300 assert_eq!(Digit::from_code(code), Some(digit));
301 assert_eq!(digit.as_char(), c);
302 }
303 }
304
305 #[test]
306 fn lowercase_letters_are_accepted_and_normalised() {
307 assert_eq!(Digit::from_char('b'), Digit::from_char('B'));
308 assert_eq!(Digit::from_char('b').expect("a digit").as_char(), 'B');
309 }
310
311 #[test]
312 fn a_character_that_is_not_a_digit_is_refused() {
313 for c in ['x', ' ', '+', 'E', '\n'] {
314 assert!(Digit::from_char(c).is_none(), "{c:?} is not a DTMF digit");
315 }
316 }
317
318 #[test]
321 fn event_codes_above_fifteen_are_not_digits() {
322 assert!(Digit::from_code(16).is_none(), "16 is flash, not a digit");
323 assert!(Digit::from_code(255).is_none());
324 }
325
326 #[test]
327 fn an_event_round_trips_through_its_payload() {
328 let event = Event {
329 digit: Digit::Hash,
330 end: true,
331 volume: 7,
332 duration: 1600,
333 };
334 let decoded = Event::decode(&event.encode()).expect("decodes");
335 assert_eq!(decoded, event);
336 }
337
338 #[test]
339 fn the_payload_is_four_bytes_in_the_rfc_layout() {
340 let encoded = Event::new(Digit::Number(5), 320).encode();
341 assert_eq!(encoded.len(), EVENT_LEN);
342 assert_eq!(encoded[0], 5, "event code");
343 assert_eq!(encoded[1] & 0x80, 0, "end bit clear");
344 assert_eq!(encoded[1] & 0x3F, 10, "volume");
345 assert_eq!(u16::from_be_bytes([encoded[2], encoded[3]]), 320);
346 }
347
348 #[test]
351 fn an_oversized_volume_cannot_set_the_end_bit() {
352 let event = Event {
353 digit: Digit::Number(1),
354 end: false,
355 volume: 255,
356 duration: 160,
357 };
358 let encoded = event.encode();
359 assert_eq!(encoded[1] & 0x80, 0, "the end bit must stay clear");
360 let decoded = Event::decode(&encoded).expect("decodes");
361 assert!(!decoded.end);
362 }
363
364 #[test]
365 fn a_short_payload_is_refused() {
366 assert!(Event::decode(&[]).is_none());
367 assert!(Event::decode(&[5, 0, 1]).is_none());
368 }
369
370 #[test]
373 fn a_tone_grows_in_duration_and_ends_three_times() {
374 let events = tone(Digit::Number(7), 4, 160);
375 assert_eq!(events.len(), 4 + END_RETRANSMISSIONS);
376
377 let sounding: Vec<u16> = events
378 .iter()
379 .filter(|p| !p.event.end)
380 .map(|p| p.event.duration)
381 .collect();
382 assert_eq!(sounding, vec![160, 320, 480, 640], "duration accumulates");
383
384 let ends: Vec<&Event> = events
385 .iter()
386 .filter(|p| p.event.end)
387 .map(|p| &p.event)
388 .collect();
389 assert_eq!(ends.len(), 3);
390 assert!(
391 ends.iter().all(|e| e.duration == 640),
392 "every end packet reports the full duration"
393 );
394 assert!(events.iter().all(|p| p.event.digit == Digit::Number(7)));
395 assert!(
396 events.iter().all(|p| p.segment_offset == 0),
397 "a short keypress is one segment"
398 );
399 }
400
401 #[test]
405 fn a_long_event_is_segmented_rather_than_saturated() {
406 let events = tone(Digit::Number(1), 500, 160);
409 assert!(
410 events
411 .iter()
412 .all(|packet| packet.event.duration != u16::MAX),
413 "no packet may report a saturated duration"
414 );
415
416 let second = events
418 .iter()
419 .find(|packet| packet.segment_offset > 0)
420 .expect("the event is too long for one segment");
421 assert_eq!(second.segment_offset, 65_440);
422 assert_eq!(second.event.duration, 160, "the duration restarts");
423
424 assert!(
426 events
427 .iter()
428 .filter(|packet| packet.event.end)
429 .all(|packet| packet.segment_offset == 65_440),
430 "the end bit belongs to the event, not to a segment"
431 );
432
433 let last = events.last().expect("an end packet");
435 assert_eq!(
436 last.segment_offset + u32::from(last.event.duration),
437 80_000,
438 "500 packets of 160 units"
439 );
440 }
441
442 #[test]
445 fn a_tone_is_reported_exactly_once() {
446 let mut receiver = Receiver::new();
447 let mut digits = Vec::new();
448 for packet in tone(Digit::Number(3), 5, 160) {
449 if let Some(digit) = receiver.push(1000, &packet.event) {
450 digits.push(digit);
451 }
452 }
453 assert_eq!(digits, vec![Digit::Number(3)], "one keypress, one digit");
454 }
455
456 #[test]
459 fn the_same_digit_pressed_twice_is_two_digits() {
460 let mut receiver = Receiver::new();
461 let mut digits = Vec::new();
462 for timestamp in [1000u32, 5000] {
463 for packet in tone(Digit::Number(4), 3, 160) {
464 if let Some(digit) = receiver.push(timestamp, &packet.event) {
465 digits.push(digit);
466 }
467 }
468 }
469 assert_eq!(digits, vec![Digit::Number(4), Digit::Number(4)]);
470 }
471
472 #[test]
474 fn a_sequence_of_digits_arrives_in_order() {
475 let mut receiver = Receiver::new();
476 let mut collected = String::new();
477 for (index, c) in "1234*#".chars().enumerate() {
478 let digit = Digit::from_char(c).expect("a digit");
479 let timestamp = 1000 + u32::try_from(index).unwrap_or(0) * 2000;
480 for packet in tone(digit, 3, 160) {
481 if let Some(reported) = receiver.push(timestamp, &packet.event) {
482 collected.push(reported.as_char());
483 }
484 }
485 }
486 assert_eq!(collected, "1234*#");
487 }
488
489 #[test]
492 fn a_digit_survives_losing_all_but_one_end_packet() {
493 let mut receiver = Receiver::new();
494 let events = tone(Digit::Star, 5, 160);
495 let last = events.last().expect("an end packet");
497 assert_eq!(receiver.push(2000, &last.event), Some(Digit::Star));
498 }
499
500 #[test]
501 fn the_digit_in_progress_is_visible_before_the_tone_ends() {
502 let mut receiver = Receiver::new();
503 let events = tone(Digit::Hash, 3, 160);
504 assert!(receiver.in_progress().is_none());
505 receiver.push(1000, &events[0].event);
506 assert_eq!(receiver.in_progress(), Some(Digit::Hash));
507 for packet in &events[1..] {
508 receiver.push(1000, &packet.event);
509 }
510 assert!(receiver.in_progress().is_none(), "the tone is over");
511 }
512}