-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathflow.json
executable file
·1 lines (1 loc) · 11.5 KB
/
flow.json
1
[{"id":"f7cf7a422dd57947","type":"tab","label":"Apstra Protobuf","disabled":false,"info":"","env":[]},{"id":"93b829d73f83aeca","type":"function","z":"f7cf7a422dd57947","name":"Buffer","func":"// Assuming msg.sessionID contains your session ID\nlet sessionID = msg._session['id'];\n\n// Retrieve the session-specific buffer object from flow context (or initialize if not existent)\nlet buffer = flow.get(\"buffer\") || {}; // Get all buffer. Initialize if it doesn't exist.\n\n// Check if a buffer for this session ID already exists, if not create an empty Buffer\nif (!buffer[sessionID]) {\n buffer[sessionID] = Buffer.from([]); // Initialize an empty buffer for this session ID\n}\n\n// Appending message payload if it's of Buffer type\nif (Buffer.isBuffer(msg.payload)) {\n buffer[sessionID] = Buffer.concat([buffer[sessionID], msg.payload]);\n}\n\n// Access our session buffer\nlet loop_msg_id = 0\n\n// Ensure there's enough data to read buffer[sessionID] and lenght of ProtoBuf Message\nif (buffer[sessionID].length > 0) {\n let decodedProtoBufMsgLength = buffer[sessionID].readUInt16BE(0);\n\n // Until buffer[sessionID] is bigger than length of Buffer, then we have full message inside buffer[sessionID]\n while (buffer[sessionID].length >= decodedProtoBufMsgLength + 2) {\n\n let bufferLenghtBefore = buffer[sessionID].length\n const protoBufMsg = buffer[sessionID].slice(2, decodedProtoBufMsgLength + 2);\n buffer[sessionID] = buffer[sessionID].slice(decodedProtoBufMsgLength + 2);\n let bufferLenghtAfter = buffer[sessionID].length\n\n // Create a base newMsg object\n const newMsg = {\n payload: protoBufMsg,\n //_buffer: { payload_recived: msg.payload, sizePayload: msg.payload.length, sizeBefore: bufferLenghtBefore, protoSizeDecoded: decodedProtoBufMsgLength, sizeAfter: bufferLenghtAfter, data: buffer[sessionID], loopMsgId: loop_msg_id },\n _session: { id: sessionID, type: msg._session['type'], ip: msg.ip.split(':').pop(), port: msg.port }\n };\n node.send(newMsg);\n\n // Add +1 to Loop Message ID Counter\n loop_msg_id++;\n \n // Check if there's enough buffer left to read another length\n if (buffer[sessionID].length > 4) {\n decodedProtoBufMsgLength = buffer[sessionID].readUInt16BE(0);\n } else {\n break; // Exit loop if not enough data left\n }\n }\n}\n\n// Just for Debug\n//if (buffer[sessionID].length > 0) {\n// node.warn('Data chunked. In buffer left for session '+sessionID+' => '+buffer[sessionID].length+' bytes');\n//}\n\n\n// Save the updated buffer object back to the flow context\nflow.set(\"buffer\", buffer);\n\nreturn null;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":110,"y":160,"wires":[["6852a911f985886f"]]},{"id":"43f325e0c033541b","type":"tcp in","z":"f7cf7a422dd57947","name":"","server":"server","host":"","port":"5555","datamode":"stream","datatype":"buffer","newline":"","topic":"","trim":false,"base64":false,"tls":"","x":100,"y":80,"wires":[["e037ab40a1fe7485","b9f0d14f4aa76815"]]},{"id":"7698ccba0406bb99","type":"debug","z":"f7cf7a422dd57947","name":"Msg Buffer","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":510,"y":240,"wires":[]},{"id":"e037ab40a1fe7485","type":"debug","z":"f7cf7a422dd57947","name":"TCP In","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":290,"y":40,"wires":[]},{"id":"4c977c2cf7bfe1e7","type":"decode","z":"f7cf7a422dd57947","name":"Protobuf Decoder","protofile":"55ccc9e449c86c3e","protoType":"","x":530,"y":160,"wires":[["9c53c2f32d21776f"]]},{"id":"369a12e3368b1fca","type":"debug","z":"f7cf7a422dd57947","name":"Msg Decoded","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1220,"y":100,"wires":[]},{"id":"90809af880102c72","type":"websocket out","z":"f7cf7a422dd57947","name":"WebSocket","server":"e1c57595efcaaee8","client":"","x":1210,"y":160,"wires":[]},{"id":"6852a911f985886f","type":"function","z":"f7cf7a422dd57947","name":"Mode Recognition","func":"// Load Functions\nvar recognizeStreamMode = context.get('recognizeStreamMode');\nvar decodeFirstUInt64Field = context.get('decodeFirstUInt64Field');\n\n// Assuming msg.sessionID contains your session ID\nlet sessionID = msg._session['id'];\n\n// Retrieve the session-specific buffer object from flow context (or initialize if not existent)\nlet seqMode = flow.get(\"seqMode\") || {};\n\n// Check if a seqMode for this session ID already exists, if not create an empty string\nif (!seqMode[sessionID]) {\n seqMode[sessionID] = ''; // Initialize an empty string for this session ID\n}\n\n\n// Define variables\nlet streamMode, firstUInt64Field, protobufType;\n\n// Check stream mode cache\nif (seqMode[sessionID] == '' && msg.payload) {\n // We need to recognize Stream Mode\n streamMode = recognizeStreamMode(msg.payload);\n firstUInt64Field = decodeFirstUInt64Field(msg.payload);\n seqMode[sessionID] = streamMode;\n }\nelse\n {\n // We have seqMode[sessionID] - set 'streamMode' from cache \n streamMode = seqMode[sessionID];\n firstUInt64Field = 'cache';\n }\n\n// Save the updated seqMode object back to the flow context\nflow.set(\"seqMode\", seqMode);\n\n// In case of Debug - set _streamMode in msg object\n//msg._stream = { mode: streamMode, firstUInt64Field: firstUInt64Field }\n\n\n// Set Protobuf Message Type\n// this will be used by GBP Decoder\nif (streamMode == \"sequenced\") {\n msg.protobufType = \"AosSequencedMessage\"\n}\nif (streamMode == \"unsequenced\") {\n msg.protobufType = \"AosMessage\"\n}\n\n\n\n// In case of unsequenced - set seqNum always to 0\nmsg.seqNum = 0\n\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"// Function to decode the varint\nfunction decodeVarint(byteStream, startPos) {\n let result = BigInt(0);\n let shift = BigInt(0);\n let pos = startPos;\n\n let byte;\n do {\n byte = BigInt(byteStream[pos++]);\n result |= (byte & BigInt(0x7F)) << shift;\n shift += BigInt(7);\n } while (byte & BigInt(0x80));\n\n return { value: result, position: pos };\n}\n\n// Function to decode the first uint64 field from a byte stream\nfunction decodeFirstUInt64Field(byteStream) {\n let pos = 0;\n\n while (pos < byteStream.length) {\n // Decode the field number and wire type\n const { value: fieldWireType, position: newPos } = decodeVarint(byteStream, pos);\n pos = newPos;\n const fieldNumber = fieldWireType >> BigInt(3);\n const wireType = fieldWireType & BigInt(0x07);\n\n // Check if the wire type is compatible with varint encoding and field number is the first field\n if (wireType === BigInt(0) && fieldNumber === BigInt(1)) {\n // Decode the uint64 value\n const { value: uint64Value, position: newPos } = decodeVarint(byteStream, pos);\n return uint64Value;\n } else {\n // Skip the field value based on wire type\n if (wireType === BigInt(0)) { // Varint\n pos = decodeVarint(byteStream, pos).position;\n } else if (wireType === BigInt(2)) { // Length-delimited\n const { value: fieldLength, position: newPos } = decodeVarint(byteStream, pos);\n pos = newPos + Number(fieldLength);\n } else {\n throw new Error(\"Unsupported wire type\");\n }\n }\n }\n\n throw new Error(\"No uint64 field found\");\n}\n\n// Function to recognize stream mode based on varint value\nfunction recognizeStreamMode(msgBytes) {\n const varint_value = decodeFirstUInt64Field(msgBytes);\n if (varint_value > BigInt('1700000000000000')) {\n return 'unsequenced';\n } else {\n return 'sequenced';\n }\n}\n\ncontext.set('decodeFirstUInt64Field', decodeFirstUInt64Field);\ncontext.set('recognizeStreamMode', recognizeStreamMode);\n","finalize":"","libs":[],"x":270,"y":160,"wires":[["4c977c2cf7bfe1e7","7698ccba0406bb99"]]},{"id":"9c53c2f32d21776f","type":"function","z":"f7cf7a422dd57947","name":"Seq Decoder","func":"// Check if 'seqNum' exists in msg.payload\nif (msg.payload && msg.payload.seqNum && msg.protobufType == \"AosSequencedMessage\") {\n // Move 'seqNum' to msg.seqNum\n msg.seqNum = msg.payload.seqNum;\n // Move 'aosProto' to msg.payload as bytes\n let new_payload = Buffer.from(msg.payload.aosProto, 'base64');\n msg.protobufType = \"AosMessage\"\n msg.payload = new_payload;\n\n // Send message through the second output\n return [null, msg]; // Second output\n} else {\n delete msg.protobufType;\n\n // Send message through the first output\n return [msg, null]; // First output\n\n\n}\n\n","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":710,"y":160,"wires":[["869e130394013e70","df1526f0e4bdbe49"],["4c977c2cf7bfe1e7"]]},{"id":"df1526f0e4bdbe49","type":"function","z":"f7cf7a422dd57947","name":"MyInjects","func":"\n// Load Functions\nvar decodeTimestamp = context.get('decodeTimestamp');\n\nif (msg.payload && msg.seqNum >= 0 && msg._session) {\n // Add 'seqNum' to msg.payload\n let seqNumInt = parseInt(msg.seqNum);\n msg.payload.seqNum = seqNumInt;\n\n // Add 'msgSource' to msg.payload\n msg.payload.msgSource = msg._session.ip+':'+msg._session.port\n \n} else { \n node.warn(\"Not able to add Seq Number to payload\");\n}\n\n\nif (msg.payload.timestamp) {\n let tz;\n\n if (global.get('timezone')) {\n tz = global.get('timezone')\n } else {\n tz = null\n }\n\n msg.payload.date = decodeTimestamp(msg.payload.timestamp, tz);\n msg.payload.dateTZ = tz;\n}\n\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"function decodeTimestamp(timestamp, timezone = \"UTC\") {\n // Extract seconds and the rest\n let ts_sec = timestamp.substring(0, 10);\n let rest = timestamp.substring(10, 16);\n\n return moment.unix(ts_sec).tz(timezone).format();\n}\n\n\ncontext.set('decodeTimestamp', decodeTimestamp);\n","finalize":"","libs":[{"var":"moment","module":"moment"}],"x":880,"y":160,"wires":[["8906cb4e71ab5339"]]},{"id":"8906cb4e71ab5339","type":"json","z":"f7cf7a422dd57947","name":"","property":"payload","action":"","pretty":false,"x":1030,"y":160,"wires":[["369a12e3368b1fca","90809af880102c72"]]},{"id":"b9f0d14f4aa76815","type":"function","z":"f7cf7a422dd57947","name":"Set Globals","func":"// Check if the global parameter is already set\nif (!global.get('timezone')) {\n global.set('timezone', 'Europe/Warsaw');\n var current_timezone = global.get(\"timezone\");\n node.log(\"Current timezone: \" + current_timezone);\n}\n\n// Return the message\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":290,"y":80,"wires":[["93b829d73f83aeca"]]},{"id":"869e130394013e70","type":"debug","z":"f7cf7a422dd57947","name":"Msg Decoder","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":890,"y":120,"wires":[]},{"id":"c5ed7885ba5f6ef7","type":"comment","z":"f7cf7a422dd57947","name":"Apstra Stream","info":"","x":110,"y":40,"wires":[]},{"id":"55ccc9e449c86c3e","type":"protobuf-file","protopath":"/data/streaming-telemetry-schema.proto","watchFile":true,"keepCase":false},{"id":"e1c57595efcaaee8","type":"websocket-listener","path":"/ws/apstra","wholemsg":"false"}]