Update: so AMQ can support binary data if its content-length field in the
headers is used.
If I take the stomp client's string value and convert it to a buffer using
a "binary" encoding (rather than the default utf-8/ucs-2), then I seem to
be able to create the proper GPB object.
sample code (replace utils with console.log):
gpbencodedecodetest.js:
let fs = require('fs');
const path = require('path');
var protobuf = require("protobufjs");
const utils = require('../utils/testutils'); << just use console.log for
these
let TEST_PROTO_FILE_NAME = 'Test.proto';
let protoDir = path.normalize(__dirname);
console.log(`Proto dir [${protoDir}] exists? ${fs.existsSync(protoDir)}`);
let TestRespGPBDef = null;
(async ()=> {
try {
TestRespGPBDef = await asyncLoadGBP(protoDir,
TEST_PROTO_FILE_NAME,'testt.TestResponse');
//build payload object with just 15 of the fields
let payload = {
myvaluea1: 'A',
myvalueb2: 'B',
myvaluec3: 'C',
myvalued4: 'D',
myvaluee5: 'E',
myvaluef6: 'F',
myvalueg7: 'G',
myvalueh8: 'H',
myvaluei9: 'I',
myvaluej10: 'J',
myvaluek11: 'K',
myvaluel12: 'L',
myvaluem13: 'M',
myvaluen14: 'N',
myvalueo15: 'O',
//myvaluep16: 'P',
};
testEncodeAndDecodeToFromGPB(payload);
console.log('++++++++++++++++++++++++++++++++++++++++++++++++++++++++');
// repeat, but with 16 fields set, this one fails
payload.myvaluep16 = 'P';;
testEncodeAndDecodeToFromGPB(payload);
} catch (exc) {
console.log(`ERROR`,exc);
}
})();
/**
* This function takes a payload object literal and converts it into
* a GPB object.
*
* It then encodes the GPB object into a buffer and then into a
* string (that string would normally then be
* sent over the AMQ stomp client).
*
* It then takes the string and tries to rebuild
* the GPB object by converting the string back into a buffer.
* The buffer is then decoded into a GPB object.
*
* @param {*} payload object to be converted to a GPB
*/
function testEncodeAndDecodeToFromGPB(payload) {
utils.printObj(`payload`,payload);
// verify that payload is legit
let verifyErr = TestRespGPBDef.verify(payload);
if( verifyErr ) {
console.log(`GPB OBJECT/PAYLOAD ERROR`, {verifyErr});
process.exit(-1);
}
// create GPB object from payload
let gpbObj1 = TestRespGPBDef.create(payload);
utils.printObj(`Gpb obj1`,gpbObj1);
// encode GPB into a byte buffer
let buff1 = TestRespGPBDef.encode(gpbObj1).finish();
utils.printObj(`\ngmsg encodded as buff (len ${buff1.length})`, buff1);
// we should be able to decode buffer back into a GPB objerct
let gpbObj2 = TestRespGPBDef.decode(buff1);
utils.printObj(`sanity check, Gpb obj2`, gpbObj2);
// we need to convert to a string before sending to an AMQ stream
// supposed to encode an array to utf8 string, but eh not completely
let gmsgAsStr = buff1.toString('binary');
//let gmsgAsStr = buff1.toString('utf8');
console.log(`\n>>>>>>>gmsg as string (len ${gmsgAsStr.length})
[${gmsgAsStr}]`);
utils.printObj(`gmsg as string (len ${gmsgAsStr.length})`,gmsgAsStr);
let teArray = new TextEncoder().encode(gmsgAsStr);
console.log(`gmsg str encoded by node (len ${teArray.length})`,teArray);
//let barray = utils.stringToBinArray(gmsgAsStr);
//console.log(`gmsg as supposed binary array (len ${barray.length})`,
barray);
// now lets take the string and try to reconstruct the GPB object
// from a string, first we need to create a buffer
let buff2 = Buffer.from(gmsgAsStr,'binary'); // ERROR RangeError: index out
of range: 47 + 4 > 49
//let buff2 = Buffer.from(gmsgAsStr); // ERROR Error: invalid wire type 7
at offset 49
utils.printObj(`\nbuff2 from string (len ${buff2.length})`, buff2);
// and from the buffer we should be able to re-create gpb obj
let gpbObj3 = TestRespGPBDef.decode(buff2);
utils.printObj(`\nsanity check 3, resulting GPB`,gpbObj3);
console.log('w00t!');
}
/**
* Load GPB proto file into a defintion object
* @param protoDir directory name of protofile
* @param protoFile name of protofile
* @param message definition name of proto you are interested in from within
the file
* @return Promise
* @resolves GPB object
* @rejects if file cannot be read/parsed/loaded
*/
async function asyncLoadGBP(protoDir, protoFile, protoMsgName) {
return new Promise( ((resolve,reject) => {
let retGpbTypeDef = null;
let gpbAbsFName = protoDir + '/' + protoFile;
protobuf.load(gpbAbsFName, function(err, root) {
if (err) {
let e = `ERROR LOADING GBP [${gpbAbsFName}] +
${utils.getObjAsString(err)}]`;
console.log(e);
reject(e);
}
console.log(`GPB [${gpbAbsFName}] loaded ok.`);
try {
retGpbTypeDef = root.lookupType(protoMsgName);
console.log(`GPB Message Type [${protoMsgName}] look up ok.`);
} catch( luExc) {
let e = `ERROR LOOKING UP GBP TYPE [${protoMsgName}]
[${utils.getObjAsString(luExc)}]`;
console.log(e);
reject(e);
}
resolve(retGpbTypeDef);
});
})); // end promise
}
Test.proto:
syntax = "proto2";
package testt;
message TestResponse
{
//optional Monkey header = 16;
optional string myvaluea1 = 1;
optional string myvalueb2 = 2;
optional string myvaluec3 = 3;
optional string myvalued4 = 4;
optional string myvaluee5 = 5;
optional string myvaluef6 = 6;
optional string myvalueg7 = 7;
optional string myvalueh8 = 8;
optional string myvaluei9 = 9;
optional string myvaluej10 = 10;
optional string myvaluek11 = 11;
optional string myvaluel12 = 12;
optional string myvaluem13 = 13;
optional string myvaluen14 = 14;
optional string myvalueo15 = 15;
optional string myvaluep16 = 16;
}
On Monday, January 11, 2021 at 6:51:32 AM UTC-7 peo stri tise safe wrote:
> Yes, I agree, thank you very much for your input. The incoming data is
> being treated as a string by the node AMQ stomp client library, and,
> attempting to convert that string to a binary array is what is not working,
> (thus the funky 65533 value). I either need to find a way for the AMQ
> client to accept in-coming data as binary or over-ride the charCodeAt to
> truly return a data byte. Eesh. Thanks again!
>
> On Monday, January 11, 2021 at 2:13:59 AM UTC-7 [email protected] wrote:
>
>> hex "82 01" is indeed the expected field header for field 16,
>> length-prefixed (i.e. wire type 2); further, the wider hex "12 02 68 69 1a
>> 05 74 68 65 72 65 82 01 0c 0a 0a 6d 6f 6e 6b 65 79 62 75 74 74" is
>> perfectly well-formed:
>>
>> [image: image.png]
>>
>> (via https://protogen.marcgravell.com/decode)
>>
>> So: if it isn't working, there are two likely scenarios:
>>
>> 1. the serializer has a bug
>> 2. the data is being corrupted during processing, perhaps (usually) by
>> treating it as text data rather than binary (meaning: the buffer you're
>> passing to the serializer doesn't actually contain hex "12 02 68 69 1a 05
>> 74 68 65 72 65 82 01 0c 0a 0a 6d 6f 6e 6b 65 79 62 75 74 74"
>>
>> In my experience, option 2 is *vastly* more common than option 1
>>
>> Marc
>>
>> On Fri, 8 Jan 2021 at 15:06, peo stri tise safe <[email protected]>
>> wrote:
>>
>>> More info-- when the "header" is set with an index of >= 16, the
>>> encoding process appears to insert an extra byte in the encoded value, and
>>> a decode attempt will fail, here is an example where an index of 15 can
>>> allow for an encode and subsequent decode, but an index of 16 fails at the
>>> subsequent decode:
>>>
>>> using a header with an index = 15
>>> obj to publish (typeof [object]): [TestResponse {
>>> header: { timestamp: 'monkeybutt' },
>>> mykey: 'hi',
>>> myvalue: 'there'
>>> }]
>>> encodded as buff (typeof [object]): [<Buffer 12 02 68 69 1a 05 74 68 65
>>> 72 65 7a 0c 0a 0a 6d 6f 6e 6b 65 79 62 75 74 74>]
>>> encoded as str (typeof [string]):
>>> ['\u0012\u0002hi\u001a\u0005therez\f\n\nmonkeybutt']
>>> sanBinArray (typeof [object]): [[
>>> 18, 2, 104,
>>> 105, 26, 5,
>>> 116, 104, 101,
>>> 114, 101, 122,
>>> 12, 10, 10,
>>> 109, 111, 110,
>>> 107, 101, 121,
>>> 98, 117, 116,
>>> 116, [length]: 25
>>> ]]
>>> Sanity check, decoding encoded msg: (typeof [object]): [TestResponse {
>>> mykey: 'hi',
>>> myvalue: 'there',
>>> header: Monkey { timestamp: 'monkeybutt' }
>>> }]
>>> pinging
>>> CONSUMER << RXD [25] elements (typeof [string]):
>>> ['\u0012\u0002hi\u001a\u0005therez\f\n\nmonkeybutt']
>>> As binArray (typeof [object]): [[
>>> 18, 2, 104,
>>> 105, 26, 5,
>>> 116, 104, 101,
>>> 114, 101, 122,
>>> 12, 10, 10,
>>> 109, 111, 110,
>>> 107, 101, 121,
>>> 98, 117, 116,
>>> 116, [length]: 25
>>> ]]
>>> (typeof [object]): [TestResponse {
>>> mykey: 'hi',
>>> myvalue: 'there',
>>> header: Monkey { timestamp: 'monkeybutt' }
>>> }]
>>>
>>>
>>> using a header with index = 16
>>> obj to publish (typeof [object]): [TestResponse {
>>> header: { timestamp: 'monkeybutt' },
>>> mykey: 'hi',
>>> myvalue: 'there'
>>> }]
>>> encodded as buff (typeof [object]): [<Buffer 12 02 68 69 1a 05 74 68 65
>>> 72 65 82 01 0c 0a 0a 6d 6f 6e 6b 65 79 62 75 74 74>]
>>>
>>> == == <-- two bytes
>>>
>>> encoded as str (typeof [string]):
>>> ['\u0012\u0002hi\u001a\u0005there�\u0001\f\n\nmonkeybutt']
>>> sanBinArray (typeof [object]): [[
>>> 18, 2, 104,
>>> 105, 26, 5,
>>> 116, 104, 101,
>>> 114, 101, 65533, <---- suspicious!
>>> 1, 12, 10,
>>> 10, 109, 111,
>>> 110, 107, 101,
>>> 121, 98, 117,
>>> 116, 116, [length]: 26
>>> ]]
>>> Sanity check error: Could not decode msg {
>>> decodeExc: Error: invalid wire type 7 at offset 18
>>> at Reader.skipType (node_modules/protobufjs/src/reader.js:377:19)
>>> at Type.TestResponse$decode [as decode] (eval at Codegen
>>> (node_modules/@protobufjs/codegen/index.js:50:33), <anonymous>:20:5)
>>> at buildEncodedStr (test/utils/amqprototest.js:94:34)
>>> at Timeout._onTimeout (test/utils/amqprototest.js:57:17)
>>> at listOnTimeout (internal/timers.js:531:17)
>>> at processTimers (internal/timers.js:475:7)
>>> }
>>>
>>> On Friday, January 8, 2021 at 6:25:41 AM UTC-7 peo stri tise safe wrote:
>>>
>>>> Gotcha, so the index value of 100 is not a non-standard value.
>>>>
>>>> So I'm seeing "invalid wire type" when using the decode method of
>>>> protobuf.js of an AMQ body. This is in a Node using the protobuf.js,
>>>> stomp-client.js and an AMQ broker.
>>>>
>>>> The GPB that I'm using has a basic format of this:
>>>>
>>>> syntax = "proto2";
>>>> package testt;
>>>> message Monkey
>>>> {
>>>> optional string timestamp = 1;
>>>> }
>>>> message TestResponse
>>>> {
>>>> optional Monkey header = 100;
>>>> optional string mykey = 2;
>>>> optional string myvalue = 3;
>>>> }
>>>>
>>>> And the message is encoded and sent to the broker like this:
>>>>
>>>> // note TestRespGPBDef is created earlier
>>>> let payload = {
>>>> header: {
>>>> timestamp: 'monkeybutt'
>>>> },
>>>> mykey: 'hi',
>>>> myvalue: 'there'
>>>> };
>>>> let verifyErr = TestRespGPBDef.verify(payload);
>>>> if( verifyErr ) {
>>>> console.log(`GPB OBJECT/PAYLOAD ERROR`, {verifyErr});
>>>> return;
>>>> }
>>>> let gmsg = TestRespGPBDef.create(payload);
>>>> let buff = TestRespGPBDef.encode(gmsg).finish();
>>>> let str = buff.toString();
>>>> stompClient.publish('mytopic',str);
>>>>
>>>>
>>>> And the message is rd'x and decoded by the consumer like this:
>>>>
>>>> // note TestRespGPBDef is created earlier
>>>> client.subscribe('mytopic', function(body, headers) {
>>>> utils.printObj(`CONSUMER << RXD AMQ header and body`);
>>>> utils.printObj(`AMQ body\n [${body.length}] elements`, body);
>>>> let binArray = utils.stringToBinArray(body);
>>>> utils.printObj(`As binArray`,binArray);
>>>> try {
>>>> let gpbMsg = TestRespGPBDef.decode(binArray);
>>>> utils.printObj('',gpbMsg);
>>>> } catch (decodeExc) {
>>>> console.log(`Could not decode msg`,{decodeExc});
>>>> }
>>>> });
>>>>
>>>>
>>>> When the consumer receives the body from AMQ and tries to decode it,
>>>> this is the exception:
>>>>
>>>> CONSUMER << RXD AMQ header and body
>>>> AMQ body:
>>>> [26] elements (typeof [string]):
>>>> ['\u0012\u0002hi\u001a\u0005there�\u0006\f\n\nmonkeybutt']
>>>> As binArray (typeof [object]): [[
>>>> 18, 2, 104,
>>>> 105, 26, 5,
>>>> 116, 104, 101,
>>>> 114, 101, 65533,
>>>> 6, 12, 10,
>>>> 10, 109, 111,
>>>> 110, 107, 101,
>>>> 121, 98, 117,
>>>> 116, 116, [length]: 26
>>>> ]]
>>>> Could not decode msg {
>>>> decodeExc: Error: invalid wire type 7 at offset 18
>>>> at Reader.skipType
>>>> (/mytest/node_modules/protobufjs/src/reader.js:377:19)
>>>> at Type.TestResponse$decode [as decode] (eval at Codegen
>>>> (/mytest/node_modules/@protobufjs/codegen/index.js:50:33),
>>>> <anonymous>:20:5)
>>>> at
>>>> /Users/rcox/gitprojects/POSTAPOCALYPSE/speb-hmi/deviceModules/keplerAdapter/test/utils/amqprototest.js:37:39
>>>> at
>>>> /Users/rcox/gitprojects/POSTAPOCALYPSE/speb-hmi/deviceModules/keplerAdapter/node_modules/stomp-client/lib/client.js:206:9
>>>> at Array.map (<anonymous>)
>>>> at StompFrameEmitter.<anonymous>
>>>> (/mytest/node_modules/stomp-client/lib/client.js:205:28)
>>>> at StompFrameEmitter.emit (events.js:210:5)
>>>> at StompFrameEmitter.parseBody
>>>> (/mytest/node_modules/stomp-client/lib/parser.js:136:12)
>>>> at StompFrameEmitter.handleData
>>>> (/mytest/node_modules/stomp-client/lib/parser.js:42:12)
>>>> at Socket.<anonymous>
>>>> (/mytest/node_modules/stomp-client/lib/client.js:188:18)
>>>> }
>>>>
>>>> When the "header" index is set to 1, and the experiment is repeated,
>>>> the decoding does not throw the exception.
>>>> On Friday, January 8, 2021 at 12:52:16 AM UTC-7 [email protected]
>>>> wrote:
>>>>
>>>>> 100 isn't "non-standard" as such, and shouldn't cause anything to
>>>>> fail. What exactly are you seeing?
>>>>>
>>>>> The valid range is 1-536870911, omitting 19000-19999 (and any reserved
>>>>> areas in your specific messages) smaller numbers are cheaper (fewer
>>>>> bytes)
>>>>> to encode, so are usually preferred - but: that's it.
>>>>>
>>>>> I'm guessing you're actually using some non-compliant code that is
>>>>> assuming field-headers are single bytes? That is true for very low field
>>>>> numbers (4 bytes, so: 1-15), but field 100 will take two bytes for the
>>>>> header. Is that the problem here?
>>>>>
>>>>> On Thu, 7 Jan 2021, 22:00 peo stri tise safe, <[email protected]>
>>>>> wrote:
>>>>>
>>>>>> What is the purpose of setting starting index values for a GPB
>>>>>> message definition to something other than 1? I am seeing a value of
>>>>>> 100
>>>>>> in a particular application and it is causing the encoding of the
>>>>>> message
>>>>>> to fail. Just wondered why the use of values other than 1 for the first
>>>>>> item in the message.
>>>>>>
>>>>>> --
>>>>>> You received this message because you are subscribed to the Google
>>>>>> Groups "Protocol Buffers" group.
>>>>>> To unsubscribe from this group and stop receiving emails from it,
>>>>>> send an email to [email protected].
>>>>>> To view this discussion on the web visit
>>>>>> https://groups.google.com/d/msgid/protobuf/957aae3b-32f8-4d9d-8f5f-732e26e08cedn%40googlegroups.com
>>>>>>
>>>>>> <https://groups.google.com/d/msgid/protobuf/957aae3b-32f8-4d9d-8f5f-732e26e08cedn%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>>>> .
>>>>>>
>>>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "Protocol Buffers" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to [email protected].
>>>
>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/protobuf/2a726cea-c829-4527-9878-232f746b32b8n%40googlegroups.com
>>>
>>> <https://groups.google.com/d/msgid/protobuf/2a726cea-c829-4527-9878-232f746b32b8n%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>>
>>
>> --
>> Regards,
>>
>> Marc
>>
>
--
You received this message because you are subscribed to the Google Groups
"Protocol Buffers" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/protobuf/dfd8e4cd-cc7a-4952-83b4-f2bd59e860f5n%40googlegroups.com.