Skip to main content

sipx_rtp/
dtmf.rs

1//! DTMF as named telephone events (RFC 4733).
2//!
3//! A keypress is not audio. Sending it as audio works over a clean codec and falls apart the
4//! moment anything transcodes, so RFC 4733 carries the *digit* instead, in a four-byte
5//! payload on its own payload type.
6//!
7//! ```text
8//!  0                   1                   2                   3
9//!  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
10//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
11//! |     event     |E|R| volume    |          duration             |
12//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
13//! ```
14//!
15//! The part that is easy to get wrong is not the layout, it is the timing. One keypress is a
16//! *run* of packets that all share the RTP timestamp of the moment the tone started; the
17//! duration field grows while the digit is held. A sender that advances the timestamp per
18//! packet turns one keypress into a stream of separate digits, and the far end dials
19//! something nobody typed.
20
21use bytes::{BufMut, Bytes, BytesMut};
22
23/// How many bytes a telephone event occupies.
24pub const EVENT_LEN: usize = 4;
25
26/// The conventional payload type for `telephone-event`. Dynamic, so the SDP decides — this is
27/// only the value sipx offers.
28pub const DEFAULT_PAYLOAD_TYPE: u8 = 101;
29
30/// A DTMF digit.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum Digit {
33    /// `0`–`9`.
34    Number(u8),
35    /// `*`.
36    Star,
37    /// `#`.
38    Hash,
39    /// `A`–`D`, the fourth column that most keypads do not have.
40    Letter(u8),
41}
42
43impl Digit {
44    /// The RFC 4733 event code.
45    #[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    /// The digit an event code names, if it is one.
56    #[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            // 16 is flash and above that are other signals; sipx carries DTMF only, and
64            // reporting a flash as a digit would be worse than not reporting it.
65            _ => None,
66        }
67    }
68
69    /// The digit a character names.
70    #[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    /// How the digit is written.
89    #[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/// One telephone-event payload.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct Event {
109    /// Which digit.
110    pub digit: Digit,
111    /// Whether this packet ends the tone.
112    pub end: bool,
113    /// Power, in -dBm0. Zero is loudest; RFC 4733 §2.3.2 recommends no louder than -3.
114    pub volume: u8,
115    /// How long the tone has lasted so far, in timestamp units.
116    pub duration: u16,
117}
118
119impl Event {
120    /// An event for a digit that is still sounding.
121    #[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    /// Serialize to the four-byte payload.
132    #[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        // The reserved bit stays zero. Volume is six bits, so a louder-than-representable
137        // value has to be clamped rather than allowed to overflow into the reserved bit.
138        out.put_u8((u8::from(self.end) << 7) | (self.volume & 0x3F));
139        out.put_u16(self.duration);
140        out.freeze()
141    }
142
143    /// Read a four-byte payload.
144    ///
145    /// Returns `None` for anything that is not a telephone event this crate understands —
146    /// including event codes above 15, which are signals rather than digits.
147    #[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
163/// How many copies of the final packet to send.
164///
165/// RFC 4733 §2.5.1.3: the end of a tone is retransmitted so that losing one packet does not
166/// leave the far end holding a digit down forever. Three is the RFC's number.
167pub const END_RETRANSMISSIONS: usize = 3;
168
169/// One packet of a keypress: the event payload, plus where its segment sits in the
170/// event's timeline.
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct TonePacket {
173    /// The four-byte payload.
174    pub event: Event,
175    /// The segment's start, in timestamp units after the event began.
176    ///
177    /// Zero for any event short enough to fit one segment. RFC 4733 §2.5.1.3: an event
178    /// that outlives the 16-bit duration field is continued as a new segment with a fresh
179    /// RTP timestamp, and this offset is what that timestamp moves by.
180    pub segment_offset: u32,
181}
182
183/// Build the packets for one keypress.
184///
185/// Every packet of a segment shares one RTP timestamp — the segment's start — which is
186/// what marks them as one tone. The duration grows across the run, and the last packet is
187/// repeated with the end bit set. An event too long for the duration field continues as a
188/// new segment (RFC 4733 §2.5.1.3): the duration restarts, the offset advances, and only
189/// the final segment carries the end bit.
190#[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            // The field is full: the event continues as a new segment rather than
200            // saturating, which would report a key stuck at 65535 for as long as it is
201            // held (RFC 4733 §2.5.1.3). The segment before it ends without the end bit —
202            // that bit ends the *event*, and only the last segment carries it.
203            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/// Reassembles received events into digits.
228///
229/// The whole job is reporting each keypress exactly once. A tone arrives as many packets and
230/// its end arrives three times, so a receiver that reports what it receives reports every
231/// digit four or more times — which, for a caller entering a PIN, is a wrong PIN.
232#[derive(Debug, Default)]
233pub struct Receiver {
234    /// The tone currently sounding, identified by its RTP timestamp.
235    current: Option<(u32, Digit)>,
236    /// Timestamps already reported, so the end retransmissions are absorbed.
237    reported: Option<u32>,
238}
239
240impl Receiver {
241    /// A receiver with nothing in progress.
242    #[must_use]
243    pub fn new() -> Self {
244        Self::default()
245    }
246
247    /// Feed one telephone-event packet.
248    ///
249    /// Returns the digit when the tone ends, and `None` while it is still sounding or if this
250    /// packet is a repeat of an end already reported.
251    pub fn push(&mut self, timestamp: u32, event: &Event) -> Option<Digit> {
252        // The timestamp identifies the tone. A new one is a new keypress even if the digit is
253        // the same, which is how "44" is told apart from a single long "4".
254        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    /// The digit currently sounding, if any.
272    #[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    /// Event code 16 is flash, and above that are other signals. Reporting one as a digit
319    /// would be worse than not reporting it: the application would dial something.
320    #[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    /// The volume field is six bits. A value that does not fit must be clamped, not allowed to
349    /// overflow into the end bit — where it would end a tone that is still sounding.
350    #[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    /// The duration grows across a tone, and the end is repeated three times so that losing
371    /// one packet does not leave the far end holding the digit down.
372    #[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    /// RFC 4733 §2.5.1.3: an event that outlives the 16-bit duration field MUST be
402    /// continued as a new segment — fresh start timestamp, duration restarting — never
403    /// saturated at 65535, which reports a stuck key from ~8.19 s onward at 8 kHz.
404    #[test]
405    fn a_long_event_is_segmented_rather_than_saturated() {
406        // Ten seconds at 8 kHz, 160 samples per packet: 500 packets, 80000 units in all —
407        // more than one segment can carry.
408        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        // 409 packets fill the first segment (65440 units); packet 410 starts the second.
417        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        // Only the last segment ends the event; a non-final segment just stops.
425        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        // The segments cover the whole keypress, no more and no less.
434        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    /// The receiver's whole job: one keypress reported once, however many packets carried it.
443    /// A receiver that reports what it receives turns a four-digit PIN into a wrong one.
444    #[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    /// Two presses of the same key are two digits, told apart by their timestamps. Without
457    /// that, "44" is indistinguishable from one long "4".
458    #[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    /// A whole sequence, as an application would see it.
473    #[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    /// Losing packets in the middle of a tone must not lose the digit: the end packet is what
490    /// reports it, and there are three of those.
491    #[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        // Only the last packet arrives.
496        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}