123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- /**
- * @module node-opcua-nodeid
- */
- import { assert } from "@/util/assert";
- import { emptyGuid, isValidGuid, type Guid, normalizeGuid } from "./guid";
- import { DataTypeIds, MethodIds, ObjectIds, ObjectTypeIds, ReferenceTypeIds, VariableIds, VariableTypeIds } from "./opcua_node_ids";
- /**
- * `NodeIdType` an enumeration that specifies the possible types of a `NodeId` value.
- */
- export enum NodeIdType {
- /**
- * @static
- * @property NUMERIC
- * @default 0x1
- */
- NUMERIC = 0x01,
- /**
- * @static
- * @property STRING
- * @default 0x2
- */
- STRING = 0x02,
- /**
- * @static
- * @property GUID
- * @default 0x3
- */
- GUID = 0x03,
- /**
- * @static
- * @property BYTESTRING
- * @default 0x4
- */
- BYTESTRING = 0x04
- }
- /*function defaultValue(identifierType: NodeIdType.BYTESTRING): null;
- function defaultValue(identifierType: NodeIdType.STRING): null;
- function defaultValue(identifierType: NodeIdType.NUMERIC): 0;
- function defaultValue(identifierType: NodeIdType.GUID): null;
- */
- function defaultValue(identifierType: NodeIdType): string | number | Buffer {
- switch (identifierType) {
- case NodeIdType.GUID: return emptyGuid;
- case NodeIdType.BYTESTRING: return null as any as Buffer;// Buffer.alloc(0);
- case NodeIdType.STRING: return "";
- case NodeIdType.NUMERIC: return 0;
- }
- }
- export interface INodeIdNumeric extends NodeId {
- identifierType: NodeIdType.NUMERIC;
- value: number;
- }
- export interface INodeIdGuid extends NodeId {
- identifierType: NodeIdType.GUID;
- value: string;
- }
- export interface INodeIdByteString extends NodeId {
- identifierType: NodeIdType.BYTESTRING;
- value: Buffer;
- }
- export interface INodeIdString extends NodeId {
- identifierType: NodeIdType.STRING;
- value: string;
- }
- export type INodeId = INodeIdNumeric | INodeIdGuid | INodeIdString | INodeIdByteString;
- /**
- * Construct a node ID
- *
- * @class NodeId
- * @example
- *
- * ``` javascript
- * const nodeId = new NodeId(NodeIdType.NUMERIC,123,1);
- * ```
- * @constructor
- */
- export class NodeId {
- public static NodeIdType = NodeIdType;
- public static nullNodeId: NodeId;
- public static resolveNodeId: (a: string | NodeId) => NodeId;
- public static sameNodeId: (n1: NodeId, n2: NodeId) => boolean;
- public identifierType: NodeIdType;
- public value: number | string | Buffer | Guid;
- public namespace: number;
- /**
- * @param identifierType - the nodeID type
- * @param value - the node id value. The type of Value depends on identifierType.
- * @param namespace - the index of the related namespace (optional , default value = 0 )
- */
- constructor(identifierType?: NodeIdType | null, value?: number | string | Buffer | Guid, namespace?: number) {
- if (identifierType === null || identifierType === undefined) {
- this.identifierType = NodeIdType.NUMERIC;
- this.value = 0;
- this.namespace = 0;
- return;
- }
- this.identifierType = identifierType;
- this.value = value || defaultValue(identifierType);
- this.namespace = namespace || 0;
- // namespace shall be a UInt16
- assert(this.namespace >= 0 && this.namespace <= 0xffff, "NodeId: invalid namespace value");
- assert(this.identifierType !== NodeIdType.NUMERIC || (this.value !== null && this.value as number >= 0 && this.value as number <= 0xffffffff));
- assert(this.identifierType !== NodeIdType.GUID || isValidGuid(this.value as string), "NodeId: Guid is invalid");
- assert(this.identifierType !== NodeIdType.STRING || typeof this.value === "string", "cannot empty string");
- if (this.identifierType === NodeIdType.GUID) {
- this.value = normalizeGuid(value as string);
- }
- }
- /**
- * get the string representation of the nodeID.
- *
- * @method toString
- * @example
- *
- * ``` javascript
- * const nodeid = new NodeId(NodeIdType.NUMERIC, 123,1);
- * console.log(nodeid.toString());
- * ```
- *
- * ```
- * >"ns=1;i=123"
- * ```
- *
- * @param [options.addressSpace] {AddressSpace}
- * @return {String}
- */
- public toString(options?: { addressSpace?: any }): string {
- const addressSpace = options ? options.addressSpace : null;
- let str;
- const _this = this as INodeId;
- switch (_this.identifierType) {
- case NodeIdType.NUMERIC:
- if(this.namespace===0)
- str = "i=" + _this.value;
- else
- str = "ns=" + this.namespace + ";i=" + _this.value;
- break;
- case NodeIdType.STRING:
- str = "ns=" + this.namespace + ";s=" + _this.value;
- break;
- case NodeIdType.GUID:
- str = "ns=" + this.namespace + ";g=" + normalizeGuid(_this.value);
- break;
- default:
- assert(this.identifierType === NodeIdType.BYTESTRING, "invalid identifierType in NodeId : " + this.identifierType);
- if (this.value) {
- str = "ns=" + this.namespace + ";b=" + (this.value as Buffer).toString("base64");
- } else {
- str = "ns=" + this.namespace + ";b=<null>";
- }
- break;
- }
- if (addressSpace) {
- if (this.namespace === 0 && _this.identifierType === NodeIdType.NUMERIC) {
- // find standard browse name
- const name = reverse_map((this.value || 0).toString()) || "<undefined>";
- str += " " + name;
- } else if (addressSpace.findNode) {
- // let use the provided address space to figure out the browseNode of this node.
- // to make the message a little bit more useful.
- const n = addressSpace.findNode(this);
- str += " " + (n ? n.browseName.toString() : " (????)");
- }
- }
- return str;
- }
- /**
- * convert nodeId to a JSON string. same as {@link NodeId#toString }
- */
- public toJSON(): string {
- return this.toString();
- }
- public displayText(): string {
- if (this.namespace === 0 && this.identifierType === NodeIdType.NUMERIC) {
- const name = reverse_map(this.value.toString());
- if (name) {
- return name + " (" + this.toString() + ")";
- }
- }
- return this.toString();
- }
- /**
- * returns true if the NodeId is null or empty
- */
- public isEmpty(): boolean {
- const _this = this as INodeId;
- switch (_this.identifierType) {
- case NodeIdType.NUMERIC:
- return _this.value === 0;
- case NodeIdType.STRING:
- return !_this.value;
- case NodeIdType.GUID:
- return !_this.value || _this.value === emptyGuid;
- default:
- return !_this.value || (_this.value as Buffer).length === 0;
- }
- }
- }
- NodeId.nullNodeId = new Proxy(
- new NodeId(NodeIdType.NUMERIC, 0, 0),
- {
- get: (target: NodeId, prop: string) => {
- return (target as any)[prop];
- },
- set: () => {
- throw new Error("Cannot assign a value to constant NodeId.nullNodeId");
- }
- });
- export type NodeIdLike = string | NodeId | number;
- const regexNamespaceI = /ns=([0-9]+);i=([0-9]+)/;
- const regexNamespaceS = /ns=([0-9]+);s=(.*)/;
- const regexNamespaceB = /ns=([0-9]+);b=(.*)/;
- const regexNamespaceG = /ns=([0-9]+);g=([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})/;
- /**
- * Convert a value into a nodeId:
- * @class opcua
- * @method coerceNodeId
- * @static
- *
- * @description:
- * - if nodeId is a string of form : "i=1234" => nodeId({value=1234, identifierType: NodeIdType.NUMERIC})
- * - if nodeId is a string of form : "s=foo" => nodeId({value="foo", identifierType: NodeIdType.STRING})
- * - if nodeId is a string of form : "b=ABCD=" => nodeId({value=decodeBase64("ABCD="), identifierType: NodeIdType.BYTESTRING})
- * - if nodeId is a {@link NodeId} : coerceNodeId returns value
- * @param value
- * @param namespace {number}
- */
- // eslint-disable-next-line max-statements
- export function coerceNodeId(value: unknown, namespace?: number): NodeId {
- let matches;
- let twoFirst;
- if (value instanceof NodeId) {
- return value;
- }
- value = value || 0;
- namespace = namespace || 0;
- let identifierType = NodeIdType.NUMERIC;
- if (typeof value === "string") {
- identifierType = NodeIdType.STRING;
- twoFirst = value.substring(0, 2);
- if (twoFirst === "i=") {
- identifierType = NodeIdType.NUMERIC;
- value = parseInt(value.substring(2), 10);
- } else if (twoFirst === "s=") {
- identifierType = NodeIdType.STRING;
- value = value.substring(2);
- } else if (twoFirst === "b=") {
- identifierType = NodeIdType.BYTESTRING;
- value = Buffer.from(value.substring(2), "base64");
- } else if (twoFirst === "g=") {
- identifierType = NodeIdType.GUID;
- value = normalizeGuid(value.substring(2));
- assert(isValidGuid(value as string));
- } else if (isValidGuid(value)) {
- identifierType = NodeIdType.GUID;
- value = normalizeGuid(value);
- } else if ((matches = regexNamespaceI.exec(value)) !== null) {
- identifierType = NodeIdType.NUMERIC;
- namespace = parseInt(matches[1], 10);
- value = parseInt(matches[2], 10);
- } else if ((matches = regexNamespaceS.exec(value)) !== null) {
- identifierType = NodeIdType.STRING;
- namespace = parseInt(matches[1], 10);
- value = matches[2];
- } else if ((matches = regexNamespaceB.exec(value)) !== null) {
- identifierType = NodeIdType.BYTESTRING;
- namespace = parseInt(matches[1], 10);
- value = Buffer.from(matches[2], "base64");
- } else if ((matches = regexNamespaceG.exec(value)) !== null) {
- identifierType = NodeIdType.GUID;
- namespace = parseInt(matches[1], 10);
- value = normalizeGuid(matches[2]);
- } else {
- throw new Error("String cannot be coerced to a nodeId : " + value);
- }
- } else if (value instanceof Buffer) {
- identifierType = NodeIdType.BYTESTRING;
- } else if (value instanceof Object) {
- // it could be a Enum or a NodeId Like object
- const tmp = value as any;
- value = tmp.value;
- namespace = namespace || tmp.namespace;
- identifierType = tmp.identifierType || identifierType;
- return new NodeId(identifierType, value as any, namespace);
- }
- return new NodeId(identifierType, value as any, namespace);
- }
- const regEx1 = /^(s|g|b|i|ns)=/;
- /**
- * construct a node Id from a value and a namespace.
- * @class opcua
- * @method makeNodeId
- * @static
- * @param {String|Buffer} value
- * @param [namespace]=0 {Number} the node id namespace
- * @return {NodeId}
- */
- export function makeNodeId(value: string | Buffer | number, namespace?: number): NodeId {
- value = value || 0;
- namespace = namespace || 0;
- let identifierType = NodeIdType.NUMERIC;
- if (typeof value === "string") {
- if (value.match(regEx1)) {
- throw new Error("please use coerce NodeId instead");
- }
- // 1 2 3
- // 012345678901234567890123456789012345
- // "72962B91-FA75-4AE6-8D28-B404DC7DAF63"
- if (isValidGuid(value)) {
- identifierType = NodeIdType.GUID;
- value = normalizeGuid(value);
- } else {
- identifierType = NodeIdType.STRING;
- }
- } else if (value instanceof Buffer) {
- identifierType = NodeIdType.BYTESTRING;
- }
- const nodeId = new NodeId(identifierType, value, namespace);
- return nodeId;
- }
- // reverse maps
- let _nodeIdToNameIndex: any = {};
- let _nameToNodeIdIndex: any = {};
- const regName = /[a-zA-Z_].*/;
- (function build_standard_nodeid_indexes() {
- function expand_map(directIndex: any) {
- for (const name in directIndex) {
- if (Object.prototype.hasOwnProperty.call(directIndex, name) && regName.exec(name) !== null) {
- const value = directIndex[name];
- _nodeIdToNameIndex[value] = name;
- _nameToNodeIdIndex[name] = new NodeId(NodeIdType.NUMERIC, value, 0);
- }
- }
- }
- _nodeIdToNameIndex = {};
- _nameToNodeIdIndex = {};
- expand_map(ObjectIds);
- expand_map(ObjectTypeIds);
- expand_map(VariableIds);
- expand_map(VariableTypeIds);
- expand_map(MethodIds);
- expand_map(ReferenceTypeIds);
- expand_map(DataTypeIds);
- })();
- function reverse_map(nodeId: string) {
- return _nodeIdToNameIndex[nodeId];
- }
- /**
- * @class opcua
- * @method resolveNodeId
- * @static
- * @param nodeIdOrString
- * @return the nodeId
- */
- export function resolveNodeId(nodeIdOrString: NodeIdLike): NodeId {
- let nodeId;
- const rawId = typeof nodeIdOrString === "string" ? _nameToNodeIdIndex[nodeIdOrString] : undefined;
- if (rawId !== undefined) {
- return rawId;
- } else {
- nodeId = coerceNodeId(nodeIdOrString);
- }
- return nodeId;
- }
- NodeId.resolveNodeId = resolveNodeId;
- export function sameNodeId(n1: NodeId, n2: NodeId): boolean {
- if (n1.identifierType !== n2.identifierType) {
- return false;
- }
- if (n1.namespace !== n2.namespace) {
- return false;
- }
- switch (n1.identifierType) {
- case NodeIdType.NUMERIC:
- case NodeIdType.STRING:
- case NodeIdType.GUID:
- return n1.value === n2.value;
- case NodeIdType.BYTESTRING:
- return (n1.value as Buffer).toString("hex") === (n2.value as Buffer).toString("hex");
- default:
- throw new Error("Invalid identifier type");
- }
- }
- NodeId.sameNodeId = sameNodeId;
|