Skip to main content

sipx_sip/
push.rs

1//! Push notifications (RFC 8599), at the URI and header level.
2//!
3//! The problem: a mobile client is not running, or is running with every socket torn down by the
4//! operating system. There is no flow to route a call down and no keep-alive that could hold one
5//! open, so the registrar's binding names an address that reaches nothing. RFC 8599's answer is to
6//! route *around* SIP for one hop — the proxy asks the client's push notification service to wake
7//! it, and the client, once awake, goes and gets a flow.
8//!
9//! What lives here is the part that is pure syntax, and it is three separate things that are easy
10//! to conflate:
11//!
12//! - **The `pn-*` parameters** (§8.7) a `Contact` URI carries, which tell the registrar which push
13//!   service to ask and how that service names this device. They are *URI* parameters, so they go
14//!   inside the angle brackets of a `Contact`; outside them a `;` starts a header parameter and a
15//!   registrar reading the URI would never see them (RFC 3261 §20).
16//! - **The feature-capability indicators** (§8.2), which travel in `Feature-Caps` (RFC 6809) and
17//!   are how the registrar answers back: which push service it actually supports, whether it wants
18//!   refreshes anyway, and what it will call this binding.
19//! - **555** (§8.1), the status code that says the client's whole reachability plan is wrong.
20//!
21//! Nothing here sends or receives a push notification. sipx implements the SIP half of RFC 8599
22//! and nothing else — the push service is behind a trait in `sipx-ua` and this repository ships no
23//! implementation of one. Deciding what a registration should say, and reading what came back,
24//! belongs in `sipx-ua` for the same reason [`crate::gruu`]'s registration half does.
25
26use std::time::Duration;
27
28use bytes::Bytes;
29
30use crate::error::{BuildError, HeaderError};
31use crate::headers::grammar::{self, HeaderParam, trim};
32use crate::message::{StatusCode, TypedHeader};
33use crate::name::HeaderName;
34use crate::params::Param;
35use crate::uri::Uri;
36
37/// The `Contact` URI parameter naming the push notification service (§8.7).
38pub const PN_PROVIDER: &str = "pn-provider";
39
40/// The `Contact` URI parameter carrying whatever else the named service needs (§8.7).
41///
42/// Its meaning is the service's, not SIP's, which is why nothing here interprets it.
43pub const PN_PARAM: &str = "pn-param";
44
45/// The `Contact` URI parameter carrying the identifier the service knows this device by (§8.7).
46pub const PN_PRID: &str = "pn-prid";
47
48/// The `Contact` URI parameter carrying the PURR — the Push Resource Reachability Reference the
49/// proxy assigned this binding (§8.7).
50///
51/// Read, never minted here: a UA does not choose its own PURR. See [`purr`].
52pub const PN_PURR: &str = "pn-purr";
53
54/// The feature-capability indicator naming a push notification service (§8.2).
55pub const SIP_PNS: &str = "+sip.pns";
56
57/// The feature-capability indicator asking for binding refreshes even without a push (§8.2).
58pub const SIP_PNSREG: &str = "+sip.pnsreg";
59
60/// The feature-capability indicator carrying the PURR assigned to a binding (§8.2).
61pub const SIP_PNSPURR: &str = "+sip.pnspurr";
62
63/// 555 (Push Notification Service Not Supported), registered in §8.1.
64pub const NOT_SUPPORTED: u16 = 555;
65
66/// The reason phrase §8.1 registers alongside [`NOT_SUPPORTED`].
67pub const NOT_SUPPORTED_REASON: &str = "Push Notification Service Not Supported";
68
69/// Whether a status is §8.1's 555.
70///
71/// Worth a name because of what it is not: 555 is not "the server said no" in the way a 403 is. It
72/// says the push service the request named cannot be used here, so every retry against this
73/// registrar with these parameters will fail the same way. A client that folds it into a generic
74/// 5xx retries forever against a plan that cannot work.
75#[must_use]
76pub fn is_not_supported(status: StatusCode) -> bool {
77    status.code() == NOT_SUPPORTED
78}
79
80/// How a push notification service names one device (§4.1.2, §8.7).
81///
82/// The three values a `Contact` URI carries so that a proxy can wake this client: which service to
83/// ask, what that service calls this device, and whatever else the service needs in between.
84///
85/// # Why constructing one can fail
86///
87/// A `pn-prid` is an opaque token minted by somebody else, and RFC 3261 §25.1's `pvalue` does not
88/// admit every octet — `=`, `;`, `?` and `@` among them. A value carrying one of those, pasted into
89/// a URI unchecked, does not produce a rejected registration; it produces a *different* URI, with
90/// the tail of the token read as another parameter. So the values are checked once, here, and a
91/// caller holding a token that needs octets outside `pvalue` percent-escapes it — `escaped` is part
92/// of the grammar and survives [`Uri`] round-tripping unchanged.
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct Device {
95    provider: String,
96    param: Option<String>,
97    prid: String,
98}
99
100impl Device {
101    /// The service to ask, and the identifier it knows this device by (§4.1.2).
102    ///
103    /// `provider` is a value from the registry §8.8 creates; `prid` is the token the service
104    /// issued for this device.
105    pub fn new(provider: &str, prid: &str) -> Result<Self, BuildError> {
106        Ok(Self {
107            provider: pvalue(provider, PN_PROVIDER)?,
108            param: None,
109            prid: pvalue(prid, PN_PRID)?,
110        })
111    }
112
113    /// Add the `pn-param` the named service needs (§4.1.2).
114    ///
115    /// Optional because it is service-specific: some need nothing beyond the identifier.
116    pub fn with_param(mut self, param: &str) -> Result<Self, BuildError> {
117        self.param = Some(pvalue(param, PN_PARAM)?);
118        Ok(self)
119    }
120
121    /// The `pn-provider` value.
122    #[must_use]
123    pub fn provider(&self) -> &str {
124        &self.provider
125    }
126
127    /// The `pn-param` value, if the service needs one.
128    #[must_use]
129    pub fn param(&self) -> Option<&str> {
130        self.param.as_deref()
131    }
132
133    /// The `pn-prid` value.
134    #[must_use]
135    pub fn prid(&self) -> &str {
136        &self.prid
137    }
138
139    /// Put these parameters on a URI, replacing any already there (§4.1.2).
140    ///
141    /// Replacing rather than appending is not tidiness: RFC 3261 §19.1.1 says "any given
142    /// parameter-name MUST NOT appear more than once" in a URI, and a duplicate makes the whole
143    /// URI unparseable at the far end. A stale `pn-prid` left beside a fresh one would take the
144    /// registration down rather than merely be ignored.
145    pub fn set_on(&self, uri: &mut Uri) {
146        for name in [PN_PROVIDER, PN_PARAM, PN_PRID] {
147            uri.remove_param(name);
148        }
149        uri.push_param(Param::new(
150            Bytes::from_static(PN_PROVIDER.as_bytes()),
151            Bytes::from(self.provider.clone()),
152        ));
153        if let Some(param) = &self.param {
154            uri.push_param(Param::new(
155                Bytes::from_static(PN_PARAM.as_bytes()),
156                Bytes::from(param.clone()),
157            ));
158        }
159        uri.push_param(Param::new(
160            Bytes::from_static(PN_PRID.as_bytes()),
161            Bytes::from(self.prid.clone()),
162        ));
163    }
164
165    /// Read the push parameters off a URI (§8.7).
166    ///
167    /// `None` unless both `pn-provider` and `pn-prid` are there: §4.1.2 has a UA insert them
168    /// together, and either alone names no service that could be asked to wake anything.
169    #[must_use]
170    pub fn from_uri(uri: &Uri) -> Option<Self> {
171        let params = uri.params()?;
172        let text = |name: &str| {
173            params
174                .value(name)
175                .and_then(|raw| std::str::from_utf8(raw).ok())
176        };
177        Some(Self {
178            provider: pvalue(text(PN_PROVIDER)?, PN_PROVIDER).ok()?,
179            param: text(PN_PARAM).and_then(|value| pvalue(value, PN_PARAM).ok()),
180            prid: pvalue(text(PN_PRID)?, PN_PRID).ok()?,
181        })
182    }
183}
184
185/// The PURR a URI carries, if it carries one (§8.7).
186///
187/// Returned raw and not interpreted. The PURR is the proxy's name for a binding, so that a
188/// mid-dialog request can be matched to the binding it belongs to without re-deriving it from the
189/// `pn-*` values — which means it is only useful to a party that *stores* bindings. sipx stores
190/// none: it is a user agent, not a registrar or a proxy, so it reads the PURR its registrar
191/// assigned (see [`Indicators::purr`]), carries it, and does no matching with it. That half of
192/// §5.6 belongs where the other proxy roles do.
193#[must_use]
194pub fn purr(uri: &Uri) -> Option<&[u8]> {
195    uri.params().and_then(|params| params.value(PN_PURR))
196}
197
198/// The push feature-capability indicators one `Feature-Caps` value carries (§8.2).
199///
200/// RFC 6809 §4 gives the header the shape `*` followed by `;`-separated indicators, and RFC 8599
201/// §8.2 registers three of them that a client cares about. Indicators belonging to other
202/// mechanisms are ignored rather than rejected — that is what an extensible list is for, and a
203/// parser that failed on an unknown one would break against every proxy that grows a feature.
204///
205/// This models one value. A registrar may send several, on one row or on several; read them with
206/// [`crate::message::Headers::typed_all`], which treats those two spellings as the same message.
207#[derive(Debug, Clone, Default, PartialEq, Eq)]
208pub struct Indicators {
209    pns: Option<Vec<u8>>,
210    pnsreg: Option<Vec<u8>>,
211    pnspurr: Option<Vec<u8>>,
212}
213
214impl Indicators {
215    /// The push notification service this value names (§8.2's `sip.pns`).
216    ///
217    /// This is the answer to "does this registrar support the service I asked for". A registrar
218    /// naming a different one has not refused the registration — it has accepted a binding that
219    /// nothing will ever wake, which is worse, because it looks like success.
220    #[must_use]
221    pub fn pns(&self) -> Option<&[u8]> {
222        self.pns.as_deref()
223    }
224
225    /// Whether the registrar asked for binding refreshes even in the absence of a push (§8.2's
226    /// `sip.pnsreg`).
227    ///
228    /// Presence is a fact of its own, separate from the interval: an indicator sent with a value
229    /// this side cannot read still says the registrar wants refreshes, and treating it as absent
230    /// would let the binding lapse.
231    #[must_use]
232    pub fn refreshes_required(&self) -> bool {
233        self.pnsreg.is_some()
234    }
235
236    /// How long the registrar said to leave between those refreshes (§8.2's `sip.pnsreg`).
237    ///
238    /// `None` when the indicator is absent *and* when its value is not a number of seconds; see
239    /// [`Indicators::refreshes_required`] for the difference.
240    #[must_use]
241    pub fn refresh_interval(&self) -> Option<Duration> {
242        let raw = self.pnsreg.as_deref()?;
243        std::str::from_utf8(raw)
244            .ok()?
245            .trim()
246            .parse::<u64>()
247            .ok()
248            .map(Duration::from_secs)
249    }
250
251    /// The PURR the proxy assigned this binding (§8.2's `sip.pnspurr`).
252    #[must_use]
253    pub fn purr(&self) -> Option<&[u8]> {
254        self.pnspurr.as_deref()
255    }
256
257    /// Whether this value said nothing about push at all.
258    #[must_use]
259    pub fn is_empty(&self) -> bool {
260        self.pns.is_none() && self.pnsreg.is_none() && self.pnspurr.is_none()
261    }
262
263    /// Pick the three push indicators out of a parsed indicator list.
264    ///
265    /// Two readings of a valueless indicator, because the three do not mean the same kind of
266    /// thing. `sip.pns` and `sip.pnspurr` **name** something — a push service, a binding — and a
267    /// name with no characters in it names nothing; kept, it would answer
268    /// [`crate::push::Indicators::pns`] with a service that compares equal to the empty string,
269    /// which is a service no client asked for and every client with an empty provider matches.
270    /// `sip.pnsreg` asks for something, and the asking is the whole of it: present without a
271    /// readable interval it still says the registrar wants refreshes, and dropping it would let
272    /// the binding lapse.
273    fn from_params(params: &[HeaderParam]) -> Self {
274        let named = |name: &str| {
275            grammar::param(params, name)
276                .and_then(|found| found.value.clone())
277                .filter(|value| !value.is_empty())
278        };
279        Self {
280            pns: named(SIP_PNS),
281            pnsreg: grammar::param(params, SIP_PNSREG)
282                .map(|found| found.value.clone().unwrap_or_default()),
283            pnspurr: named(SIP_PNSPURR),
284        }
285    }
286}
287
288impl TypedHeader for Indicators {
289    const NAME: HeaderName = HeaderName::FeatureCaps;
290
291    /// Decodes the **first** value in the row; use
292    /// [`crate::message::Headers::typed_all`] when every one is needed.
293    fn decode(value: &[u8]) -> Result<Self, HeaderError> {
294        let parts = grammar::split_list(value, "Feature-Caps")?;
295        decode_one(parts.first().copied().unwrap_or(&[]))
296    }
297
298    fn decode_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
299        grammar::split_list(value, "Feature-Caps")?
300            .into_iter()
301            .map(decode_one)
302            .collect()
303    }
304}
305
306/// One `fc-value` (RFC 6809 §4): `"*" *( SEMI feature-cap )`.
307fn decode_one(value: &[u8]) -> Result<Indicators, HeaderError> {
308    let value = trim(value);
309    // The `*` is not decoration: RFC 6809 §4 makes it the whole of the value's non-parameter
310    // part, and a row that opens with anything else is not a `Feature-Caps` value.
311    if value.first() != Some(&b'*') {
312        return Err(HeaderError::Syntax {
313            header: "Feature-Caps",
314        });
315    }
316    let params = grammar::parse_params(trim(value.get(1..).unwrap_or(&[])), "Feature-Caps")?;
317    Ok(Indicators::from_params(&params))
318}
319
320/// Check a value against RFC 3261 §25.1's `pvalue`, which is what a URI parameter may hold.
321///
322/// ```abnf
323/// pvalue           = 1*paramchar
324/// paramchar        = param-unreserved / unreserved / escaped
325/// param-unreserved = "[" / "]" / "/" / ":" / "&" / "+" / "$"
326/// ```
327fn pvalue(value: &str, field: &'static str) -> Result<String, BuildError> {
328    let bytes = value.as_bytes();
329    if bytes.is_empty() {
330        return Err(BuildError::NotAToken { field });
331    }
332    let mut at = 0usize;
333    while let Some(&byte) = bytes.get(at) {
334        if byte == b'%' {
335            // `escaped = "%" HEXDIG HEXDIG`, and a lone `%` is not one.
336            if !bytes.get(at + 1).is_some_and(u8::is_ascii_hexdigit)
337                || !bytes.get(at + 2).is_some_and(u8::is_ascii_hexdigit)
338            {
339                return Err(BuildError::NotAToken { field });
340            }
341            at += 3;
342            continue;
343        }
344        if !is_paramchar(byte) {
345            return Err(BuildError::NotAToken { field });
346        }
347        at += 1;
348    }
349    Ok(value.to_owned())
350}
351
352/// `paramchar` less `escaped`, which [`pvalue`] handles separately.
353#[must_use]
354fn is_paramchar(byte: u8) -> bool {
355    byte.is_ascii_alphanumeric()
356        || matches!(
357            byte,
358            // unreserved: mark
359            b'-' | b'_' | b'.' | b'!' | b'~' | b'*' | b'\'' | b'(' | b')'
360            // param-unreserved
361            | b'[' | b']' | b'/' | b':' | b'&' | b'+' | b'$'
362        )
363}
364
365#[cfg(test)]
366#[allow(
367    clippy::unwrap_used,
368    clippy::expect_used,
369    clippy::panic,
370    clippy::indexing_slicing
371)]
372mod tests {
373    use super::*;
374    use crate::message::Headers;
375    use crate::parser::{Limits, parse_datagram};
376    use crate::{Message, Response};
377
378    fn uri(text: &str) -> Uri {
379        Uri::parse(Bytes::from(text.to_owned())).unwrap_or_else(|e| panic!("{text:?}: {e}"))
380    }
381
382    fn device() -> Device {
383        Device::new("webpush", "c1a5b3e7d9f2")
384            .expect("valid")
385            .with_param("7f3ad0")
386            .expect("valid")
387    }
388
389    /// §4.1.2's three parameters, in a URI, in the order the section lists them.
390    #[test]
391    fn the_push_parameters_go_into_the_uri_grammar() {
392        let mut contact = uri("sip:alice@192.0.2.5:5060");
393        device().set_on(&mut contact);
394        assert_eq!(
395            contact.to_bytes(),
396            Bytes::from_static(
397                b"sip:alice@192.0.2.5:5060;pn-provider=webpush;pn-param=7f3ad0;pn-prid=c1a5b3e7d9f2"
398            )
399        );
400        // And back out again: what a registrar reads is what the UA meant to say.
401        assert_eq!(Device::from_uri(&contact), Some(device()));
402    }
403
404    /// §4.1.2 makes `pn-param` service-specific, so a service that needs nothing beyond the
405    /// identifier sends two parameters, not three.
406    #[test]
407    fn a_service_that_needs_no_pn_param_sends_none() {
408        let mut contact = uri("sip:alice@192.0.2.5:5060");
409        Device::new("webpush", "c1a5b3e7d9f2")
410            .expect("valid")
411            .set_on(&mut contact);
412        assert_eq!(
413            contact.to_bytes(),
414            Bytes::from_static(
415                b"sip:alice@192.0.2.5:5060;pn-provider=webpush;pn-prid=c1a5b3e7d9f2"
416            )
417        );
418        assert!(Device::from_uri(&contact).is_some_and(|d| d.param().is_none()));
419    }
420
421    /// RFC 3261 §19.1.1: "any given parameter-name MUST NOT appear more than once". Appending a
422    /// second `pn-prid` beside a stale one produces a URI the registrar cannot parse at all —
423    /// which takes the registration down rather than merely losing the push parameters.
424    #[test]
425    fn setting_the_parameters_twice_replaces_them_rather_than_repeating_them() {
426        let mut contact = uri("sip:alice@192.0.2.5:5060");
427        device().set_on(&mut contact);
428        Device::new("webpush", "0000deadbeef")
429            .expect("valid")
430            .set_on(&mut contact);
431        assert_eq!(
432            contact.to_bytes(),
433            Bytes::from_static(
434                b"sip:alice@192.0.2.5:5060;pn-provider=webpush;pn-prid=0000deadbeef"
435            )
436        );
437        // The proof that matters: it still parses.
438        assert!(Uri::parse(contact.to_bytes()).is_ok());
439    }
440
441    /// Other URI parameters are none of this mechanism's business and must survive it.
442    #[test]
443    fn the_other_uri_parameters_are_left_alone() {
444        let mut contact = uri("sip:alice@192.0.2.5:5060;transport=tcp;ob");
445        device().set_on(&mut contact);
446        let text = String::from_utf8_lossy(&contact.to_bytes()).into_owned();
447        assert!(
448            text.starts_with("sip:alice@192.0.2.5:5060;transport=tcp;ob;"),
449            "{text}"
450        );
451        assert!(text.contains(";pn-prid=c1a5b3e7d9f2"), "{text}");
452    }
453
454    /// A token carrying an octet `pvalue` does not admit would not be rejected by the registrar —
455    /// it would silently become a *different* URI, its tail read as further parameters. So it is
456    /// refused here, where the caller can still do something about it.
457    #[test]
458    fn a_value_outside_pvalue_is_refused_rather_than_pasted_in() {
459        for bad in [
460            "tok=en", "tok;en", "tok en", "tok@en", "tok?en", "", "tok%zz", "tok%4",
461        ] {
462            assert!(
463                Device::new("webpush", bad).is_err(),
464                "{bad:?} was accepted into a URI parameter"
465            );
466        }
467        // `escaped` is in the grammar, which is how a caller carries the rest.
468        assert!(Device::new("webpush", "tok%3Den").is_ok());
469        // As are the characters `param-unreserved` names, which base64url tokens use.
470        assert!(Device::new("webpush", "a-b_c.d~e+f/g").is_ok());
471    }
472
473    /// A URI with only half the pair names no service that anything could be asked to wake.
474    #[test]
475    fn half_a_binding_is_not_one() {
476        assert!(Device::from_uri(&uri("sip:alice@192.0.2.5;pn-provider=webpush")).is_none());
477        assert!(Device::from_uri(&uri("sip:alice@192.0.2.5;pn-prid=c1a5b3e7d9f2")).is_none());
478        assert!(Device::from_uri(&uri("sip:alice@192.0.2.5")).is_none());
479    }
480
481    /// §8.7 registers `pn-purr` as a URI parameter, and it is read rather than minted.
482    #[test]
483    fn the_purr_is_read_off_a_uri_and_not_interpreted() {
484        assert_eq!(
485            purr(&uri("sip:alice@192.0.2.5;pn-purr=opaque-purr-1")),
486            Some(&b"opaque-purr-1"[..])
487        );
488        assert_eq!(purr(&uri("sip:alice@192.0.2.5")), None);
489    }
490
491    fn response(caps: &str) -> Response {
492        let text = format!(
493            "SIP/2.0 200 OK\r\n\
494             Via: SIP/2.0/UDP 192.0.2.5:5060;branch=z9hG4bKx\r\n\
495             To: <sip:alice@example.com>;tag=r\r\n\
496             From: <sip:alice@example.com>;tag=1\r\n\
497             Call-ID: reg-1@192.0.2.5\r\n\
498             CSeq: 1 REGISTER\r\n\
499             {caps}\
500             Content-Length: 0\r\n\r\n"
501        );
502        match parse_datagram(Bytes::from(text), &Limits::datagram()).expect("parses") {
503            Message::Response(r) => r,
504            Message::Request(_) => panic!("a response"),
505        }
506    }
507
508    fn read(caps: &str) -> Vec<Indicators> {
509        response(caps)
510            .headers
511            .typed_all::<Indicators>()
512            .collect::<Result<Vec<_>, _>>()
513            .expect("parses")
514    }
515
516    /// §8.2's three indicators, as RFC 6809 §4 spells them.
517    #[test]
518    fn the_push_indicators_are_read_out_of_feature_caps() {
519        let read = read(
520            "Feature-Caps: *;+sip.pns=\"webpush\";+sip.pnsreg=\"120\"\
521             ;+sip.pnspurr=\"opaque-purr-1\"\r\n",
522        );
523        let one = read.first().expect("one value");
524        assert_eq!(one.pns(), Some(&b"webpush"[..]));
525        assert!(one.refreshes_required());
526        assert_eq!(one.refresh_interval(), Some(Duration::from_secs(120)));
527        assert_eq!(one.purr(), Some(&b"opaque-purr-1"[..]));
528    }
529
530    /// RFC 6809 §4 makes the header a comma-separated list, so one row of two values and two rows
531    /// of one are the same message (RFC 3261 §7.3).
532    #[test]
533    fn a_comma_joined_row_is_the_same_as_separate_rows() {
534        let joined = read("Feature-Caps: *;+sip.pns=\"webpush\", *;+sip.pnspurr=\"p1\"\r\n");
535        let separate = read(
536            "Feature-Caps: *;+sip.pns=\"webpush\"\r\n\
537             Feature-Caps: *;+sip.pnspurr=\"p1\"\r\n",
538        );
539        assert_eq!(joined.len(), 2, "the comma-joined row was not split");
540        assert_eq!(joined, separate);
541    }
542
543    /// An indicator belonging to some other mechanism is not an error. A parser that rejected one
544    /// would break against every proxy that ever grows a feature.
545    #[test]
546    fn indicators_this_side_does_not_know_are_ignored() {
547        let read = read("Feature-Caps: *;+sip.something.else=\"x\";+sip.pns=\"webpush\"\r\n");
548        assert_eq!(
549            read.first().and_then(Indicators::pns),
550            Some(&b"webpush"[..])
551        );
552    }
553
554    /// A registrar that says nothing about push has said nothing about push — not "no".
555    #[test]
556    fn a_value_with_no_push_indicators_is_empty_rather_than_negative() {
557        assert!(read("Feature-Caps: *;+sip.other=\"x\"\r\n")[0].is_empty());
558        assert!(
559            response("")
560                .headers
561                .typed_all::<Indicators>()
562                .next()
563                .is_none()
564        );
565    }
566
567    /// A valueless `sip.pns` names no service, and a name of no characters must not become one:
568    /// kept, it would answer [`Indicators::pns`] with a service equal to the empty string, which
569    /// no client asked for and any client with an empty provider would match. Same for the PURR,
570    /// which names a binding. `sip.pnsreg` is the exception because its meaning is the asking.
571    #[test]
572    fn a_valueless_indicator_names_nothing_rather_than_naming_the_empty_string() {
573        let values = read("Feature-Caps: *;+sip.pns;+sip.pnspurr;+sip.pnsreg\r\n");
574        let one = values.first().expect("one value");
575        assert_eq!(one.pns(), None, "an empty service name became a service");
576        assert_eq!(one.purr(), None, "an empty PURR named a binding");
577        assert!(
578            one.refreshes_required(),
579            "sip.pnsreg asks for refreshes by being there at all"
580        );
581        assert_eq!(one.refresh_interval(), None);
582        // And an explicitly empty value is the same claim written differently.
583        assert_eq!(
584            read("Feature-Caps: *;+sip.pns=\"\"\r\n")
585                .first()
586                .and_then(Indicators::pns),
587            None
588        );
589    }
590
591    /// `sip.pnsreg` present with a value this side cannot read still says the registrar wants
592    /// refreshes. Reading it as absent would let the binding lapse.
593    #[test]
594    fn an_unreadable_refresh_interval_is_still_a_demand_for_refreshes() {
595        let read = read("Feature-Caps: *;+sip.pnsreg=\"soon\"\r\n");
596        let one = read.first().expect("one value");
597        assert!(one.refreshes_required());
598        assert_eq!(one.refresh_interval(), None);
599    }
600
601    /// RFC 6809 §4's `fc-value` opens with `*`. A row that does not is not one of these.
602    #[test]
603    fn a_value_that_is_not_a_feature_caps_value_is_a_parse_error() {
604        assert!(Indicators::decode(b"+sip.pns=\"webpush\"").is_err());
605        assert!(Indicators::decode(b"").is_err());
606        // A bare `*` is legal and says nothing.
607        assert!(Indicators::decode(b"*").expect("parses").is_empty());
608    }
609
610    /// §8.1's code, and the reason it is worth telling apart from every other refusal.
611    #[test]
612    fn the_push_notification_status_code_is_555() {
613        assert_eq!(NOT_SUPPORTED, 555);
614        assert!(is_not_supported(StatusCode::new(555).expect("valid")));
615        assert!(!is_not_supported(StatusCode::new(500).expect("valid")));
616        assert!(!is_not_supported(StatusCode::new(403).expect("valid")));
617    }
618
619    /// The name table has to know the header, or `typed_all` looks for a variant nothing carries.
620    #[test]
621    fn feature_caps_resolves_to_the_header_this_reads() {
622        let headers = response("Feature-Caps: *;+sip.pns=\"webpush\"\r\n").headers;
623        assert!(matches!(
624            Headers::typed::<Indicators>(&headers),
625            Some(Ok(_))
626        ));
627    }
628}