opcua_types/type_loader/
mod.rs

1//! The [`TypeLoader`] trait and associated tools.
2//!
3//! When deserializing from OPC UA formats, extension objects can contain
4//! a large variety of structures, including custom ones defined by extensions to the standard.
5//!
6//! In order to work with these, each set of types implements [`TypeLoader`], and a list
7//! of type loaders are passed along during decoding.
8
9mod fallback;
10
11pub use fallback::{ByteStringBody, FallbackTypeLoader, JsonBody, XmlBody};
12
13use std::{borrow::Cow, io::Read, sync::Arc};
14
15use chrono::TimeDelta;
16use hashbrown::HashMap;
17
18use crate::{
19    BinaryDecodable, DecodingOptions, DynEncodable, EncodingResult, Error, GeneratedTypeLoader,
20    NamespaceMap, NodeId, UninitializedIndex,
21};
22
23type BinaryLoadFun = fn(&mut dyn Read, &Context<'_>) -> EncodingResult<Box<dyn DynEncodable>>;
24
25#[cfg(feature = "xml")]
26type XmlLoadFun = fn(
27    &mut crate::xml::XmlStreamReader<&mut dyn std::io::Read>,
28    &Context<'_>,
29) -> EncodingResult<Box<dyn DynEncodable>>;
30
31#[cfg(feature = "json")]
32type JsonLoadFun = fn(
33    &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
34    &Context<'_>,
35) -> EncodingResult<Box<dyn DynEncodable>>;
36
37#[derive(Default)]
38/// Type used by generated type loaders to store deserialization functions.
39pub struct TypeLoaderInstance {
40    binary_types: HashMap<u32, BinaryLoadFun>,
41
42    #[cfg(feature = "xml")]
43    xml_types: HashMap<u32, XmlLoadFun>,
44
45    #[cfg(feature = "json")]
46    json_types: HashMap<u32, JsonLoadFun>,
47}
48
49/// Convenience method to decode a type into a DynEncodable.
50pub fn binary_decode_to_enc<T: DynEncodable + BinaryDecodable>(
51    stream: &mut dyn Read,
52    ctx: &Context<'_>,
53) -> EncodingResult<Box<dyn DynEncodable>> {
54    Ok(Box::new(T::decode(stream, ctx)?))
55}
56
57#[cfg(feature = "json")]
58/// Convenience method to decode a type into a DynEncodable.
59pub fn json_decode_to_enc<T: DynEncodable + crate::json::JsonDecodable>(
60    stream: &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
61    ctx: &Context<'_>,
62) -> EncodingResult<Box<dyn DynEncodable>> {
63    Ok(Box::new(T::decode(stream, ctx)?))
64}
65
66#[cfg(feature = "xml")]
67/// Convenience method to decode a type into a DynEncodable.
68pub fn xml_decode_to_enc<T: DynEncodable + crate::xml::XmlDecodable>(
69    stream: &mut crate::xml::XmlStreamReader<&mut dyn std::io::Read>,
70    ctx: &Context<'_>,
71) -> EncodingResult<Box<dyn DynEncodable>> {
72    Ok(Box::new(T::decode(stream, ctx)?))
73}
74
75impl TypeLoaderInstance {
76    /// Create a new empty type loader instance.
77    pub fn new() -> Self {
78        Self::default()
79    }
80
81    /// Add a binary type decoding function.
82    pub fn add_binary_type(&mut self, data_type: u32, encoding_type: u32, fun: BinaryLoadFun) {
83        self.binary_types.insert(data_type, fun);
84        self.binary_types.insert(encoding_type, fun);
85    }
86
87    #[cfg(feature = "xml")]
88    /// Add an XML type decoding function.
89    pub fn add_xml_type(&mut self, data_type: u32, encoding_type: u32, fun: XmlLoadFun) {
90        self.xml_types.insert(data_type, fun);
91        self.xml_types.insert(encoding_type, fun);
92    }
93
94    #[cfg(feature = "json")]
95    /// Add a JSON type decoding function.
96    pub fn add_json_type(&mut self, data_type: u32, encoding_type: u32, fun: JsonLoadFun) {
97        self.json_types.insert(data_type, fun);
98        self.json_types.insert(encoding_type, fun);
99    }
100
101    /// Decode the type with ID `ty` using binary encoding.
102    pub fn decode_binary(
103        &self,
104        ty: u32,
105        stream: &mut dyn Read,
106        context: &Context<'_>,
107    ) -> Option<EncodingResult<Box<dyn DynEncodable>>> {
108        let fun = self.binary_types.get(&ty)?;
109        Some(fun(stream, context))
110    }
111
112    #[cfg(feature = "xml")]
113    /// Decode the type with ID `ty` from a NodeSet2 XML node.
114    pub fn decode_xml(
115        &self,
116        ty: u32,
117        stream: &mut crate::xml::XmlStreamReader<&mut dyn std::io::Read>,
118        context: &Context<'_>,
119    ) -> Option<EncodingResult<Box<dyn DynEncodable>>> {
120        let fun = self.xml_types.get(&ty)?;
121        Some(fun(stream, context))
122    }
123
124    #[cfg(feature = "json")]
125    /// Decode the type with ID `ty` using JSON encoding.
126    pub fn decode_json(
127        &self,
128        ty: u32,
129        stream: &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
130        context: &Context<'_>,
131    ) -> Option<EncodingResult<Box<dyn DynEncodable>>> {
132        let fun = self.json_types.get(&ty)?;
133        Some(fun(stream, context))
134    }
135}
136
137/// Convenience trait for a type loader using a static [`TypeLoaderInstance`] and
138/// namespace known at compile time.
139///
140/// Types implementing this blanket implement [`TypeLoader`]
141pub trait StaticTypeLoader {
142    /// Get the type loader instance used by this type loader.
143    fn instance() -> &'static TypeLoaderInstance;
144
145    /// Get the namespace this type loader manages.
146    fn namespace() -> &'static str;
147}
148
149impl<T> TypeLoader for T
150where
151    T: StaticTypeLoader + Send + Sync + 'static,
152{
153    #[cfg(feature = "xml")]
154    fn load_from_xml(
155        &self,
156        node_id: &crate::NodeId,
157        stream: &mut crate::xml::XmlStreamReader<&mut dyn std::io::Read>,
158        ctx: &Context<'_>,
159        _name: &str,
160    ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>> {
161        let idx = ctx.namespaces().get_index(Self::namespace())?;
162        if idx != node_id.namespace {
163            return None;
164        }
165        let Some(num_id) = node_id.as_u32() else {
166            return Some(Err(Error::decoding(
167                "Unsupported encoding ID. Only numeric encoding IDs are currently supported",
168            )));
169        };
170        Self::instance().decode_xml(num_id, stream, ctx)
171    }
172
173    #[cfg(feature = "json")]
174    fn load_from_json(
175        &self,
176        node_id: &crate::NodeId,
177        stream: &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
178        ctx: &Context<'_>,
179    ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>> {
180        let idx = ctx.namespaces().get_index(Self::namespace())?;
181        if idx != node_id.namespace {
182            return None;
183        }
184        let Some(num_id) = node_id.as_u32() else {
185            return Some(Err(Error::decoding(
186                "Unsupported encoding ID. Only numeric encoding IDs are currently supported",
187            )));
188        };
189        Self::instance().decode_json(num_id, stream, ctx)
190    }
191
192    fn load_from_binary(
193        &self,
194        node_id: &NodeId,
195        stream: &mut dyn Read,
196        ctx: &Context<'_>,
197        _length: Option<usize>,
198    ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>> {
199        let idx = ctx.namespaces().get_index(Self::namespace())?;
200        if idx != node_id.namespace {
201            return None;
202        }
203        let Some(num_id) = node_id.as_u32() else {
204            return Some(Err(Error::decoding(
205                "Unsupported encoding ID. Only numeric encoding IDs are currently supported",
206            )));
207        };
208        Self::instance().decode_binary(num_id, stream, ctx)
209    }
210
211    fn priority(&self) -> TypeLoaderPriority {
212        TypeLoaderPriority::Generated
213    }
214}
215
216/// Owned variant of [Context], this is stored by clients and servers, which
217/// call the [ContextOwned::context] method to produce a [Context]
218/// for decoding/encoding.
219pub struct ContextOwned {
220    namespaces: NamespaceMap,
221    loaders: TypeLoaderCollection,
222    options: DecodingOptions,
223}
224
225impl std::fmt::Debug for ContextOwned {
226    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227        f.debug_struct("ContextOwned")
228            .field("namespaces", &self.namespaces)
229            .field("options", &self.options)
230            .finish()
231    }
232}
233
234impl ContextOwned {
235    /// Create a new context.
236    pub fn new(
237        namespaces: NamespaceMap,
238        loaders: TypeLoaderCollection,
239        options: DecodingOptions,
240    ) -> Self {
241        Self {
242            namespaces,
243            loaders,
244            options,
245        }
246    }
247
248    /// Create a new context, including the core type loader.
249    pub fn new_default(namespaces: NamespaceMap, options: DecodingOptions) -> Self {
250        Self::new(namespaces, TypeLoaderCollection::new(), options)
251    }
252
253    /// Return a context for decoding.
254    pub fn context(&self) -> Context<'_> {
255        Context {
256            namespaces: &self.namespaces,
257            loaders: &self.loaders,
258            options: self.options.clone(),
259            aliases: None,
260            index_map: None,
261        }
262    }
263
264    /// Get the namespace map.
265    pub fn namespaces(&self) -> &NamespaceMap {
266        &self.namespaces
267    }
268
269    /// Get the namespace map mutably.
270    pub fn namespaces_mut(&mut self) -> &mut NamespaceMap {
271        &mut self.namespaces
272    }
273
274    /// Get the decoding options.
275    pub fn options(&self) -> &DecodingOptions {
276        &self.options
277    }
278
279    /// Get the decoding options mutably.
280    pub fn options_mut(&mut self) -> &mut DecodingOptions {
281        &mut self.options
282    }
283
284    /// Get a mutable reference to the type loaders.
285    pub fn loaders_mut(&mut self) -> &mut TypeLoaderCollection {
286        &mut self.loaders
287    }
288}
289
290impl Default for ContextOwned {
291    fn default() -> Self {
292        Self::new_default(Default::default(), Default::default())
293    }
294}
295
296#[derive(Clone)]
297/// Wrapper type around a vector of type loaders that maintains
298/// sorted order according to the `priority` of each type loader.
299pub struct TypeLoaderCollection {
300    loaders: Vec<Arc<dyn TypeLoader>>,
301}
302
303impl Default for TypeLoaderCollection {
304    fn default() -> Self {
305        Self::new()
306    }
307}
308
309impl TypeLoaderCollection {
310    /// Create a new type loader collection containing only the
311    /// generated type loader and the fallback type loader.
312    pub fn new() -> Self {
313        Self {
314            loaders: vec![Arc::new(GeneratedTypeLoader), Arc::new(FallbackTypeLoader)],
315        }
316    }
317
318    /// Create a new type loader collection without any type loaders at all,
319    /// not even the built-ins. This is usually only useful for testing.
320    pub fn new_empty() -> Self {
321        Self {
322            loaders: Vec::new(),
323        }
324    }
325
326    /// Add a type loader to the collection.
327    pub fn add_type_loader(&mut self, loader: impl TypeLoader + 'static) {
328        self.add(Arc::new(loader));
329    }
330
331    /// Add a type loader to the collection.
332    pub fn add(&mut self, loader: Arc<dyn TypeLoader>) {
333        let priority = loader.priority();
334        for i in 0..self.loaders.len() {
335            if self.loaders[i].priority() > priority {
336                self.loaders.insert(i, loader);
337                return;
338            }
339        }
340        self.loaders.push(loader);
341    }
342
343    /// Iterate over the type loaders.
344    pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
345        self.into_iter()
346    }
347}
348
349impl<'a> IntoIterator for &'a TypeLoaderCollection {
350    type Item = &'a Arc<dyn TypeLoader>;
351
352    type IntoIter = <&'a [Arc<dyn TypeLoader>] as IntoIterator>::IntoIter;
353
354    fn into_iter(self) -> Self::IntoIter {
355        self.loaders.iter()
356    }
357}
358
359#[derive(Clone)]
360/// Decoding/encoding context. Lifetime is typically tied to an instance of [ContextOwned].
361pub struct Context<'a> {
362    namespaces: &'a NamespaceMap,
363    loaders: &'a TypeLoaderCollection,
364    options: DecodingOptions,
365    aliases: Option<&'a HashMap<String, String>>,
366    index_map: Option<&'a HashMap<u16, u16>>,
367}
368
369#[derive(Debug, Copy, Clone, PartialEq, Eq)]
370/// Priority of the given type loader.
371/// Type loaders should be sorted by this value, to ensure that
372/// correct implementations are selected if multiple type loaders
373/// handle the same type.
374pub enum TypeLoaderPriority {
375    /// Reserved for the core namespace.
376    Core,
377    /// Any generated type loader.
378    Generated,
379    /// Some form of dynamic type loader, can specify a custom
380    /// priority greater than 1.
381    Dynamic(u32),
382    /// Fallback, will always be sorted last.
383    Fallback,
384}
385
386impl TypeLoaderPriority {
387    /// Get the priority of the type loader as a number.
388    pub fn priority(&self) -> u32 {
389        match self {
390            Self::Core => 0,
391            Self::Generated => 1,
392            Self::Dynamic(v) => *v,
393            Self::Fallback => u32::MAX,
394        }
395    }
396}
397
398impl PartialOrd for TypeLoaderPriority {
399    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
400        Some(self.cmp(other))
401    }
402}
403
404impl Ord for TypeLoaderPriority {
405    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
406        self.priority().cmp(&other.priority())
407    }
408}
409
410/// Trait for a collection of types.
411/// Each method in this trait should try to decode the passed stream/body
412/// into a [DynEncodable], and return `None` if the `node_id` does not match
413/// any variant. It should only return an error if the `node_id` is a match,
414/// but decoding failed.
415pub trait TypeLoader: Send + Sync {
416    #[cfg(feature = "xml")]
417    /// Load the type given by `node_id` from XML by trying each
418    /// registered type loader until one returns `Some`.
419    fn load_from_xml(
420        &self,
421        node_id: &crate::NodeId,
422        stream: &mut crate::xml::XmlStreamReader<&mut dyn std::io::Read>,
423        ctx: &Context<'_>,
424        name: &str,
425    ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>>;
426
427    #[cfg(feature = "json")]
428    /// Load the type given by `node_id` from JSON by trying each
429    /// registered type loader until one returns `Some`.
430    fn load_from_json(
431        &self,
432        node_id: &crate::NodeId,
433        stream: &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
434        ctx: &Context<'_>,
435    ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>>;
436
437    /// Load the type given by `node_id` from Binary by trying each
438    /// registered type loader until one returns `Some`.
439    fn load_from_binary(
440        &self,
441        node_id: &NodeId,
442        stream: &mut dyn Read,
443        ctx: &Context<'_>,
444        length: Option<usize>,
445    ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>>;
446
447    /// Get the priority of this type loader.
448    fn priority(&self) -> TypeLoaderPriority {
449        TypeLoaderPriority::Generated
450    }
451}
452
453impl<'a> Context<'a> {
454    /// Constructor. Prefer to use `ContextOwned` to avoid having to juggle
455    /// NamespaceMap and TypeLoaderCollection yourself.
456    pub fn new(
457        namespaces: &'a NamespaceMap,
458        loaders: &'a TypeLoaderCollection,
459        options: DecodingOptions,
460    ) -> Self {
461        Self {
462            namespaces,
463            loaders,
464            options,
465            aliases: None,
466            index_map: None,
467        }
468    }
469
470    #[cfg(feature = "json")]
471    /// Try to load a type dynamically from JSON, returning an error if no
472    /// matching type loader was found.
473    pub fn load_from_json(
474        &self,
475        node_id: &NodeId,
476        stream: &mut crate::json::JsonStreamReader<&mut dyn Read>,
477    ) -> crate::EncodingResult<crate::ExtensionObject> {
478        for loader in self.loaders {
479            if let Some(r) = loader.load_from_json(node_id, stream, self) {
480                return Ok(crate::ExtensionObject { body: Some(r?) });
481            }
482        }
483        Err(Error::decoding(format!(
484            "No type loader defined for {node_id}"
485        )))
486    }
487
488    /// Try to load a type dynamically from OPC-UA binary, returning an error if no
489    /// matching type loader was found.
490    pub fn load_from_binary(
491        &self,
492        node_id: &NodeId,
493        stream: &mut dyn Read,
494        length: Option<usize>,
495    ) -> crate::EncodingResult<crate::ExtensionObject> {
496        for loader in self.loaders {
497            if let Some(r) = loader.load_from_binary(node_id, stream, self, length) {
498                return Ok(crate::ExtensionObject { body: Some(r?) });
499            }
500        }
501        Err(Error::decoding(format!(
502            "No type loader defined for {node_id}"
503        )))
504    }
505
506    #[cfg(feature = "xml")]
507    /// Try to load a type dynamically from XML, returning an error if no
508    /// matching type loader was found.
509    pub fn load_from_xml(
510        &self,
511        node_id: &NodeId,
512        stream: &mut crate::xml::XmlStreamReader<&mut dyn std::io::Read>,
513        name: &str,
514    ) -> crate::EncodingResult<crate::ExtensionObject> {
515        for loader in self.loaders {
516            if let Some(r) = loader.load_from_xml(node_id, stream, self, name) {
517                return Ok(crate::ExtensionObject { body: Some(r?) });
518            }
519        }
520        Err(Error::decoding(format!(
521            "No type loader defined for {node_id}"
522        )))
523    }
524
525    /// Get the decoding options.
526    pub fn options(&self) -> &DecodingOptions {
527        &self.options
528    }
529
530    /// Get the namespace map.
531    pub fn namespaces(&self) -> &'a NamespaceMap {
532        self.namespaces
533    }
534
535    /// Set the index map used for resolving namespace indices during XML decoding.
536    pub fn set_index_map(&mut self, index_map: &'a HashMap<u16, u16>) {
537        self.index_map = Some(index_map);
538    }
539
540    /// Set the alias table used for resolving node ID aliases during XML decoding.
541    pub fn set_aliases(&mut self, aliases: &'a HashMap<String, String>) {
542        self.aliases = Some(aliases);
543    }
544
545    /// Resolve the given namespace index to the real, server namespace index.
546    /// Used when loading nodeset files.
547    pub fn resolve_namespace_index(
548        &self,
549        index_in_node_set: u16,
550    ) -> Result<u16, UninitializedIndex> {
551        if index_in_node_set == 0 {
552            return Ok(0);
553        }
554
555        let Some(index_map) = self.index_map else {
556            return Ok(index_in_node_set);
557        };
558        let Some(idx) = index_map.get(&index_in_node_set) else {
559            return Err(UninitializedIndex(index_in_node_set));
560        };
561        Ok(*idx)
562    }
563
564    /// Look up namespace index in reverse, finding the index in the node set
565    /// given the index in the server.
566    pub fn resolve_namespace_index_inverse(
567        &self,
568        index_in_server: u16,
569    ) -> Result<u16, UninitializedIndex> {
570        if index_in_server == 0 {
571            return Ok(0);
572        }
573
574        let Some(index_map) = self.index_map else {
575            return Ok(index_in_server);
576        };
577        let Some((idx, _)) = index_map.iter().find(|(_, &v)| v == index_in_server) else {
578            return Err(UninitializedIndex(index_in_server));
579        };
580        Ok(*idx)
581    }
582
583    /// Resolve a node ID alias, if the alias table is registered.
584    /// Only used for XML decoding when loading nodeset files.
585    pub fn resolve_alias<'b>(&self, node_id_str: &'b str) -> &'b str
586    where
587        'a: 'b,
588    {
589        if let Some(aliases) = self.aliases {
590            if let Some(alias) = aliases.get(node_id_str) {
591                return alias.as_str();
592            }
593        }
594        node_id_str
595    }
596
597    /// Resolve a node ID alias in inverse, getting the alias value given the node ID.
598    pub fn resolve_alias_inverse<'b>(&self, node_id_str: &'b str) -> &'b str
599    where
600        'a: 'b,
601    {
602        if let Some(aliases) = self.aliases {
603            for (k, v) in aliases.iter() {
604                if v == node_id_str {
605                    return k.as_str();
606                }
607            }
608        }
609        node_id_str
610    }
611
612    /// Produce a copy of self with zero client_offset, or a borrow if
613    /// the offset is already zero.
614    pub fn with_zero_offset(&self) -> Cow<'_, Self> {
615        if self.options.client_offset.is_zero() {
616            Cow::Borrowed(self)
617        } else {
618            Cow::Owned(Self {
619                namespaces: self.namespaces,
620                loaders: self.loaders,
621                options: DecodingOptions {
622                    client_offset: TimeDelta::zero(),
623                    ..self.options.clone()
624                },
625                aliases: self.aliases,
626                index_map: self.index_map,
627            })
628        }
629    }
630}