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
 };
 
 /**

Reply via email to