Skip to main content

opentele2_browser/
network.rs

1//! The Bayesian networks of the Apify fingerprint datapoints and their
2//! sampler, based on browserforge's `bayesian_network.py`.
3//!
4//! A network is a list of nodes in sampling order. Each node holds a
5//! conditional probability table (CPT): a flat value-to-probability map when
6//! the node has no parents, otherwise one `{"deeper": {parentValue: ...},
7//! "skip": ...}` level per parent, in `parentNames` order. A parent value
8//! missing from `deeper` descends into `skip`, or into an empty distribution
9//! when there is none.
10//!
11//! The tree keeps the JSON key order, which fixes the order in which a draw
12//! walks the candidates, and every string is interned once per network.
13
14use std::borrow::Cow;
15use std::collections::{HashMap, HashSet};
16use std::fmt;
17use std::sync::{Arc, OnceLock};
18
19use serde::Deserialize;
20use serde::de::{
21    self, DeserializeSeed, IgnoredAny, IntoDeserializer, MapAccess, SeqAccess, Visitor,
22};
23
24use crate::rng::Rng;
25use crate::{Error, Restrictions, Values};
26
27/// The zipped input network: `*DEVICE`, `*OPERATING_SYSTEM`, `*BROWSER_HTTP`,
28/// `*HTTP_VERSION`, `*BROWSER`.
29pub const INPUT_NETWORK_ZIP: &[u8] = include_bytes!("../data/input-network-definition.zip");
30/// The zipped HTTP header network.
31pub const HEADER_NETWORK_ZIP: &[u8] = include_bytes!("../data/header-network-definition.zip");
32/// The zipped browser fingerprint network.
33pub const FINGERPRINT_NETWORK_ZIP: &[u8] =
34    include_bytes!("../data/fingerprint-network-definition.zip");
35
36/// An interned string of one network.
37type Id = u32;
38
39/// Above this many candidate-times-entry comparisons a restricted draw
40/// indexes the distribution in a hash map instead of scanning it.
41const LINEAR_LOOKUP_LIMIT: usize = 4096;
42
43#[derive(Debug, Default)]
44struct Interner {
45    strings: Vec<Arc<str>>,
46    ids: HashMap<Arc<str>, Id>,
47}
48
49impl Interner {
50    fn intern(&mut self, value: &str) -> Option<Id> {
51        if let Some(&id) = self.ids.get(value) {
52            return Some(id);
53        }
54        let id = Id::try_from(self.strings.len()).ok()?;
55        let shared: Arc<str> = Arc::from(value);
56        self.strings.push(Arc::clone(&shared));
57        self.ids.insert(shared, id);
58        Some(id)
59    }
60
61    fn get(&self, value: &str) -> Option<Id> {
62        self.ids.get(value).copied()
63    }
64
65    fn resolve(&self, id: Id) -> &str {
66        &self.strings[id as usize]
67    }
68}
69
70/// One level of a conditional probability table.
71#[derive(Debug, Default)]
72struct Cpt {
73    /// The distribution, when this level is a leaf.
74    leaf_values: Vec<Id>,
75    leaf_probabilities: Vec<f64>,
76    /// `deeper`: the parent values and the subtree each selects.
77    deeper_values: Vec<Id>,
78    deeper: Vec<Cpt>,
79    /// `skip`: the subtree for a parent value absent from `deeper`.
80    skip: Option<Box<Cpt>>,
81}
82
83impl Cpt {
84    /// Descends one parent level; `None` is the empty distribution.
85    fn child(&self, parent_value: Option<Id>) -> Option<&Cpt> {
86        parent_value
87            .and_then(|value| self.deeper_values.iter().position(|&k| k == value))
88            .map(|i| &self.deeper[i])
89            .or(self.skip.as_deref())
90    }
91
92    fn leaf(&self) -> impl ExactSizeIterator<Item = (Id, f64)> + '_ {
93        self.leaf_values
94            .iter()
95            .copied()
96            .zip(self.leaf_probabilities.iter().copied())
97    }
98
99    fn probability(&self, value: Id) -> Option<f64> {
100        self.leaf_values
101            .iter()
102            .position(|&k| k == value)
103            .map(|i| self.leaf_probabilities[i])
104    }
105}
106
107#[derive(Debug)]
108struct NodeDef {
109    name: Box<str>,
110    parent_names: Vec<Box<str>>,
111    /// The sampling-order index of each parent, `None` for a parent that is
112    /// not a node of the network.
113    parents: Vec<Option<usize>>,
114    possible_values: Vec<Id>,
115    cpt: Cpt,
116}
117
118impl NodeDef {
119    /// browserforge's `get_probabilities_given_known_values`: the CPT level
120    /// selected by the parent values, `None` for the empty distribution.
121    fn level(&self, mut parent_value: impl FnMut(usize) -> Option<Id>) -> Option<&Cpt> {
122        let mut level = &self.cpt;
123        for position in 0..self.parent_names.len() {
124            level = level.child(parent_value(position))?;
125        }
126        Some(level)
127    }
128}
129
130/// browserforge's draw: one anchor in `[0, 1)`, then the first candidate whose
131/// running probability total exceeds it, else the first candidate. The total
132/// is not renormalised. No candidate draws nothing and yields `None`.
133fn draw(rng: &mut Rng, candidates: impl IntoIterator<Item = (Id, f64)>) -> Option<Id> {
134    let mut candidates = candidates.into_iter();
135    let (first, first_probability) = candidates.next()?;
136    let anchor = rng.next_f64();
137    let mut cumulative = first_probability;
138    if cumulative > anchor {
139        return Some(first);
140    }
141    for (value, probability) in candidates {
142        cumulative += probability;
143        if cumulative > anchor {
144            return Some(value);
145        }
146    }
147    Some(first)
148}
149
150/// browserforge's `sample_according_to_restrictions`: the candidates are the
151/// `allowed` values, in their order, that are not banned and have a
152/// probability at this level.
153fn draw_restricted(rng: &mut Rng, level: &Cpt, allowed: &[Id], banned: &[Id]) -> Option<Id> {
154    let usable = |value: &Id| !banned.contains(value);
155    let candidates: Vec<(Id, f64)> =
156        if allowed.len().saturating_mul(level.leaf_values.len()) > LINEAR_LOOKUP_LIMIT {
157            let mut index = HashMap::with_capacity(level.leaf_values.len());
158            for (value, probability) in level.leaf() {
159                index.entry(value).or_insert(probability);
160            }
161            allowed
162                .iter()
163                .filter(|v| usable(v))
164                .filter_map(|&v| index.get(&v).map(|&p| (v, p)))
165                .collect()
166        } else {
167            allowed
168                .iter()
169                .filter(|v| usable(v))
170                .filter_map(|&v| level.probability(v).map(|p| (v, p)))
171                .collect()
172        };
173    draw(rng, candidates)
174}
175
176/// A Bayesian network that samples browser attributes.
177pub struct BayesianNetwork {
178    nodes: Vec<NodeDef>,
179    by_name: HashMap<Box<str>, usize>,
180    interner: Interner,
181}
182
183impl fmt::Debug for BayesianNetwork {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        f.debug_struct("BayesianNetwork")
186            .field(
187                "nodes",
188                &self.nodes.iter().map(|n| &*n.name).collect::<Vec<_>>(),
189            )
190            .field("interned_values", &self.interner.strings.len())
191            .finish_non_exhaustive()
192    }
193}
194
195/// A node of a [`BayesianNetwork`].
196#[derive(Clone, Copy)]
197pub struct Node<'a> {
198    network: &'a BayesianNetwork,
199    def: &'a NodeDef,
200}
201
202impl fmt::Debug for Node<'_> {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        f.debug_struct("Node")
205            .field("name", &self.def.name)
206            .field("parent_names", &self.def.parent_names)
207            .finish_non_exhaustive()
208    }
209}
210
211impl<'a> Node<'a> {
212    /// The node name.
213    #[must_use]
214    pub fn name(&self) -> &'a str {
215        &self.def.name
216    }
217
218    /// The names of the parents, in CPT level order.
219    pub fn parent_names(&self) -> impl ExactSizeIterator<Item = &'a str> + use<'a> {
220        self.def.parent_names.iter().map(|name| &**name)
221    }
222
223    /// The values the node can take, in definition order.
224    pub fn possible_values(&self) -> impl ExactSizeIterator<Item = &'a str> + use<'a> {
225        let interner = &self.network.interner;
226        self.def
227            .possible_values
228            .iter()
229            .map(|&id| interner.resolve(id))
230    }
231
232    fn level(&self, parent_values: &Values) -> Option<&'a Cpt> {
233        let def: &'a NodeDef = self.def;
234        let interner = &self.network.interner;
235        def.level(|position| {
236            parent_values
237                .get(&def.parent_names[position])
238                .and_then(|value| interner.get(value))
239        })
240    }
241
242    /// The distribution of the node given the values of its parents, looked up
243    /// by name in `parent_values` (browserforge's
244    /// `get_probabilities_given_known_values`). Empty when the CPT has no
245    /// entry for those parent values.
246    #[must_use]
247    pub fn probabilities(&self, parent_values: &Values) -> Vec<(&'a str, f64)> {
248        let interner = &self.network.interner;
249        self.level(parent_values)
250            .map(|level| {
251                level
252                    .leaf()
253                    .map(|(id, p)| (interner.resolve(id), p))
254                    .collect()
255            })
256            .unwrap_or_default()
257    }
258
259    /// Draws a value given the values of the parents; `None` when the
260    /// distribution is empty.
261    pub fn sample(&self, rng: &mut Rng, parent_values: &Values) -> Option<&'a str> {
262        let level = self.level(parent_values)?;
263        draw(rng, level.leaf()).map(|id| self.network.interner.resolve(id))
264    }
265}
266
267impl BayesianNetwork {
268    /// Parses a network from a zip archive holding its `network.json`.
269    ///
270    /// # Errors
271    ///
272    /// [`Error::Zip`] when the archive is unreadable, [`Error::Network`] when
273    /// the JSON is not a network definition.
274    pub fn from_zip(archive: &[u8]) -> crate::Result<Self> {
275        Self::from_json(&crate::zip::first_json_entry(archive)?)
276    }
277
278    /// Parses a network from its `network.json` bytes.
279    ///
280    /// # Errors
281    ///
282    /// [`Error::Network`] when the JSON is not a network definition.
283    pub fn from_json(json: &[u8]) -> crate::Result<Self> {
284        let network_error = |e: serde_json::Error| Error::Network(e.to_string());
285        let mut interner = Interner::default();
286        let mut deserializer = serde_json::Deserializer::from_slice(json);
287        let mut nodes = NetworkSeed(&mut interner)
288            .deserialize(&mut deserializer)
289            .map_err(network_error)?;
290        deserializer.end().map_err(network_error)?;
291        let by_name: HashMap<Box<str>, usize> = nodes
292            .iter()
293            .enumerate()
294            .map(|(i, node)| (node.name.clone(), i))
295            .collect();
296        for node in &mut nodes {
297            node.parents = node
298                .parent_names
299                .iter()
300                .map(|parent| by_name.get(parent).copied())
301                .collect();
302        }
303        Ok(Self {
304            nodes,
305            by_name,
306            interner,
307        })
308    }
309
310    /// The number of nodes.
311    #[must_use]
312    pub fn len(&self) -> usize {
313        self.nodes.len()
314    }
315
316    /// Returns `true` for a network without nodes.
317    #[must_use]
318    pub fn is_empty(&self) -> bool {
319        self.nodes.is_empty()
320    }
321
322    /// The node called `name`.
323    #[must_use]
324    pub fn node(&self, name: &str) -> Option<Node<'_>> {
325        self.by_name.get(name).map(|&i| Node {
326            network: self,
327            def: &self.nodes[i],
328        })
329    }
330
331    /// The nodes in sampling order.
332    pub fn nodes(&self) -> impl ExactSizeIterator<Item = Node<'_>> {
333        self.nodes.iter().map(|def| Node { network: self, def })
334    }
335
336    /// browserforge's `generate_sample`: starts from a copy of `fixed`, then
337    /// samples every node it does not name, in sampling order, each given the
338    /// values chosen so far. A node whose distribution is empty for its
339    /// parent values stays out of the sample.
340    #[must_use]
341    pub fn sample(&self, rng: &mut Rng, fixed: &Values) -> Values {
342        let mut sample = fixed.clone();
343        for node in self.nodes() {
344            if sample.contains_key(node.name()) {
345                continue;
346            }
347            if let Some(value) = node.sample(rng, &sample) {
348                sample.insert(node.name(), value.to_owned());
349            }
350        }
351        sample
352    }
353
354    /// browserforge's `generate_consistent_sample_when_possible`: a sample in
355    /// which every node named in `possibilities` takes one of its listed
356    /// values (other nodes range over their possible values). Searches depth
357    /// first in sampling order, banning a value whose subtree has no
358    /// consistent completion and backtracking when a node runs out of values.
359    /// `None` when no consistent sample exists.
360    #[must_use]
361    pub fn consistent_sample(&self, rng: &mut Rng, possibilities: &Restrictions) -> Option<Values> {
362        let allowed: Vec<Cow<'_, [Id]>> = self
363            .nodes
364            .iter()
365            .map(|node| match possibilities.get(&node.name) {
366                // A value the network never mentions has no probability
367                // anywhere, so dropping it changes no draw.
368                Some(values) => {
369                    Cow::Owned(values.iter().filter_map(|v| self.interner.get(v)).collect())
370                }
371                None => Cow::Borrowed(node.possible_values.as_slice()),
372            })
373            .collect();
374        let mut chosen = vec![None; self.nodes.len()];
375        if !self.consistent_from(rng, &allowed, &mut chosen, 0) {
376            return None;
377        }
378        Some(
379            self.nodes
380                .iter()
381                .zip(chosen)
382                .filter_map(|(node, id)| {
383                    id.map(|id| (&*node.name, self.interner.resolve(id).to_owned()))
384                })
385                .collect(),
386        )
387    }
388
389    fn consistent_from(
390        &self,
391        rng: &mut Rng,
392        allowed: &[Cow<'_, [Id]>],
393        chosen: &mut [Option<Id>],
394        depth: usize,
395    ) -> bool {
396        let Some(node) = self.nodes.get(depth) else {
397            return true;
398        };
399        // The parents precede the node, so their values (and the level they
400        // select) stay fixed while this node tries its candidates.
401        let Some(level) = node.level(|position| node.parents[position].and_then(|i| chosen[i]))
402        else {
403            return false;
404        };
405        let mut banned = Vec::new();
406        while let Some(value) = draw_restricted(rng, level, &allowed[depth], &banned) {
407            chosen[depth] = Some(value);
408            if self.consistent_from(rng, allowed, chosen, depth + 1) {
409                return true;
410            }
411            banned.push(value);
412            chosen[depth] = None;
413        }
414        false
415    }
416
417    /// [`BayesianNetwork::sample`] in which every node named in `restrictions`
418    /// (and not in `fixed`) takes one of its listed values.
419    ///
420    /// Without restrictions the draws, and so the result, are exactly those
421    /// of [`BayesianNetwork::sample`]. With restrictions the search is depth
422    /// first in sampling order, banning a value whose subtree has no
423    /// completion and backtracking, as [`BayesianNetwork::consistent_sample`]
424    /// does; unrestricted nodes draw from their full distribution and, as in
425    /// `sample`, stay out of the result when it is empty. `None` when no
426    /// sample satisfies the restrictions.
427    #[must_use]
428    pub fn restricted_sample(
429        &self,
430        rng: &mut Rng,
431        fixed: &Values,
432        restrictions: &Restrictions,
433    ) -> Option<Values> {
434        let allowed: Vec<Option<Vec<Id>>> = self
435            .nodes
436            .iter()
437            .map(|node| {
438                if fixed.contains_key(&node.name) {
439                    return None;
440                }
441                restrictions
442                    .get(&node.name)
443                    .map(|values| values.iter().filter_map(|v| self.interner.get(v)).collect())
444            })
445            .collect();
446        let mut chosen: Vec<Option<Id>> = self
447            .nodes
448            .iter()
449            .map(|node| fixed.get(&node.name).and_then(|v| self.interner.get(v)))
450            .collect();
451        if !self.restricted_from(rng, fixed, &allowed, &mut chosen, 0) {
452            return None;
453        }
454        let mut sample = fixed.clone();
455        for (node, id) in self.nodes.iter().zip(chosen) {
456            if fixed.contains_key(&node.name) {
457                continue;
458            }
459            if let Some(id) = id {
460                sample.insert(&*node.name, self.interner.resolve(id).to_owned());
461            }
462        }
463        Some(sample)
464    }
465
466    fn restricted_from(
467        &self,
468        rng: &mut Rng,
469        fixed: &Values,
470        allowed: &[Option<Vec<Id>>],
471        chosen: &mut [Option<Id>],
472        depth: usize,
473    ) -> bool {
474        let Some(node) = self.nodes.get(depth) else {
475            return true;
476        };
477        if fixed.contains_key(&node.name) {
478            return self.restricted_from(rng, fixed, allowed, chosen, depth + 1);
479        }
480        // A parent outside the network is looked up by name among the fixed
481        // values, as `sample` does.
482        let level = node.level(|position| match node.parents[position] {
483            Some(i) => chosen[i],
484            None => fixed
485                .get(&node.parent_names[position])
486                .and_then(|v| self.interner.get(v)),
487        });
488        let mut banned = Vec::new();
489        loop {
490            let drawn = match (&allowed[depth], level) {
491                (_, None) => None,
492                (Some(list), Some(level)) => draw_restricted(rng, level, list, &banned),
493                (None, Some(level)) => draw(rng, level.leaf().filter(|(v, _)| !banned.contains(v))),
494            };
495            let Some(value) = drawn else {
496                // An unrestricted node with an empty distribution stays out
497                // of the sample; anything else has run out of values.
498                if allowed[depth].is_none() && banned.is_empty() {
499                    chosen[depth] = None;
500                    return self.restricted_from(rng, fixed, allowed, chosen, depth + 1);
501                }
502                return false;
503            };
504            chosen[depth] = Some(value);
505            if self.restricted_from(rng, fixed, allowed, chosen, depth + 1) {
506                return true;
507            }
508            banned.push(value);
509            chosen[depth] = None;
510        }
511    }
512
513    /// browserforge's `get_possible_values`: extends the constraints with the
514    /// values they induce on the parents of each constrained node, then
515    /// intersects the constraints that meet on a node.
516    ///
517    /// For each constrained node the parents may take the values on any CPT
518    /// path (ignoring `skip`) that ends in one of the node's allowed values.
519    /// Every list keeps first-seen order.
520    ///
521    /// # Errors
522    ///
523    /// [`Error::TooRestrictive`] when a constraint is empty or an intersection
524    /// is; [`Error::UnknownNode`] when a constraint names no node.
525    pub fn possible_values(&self, restrictions: &Restrictions) -> crate::Result<Restrictions> {
526        let mut sets = Vec::with_capacity(restrictions.len());
527        for (name, values) in restrictions {
528            if values.is_empty() {
529                return Err(Error::TooRestrictive);
530            }
531            let &index = self
532                .by_name
533                .get(name)
534                .ok_or_else(|| Error::UnknownNode(name.to_owned()))?;
535            let node = &self.nodes[index];
536            let valid: HashSet<Id> = values.iter().filter_map(|v| self.interner.get(v)).collect();
537            let mut paths = PathLevels::default();
538            paths.collect(&node.cpt, &valid, &mut Vec::new());
539            let mut set: Restrictions = node
540                .parent_names
541                .iter()
542                .zip(paths.levels)
543                .map(|(parent, (ids, _))| {
544                    let parent_values = ids
545                        .iter()
546                        .map(|&id| self.interner.resolve(id).to_owned())
547                        .collect();
548                    (&**parent, parent_values)
549                })
550                .collect();
551            set.insert(name, values.clone());
552            sets.push(set);
553        }
554
555        let mut result = Restrictions::new();
556        for set in sets {
557            for (name, values) in set {
558                match result.get_mut(&name) {
559                    Some(existing) => {
560                        let known: HashSet<&str> = existing.iter().map(String::as_str).collect();
561                        let intersection: Vec<String> = values
562                            .into_iter()
563                            .filter(|v| known.contains(v.as_str()))
564                            .collect();
565                        if intersection.is_empty() {
566                            return Err(Error::TooRestrictive);
567                        }
568                        *existing = intersection;
569                    }
570                    None => {
571                        result.insert(name, values);
572                    }
573                }
574            }
575        }
576        Ok(result)
577    }
578}
579
580/// browserforge's `filter_by_last_level_keys` over the `undeeper`ed tree
581/// (`skip` removed): per level, the union of the keys on every path that ends
582/// in a valid leaf value, in first-seen order.
583#[derive(Default)]
584struct PathLevels {
585    levels: Vec<(Vec<Id>, HashSet<Id>)>,
586}
587
588impl PathLevels {
589    fn collect(&mut self, level: &Cpt, valid: &HashSet<Id>, path: &mut Vec<Id>) {
590        for value in &level.leaf_values {
591            if valid.contains(value) {
592                self.merge(path);
593            }
594        }
595        for (&key, child) in level.deeper_values.iter().zip(&level.deeper) {
596            path.push(key);
597            self.collect(child, valid, path);
598            path.pop();
599        }
600    }
601
602    /// `array_zip`: the first path initialises the levels, later ones union
603    /// into them; like Python's `zip`, the shorter side bounds the result.
604    fn merge(&mut self, path: &[Id]) {
605        if self.levels.is_empty() {
606            self.levels = path
607                .iter()
608                .map(|&k| (vec![k], HashSet::from([k])))
609                .collect();
610            return;
611        }
612        self.levels.truncate(path.len());
613        for ((ordered, seen), &key) in self.levels.iter_mut().zip(path) {
614            if seen.insert(key) {
615                ordered.push(key);
616            }
617        }
618    }
619}
620
621fn cached(
622    cell: &'static OnceLock<crate::Result<BayesianNetwork>>,
623    archive: &[u8],
624) -> crate::Result<&'static BayesianNetwork> {
625    cell.get_or_init(|| BayesianNetwork::from_zip(archive))
626        .as_ref()
627        .map_err(Clone::clone)
628}
629
630/// The input network, parsed once per process.
631///
632/// # Errors
633///
634/// The parse error of the embedded archive (none for the shipped data).
635pub fn input_network() -> crate::Result<&'static BayesianNetwork> {
636    static NETWORK: OnceLock<crate::Result<BayesianNetwork>> = OnceLock::new();
637    cached(&NETWORK, INPUT_NETWORK_ZIP)
638}
639
640/// The HTTP header network, parsed once per process.
641///
642/// # Errors
643///
644/// The parse error of the embedded archive (none for the shipped data).
645pub fn header_network() -> crate::Result<&'static BayesianNetwork> {
646    static NETWORK: OnceLock<crate::Result<BayesianNetwork>> = OnceLock::new();
647    cached(&NETWORK, HEADER_NETWORK_ZIP)
648}
649
650/// The browser fingerprint network, parsed once per process.
651///
652/// # Errors
653///
654/// The parse error of the embedded archive (none for the shipped data).
655pub fn fingerprint_network() -> crate::Result<&'static BayesianNetwork> {
656    static NETWORK: OnceLock<crate::Result<BayesianNetwork>> = OnceLock::new();
657    cached(&NETWORK, FINGERPRINT_NETWORK_ZIP)
658}
659
660// ---------------------------------------------------------------------------
661// Streaming JSON parse: the CPT tree is built straight from the token stream,
662// interning every string as it is read.
663
664/// A map key matched against a fixed field list.
665#[derive(Clone, Copy)]
666struct Field(&'static [&'static str]);
667
668impl<'de> DeserializeSeed<'de> for Field {
669    type Value = Option<usize>;
670
671    fn deserialize<D: de::Deserializer<'de>>(
672        self,
673        deserializer: D,
674    ) -> Result<Self::Value, D::Error> {
675        deserializer.deserialize_str(self)
676    }
677}
678
679impl Visitor<'_> for Field {
680    type Value = Option<usize>;
681
682    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
683        f.write_str("a field name")
684    }
685
686    fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
687        Ok(self.0.iter().position(|field| *field == value))
688    }
689}
690
691/// An interned string.
692struct IdSeed<'i>(&'i mut Interner);
693
694impl<'de> DeserializeSeed<'de> for IdSeed<'_> {
695    type Value = Id;
696
697    fn deserialize<D: de::Deserializer<'de>>(
698        self,
699        deserializer: D,
700    ) -> Result<Self::Value, D::Error> {
701        deserializer.deserialize_str(self)
702    }
703}
704
705impl Visitor<'_> for IdSeed<'_> {
706    type Value = Id;
707
708    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
709        f.write_str("a string")
710    }
711
712    fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
713        self.0
714            .intern(value)
715            .ok_or_else(|| E::custom("more than 2^32 distinct strings"))
716    }
717}
718
719/// A list of interned strings.
720struct IdListSeed<'i>(&'i mut Interner);
721
722impl<'de> DeserializeSeed<'de> for IdListSeed<'_> {
723    type Value = Vec<Id>;
724
725    fn deserialize<D: de::Deserializer<'de>>(
726        self,
727        deserializer: D,
728    ) -> Result<Self::Value, D::Error> {
729        deserializer.deserialize_seq(self)
730    }
731}
732
733impl<'de> Visitor<'de> for IdListSeed<'_> {
734    type Value = Vec<Id>;
735
736    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
737        f.write_str("a list of strings")
738    }
739
740    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
741        let mut ids = Vec::with_capacity(seq.size_hint().unwrap_or(0));
742        while let Some(id) = seq.next_element_seed(IdSeed(&mut *self.0))? {
743            ids.push(id);
744        }
745        Ok(ids)
746    }
747}
748
749/// `{"nodes": [...]}`.
750struct NetworkSeed<'i>(&'i mut Interner);
751
752impl<'de> DeserializeSeed<'de> for NetworkSeed<'_> {
753    type Value = Vec<NodeDef>;
754
755    fn deserialize<D: de::Deserializer<'de>>(
756        self,
757        deserializer: D,
758    ) -> Result<Self::Value, D::Error> {
759        deserializer.deserialize_map(self)
760    }
761}
762
763impl<'de> Visitor<'de> for NetworkSeed<'_> {
764    type Value = Vec<NodeDef>;
765
766    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
767        f.write_str("a network definition object")
768    }
769
770    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
771        let mut nodes = None;
772        while let Some(field) = map.next_key_seed(Field(&["nodes"]))? {
773            if field.is_some() {
774                nodes = Some(map.next_value_seed(NodeListSeed(&mut *self.0))?);
775            } else {
776                map.next_value::<IgnoredAny>()?;
777            }
778        }
779        nodes.ok_or_else(|| de::Error::missing_field("nodes"))
780    }
781}
782
783struct NodeListSeed<'i>(&'i mut Interner);
784
785impl<'de> DeserializeSeed<'de> for NodeListSeed<'_> {
786    type Value = Vec<NodeDef>;
787
788    fn deserialize<D: de::Deserializer<'de>>(
789        self,
790        deserializer: D,
791    ) -> Result<Self::Value, D::Error> {
792        deserializer.deserialize_seq(self)
793    }
794}
795
796impl<'de> Visitor<'de> for NodeListSeed<'_> {
797    type Value = Vec<NodeDef>;
798
799    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
800        f.write_str("a list of nodes")
801    }
802
803    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
804        let mut nodes = Vec::with_capacity(seq.size_hint().unwrap_or(0));
805        while let Some(node) = seq.next_element_seed(NodeSeed(&mut *self.0))? {
806            nodes.push(node);
807        }
808        Ok(nodes)
809    }
810}
811
812const NODE_FIELDS: &[&str] = &[
813    "name",
814    "parentNames",
815    "possibleValues",
816    "conditionalProbabilities",
817];
818
819struct NodeSeed<'i>(&'i mut Interner);
820
821impl<'de> DeserializeSeed<'de> for NodeSeed<'_> {
822    type Value = NodeDef;
823
824    fn deserialize<D: de::Deserializer<'de>>(
825        self,
826        deserializer: D,
827    ) -> Result<Self::Value, D::Error> {
828        deserializer.deserialize_map(self)
829    }
830}
831
832impl<'de> Visitor<'de> for NodeSeed<'_> {
833    type Value = NodeDef;
834
835    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
836        f.write_str("a network node object")
837    }
838
839    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
840        let mut name: Option<String> = None;
841        let mut parent_names: Vec<String> = Vec::new();
842        let mut possible_values = Vec::new();
843        let mut cpt = None;
844        while let Some(field) = map.next_key_seed(Field(NODE_FIELDS))? {
845            match field {
846                Some(0) => name = Some(map.next_value()?),
847                Some(1) => parent_names = map.next_value()?,
848                Some(2) => possible_values = map.next_value_seed(IdListSeed(&mut *self.0))?,
849                Some(3) => cpt = Some(map.next_value_seed(CptSeed(&mut *self.0))?),
850                _ => {
851                    map.next_value::<IgnoredAny>()?;
852                }
853            }
854        }
855        Ok(NodeDef {
856            name: name.ok_or_else(|| de::Error::missing_field("name"))?.into(),
857            parent_names: parent_names
858                .into_iter()
859                .map(String::into_boxed_str)
860                .collect(),
861            parents: Vec::new(),
862            possible_values,
863            cpt: cpt.ok_or_else(|| de::Error::missing_field("conditionalProbabilities"))?,
864        })
865    }
866}
867
868/// A key of a CPT level: one of the two structural keys, or a node value.
869enum CptKey {
870    Deeper,
871    Skip,
872    Value(Id),
873}
874
875struct CptKeySeed<'i>(&'i mut Interner);
876
877impl<'de> DeserializeSeed<'de> for CptKeySeed<'_> {
878    type Value = CptKey;
879
880    fn deserialize<D: de::Deserializer<'de>>(
881        self,
882        deserializer: D,
883    ) -> Result<Self::Value, D::Error> {
884        deserializer.deserialize_str(self)
885    }
886}
887
888impl Visitor<'_> for CptKeySeed<'_> {
889    type Value = CptKey;
890
891    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
892        f.write_str("a CPT key")
893    }
894
895    fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
896        match value {
897            "deeper" => Ok(CptKey::Deeper),
898            "skip" => Ok(CptKey::Skip),
899            _ => IdSeed(self.0).visit_str(value).map(CptKey::Value),
900        }
901    }
902}
903
904/// One CPT level.
905struct CptSeed<'i>(&'i mut Interner);
906
907impl<'de> DeserializeSeed<'de> for CptSeed<'_> {
908    type Value = Cpt;
909
910    fn deserialize<D: de::Deserializer<'de>>(
911        self,
912        deserializer: D,
913    ) -> Result<Self::Value, D::Error> {
914        deserializer.deserialize_map(self)
915    }
916}
917
918impl<'de> Visitor<'de> for CptSeed<'_> {
919    type Value = Cpt;
920
921    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
922        f.write_str("a conditional probability table")
923    }
924
925    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
926        let mut cpt = Cpt::default();
927        while let Some(key) = map.next_key_seed(CptKeySeed(&mut *self.0))? {
928            let structural = match key {
929                CptKey::Value(id) => {
930                    cpt.leaf_values.push(id);
931                    cpt.leaf_probabilities.push(map.next_value()?);
932                    continue;
933                }
934                CptKey::Deeper => (true, "deeper"),
935                CptKey::Skip => (false, "skip"),
936            };
937            match map.next_value_seed(EntrySeed {
938                interner: &mut *self.0,
939                deeper: structural.0,
940            })? {
941                Entry::Deeper(values, children) => {
942                    cpt.deeper_values = values;
943                    cpt.deeper = children;
944                }
945                Entry::Skip(child) => cpt.skip = Some(Box::new(child)),
946                // A node value literally called "deeper" or "skip".
947                Entry::Probability(probability) => {
948                    let id = IdSeed(&mut *self.0).visit_str::<A::Error>(structural.1)?;
949                    cpt.leaf_values.push(id);
950                    cpt.leaf_probabilities.push(probability);
951                }
952            }
953        }
954        Ok(cpt)
955    }
956}
957
958/// The value under a `deeper` or `skip` key: a subtree, or a probability
959/// when the key is in fact a node value.
960enum Entry {
961    Deeper(Vec<Id>, Vec<Cpt>),
962    Skip(Cpt),
963    Probability(f64),
964}
965
966struct EntrySeed<'i> {
967    interner: &'i mut Interner,
968    deeper: bool,
969}
970
971impl<'de> DeserializeSeed<'de> for EntrySeed<'_> {
972    type Value = Entry;
973
974    fn deserialize<D: de::Deserializer<'de>>(
975        self,
976        deserializer: D,
977    ) -> Result<Self::Value, D::Error> {
978        deserializer.deserialize_any(self)
979    }
980}
981
982impl<'de> Visitor<'de> for EntrySeed<'_> {
983    type Value = Entry;
984
985    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
986        f.write_str("a CPT subtree or a probability")
987    }
988
989    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
990        if !self.deeper {
991            return CptSeed(self.interner).visit_map(map).map(Entry::Skip);
992        }
993        let mut values = Vec::with_capacity(map.size_hint().unwrap_or(0));
994        let mut children = Vec::with_capacity(values.capacity());
995        while let Some(value) = map.next_key_seed(IdSeed(&mut *self.interner))? {
996            values.push(value);
997            children.push(map.next_value_seed(CptSeed(&mut *self.interner))?);
998        }
999        Ok(Entry::Deeper(values, children))
1000    }
1001
1002    fn visit_f64<E: de::Error>(self, value: f64) -> Result<Self::Value, E> {
1003        Ok(Entry::Probability(value))
1004    }
1005
1006    fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
1007        let integer: de::value::U64Deserializer<E> = value.into_deserializer();
1008        f64::deserialize(integer).map(Entry::Probability)
1009    }
1010
1011    fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
1012        let integer: de::value::I64Deserializer<E> = value.into_deserializer();
1013        f64::deserialize(integer).map(Entry::Probability)
1014    }
1015}