From bec68528a595727279667aa54b756bf1a94ca5de Mon Sep 17 00:00:00 2001 From: Peter Hall <33176108+IamPete1@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:31:02 +0000 Subject: [PATCH 1/4] Generator: Javascript: whitespace fixup --- generator/mavgen_javascript.py | 443 ++++++++++++++++----------------- 1 file changed, 220 insertions(+), 223 deletions(-) diff --git a/generator/mavgen_javascript.py b/generator/mavgen_javascript.py index 48fb83ba2..516d91785 100644 --- a/generator/mavgen_javascript.py +++ b/generator/mavgen_javascript.py @@ -108,7 +108,7 @@ def generate_preamble(outf, msgs, args, xml): ${MAVHEAD}.header.prototype.pack = function() { return jspack.Pack('BBBBBBBHB', [${PROTOCOL_MARKER}, this.mlen, this.incompat_flags, this.compat_flags, this.seq, this.srcSystem, this.srcComponent, ((this.msgId & 0xFF) << 8) | ((this.msgId >> 8) & 0xFF), this.msgId>>16]); } - """, {'PROTOCOL_MARKER' : xml.protocol_marker, +""", {'PROTOCOL_MARKER' : xml.protocol_marker, 'MAVHEAD': get_mavhead(xml)}) # Mavlink1 else: @@ -117,7 +117,7 @@ def generate_preamble(outf, msgs, args, xml): ${MAVHEAD}.header.prototype.pack = function() { return jspack.Pack('BBBBBB', [${PROTOCOL_MARKER}, this.mlen, this.seq, this.srcSystem, this.srcComponent, this.msgId]); } - """, {'PROTOCOL_MARKER' : xml.protocol_marker, +""", {'PROTOCOL_MARKER' : xml.protocol_marker, 'MAVHEAD': get_mavhead(xml)}) t.write(outf, """ @@ -128,7 +128,7 @@ def generate_preamble(outf, msgs, args, xml): // Convenience setter to facilitate turning the unpacked array of data into member properties ${MAVHEAD}.message.prototype.set = function(args,verbose) { -// inspect + // inspect _.each(this.fieldnames, function(e, i) { var num = parseInt(i,10); if (this.hasOwnProperty(e) && isNaN(num) ){ // asking for an attribute that's non-numeric is ok unless its already an attribute we have @@ -136,6 +136,7 @@ def generate_preamble(outf, msgs, args, xml): } }, this); //console.log(this.fieldnames); + // then modify _.each(this.fieldnames, function(e, i) { this[e] = args[i]; @@ -387,19 +388,19 @@ def generate_mavlink_class(outf, msgs, xml): ${MAVHEAD}.messages.bad_data.prototype = new ${MAVHEAD}.message; // MAVLink signing state class -MAVLinkSigning = function MAVLinkSigning(object){ - this.secret_key = new Buffer.from([]) ; //new Buffer.from([ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42 ]) // secret key must be a Buffer obj of 32 length - this.timestamp = 1 - this.link_id = 0 - this.sign_outgoing = false // todo false this - this.allow_unsigned_callback = undefined - this.stream_timestamps = {} - this.sig_count = 0 - this.badsig_count = 0 - this.goodsig_count = 0 - this.unsigned_count = 0 - this.reject_count = 0 -} +MAVLinkSigning = function MAVLinkSigning(object){ + this.secret_key = new Buffer.from([]); //new Buffer.from([ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42 ]) // secret key must be a Buffer obj of 32 length + this.timestamp = 1 + this.link_id = 0 + this.sign_outgoing = false // todo false this + this.allow_unsigned_callback = undefined + this.stream_timestamps = {} + this.sig_count = 0 + this.badsig_count = 0 + this.goodsig_count = 0 + this.unsigned_count = 0 + this.reject_count = 0 +} /* MAVLink protocol handling class */ ${MAVPROCESSOR} = function(logger, srcSystem, srcComponent) { @@ -409,14 +410,14 @@ def generate_mavlink_class(outf, msgs, xml): this.seq = 0; this.buf = new Buffer.from([]); this.bufInError = new Buffer.from([]); - + this.srcSystem = (typeof srcSystem === 'undefined') ? 0 : srcSystem; this.srcComponent = (typeof srcComponent === 'undefined') ? 0 : srcComponent; this.have_prefix_error = false; // The first packet we expect is a valid header, 6 bytes. - this.protocol_marker = ${PROTOCOL_MARKER}; + this.protocol_marker = ${PROTOCOL_MARKER}; this.expected_length = ${MAVHEAD}.HEADER_LEN; this.little_endian = true; @@ -429,7 +430,7 @@ def generate_mavlink_class(outf, msgs, xml): this.total_receive_errors = 0; this.startup_time = Date.now(); - // optional , but when used we store signing state in this object: + // optional , but when used we store signing state in this object: this.signing = new MAVLinkSigning(); } @@ -493,16 +494,16 @@ def generate_mavlink_class(outf, msgs, xml): // us know if we have signing enabled, which affects the real-world length by the signature-block length of 13 bytes. // once successful, 'this.expected_length' is correctly set for the whole packet. ${MAVPROCESSOR}.prototype.parseLength = function() { - - if( this.buf.length >= 3 ) { - var unpacked = jspack.Unpack('BBB', this.buf.slice(0, 3)); - var magic = unpacked[0]; // stx ie fd or fe etc - this.expected_length = unpacked[1] + ${MAVHEAD}.HEADER_LEN + 2 // length of message + header + CRC (ie non-signed length) - this.incompat_flags = unpacked[2]; - // mavlink2 only.. in mavlink1, incompat_flags var above is actually the 'seq', but for this test its ok. - if ((magic == ${MAVHEAD}.PROTOCOL_MARKER_V2 ) && ( this.incompat_flags & ${MAVHEAD}.MAVLINK_IFLAG_SIGNED )){ - this.expected_length += ${MAVHEAD}.MAVLINK_SIGNATURE_BLOCK_LEN; - } + + if( this.buf.length >= 3 ) { + var unpacked = jspack.Unpack('BBB', this.buf.slice(0, 3)); + var magic = unpacked[0]; // stx ie fd or fe etc + this.expected_length = unpacked[1] + ${MAVHEAD}.HEADER_LEN + 2 // length of message + header + CRC (ie non-signed length) + this.incompat_flags = unpacked[2]; + // mavlink2 only.. in mavlink1, incompat_flags var above is actually the 'seq', but for this test its ok. + if ((magic == ${MAVHEAD}.PROTOCOL_MARKER_V2 ) && ( this.incompat_flags & ${MAVHEAD}.MAVLINK_IFLAG_SIGNED )){ + this.expected_length += ${MAVHEAD}.MAVLINK_SIGNATURE_BLOCK_LEN; + } } } @@ -513,19 +514,17 @@ def generate_mavlink_class(outf, msgs, xml): var m = null; try { - this.pushBuffer(c); this.parsePrefix(); this.parseLength(); m = this.parsePayload(); } catch(e) { - this.log('error', e.message); this.total_receive_errors += 1; m = new ${MAVHEAD}.messages.bad_data(this.bufInError, e.message); this.bufInError = new Buffer.from([]); - + } // emit a packet-specific message as well as a generic message, user/s can choose to use either or both of these. @@ -621,91 +620,91 @@ def generate_mavlink_class(outf, msgs, xml): //check signature on incoming message , many of the comments in this file come from the python impl ${MAVPROCESSOR}.prototype.check_signature = function(msgbuf, srcSystem, srcComponent) { - //if (isinstance(msgbuf, array.array)){ - // msgbuf = msgbuf.tostring() - //} - if ( Buffer.isBuffer(msgbuf) ) { - msgbuf = toArrayBuffer(msgbuf); - } - - //timestamp_buf = msgbuf[-12:-6] - var timestamp_buf= msgbuf.slice(-12,-6); - - //link_id = msgbuf[-13] - var link_id= new Buffer.from(msgbuf.slice(-13,-12)); // just a single byte really, but returned as a buffer + //if (isinstance(msgbuf, array.array)){ + // msgbuf = msgbuf.tostring() + //} + if ( Buffer.isBuffer(msgbuf) ) { + msgbuf = toArrayBuffer(msgbuf); + } + + //timestamp_buf = msgbuf[-12:-6] + var timestamp_buf= msgbuf.slice(-12,-6); + + //link_id = msgbuf[-13] + var link_id= new Buffer.from(msgbuf.slice(-13,-12)); // just a single byte really, but returned as a buffer link_id = link_id[0]; // get the first byte. - - //self.mav_sign_unpacker = jspack.Unpack(' 0)){ - var len_if_signed = mlen+signature_len; - //console.log("Packet appears signed && labeled as signed, OK. msgId=" + msgId); - - } else if ((mlen == actual_len_nosign) && (signature_len > 0)){ - - var len_if_signed = mlen+signature_len; + // is packet supposed to be signed? + if ( incompat_flags & ${MAVHEAD}.MAVLINK_IFLAG_SIGNED ){ + signature_len = ${MAVHEAD}.MAVLINK_SIGNATURE_BLOCK_LEN; + } else { + signature_len = 0; + } + + // header's declared len compared to packets actual len + var actual_len = (msgbuf.length - (${MAVHEAD}.HEADER_LEN + 2 + signature_len)); + var actual_len_nosign = (msgbuf.length - (${MAVHEAD}.HEADER_LEN + 2 )); + + if ((mlen == actual_len) && (signature_len > 0)){ + var len_if_signed = mlen+signature_len; + //console.log("Packet appears signed && labeled as signed, OK. msgId=" + msgId); + + } else if ((mlen == actual_len_nosign) && (signature_len > 0)){ + + var len_if_signed = mlen+signature_len; throw new Error("Packet appears unsigned when labeled as signed. Got actual_len "+actual_len_nosign+" expected " + len_if_signed + ", msgId=" + msgId); - - } else if( mlen != actual_len) { + + } else if( mlen != actual_len) { throw new Error("Invalid MAVLink message length. Got " + (msgbuf.length - (${MAVHEAD}.HEADER_LEN + 2)) + " expected " + mlen + ", msgId=" + msgId); - } - + } + if( false === _.has(${MAVHEAD}.map, msgId) ) { throw new Error("Unknown MAVLink message ID (" + msgId + ")"); } - // here's the common chunks of packet we want to work with below.. - var headerBuf= msgbuf.slice(${MAVHEAD}.HEADER_LEN); // first10 - var sigBuf = msgbuf.slice(-signature_len); // last 13 or nothing - var crcBuf1 = msgbuf.slice(-2); // either last-2 or last-2-prior-to-signature - var crcBuf2 = msgbuf.slice(-15,-13); // either last-2 or last-2-prior-to-signature - var payloadBuf = msgbuf.slice(${MAVHEAD}.HEADER_LEN, -(signature_len+2)); // the remaining bit between the header and the crc - var crcCheckBuf = msgbuf.slice(1, -(signature_len+2)); // the part uses to calculate the crc - ie between the magic and signature, + // here's the common chunks of packet we want to work with below.. + var headerBuf= msgbuf.slice(${MAVHEAD}.HEADER_LEN); // first10 + var sigBuf = msgbuf.slice(-signature_len); // last 13 or nothing + var crcBuf1 = msgbuf.slice(-2); // either last-2 or last-2-prior-to-signature + var crcBuf2 = msgbuf.slice(-15,-13); // either last-2 or last-2-prior-to-signature + var payloadBuf = msgbuf.slice(${MAVHEAD}.HEADER_LEN, -(signature_len+2)); // the remaining bit between the header and the crc + var crcCheckBuf = msgbuf.slice(1, -(signature_len+2)); // the part uses to calculate the crc - ie between the magic and signature, // decode the payload // refs: (fmt, type, order_map, crc_extra) = ${MAVHEAD}.map[msgId] var decoder = ${MAVHEAD}.map[msgId]; // decode the checksum - var receivedChecksum = undefined; - if ( signature_len == 0 ) { // unsigned - try { - receivedChecksum = jspack.Unpack(' Date: Mon, 11 Nov 2024 22:52:56 +0000 Subject: [PATCH 2/4] Generator: Javascript: use `forEach` and `Uint8Array` --- generator/mavgen_javascript.py | 115 +++++++++++++-------------------- 1 file changed, 45 insertions(+), 70 deletions(-) diff --git a/generator/mavgen_javascript.py b/generator/mavgen_javascript.py index 516d91785..0bece6021 100644 --- a/generator/mavgen_javascript.py +++ b/generator/mavgen_javascript.py @@ -32,17 +32,9 @@ def generate_preamble(outf, msgs, args, xml): */ jspack = require("jspack").jspack, - _ = require("underscore"), events = require("events"), // for .emit(..), MAVLink20Processor inherits from events.EventEmitter util = require("util"); -var Buffer = require('buffer').Buffer; // required in react - no impact in node -var Long = require('long'); - -// Add a convenience method to Buffer -Buffer.prototype.toByteArray = function () { - return Array.prototype.slice.call(this, 0) -} ${MAVHEAD} = function(){}; @@ -51,7 +43,7 @@ def generate_preamble(outf, msgs, args, xml): var bytes = buffer; var crcOUT = crcIN === undefined ? 0xffff : crcIN; - _.each(bytes, function(e) { + bytes.forEach(function(e) { var tmp = e ^ (crcOUT & 0xff); tmp = (tmp ^ (tmp << 4)) & 0xff; crcOUT = (crcOUT >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4); @@ -129,16 +121,15 @@ def generate_preamble(outf, msgs, args, xml): // Convenience setter to facilitate turning the unpacked array of data into member properties ${MAVHEAD}.message.prototype.set = function(args,verbose) { // inspect - _.each(this.fieldnames, function(e, i) { + this.fieldnames.forEach(function(e, i) { var num = parseInt(i,10); if (this.hasOwnProperty(e) && isNaN(num) ){ // asking for an attribute that's non-numeric is ok unless its already an attribute we have if ( verbose >= 1) { console.log("WARNING, overwriting an existing property is DANGEROUS:"+e+" ==>"+i+"==>"+args[i]+" -> "+JSON.stringify(this)); } } }, this); - //console.log(this.fieldnames); -// then modify - _.each(this.fieldnames, function(e, i) { + // then modify + this.fieldnames.forEach(function(e, i) { this[e] = args[i]; }, this); }; @@ -164,11 +155,11 @@ def generate_preamble(outf, msgs, args, xml): // first add the linkid(1 byte) and timestamp(6 bytes) that start the signature this._msgbuf = this._msgbuf.concat(jspack.Pack(' payloadBuf.length) { - payloadBuf = Buffer.concat([payloadBuf, Buffer.alloc(paylen - payloadBuf.length)]); + payloadBuf = this.concat_buffer(payloadBuf, new Uint8Array(paylen - payloadBuf.length).fill(0)); } """) @@ -912,7 +887,7 @@ def generate_mavlink_class(outf, msgs, xml): if (elementsInMsg == actualElementsInMsg) { // Reorder the fields to match the order map - _.each(t, function(e, i, l) { + t.forEach(function(e, i, l) { args[i] = t[decoder.order_map[i]] }); } else { @@ -956,7 +931,7 @@ def generate_mavlink_class(outf, msgs, xml): } // Finally reorder the fields to match the order map - _.each(t, function(e, i, l) { + t.forEach(function(e, i, l) { args[i] = tempArgs[decoder.order_map[i]] }); } From 0a33fe4d0b84d5721d209e0766d14097087032eb Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sun, 27 Jul 2025 12:14:02 +1000 Subject: [PATCH 3/4] mavgen_javascript: fixed signing and multi-message parsing --- generator/javascript/test/mavlink10.js | 42 +- generator/javascript/test/mavlink20.js | 29 +- generator/mavgen_javascript.py | 517 +++++++++++++++++-------- 3 files changed, 362 insertions(+), 226 deletions(-) diff --git a/generator/javascript/test/mavlink10.js b/generator/javascript/test/mavlink10.js index 487523e41..80eccfb53 100644 --- a/generator/javascript/test/mavlink10.js +++ b/generator/javascript/test/mavlink10.js @@ -5,7 +5,6 @@ var fs = require('fs'); // Actual data stream taken from APM. global.fixtures = global.fixtures || {}; global.fixtures.serialStream = fs.readFileSync("test/capture.mavlink"); -//global.fixtures.heartbeatBinaryStream = fs.readFileSync("javascript/test/heartbeat-data-fixture"); describe("Generated MAVLink 1.0 protocol handler object", function() { @@ -81,14 +80,7 @@ describe("Generated MAVLink 1.0 protocol handler object", function() { describe("buffer decoder (parseBuffer)", function() { - // This test prepopulates a single message as a binary buffer. - it("decodes a binary stream representation of a single message correctly", function() { - this.m.pushBuffer(global.fixtures.heartbeatBinaryStream); - var messages = this.m.parseBuffer(); - - }); - - // This test includes a "noisy" signal, with non-mavlink data/messages/noise. + // This test includes a "noisy" signal, with non-mavlink data/messages/noise. it("decodes a real serial binary stream into an array of MAVLink messages", function() { this.m.pushBuffer(global.fixtures.serialStream); var messages = this.m.parseBuffer(); @@ -113,11 +105,11 @@ describe("Generated MAVLink 1.0 protocol handler object", function() { }); it("emits a 'message' event, provisioning callbacks with the message", function(done) { - this.m.on('message', function(message) { - message.should.be.an.instanceof(mavlink10.messages.heartbeat); + this.m.on('message', function(message) { + message.should.be.an.instanceof(mavlink10.messages.heartbeat); done(); - }); - this.m.parseChar(this.heartbeatPayload); + }); + this.m.parseChar(this.heartbeatPayload); }); it("emits a 'message' event for bad messages, provisioning callbacks with the message", function(done) { @@ -129,24 +121,12 @@ describe("Generated MAVLink 1.0 protocol handler object", function() { this.m.parseChar(b); }); - it("on bad prefix: cuts-off first char in buffer and returns correct bad data", function() { - var b = new Buffer.from([3, 0, 1, 2, 3, 4, 5]); // invalid message - var message = this.m.parseChar(b); - message._msgbuf.length.should.be.eql(1); - message._msgbuf[0].should.be.eql(3); - this.m.buf.length.should.be.eql(6); - // should process next char - message = this.m.parseChar(); - message._msgbuf.length.should.be.eql(1); - message._msgbuf[0].should.be.eql(0); - this.m.buf.length.should.be.eql(5); - }); - - it("on bad message: cuts-off message length and returns correct bad data", function() { + it("on bad message: cuts-off message length and returns correct bad data", function() { var message = this.m.parseChar(this.completeInvalidMessage); - message._msgbuf.length.should.be.eql(8); - message._msgbuf.should.be.eql(this.completeInvalidMessage); - this.m.buf.length.should.be.eql(0); + + message._msgbuf.length.should.be.eql(8); + Buffer.from(message._msgbuf).should.be.eql(this.completeInvalidMessage); + this.m.buf.length.should.be.eql(0); }); it("error counter is raised on error", function() { @@ -180,7 +160,7 @@ describe("Generated MAVLink 1.0 protocol handler object", function() { var b = new Buffer.alloc(16); b.fill("h"); this.m.pushBuffer(b); - this.m.buf.should.eql(b); // eql = wiggly equality + Buffer.from(this.m.buf).should.eql(b); // eql = wiggly equality }); }); diff --git a/generator/javascript/test/mavlink20.js b/generator/javascript/test/mavlink20.js index 1e389525f..8f66a08c6 100644 --- a/generator/javascript/test/mavlink20.js +++ b/generator/javascript/test/mavlink20.js @@ -5,7 +5,6 @@ var fs = require('fs'); // Actual data stream taken from APM. global.fixtures = global.fixtures || {}; global.fixtures.serialStream = fs.readFileSync("test/capture.mavlink"); -//global.fixtures.heartbeatBinaryStream = fs.readFileSync("javascript/test/heartbeat-data-fixture"); describe("Generated MAVLink 2.0 protocol handler object", function() { @@ -85,14 +84,7 @@ describe("Generated MAVLink 2.0 protocol handler object", function() { describe("buffer decoder (parseBuffer)", function() { - // This test prepopulates a single message as a binary buffer. - it("decodes a binary stream representation of a single message correctly", function() { - this.m.pushBuffer(global.fixtures.heartbeatBinaryStream); - var messages = this.m.parseBuffer(); - - }); - - // This test includes a "noisy" signal, with non-mavlink data/messages/noise. + // This test includes a "noisy" signal, with non-mavlink data/messages/noise. it("decodes a real serial binary stream into an array of MAVLink messages", function() { this.m.pushBuffer(global.fixtures.serialStream); var messages = this.m.parseBuffer(); @@ -133,23 +125,10 @@ describe("Generated MAVLink 2.0 protocol handler object", function() { this.m.parseChar(b); }); - it("on bad prefix: cuts-off first char in buffer and returns correct bad data", function() { - var b = new Buffer.from([3, 0, 1, 2, 3, 4, 5, 6, 7]); // invalid message - var message = this.m.parseChar(b); - message._msgbuf.length.should.be.eql(1); - message._msgbuf[0].should.be.eql(3); - this.m.buf.length.should.be.eql(8); - // should process next char - message = this.m.parseChar(); - message._msgbuf.length.should.be.eql(1); - message._msgbuf[0].should.be.eql(0); - this.m.buf.length.should.be.eql(7); - }); - - it("on bad message: cuts-off message length and returns correct bad data", function() { + it("on bad message: cuts-off message length and returns correct bad data", function() { var message = this.m.parseChar(this.completeInvalidMessage); message._msgbuf.length.should.be.eql(12); - message._msgbuf.should.be.eql(this.completeInvalidMessage); + Buffer.from(message._msgbuf).should.be.eql(this.completeInvalidMessage); this.m.buf.length.should.be.eql(0); }); @@ -184,7 +163,7 @@ describe("Generated MAVLink 2.0 protocol handler object", function() { var b = new Buffer.alloc(16); b.fill("h"); this.m.pushBuffer(b); - this.m.buf.should.eql(b); // eql = wiggly equality + Buffer.from(this.m.buf).should.eql(b); // eql = wiggly equality }); }); diff --git a/generator/mavgen_javascript.py b/generator/mavgen_javascript.py index 0bece6021..08b6c3a35 100644 --- a/generator/mavgen_javascript.py +++ b/generator/mavgen_javascript.py @@ -31,9 +31,71 @@ def generate_preamble(outf, msgs, args, xml): Note: this file has been auto-generated. DO NOT EDIT */ -jspack = require("jspack").jspack, - events = require("events"), // for .emit(..), MAVLink20Processor inherits from events.EventEmitter +// Detect environment +const isNode = typeof process !== 'undefined' && + process.versions && + process.versions.node; + +// Handle jspack dependency +let jspack; +if (isNode) { + jspack = (global && global.jspack) || require("jspack").jspack; +} else { + import("./local_modules/jspack/jspack.js").then((mod) => { + jspack = new mod.default() + }).catch((e) => { + }); +} + +// Handle Node.js specific modules +let events, util; +if (isNode) { + events = require("events"); util = require("util"); +} else { + // Browser polyfills for Node.js modules + util = { + inherits: function(constructor, superConstructor) { + constructor.prototype = Object.create(superConstructor.prototype); + constructor.prototype.constructor = constructor; + } + }; + + // Simple EventEmitter polyfill for browsers + events = { + EventEmitter: function() { + this._events = {}; + + this.on = function(event, listener) { + if (!this._events[event]) { + this._events[event] = []; + } + this._events[event].push(listener); + }; + + this.emit = function(event, ...args) { + if (this._events[event]) { + this._events[event].forEach(listener => { + try { + listener.apply(this, args); + } catch (e) { + console.error('Error in event listener:', e); + } + }); + } + }; + + this.removeListener = function(event, listener) { + if (this._events[event]) { + const index = this._events[event].indexOf(listener); + if (index > -1) { + this._events[event].splice(index, 1); + } + } + }; + } + }; +} ${MAVHEAD} = function(){}; @@ -76,7 +138,7 @@ def generate_preamble(outf, msgs, args, xml): ${MAVHEAD}.MAVLINK_SIGNATURE_BLOCK_LEN = 13 // Mavlink headers incorporate sequence, source system (platform) and source component. -${MAVHEAD}.header = function(msgId, mlen, seq, srcSystem, srcComponent, incompat_flags=0, compat_flags=0,) { +${MAVHEAD}.header = function(msgId, mlen, seq, srcSystem, srcComponent, incompat_flags=0, compat_flags=0) { this.mlen = ( typeof mlen === 'undefined' ) ? 0 : mlen; this.seq = ( typeof seq === 'undefined' ) ? 0 : seq; @@ -134,36 +196,140 @@ def generate_preamble(outf, msgs, args, xml): }, this); }; -// trying to be the same-ish as the python function of the same name -${MAVHEAD}.message.prototype.sign_packet = function( mav) { - var crypto= require('crypto'); - var h = crypto.createHash('sha256'); +/* + sha256 implementation + embedded to avoid async issues in web browsers with crypto library + with thanks to https://geraintluff.github.io/sha256/ +*/ +${MAVHEAD}.sha256 = function(inputBytes) { + const K = new Uint32Array([ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, + 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, + 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, + 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, + 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, + 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, + 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, + 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, + 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ]); + + function ROTR(n, x) { return (x >>> n) | (x << (32 - n)); } + + function Σ0(x) { return ROTR(2, x) ^ ROTR(13, x) ^ ROTR(22, x); } + function Σ1(x) { return ROTR(6, x) ^ ROTR(11, x) ^ ROTR(25, x); } + function σ0(x) { return ROTR(7, x) ^ ROTR(18, x) ^ (x >>> 3); } + function σ1(x) { return ROTR(17, x) ^ ROTR(19, x) ^ (x >>> 10); } + + function Ch(x, y, z) { return (x & y) ^ (~x & z); } + function Maj(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); } + + const H = new Uint32Array([ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + ]); + + const l = inputBytes.length; + const bitLen = l * 8; + + const withPadding = new Uint8Array(((l + 9 + 63) >> 6) << 6); // pad to multiple of 64 bytes + withPadding.set(inputBytes); + withPadding[l] = 0x80; + withPadding.set([ + 0, 0, 0, 0, + (bitLen >>> 24) & 0xff, + (bitLen >>> 16) & 0xff, + (bitLen >>> 8) & 0xff, + bitLen & 0xff + ], withPadding.length - 8); + + const w = new Uint32Array(64); + for (let i = 0; i < withPadding.length; i += 64) { + for (let j = 0; j < 16; j++) { + w[j] = ( + (withPadding[i + 4 * j] << 24) | + (withPadding[i + 4 * j + 1] << 16) | + (withPadding[i + 4 * j + 2] << 8) | + (withPadding[i + 4 * j + 3]) + ) >>> 0; + } + for (let j = 16; j < 64; j++) { + w[j] = (σ1(w[j - 2]) + w[j - 7] + σ0(w[j - 15]) + w[j - 16]) >>> 0; + } + + let [a, b, c, d, e, f, g, h] = H; + + for (let j = 0; j < 64; j++) { + const T1 = (h + Σ1(e) + Ch(e, f, g) + K[j] + w[j]) >>> 0; + const T2 = (Σ0(a) + Maj(a, b, c)) >>> 0; + h = g; + g = f; + f = e; + e = (d + T1) >>> 0; + d = c; + c = b; + b = a; + a = (T1 + T2) >>> 0; + } + + H[0] = (H[0] + a) >>> 0; + H[1] = (H[1] + b) >>> 0; + H[2] = (H[2] + c) >>> 0; + H[3] = (H[3] + d) >>> 0; + H[4] = (H[4] + e) >>> 0; + H[5] = (H[5] + f) >>> 0; + H[6] = (H[6] + g) >>> 0; + H[7] = (H[7] + h) >>> 0; + } + + const output = new Uint8Array(32); + for (let i = 0; i < 8; i++) { + output[i * 4 + 0] = (H[i] >>> 24) & 0xff; + output[i * 4 + 1] = (H[i] >>> 16) & 0xff; + output[i * 4 + 2] = (H[i] >>> 8) & 0xff; + output[i * 4 + 3] = H[i] & 0xff; + } - //mav.signing.timestamp is a 48bit number, or 6 bytes. + return output; +} + +// create a message signature +${MAVHEAD}.create_signature = function(key, msgbuf) { + const input = new Uint8Array(32 + msgbuf.length); + input.set(key, 0); + input.set(msgbuf, 32); - // due to js not being able to shift numbers more than 32, we'll use this instead.. - // js stores all its numbers as a 64bit float with 53 bits of mantissa, so have room for 48 ok. - // positive shifts left, negative shifts right - function shift(number, shift) { - return number * Math.pow(2, shift); - } + const hash = mavlink20.sha256(input); + const sig = hash.slice(0, 6); - var thigh = shift(mav.signing.timestamp,-32) // 2 bytes from the top, shifted right by 32 bits - var tlow = (mav.signing.timestamp & 0xfffffff ) // 4 bytes from the bottom + return sig; +} + +// sign outgoing packet +${MAVHEAD}.message.prototype.sign_packet = function( mav) { + function packUint48LE(value) { + const bytes = [] + for (let i = 0; i < 6; i++) { + bytes.push(Number((value >> BigInt(8 * i)) & 0xFFn)); + } + return bytes; + } + + var tsbuf = packUint48LE(BigInt(mav.signing.timestamp)); - // I means unsigned 4bytes, H means unsigned 2 bytes // first add the linkid(1 byte) and timestamp(6 bytes) that start the signature - this._msgbuf = this._msgbuf.concat(jspack.Pack('= 1 && this.buf[0] != this.protocol_marker ) { + if( this.buf.length >= 1 && + this.buf[0] != ${MAVHEAD}.PROTOCOL_MARKER_V2 && + this.buf[0] != ${MAVHEAD}.PROTOCOL_MARKER_V1) { - // Strip the offending initial byte and throw an error. + // Strip the offending initial bytes and throw an error. var badPrefix = this.buf[0]; - this.bufInError = this.buf.slice(0,1); - this.buf = this.buf.slice(1); + var idx1 = this.buf.indexOf(${MAVHEAD}.PROTOCOL_MARKER_V1); + var idx2 = this.buf.indexOf(${MAVHEAD}.PROTOCOL_MARKER_V2); + if (idx1 == -1) { + idx1 = idx2; + } + if (idx1 == -1 && idx2 == -1) { + this.bufInError = this.buf; + this.buf = new Uint8Array(); + } else { + this.bufInError = this.buf.slice(0,idx1); + this.buf = this.buf.slice(idx1); + } this.expected_length = ${MAVHEAD}.HEADER_LEN; //initially we 'expect' at least the length of the header, later parseLength corrects for this. throw new Error("Bad prefix ("+badPrefix+")"); } @@ -505,17 +692,16 @@ def generate_mavlink_class(outf, msgs, xml): } -// input some data bytes, possibly returning a new message - python equiv function is called parse_char / __parse_char_legacy +// input some data bytes, possibly returning a new message - python equiv function is called parse_char / __parse_char_legacy +// c can be null to process any remaining data in the input buffer from a previous call ${MAVPROCESSOR}.prototype.parseChar = function(c) { - if (c == null) { - return - } - var m = null; try { - this.pushBuffer(c); + if (c != null) { + this.pushBuffer(c); + } this.parsePrefix(); this.parseLength(); m = this.parsePayload(); @@ -529,7 +715,7 @@ def generate_mavlink_class(outf, msgs, xml): } // emit a packet-specific message as well as a generic message, user/s can choose to use either or both of these. - if(null != m) { + if (isNode && null != m) { this.emit(m._name, m); this.emit('message', m); } @@ -587,9 +773,10 @@ def generate_mavlink_class(outf, msgs, xml): // While more valid messages can be read from the existing buffer, add // them to the array of new messages and return them. - var ret = [m]; + var ret = []; + ret.push(m); while(true) { - m = this.parseChar(); + m = this.parseChar(null); if ( null === m ) { // No more messages left. return ret; @@ -601,100 +788,90 @@ def generate_mavlink_class(outf, msgs, xml): //check signature on incoming message , many of the comments in this file come from the python impl ${MAVPROCESSOR}.prototype.check_signature = function(msgbuf, srcSystem, srcComponent) { + var timestamp_buf = msgbuf.slice(-12,-6); + + var link_id; + if (isNode) { + var link_id_buf = Buffer.from ? Buffer.from(msgbuf.slice(-13,-12)) : new Buffer(msgbuf.slice(-13,-12)); + link_id = link_id_buf[0]; // get the first byte. + } else { + // Browser-compatible buffer handling + link_id = msgbuf.slice(-13,-12)[0]; + } - //timestamp_buf = msgbuf[-12:-6] - var timestamp_buf= msgbuf.slice(-12,-6); - - //link_id = msgbuf[-13] - var link_id = new Uint8Array.from(msgbuf.slice(-13,-12)); // just a single byte really, but returned as a buffer - link_id = link_id[0]; // get the first byte. - - //self.mav_sign_unpacker = jspack.Unpack('= 0; i--) { + value = (value << 8n) | BigInt(bytes[i]); } - var thigh_shifted = shift(thigh,32); - var timestamp = tlow + thigh_shifted + return value; + } + var timestamp = Number(unpackUint48LE(timestamp_buf)); + + // see if the timestamp is acceptable + + // we'll use a STRING containing these three things in it as a unique key eg: '0,1,1' + stream_key = new Array(link_id,srcSystem,srcComponent).toString(); + + if (stream_key in this.signing.stream_timestamps){ + if (timestamp <= this.signing.stream_timestamps[stream_key]){ + //# reject old timestamp + //console.log('old timestamp') + return false + } + }else{ + //# a new stream has appeared. Accept the timestamp if it is at most + //# one minute behind our current timestamp + if (timestamp + 6000*1000 < this.signing.timestamp){ + //console.log('bad new stream ', timestamp/(100.0*1000*60*60*24*365), this.signing.timestamp/(100.0*1000*60*60*24*365)) + return false + } + this.signing.stream_timestamps[stream_key] = timestamp; + //console.log('new stream',this.signing.stream_timestamps) + } - // see if the timestamp is acceptable + // just the last 6 of 13 available are the actual sig . ie excluding the linkid(1) and timestamp(6) + var sigpart = msgbuf.slice(-6); + sigpart = Uint8Array.from(sigpart); + // not sig part 0- end-minus-6 + var notsigpart = msgbuf.slice(0,-6); + notsigpart = Uint8Array.from(notsigpart); - // we'll use a STRING containing these three things in it as a unique key eg: '0,1,1' - stream_key = new Array(link_id,srcSystem,srcComponent).toString(); + var sig1 = mavlink20.create_signature(this.signing.secret_key, notsigpart); - if (stream_key in this.signing.stream_timestamps){ - if (timestamp <= this.signing.stream_timestamps[stream_key]){ - //# reject old timestamp - //console.log('old timestamp') - return false - } - }else{ - //# a new stream has appeared. Accept the timestamp if it is at most - //# one minute behind our current timestamp - if (timestamp + 6000*1000 < this.signing.timestamp){ - //console.log('bad new stream ', timestamp/(100.0*1000*60*60*24*365), this.signing.timestamp/(100.0*1000*60*60*24*365)) - return false - } - this.signing.stream_timestamps[stream_key] = timestamp; - //console.log('new stream',this.signing.stream_timestamps) - } - - // h = hashlib.new('sha256') - // h.update(this.signing.secret_key) - // h.update(msgbuf[:-6]) - var crypto= require('crypto'); - var h = crypto.createHash('sha256'); - - // just the last 6 of 13 available are the actual sig . ie excluding the linkid(1) and timestamp(6) - var sigpart = msgbuf.slice(-6); - sigpart = new Uint8Array.from(sigpart); - // not sig part 0- end-minus-6 - var notsigpart = msgbuf.slice(0,-6); - notsigpart = new Uint8Array.from(notsigpart); - - h.update(this.signing.secret_key); // secret is already a Buffer - //var tmp = h.copy().digest(); - h.update(notsigpart); - //var tmp2 = h.copy().digest() - var hashDigest = h.digest(); - sig1 = hashDigest.slice(0,6) - - //sig1 = str(h.digest())[:6] - //sig2 = str(msgbuf)[-6:] - - // can't just compare sigs, need a full buffer compare like this... - //if (sig1 != sigpart){ - if (Uint8Array.compare(sig1,sigpart)){ - //console.log('sig mismatch',sig1,sigpart) - return false - } - //# the timestamp we next send with is the max of the received timestamp and - //# our current timestamp - this.signing.timestamp = Math.max(this.signing.timestamp, timestamp) - return true + // Browser-compatible buffer comparison + var signaturesMatch; + if (isNode) { + signaturesMatch = Buffer.from(sig1).equals(Buffer.from(sigpart)); + } else { + // Compare arrays element by element in browser + signaturesMatch = sig1.length === sigpart.length && + sig1.every((val, index) => val === sigpart[index]); + } + if (!signaturesMatch) { + return false; + } + //# the timestamp we next send with is the max of the received timestamp and + //# our current timestamp + this.signing.timestamp = Math.max(this.signing.timestamp, timestamp+1); + return true } /* decode a buffer as a MAVLink message */ ${MAVPROCESSOR}.prototype.decode = function(msgbuf) { - var magic, incompat_flags, compat_flags, mlen, seq, srcSystem, srcComponent, unpacked, msgId, signature_len; + var magic, incompat_flags, compat_flags, mlen, seq, srcSystem, srcComponent, unpacked, msgId, signature_len, header_len; // decode the header try { """, {'MAVPROCESSOR': get_mavprocessor(xml), 'MAVHEAD': get_mavhead(xml), 'PROTOCOL_MARKER': xml.protocol_marker}) - # Mavlink2 only + # Mavlink2 or mavlink1 only if (xml.protocol_marker == 253): t.write(outf, """ -unpacked = jspack.Unpack('BBBBBBBHB', msgbuf.slice(0, 10)); // the H in here causes msgIDlow to takeup 2 bytes, the rest 1 +if (msgbuf[0] == 253) { + var unpacked = jspack.Unpack('BBBBBBBHB', msgbuf.slice(0, 10)); // the H in here causes msgIDlow to takeup 2 bytes, the rest 1 magic = unpacked[0]; mlen = unpacked[1]; incompat_flags = unpacked[2]; @@ -704,19 +881,33 @@ def generate_mavlink_class(outf, msgs, xml): srcComponent = unpacked[6]; var msgIDlow = ((unpacked[7] & 0xFF) << 8) | ((unpacked[7] >> 8) & 0xFF); // first-two msgid bytes var msgIDhigh = unpacked[8]; // the 3rd msgid byte - msgId = msgIDlow | (msgIDhigh<<16); // combined result. 0 - 16777215 24bit number - """, {'MAVHEAD': get_mavhead(xml)}) - # Mavlink1 + msgId = msgIDlow | (msgIDhigh<<16); // combined result. 0 - 16777215 24bit number + header_len = 10; +} else { + var unpacked = jspack.Unpack('BBBBBB', msgbuf.slice(0, 6)); + magic = unpacked[0]; + mlen = unpacked[1]; + seq = unpacked[2]; + srcSystem = unpacked[3]; + srcComponent = unpacked[4]; + msgID = unpacked[5]; + incompat_flags = 0; + compat_flags = 0; + header_len = 6; +} + """) + # Mavlink1 only else: t.write(outf, """ -unpacked = jspack.Unpack('cBBBBB', msgbuf.slice(0, 6)); +var unpacked = jspack.Unpack('BBBBBB', msgbuf.slice(0, 6)); magic = unpacked[0]; mlen = unpacked[1]; seq = unpacked[2]; srcSystem = unpacked[3]; srcComponent = unpacked[4]; msgId = unpacked[5]; - """, {'MAVHEAD': get_mavhead(xml)}) + header_len = 6; + """); t.write(outf, """ } @@ -724,32 +915,6 @@ def generate_mavlink_class(outf, msgs, xml): throw new Error('Unable to unpack MAVLink header: ' + e.message); } - // TODO allow full parsing of 1.0 inside the 2.0 parser, this is just a start - if (magic == ${MAVHEAD}.PROTOCOL_MARKER_V1){ - //headerlen = 6; - - // these two are in the same place in both v1 and v2 so no change needed: - //magic = magic; - //mlen = mlen; - - // grab mavlink-v1 header position info from v2 unpacked position - seq1 = incompat_flags; - srcSystem1 = compat_flags; - srcComponent1 = seq; - msgId1 = srcSystem; - // override the v1 vs v2 offsets so we get the correct data either way... - seq = seq1; - srcSystem = srcSystem1; - srcComponent = srcComponent1; - msgId = msgId1; - // don't exist in mavlink1, so zero-them - incompat_flags = 0; - compat_flags = 0; - signature_len = 0; - // todo add more v1 here and don't just return - return; - } - if (magic != this.protocol_marker) { throw new Error("Invalid MAVLink prefix ("+magic+")"); } @@ -762,8 +927,8 @@ def generate_mavlink_class(outf, msgs, xml): } // header's declared len compared to packets actual len - var actual_len = (msgbuf.length - (${MAVHEAD}.HEADER_LEN + 2 + signature_len)); - var actual_len_nosign = (msgbuf.length - (${MAVHEAD}.HEADER_LEN + 2 )); + var actual_len = (msgbuf.length - (header_len + 2 + signature_len)); + var actual_len_nosign = (msgbuf.length - (header_len + 2 )); if ((mlen == actual_len) && (signature_len > 0)){ var len_if_signed = mlen+signature_len; @@ -775,7 +940,7 @@ def generate_mavlink_class(outf, msgs, xml): throw new Error("Packet appears unsigned when labeled as signed. Got actual_len "+actual_len_nosign+" expected " + len_if_signed + ", msgId=" + msgId); } else if( mlen != actual_len) { - throw new Error("Invalid MAVLink message length. Got " + (msgbuf.length - (${MAVHEAD}.HEADER_LEN + 2)) + " expected " + mlen + ", msgId=" + msgId); + throw new Error("Invalid MAVLink message length. Got " + (msgbuf.length - (header_len + 2)) + " expected " + mlen + ", msgId=" + msgId); } @@ -784,10 +949,6 @@ def generate_mavlink_class(outf, msgs, xml): } // here's the common chunks of packet we want to work with below.. - //var headerBuf= msgbuf.slice(${MAVHEAD}.HEADER_LEN); // first10 - //var sigBuf = msgbuf.slice(-signature_len); // last 13 or nothing - var crcBuf1 = msgbuf.slice(-2); // either last-2 or last-2-prior-to-signature - var crcBuf2 = msgbuf.slice(-15,-13); // either last-2 or last-2-prior-to-signature var payloadBuf = msgbuf.slice(${MAVHEAD}.HEADER_LEN, -(signature_len+2)); // the remaining bit between the header and the crc var crcCheckBuf = msgbuf.slice(1, -(signature_len+2)); // the part uses to calculate the crc - ie between the magic and signature, @@ -799,12 +960,14 @@ def generate_mavlink_class(outf, msgs, xml): var receivedChecksum = undefined; if ( signature_len == 0 ) { // unsigned try { + var crcBuf1 = msgbuf.slice(-2); receivedChecksum = jspack.Unpack(' Date: Thu, 31 Jul 2025 09:26:30 +1000 Subject: [PATCH 4/4] mavutil: added wss client support --- mavutil.py | 86 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 19 deletions(-) diff --git a/mavutil.py b/mavutil.py index 3a194a44e..61ddb6773 100644 --- a/mavutil.py +++ b/mavutil.py @@ -13,6 +13,7 @@ import re import platform from pymavlink import mavexpression +import ssl # We want to re-export x25crc here from pymavlink.generator.mavcrc import x25crc as x25crc @@ -1853,8 +1854,9 @@ def write(self, buf): self.close_port() pass + class mavwebsocket_client(mavfile): - '''client using WebSocket over TCP''' + '''client using WebSocket over TCP with WS and WSS support''' def __init__(self, device, source_system=255, @@ -1863,15 +1865,17 @@ def __init__(self, use_native=default_native): self.resource = "/" a = device.split(':') - if len(a) < 2: - raise ValueError("TCP ports must be specified as host:port") - self.host = a[0] - self.port = int(a[1]) - if len(a) > 2: - self.resource = a[2] + protocol = a[0] + if len(a) < 3: + raise ValueError("WebSocket ports must be specified as protocol:host:port") + self.host = a[1] + self.port = int(a[2]) + if len(a) > 3: + self.resource = a[3] self.sock = None + self.use_ssl = protocol.lower() == 'wss' self.connect() - mavfile.__init__(self, self.sock.fileno(), "ws:" + device, source_system=source_system, source_component=source_component, use_native=use_native) + mavfile.__init__(self, self.sock.fileno(), protocol + device, source_system=source_system, source_component=source_component, use_native=use_native) def connect(self): self.close() @@ -1883,20 +1887,42 @@ def connect(self): BytesMessage, ) try: - self.sock = socket.create_connection((self.host, self.port)) + # Create basic socket connection + raw_sock = socket.create_connection((self.host, self.port)) + + # Wrap with SSL if using WSS + if self.use_ssl: + context = ssl.create_default_context() + # Optional: For testing with self-signed certificates, uncomment: + # context.check_hostname = False + # context.verify_mode = ssl.CERT_NONE + self.sock = context.wrap_socket(raw_sock, server_hostname=self.host) + else: + self.sock = raw_sock + except socket.error as e: - if e.errno in [ errno.ECONNREFUSED, errno.EHOSTUNREACH ]: + if e.errno in [errno.ECONNREFUSED, errno.EHOSTUNREACH]: return raise + except ssl.SSLError as e: + print(f"SSL Error: {e}") + raise + self.sock.setblocking(1) self.ws = WSConnection(ConnectionType.CLIENT) b = self.ws.send(Request(host=self.host, target=self.resource)) self.sock.send(b) self.buffer = b'' - + # wait for handshake response while True: - data = self.sock.recv(4096) + try: + data = self.sock.recv(4096) + except ssl.SSLError as e: + raise RuntimeError(f"WebSocket SSL handshake failed: {e}") + except socket.error as e: + raise RuntimeError(f"WebSocket handshake failed: {e}") + if not data: raise RuntimeError("WebSocket handshake failed") self.ws.receive_data(data) @@ -1905,7 +1931,7 @@ def connect(self): self.sock.setblocking(0) return - def recv(self,n=None): + def recv(self, n=None): from wsproto.events import ( BytesMessage, CloseConnection @@ -1918,10 +1944,22 @@ def recv(self,n=None): return out try: data = self.sock.recv(n) + except ssl.SSLError as e: + # Handle SSL-specific errors + if e.errno == ssl.SSL_ERROR_WANT_READ: + return b"" # Need more data, try again later + elif e.errno == ssl.SSL_ERROR_WANT_WRITE: + return b"" # SSL needs to write, try again later + elif e.errno in [errno.EAGAIN, errno.EWOULDBLOCK]: + return b"" + else: + # Real SSL error, reconnect + self.connect() + return b'' except socket.error as e: - if e.errno in [ errno.EAGAIN, errno.EWOULDBLOCK ]: + if e.errno in [errno.EAGAIN, errno.EWOULDBLOCK]: return b"" - if e.errno in [ errno.ECONNRESET, errno.EPIPE ]: + if e.errno in [errno.ECONNRESET, errno.EPIPE]: self.connect() return b'' raise @@ -1944,8 +1982,17 @@ def write(self, data): b = self.ws.send(BytesMessage(data=data)) try: self.sock.send(b) + except ssl.SSLError as e: + # Handle SSL-specific errors + if e.errno == ssl.SSL_ERROR_WANT_READ: + return # SSL needs to read, try again later + elif e.errno == ssl.SSL_ERROR_WANT_WRITE: + return # SSL needs to write, try again later + else: + # Real SSL error, reconnect + self.connect() except socket.error as e: - if e.errno in [ errno.EPIPE ]: + if e.errno in [errno.EPIPE]: self.connect() pass @@ -1953,7 +2000,8 @@ def close(self): if self.sock: self.sock.close() self.sock = None - + + def mavlink_connection(device, baud=115200, source_system=255, source_component=0, planner_format=None, write=False, append=False, robust_parsing=True, notimestamps=False, input=True, @@ -1987,8 +2035,8 @@ def mavlink_connection(device, baud=115200, source_system=255, source_component= return mavudp(device[9:], input=False, source_system=source_system, source_component=source_component, use_native=use_native, broadcast=True) if device.startswith('wsserver:'): return mavwebsocket(device[9:], source_system=source_system, source_component=source_component, use_native=use_native) - if device.startswith('ws:'): - return mavwebsocket_client(device[3:], source_system=source_system, source_component=source_component, use_native=use_native) + if device.startswith('ws:') or device.startswith('wss:'): + return mavwebsocket_client(device, source_system=source_system, source_component=source_component, use_native=use_native) # For legacy purposes we accept the following syntax and let the caller to specify direction if device.startswith('udp:'): return mavudp(device[4:], input=input, source_system=source_system, source_component=source_component, use_native=use_native)