use std::{
fmt,
io::{Read, Write},
str::FromStr,
sync::{
atomic::{AtomicU32, Ordering},
LazyLock,
},
};
mod id_ref;
mod identifier;
#[cfg(feature = "json")]
mod json;
#[cfg(feature = "xml")]
mod xml;
pub use id_ref::{IdentifierRef, IntoNodeIdRef, NodeIdRef};
pub use identifier::Identifier;
pub use identifier::{
IDENTIFIER_HASH_BYTE_STRING, IDENTIFIER_HASH_GUID, IDENTIFIER_HASH_NUMERIC,
IDENTIFIER_HASH_STRING,
};
use crate::{
read_u16, read_u32, read_u8, write_u16, write_u32, write_u8, BinaryDecodable, BinaryEncodable,
ByteString, DataTypeId, EncodingResult, Error, Guid, MethodId, ObjectId, ReferenceTypeId,
StatusCode, UAString, UaNullable, VariableId,
};
#[derive(Debug)]
pub struct NodeIdError;
impl fmt::Display for NodeIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NodeIdError")
}
}
impl std::error::Error for NodeIdError {}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct NodeId {
pub namespace: u16,
pub identifier: Identifier,
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.namespace != 0 {
write!(f, "ns={};{}", self.namespace, self.identifier)
} else {
write!(f, "{}", self.identifier)
}
}
}
impl UaNullable for NodeId {
fn is_ua_null(&self) -> bool {
self.is_null()
}
}
impl BinaryEncodable for NodeId {
fn byte_len(&self, ctx: &crate::Context<'_>) -> usize {
let size: usize = match self.identifier {
Identifier::Numeric(value) => {
if self.namespace == 0 && value <= 255 {
2
} else if self.namespace <= 255 && value <= 65535 {
4
} else {
7
}
}
Identifier::String(ref value) => 3 + value.byte_len(ctx),
Identifier::Guid(ref value) => 3 + value.byte_len(ctx),
Identifier::ByteString(ref value) => 3 + value.byte_len(ctx),
};
size
}
fn encode<S: Write + ?Sized>(
&self,
stream: &mut S,
ctx: &crate::Context<'_>,
) -> EncodingResult<()> {
match &self.identifier {
Identifier::Numeric(value) => {
if self.namespace == 0 && *value <= 255 {
write_u8(stream, 0x0)?;
write_u8(stream, *value as u8)
} else if self.namespace <= 255 && *value <= 65535 {
write_u8(stream, 0x1)?;
write_u8(stream, self.namespace as u8)?;
write_u16(stream, *value as u16)
} else {
write_u8(stream, 0x2)?;
write_u16(stream, self.namespace)?;
write_u32(stream, *value)
}
}
Identifier::String(value) => {
write_u8(stream, 0x3)?;
write_u16(stream, self.namespace)?;
value.encode(stream, ctx)
}
Identifier::Guid(value) => {
write_u8(stream, 0x4)?;
write_u16(stream, self.namespace)?;
value.encode(stream, ctx)
}
Identifier::ByteString(value) => {
write_u8(stream, 0x5)?;
write_u16(stream, self.namespace)?;
value.encode(stream, ctx)
}
}
}
}
impl BinaryDecodable for NodeId {
fn decode<S: Read + ?Sized>(stream: &mut S, ctx: &crate::Context<'_>) -> EncodingResult<Self> {
let identifier = read_u8(stream)?;
let node_id = match identifier {
0x0 => {
let namespace = 0;
let value = read_u8(stream)?;
NodeId::new(namespace, u32::from(value))
}
0x1 => {
let namespace = read_u8(stream)?;
let value = read_u16(stream)?;
NodeId::new(u16::from(namespace), u32::from(value))
}
0x2 => {
let namespace = read_u16(stream)?;
let value = read_u32(stream)?;
NodeId::new(namespace, value)
}
0x3 => {
let namespace = read_u16(stream)?;
let value = UAString::decode(stream, ctx)?;
NodeId::new(namespace, value)
}
0x4 => {
let namespace = read_u16(stream)?;
let value = Guid::decode(stream, ctx)?;
NodeId::new(namespace, value)
}
0x5 => {
let namespace = read_u16(stream)?;
let value = ByteString::decode(stream, ctx)?;
NodeId::new(namespace, value)
}
_ => {
return Err(Error::decoding(format!(
"Unrecognized node id type {identifier}"
)));
}
};
Ok(node_id)
}
}
impl FromStr for NodeId {
type Err = StatusCode;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
use regex::Regex;
static RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^(ns=(?P<ns>[0-9]+);)?(?P<t>[isgb]=.+)$").unwrap());
let captures = RE.captures(s).ok_or(StatusCode::BadNodeIdInvalid)?;
let namespace = if let Some(ns) = captures.name("ns") {
ns.as_str()
.parse::<u16>()
.map_err(|_| StatusCode::BadNodeIdInvalid)?
} else {
0
};
let t = captures.name("t").unwrap();
Identifier::from_str(t.as_str())
.map(|t| NodeId::new(namespace, t))
.map_err(|_| StatusCode::BadNodeIdInvalid)
}
}
impl<'a> From<&'a str> for NodeId {
fn from(value: &'a str) -> Self {
(0u16, value).into()
}
}
impl From<UAString> for NodeId {
fn from(value: UAString) -> Self {
(0u16, value).into()
}
}
impl From<u32> for NodeId {
fn from(value: u32) -> Self {
(0, value).into()
}
}
impl From<Guid> for NodeId {
fn from(value: Guid) -> Self {
(0, value).into()
}
}
impl From<ByteString> for NodeId {
fn from(value: ByteString) -> Self {
(0, value).into()
}
}
impl From<&NodeId> for NodeId {
fn from(v: &NodeId) -> Self {
v.clone()
}
}
impl From<NodeId> for String {
fn from(value: NodeId) -> Self {
value.to_string()
}
}
impl<'a> From<(u16, &'a str)> for NodeId {
fn from(v: (u16, &'a str)) -> Self {
Self::new(v.0, UAString::from(v.1))
}
}
impl From<(u16, UAString)> for NodeId {
fn from(v: (u16, UAString)) -> Self {
Self::new(v.0, v.1)
}
}
impl From<(u16, u32)> for NodeId {
fn from(v: (u16, u32)) -> Self {
Self::new(v.0, v.1)
}
}
impl From<(u16, Guid)> for NodeId {
fn from(v: (u16, Guid)) -> Self {
Self::new(v.0, v.1)
}
}
impl From<(u16, ByteString)> for NodeId {
fn from(v: (u16, ByteString)) -> Self {
Self::new(v.0, v.1)
}
}
static NEXT_NODE_ID_NUMERIC: AtomicU32 = AtomicU32::new(1);
impl Default for NodeId {
fn default() -> Self {
NodeId::null()
}
}
impl NodeId {
pub fn new<T>(namespace: u16, value: T) -> NodeId
where
T: Into<Identifier>,
{
NodeId {
namespace,
identifier: value.into(),
}
}
pub fn root_folder_id() -> NodeId {
ObjectId::RootFolder.into()
}
pub fn objects_folder_id() -> NodeId {
ObjectId::ObjectsFolder.into()
}
pub fn types_folder_id() -> NodeId {
ObjectId::TypesFolder.into()
}
pub fn views_folder_id() -> NodeId {
ObjectId::ViewsFolder.into()
}
pub fn is_null(&self) -> bool {
self.namespace == 0 && self.identifier == Identifier::Numeric(0)
}
pub fn null() -> NodeId {
NodeId::new(0, 0u32)
}
pub fn next_numeric(namespace: u16) -> NodeId {
NodeId::new(
namespace,
NEXT_NODE_ID_NUMERIC.fetch_add(1, Ordering::SeqCst),
)
}
pub fn as_object_id(&self) -> std::result::Result<ObjectId, NodeIdError> {
match self.identifier {
Identifier::Numeric(id) if self.namespace == 0 => {
ObjectId::try_from(id).map_err(|_| NodeIdError)
}
_ => Err(NodeIdError),
}
}
pub fn as_variable_id(&self) -> std::result::Result<VariableId, NodeIdError> {
match self.identifier {
Identifier::Numeric(id) if self.namespace == 0 => {
VariableId::try_from(id).map_err(|_| NodeIdError)
}
_ => Err(NodeIdError),
}
}
pub fn as_reference_type_id(&self) -> std::result::Result<ReferenceTypeId, NodeIdError> {
if self.is_null() {
Err(NodeIdError)
} else {
match self.identifier {
Identifier::Numeric(id) if self.namespace == 0 => {
ReferenceTypeId::try_from(id).map_err(|_| NodeIdError)
}
_ => Err(NodeIdError),
}
}
}
pub fn as_data_type_id(&self) -> std::result::Result<DataTypeId, NodeIdError> {
match self.identifier {
Identifier::Numeric(id) if self.namespace == 0 => {
DataTypeId::try_from(id).map_err(|_| NodeIdError)
}
_ => Err(NodeIdError),
}
}
pub fn as_method_id(&self) -> std::result::Result<MethodId, NodeIdError> {
match self.identifier {
Identifier::Numeric(id) if self.namespace == 0 => {
MethodId::try_from(id).map_err(|_| NodeIdError)
}
_ => Err(NodeIdError),
}
}
pub fn is_numeric(&self) -> bool {
matches!(self.identifier, Identifier::Numeric(_))
}
pub fn is_string(&self) -> bool {
matches!(self.identifier, Identifier::String(_))
}
pub fn is_guid(&self) -> bool {
matches!(self.identifier, Identifier::Guid(_))
}
pub fn is_byte_string(&self) -> bool {
matches!(self.identifier, Identifier::ByteString(_))
}
pub fn as_u32(&self) -> Option<u32> {
match &self.identifier {
Identifier::Numeric(i) => Some(*i),
_ => None,
}
}
}