UNPKG

node-opcua-transport

Version:

pure nodejs OPCUA SDK - module transport

129 lines (109 loc) 4.35 kB
/** * @module node-opcua-transport */ import { assert } from "node-opcua-assert"; import { BinaryStream, type OutputBinaryStream } from "node-opcua-binary-stream"; import { createFastUninitializedBuffer } from "node-opcua-buffer-utils"; import { readMessageHeader } from "node-opcua-chunkmanager"; import type { BaseUAObject } from "node-opcua-factory"; import { TCPErrorMessage } from "./TCPErrorMessage"; function is_valid_msg_type(msgType: string): boolean { assert( [ "HEL", "ACK", "ERR", // Connection Layer "OPN", "MSG", "CLO" // OPC Unified Architecture, Part 6 page 36 ].indexOf(msgType) >= 0, `invalid message type ${msgType}` ); return true; } export type ConstructorFunc = new () => BaseUAObject; export function decodeMessage(stream: BinaryStream, classNameConstructor: ConstructorFunc): BaseUAObject { assert(stream instanceof BinaryStream); assert(classNameConstructor instanceof Function, ` expecting a function for ${classNameConstructor}`); const header = readMessageHeader(stream); assert(stream.length === 8); let obj: BaseUAObject; if (header.msgType === "ERR") { obj = new TCPErrorMessage(); obj.decode(stream); return obj; } else { obj = new classNameConstructor(); obj.decode(stream); return obj; } } export function packTcpMessage(msgType: string, encodableObject: BaseUAObject): Buffer { assert(is_valid_msg_type(msgType)); const messageChunk = createFastUninitializedBuffer(encodableObject.binaryStoreSize() + 8); // encode encode-ableObject in a packet const stream = new BinaryStream(messageChunk); encodeMessage(msgType, encodableObject, stream); return messageChunk; } export interface ParsedEndpointUrl { protocol: string; hostname: string; port: string | null; pathname: string | null; auth: string | null; href: string; } // opc.tcp://hostname:51210/UA/SampleServer export function parseEndpointUrl(endpointUrl: string): ParsedEndpointUrl { // Replace non-standard protocols (e.g. opc.tcp:) with http: // so the WHATWG URL parser can handle them, then restore the // original protocol in the result. const protocolMatch = endpointUrl.match(/^([a-z][a-z0-9.+-]*):/i); if (!protocolMatch) { throw new Error(`Invalid endpoint url ${endpointUrl}`); } const originalProtocol = `${protocolMatch[1].toLowerCase()}:`; const normalizedUrl = endpointUrl.replace(/^[a-z][a-z0-9.+-]*:/i, "http:"); let parsed: URL; try { parsed = new URL(normalizedUrl); } catch { throw new Error(`Invalid endpoint url ${endpointUrl}`); } if (!parsed.hostname) { throw new Error(`Invalid endpoint url ${endpointUrl}`); } return { protocol: originalProtocol, hostname: parsed.hostname, port: parsed.port || null, pathname: parsed.pathname !== "/" ? parsed.pathname : null, auth: parsed.username ? (parsed.password ? `${parsed.username}:${parsed.password}` : parsed.username) : null, href: endpointUrl }; } export function is_valid_endpointUrl(endpointUrl: string): boolean { const e = parseEndpointUrl(endpointUrl); return Object.hasOwn(e, "hostname"); } export function writeTCPMessageHeader(msgType: string, chunkType: string, totalLength: number, stream: OutputBinaryStream): void { if (stream instanceof Buffer) { stream = new BinaryStream(stream); } assert(is_valid_msg_type(msgType)); assert(["A", "F", "C"].indexOf(chunkType) !== -1); stream.writeUInt8(msgType.charCodeAt(0)); stream.writeUInt8(msgType.charCodeAt(1)); stream.writeUInt8(msgType.charCodeAt(2)); // Chunk type stream.writeUInt8(chunkType.charCodeAt(0)); // reserved stream.writeUInt32(totalLength); } function encodeMessage(msgType: string, messageContent: BaseUAObject, stream: OutputBinaryStream) { // the length of the message, in bytes. (includes the 8 bytes of the message header) const totalLength = messageContent.binaryStoreSize() + 8; writeTCPMessageHeader(msgType, "F", totalLength, stream); messageContent.encode(stream); assert(totalLength === stream.length, "invalid message size"); }