Skip to content

Commit 0958a09

Browse files
oscarArismendiStanding-Man
authored andcommitted
fix(node): deserialize void response now ignores payload length for all void commands (#3182)
1 parent ea5ba95 commit 0958a09

4 files changed

Lines changed: 101 additions & 3 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { describe, it } from 'node:test';
21+
import assert from 'node:assert/strict';
22+
import { handleResponse, deserializeVoidResponse, deserializeStatusResponse } from './client.utils.js';
23+
24+
const SUCCESS = 0;
25+
const ERROR = 1;
26+
27+
describe('handleResponse', () => {
28+
29+
it('bounds data to the length field, not the full buffer', () => {
30+
// Server says: status=0, length=0, no payload.
31+
// But the raw buffer has 4 trailing bytes (e.g. start of next response).
32+
const buf = Buffer.alloc(12);
33+
buf.writeUInt32LE(SUCCESS, 0); // status
34+
buf.writeUInt32LE(0, 4); // length = 0 (void response)
35+
buf.writeUInt32LE(42, 8); // trailing bytes — NOT part of this response
36+
37+
const r = handleResponse(buf);
38+
assert.equal(r.data.length, 0);
39+
});
40+
41+
it('deserializeVoidResponse returns true for a valid void response with trailing buffer bytes', () => {
42+
const buf = Buffer.alloc(12);
43+
buf.writeUInt32LE(SUCCESS, 0);
44+
buf.writeUInt32LE(0, 4); // length = 0
45+
buf.writeUInt32LE(42, 8); // trailing bytes
46+
47+
const r = handleResponse(buf);
48+
assert.equal(deserializeVoidResponse(r), true);
49+
});
50+
51+
});
52+
53+
describe('deserializeStatusResponse', () => {
54+
55+
it('returns true when status is SUCCESS and data is empty', () => {
56+
const r = { status: SUCCESS, length: 0, data: Buffer.alloc(0) };
57+
assert.equal(deserializeStatusResponse(r), true);
58+
});
59+
60+
it('returns true when status is SUCCESS and data is non-empty (e.g. SendMessages server payload)', () => {
61+
// Key difference from deserializeVoidResponse: non-empty data is accepted.
62+
const r = { status: SUCCESS, length: 4, data: Buffer.from([1, 2, 3, 4]) };
63+
assert.equal(deserializeStatusResponse(r), true);
64+
});
65+
66+
it('returns false when status is an error code', () => {
67+
const r = { status: ERROR, length: 0, data: Buffer.alloc(0) };
68+
assert.equal(deserializeStatusResponse(r), false);
69+
});
70+
71+
});

foreign/node/src/client/client.utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const handleResponse = (r: Buffer) => {
3636
const length = r.readUint32LE(4);
3737
debug('<== handleResponse', { status, length });
3838
return {
39-
status, length, data: r.subarray(8)
39+
status, length, data: r.subarray(8, 8 + length)
4040
}
4141
};
4242

@@ -68,6 +68,9 @@ export const handleResponseTransform = () => new Transform({
6868
export const deserializeVoidResponse =
6969
(r: CommandResponse) => r.status === 0 && r.data.length === 0;
7070

71+
export const deserializeStatusResponse =
72+
(r: CommandResponse) => r.status === 0;
73+
7174
/** Length of the command code in bytes */
7275
const COMMAND_LENGTH = 4;
7376

foreign/node/src/wire/message/send-messages.command.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import { uuidv7, uuidv4 } from "uuidv7";
2323
import { SEND_MESSAGES, type SendMessages } from "./send-messages.command.js";
2424
import { HeaderValue, HeaderKeyFactory } from "./header.utils.js";
2525

26+
const SUCCESS = 0;
27+
const ERROR = 1;
28+
2629
describe("SendMessages", () => {
2730
describe("serialize", () => {
2831
const t1 = {
@@ -159,4 +162,25 @@ describe("SendMessages", () => {
159162
assert.doesNotThrow(() => SEND_MESSAGES.serialize(t));
160163
});
161164
});
165+
166+
describe('deserialize', () => {
167+
168+
it('returns true when status is SUCCESS with empty data', () => {
169+
const r = { status: SUCCESS, length: 0, data: Buffer.alloc(0) };
170+
assert.equal(SEND_MESSAGES.deserialize(r), true);
171+
});
172+
173+
it('returns true when status is SUCCESS with non-empty server payload', () => {
174+
// SendMessages server response includes data (e.g. partition/offset info).
175+
// The deserializer must accept non-empty data unlike deserializeVoidResponse.
176+
const r = { status: SUCCESS, length: 4, data: Buffer.from([1, 2, 3, 4]) };
177+
assert.equal(SEND_MESSAGES.deserialize(r), true);
178+
});
179+
180+
it('returns false when status is an error code', () => {
181+
const r = { status: ERROR, length: 0, data: Buffer.alloc(0) };
182+
assert.equal(SEND_MESSAGES.deserialize(r), false);
183+
});
184+
185+
});
162186
});

foreign/node/src/wire/message/send-messages.command.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import { type Id } from '../identifier.utils.js';
2222
import { serializeSendMessages, type CreateMessage } from './message.utils.js';
2323
import type { Partitioning } from './partitioning.utils.js';
24-
import { deserializeVoidResponse } from '../../client/client.utils.js';
24+
import {deserializeStatusResponse} from '../../client/client.utils.js';
2525
import { wrapCommand } from '../command.utils.js';
2626
import { COMMAND_CODE } from '../command.code.js';
2727

@@ -50,7 +50,7 @@ export const SEND_MESSAGES = {
5050
return serializeSendMessages(streamId, topicId, messages, partition);
5151
},
5252

53-
deserialize: deserializeVoidResponse
53+
deserialize: deserializeStatusResponse
5454
};
5555

5656
/**

0 commit comments

Comments
 (0)