This is an automated email from the ASF dual-hosted git repository.
hubcio pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new c18c56ba2 fix(node): deserialize void response now ignores payload
length for all void commands (#3182)
c18c56ba2 is described below
commit c18c56ba21a7489d511fc0fba237ea9bbc4a2922
Author: oscarArismendi <[email protected]>
AuthorDate: Wed May 6 05:18:17 2026 -0500
fix(node): deserialize void response now ignores payload length for all
void commands (#3182)
---
foreign/node/src/client/client.utils.test.ts | 71 ++++++++++++++++++++++
foreign/node/src/client/client.utils.ts | 5 +-
.../src/wire/message/send-messages.command.test.ts | 24 ++++++++
.../node/src/wire/message/send-messages.command.ts | 4 +-
4 files changed, 101 insertions(+), 3 deletions(-)
diff --git a/foreign/node/src/client/client.utils.test.ts
b/foreign/node/src/client/client.utils.test.ts
new file mode 100644
index 000000000..d0bfa12d0
--- /dev/null
+++ b/foreign/node/src/client/client.utils.test.ts
@@ -0,0 +1,71 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { describe, it } from 'node:test';
+import assert from 'node:assert/strict';
+import { handleResponse, deserializeVoidResponse, deserializeStatusResponse }
from './client.utils.js';
+
+const SUCCESS = 0;
+const ERROR = 1;
+
+describe('handleResponse', () => {
+
+ it('bounds data to the length field, not the full buffer', () => {
+ // Server says: status=0, length=0, no payload.
+ // But the raw buffer has 4 trailing bytes (e.g. start of next response).
+ const buf = Buffer.alloc(12);
+ buf.writeUInt32LE(SUCCESS, 0); // status
+ buf.writeUInt32LE(0, 4); // length = 0 (void response)
+ buf.writeUInt32LE(42, 8); // trailing bytes — NOT part of this
response
+
+ const r = handleResponse(buf);
+ assert.equal(r.data.length, 0);
+ });
+
+ it('deserializeVoidResponse returns true for a valid void response with
trailing buffer bytes', () => {
+ const buf = Buffer.alloc(12);
+ buf.writeUInt32LE(SUCCESS, 0);
+ buf.writeUInt32LE(0, 4); // length = 0
+ buf.writeUInt32LE(42, 8); // trailing bytes
+
+ const r = handleResponse(buf);
+ assert.equal(deserializeVoidResponse(r), true);
+ });
+
+});
+
+describe('deserializeStatusResponse', () => {
+
+ it('returns true when status is SUCCESS and data is empty', () => {
+ const r = { status: SUCCESS, length: 0, data: Buffer.alloc(0) };
+ assert.equal(deserializeStatusResponse(r), true);
+ });
+
+ it('returns true when status is SUCCESS and data is non-empty (e.g.
SendMessages server payload)', () => {
+ // Key difference from deserializeVoidResponse: non-empty data is accepted.
+ const r = { status: SUCCESS, length: 4, data: Buffer.from([1, 2, 3, 4]) };
+ assert.equal(deserializeStatusResponse(r), true);
+ });
+
+ it('returns false when status is an error code', () => {
+ const r = { status: ERROR, length: 0, data: Buffer.alloc(0) };
+ assert.equal(deserializeStatusResponse(r), false);
+ });
+
+});
diff --git a/foreign/node/src/client/client.utils.ts
b/foreign/node/src/client/client.utils.ts
index c4de16f36..5e9c08b8d 100644
--- a/foreign/node/src/client/client.utils.ts
+++ b/foreign/node/src/client/client.utils.ts
@@ -36,7 +36,7 @@ export const handleResponse = (r: Buffer) => {
const length = r.readUint32LE(4);
debug('<== handleResponse', { status, length });
return {
- status, length, data: r.subarray(8)
+ status, length, data: r.subarray(8, 8 + length)
}
};
@@ -68,6 +68,9 @@ export const handleResponseTransform = () => new Transform({
export const deserializeVoidResponse =
(r: CommandResponse) => r.status === 0 && r.data.length === 0;
+export const deserializeStatusResponse =
+ (r: CommandResponse) => r.status === 0;
+
/** Length of the command code in bytes */
const COMMAND_LENGTH = 4;
diff --git a/foreign/node/src/wire/message/send-messages.command.test.ts
b/foreign/node/src/wire/message/send-messages.command.test.ts
index 9caf2ce1e..2e5233439 100644
--- a/foreign/node/src/wire/message/send-messages.command.test.ts
+++ b/foreign/node/src/wire/message/send-messages.command.test.ts
@@ -23,6 +23,9 @@ import { uuidv7, uuidv4 } from "uuidv7";
import { SEND_MESSAGES, type SendMessages } from "./send-messages.command.js";
import { HeaderValue, HeaderKeyFactory } from "./header.utils.js";
+const SUCCESS = 0;
+const ERROR = 1;
+
describe("SendMessages", () => {
describe("serialize", () => {
const t1 = {
@@ -159,4 +162,25 @@ describe("SendMessages", () => {
assert.doesNotThrow(() => SEND_MESSAGES.serialize(t));
});
});
+
+ describe('deserialize', () => {
+
+ it('returns true when status is SUCCESS with empty data', () => {
+ const r = { status: SUCCESS, length: 0, data: Buffer.alloc(0) };
+ assert.equal(SEND_MESSAGES.deserialize(r), true);
+ });
+
+ it('returns true when status is SUCCESS with non-empty server payload', ()
=> {
+ // SendMessages server response includes data (e.g. partition/offset
info).
+ // The deserializer must accept non-empty data unlike
deserializeVoidResponse.
+ const r = { status: SUCCESS, length: 4, data: Buffer.from([1, 2, 3, 4])
};
+ assert.equal(SEND_MESSAGES.deserialize(r), true);
+ });
+
+ it('returns false when status is an error code', () => {
+ const r = { status: ERROR, length: 0, data: Buffer.alloc(0) };
+ assert.equal(SEND_MESSAGES.deserialize(r), false);
+ });
+
+ });
});
diff --git a/foreign/node/src/wire/message/send-messages.command.ts
b/foreign/node/src/wire/message/send-messages.command.ts
index 0d3bb7a48..fcf62b35f 100644
--- a/foreign/node/src/wire/message/send-messages.command.ts
+++ b/foreign/node/src/wire/message/send-messages.command.ts
@@ -21,7 +21,7 @@
import { type Id } from '../identifier.utils.js';
import { serializeSendMessages, type CreateMessage } from './message.utils.js';
import type { Partitioning } from './partitioning.utils.js';
-import { deserializeVoidResponse } from '../../client/client.utils.js';
+import {deserializeStatusResponse} from '../../client/client.utils.js';
import { wrapCommand } from '../command.utils.js';
import { COMMAND_CODE } from '../command.code.js';
@@ -50,7 +50,7 @@ export const SEND_MESSAGES = {
return serializeSendMessages(streamId, topicId, messages, partition);
},
- deserialize: deserializeVoidResponse
+ deserialize: deserializeStatusResponse
};
/**