Skip to content

Commit

Permalink
fix(NODE-3928): don't throw error in Response constructor (#3199)
Browse files Browse the repository at this point in the history
  • Loading branch information
durran committed Apr 20, 2022
1 parent f1c1ca0 commit 441fc63
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 22 deletions.
45 changes: 23 additions & 22 deletions src/cmap/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,15 +484,15 @@ export class Response {
responseTo: number;
opCode: number;
fromCompressed?: boolean;
responseFlags: number;
cursorId: Long;
startingFrom: number;
numberReturned: number;
documents: (Document | Buffer)[];
cursorNotFound: boolean;
queryFailure: boolean;
shardConfigStale: boolean;
awaitCapable: boolean;
responseFlags?: number;
cursorId?: Long;
startingFrom?: number;
numberReturned?: number;
documents: (Document | Buffer)[] = new Array(0);
cursorNotFound?: boolean;
queryFailure?: boolean;
shardConfigStale?: boolean;
awaitCapable?: boolean;
promoteLongs: boolean;
promoteValues: boolean;
promoteBuffers: boolean;
Expand Down Expand Up @@ -522,20 +522,7 @@ export class Response {
this.opCode = msgHeader.opCode;
this.fromCompressed = msgHeader.fromCompressed;

// Read the message body
this.responseFlags = msgBody.readInt32LE(0);
this.cursorId = new BSON.Long(msgBody.readInt32LE(4), msgBody.readInt32LE(8));
this.startingFrom = msgBody.readInt32LE(12);
this.numberReturned = msgBody.readInt32LE(16);

// Preallocate document array
this.documents = new Array(this.numberReturned);

// Flag values
this.cursorNotFound = (this.responseFlags & CURSOR_NOT_FOUND) !== 0;
this.queryFailure = (this.responseFlags & QUERY_FAILURE) !== 0;
this.shardConfigStale = (this.responseFlags & SHARD_CONFIG_STALE) !== 0;
this.awaitCapable = (this.responseFlags & AWAIT_CAPABLE) !== 0;
this.promoteLongs = typeof this.opts.promoteLongs === 'boolean' ? this.opts.promoteLongs : true;
this.promoteValues =
typeof this.opts.promoteValues === 'boolean' ? this.opts.promoteValues : true;
Expand Down Expand Up @@ -574,6 +561,20 @@ export class Response {
// (See https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#wire-op-reply)
this.index = 20;

// Read the message body
this.responseFlags = this.data.readInt32LE(0);
this.cursorId = new BSON.Long(this.data.readInt32LE(4), this.data.readInt32LE(8));
this.startingFrom = this.data.readInt32LE(12);
this.numberReturned = this.data.readInt32LE(16);

// Preallocate document array
this.documents = new Array(this.numberReturned);

this.cursorNotFound = (this.responseFlags & CURSOR_NOT_FOUND) !== 0;
this.queryFailure = (this.responseFlags & QUERY_FAILURE) !== 0;
this.shardConfigStale = (this.responseFlags & SHARD_CONFIG_STALE) !== 0;
this.awaitCapable = (this.responseFlags & AWAIT_CAPABLE) !== 0;

// Parse Body
for (let i = 0; i < this.numberReturned; i++) {
bsonSize =
Expand Down
111 changes: 111 additions & 0 deletions test/unit/cmap/commands.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const { expect } = require('chai');
const { Response } = require('../../../src/cmap/commands');

describe('commands', function () {
describe('Response', function () {
describe('#parse', function () {
context('when the message body is invalid', function () {
context('when the buffer is empty', function () {
const message = Buffer.from([]);
const header = {
length: 0,
requestId: 0,
responseTo: 0,
opCode: 0
};
const body = Buffer.from([]);

it('throws an exception', function () {
const response = new Response(message, header, body);
expect(() => response.parse()).to.throw(RangeError, /outside buffer bounds/);
});
});

context('when numReturned is invalid', function () {
const message = Buffer.from([]);
const header = {
length: 0,
requestId: 0,
responseTo: 0,
opCode: 0
};
const body = Buffer.alloc(5 * 4);
body.writeInt32LE(-1, 16);

it('throws an exception', function () {
const response = new Response(message, header, body);
expect(() => response.parse()).to.throw(RangeError, /Invalid array length/);
});
});
});
});

describe('#constructor', function () {
context('when the message body is invalid', function () {
const message = Buffer.from([]);
const header = {
length: 0,
requestId: 0,
responseTo: 0,
opCode: 0
};
const body = Buffer.from([]);

it('does not throw an exception', function () {
let error;
try {
new Response(message, header, body);
} catch (err) {
error = err;
}
expect(error).to.be.undefined;
});

it('initializes the documents to an empty array', function () {
const response = new Response(message, header, body);
expect(response.documents).to.be.empty;
});

it('does not set the responseFlags', function () {
const response = new Response(message, header, body);
expect(response.responseFlags).to.be.undefined;
});

it('does not set the cursorNotFound flag', function () {
const response = new Response(message, header, body);
expect(response.cursorNotFound).to.be.undefined;
});

it('does not set the cursorId', function () {
const response = new Response(message, header, body);
expect(response.cursorId).to.be.undefined;
});

it('does not set startingFrom', function () {
const response = new Response(message, header, body);
expect(response.startingFrom).to.be.undefined;
});

it('does not set numberReturned', function () {
const response = new Response(message, header, body);
expect(response.numberReturned).to.be.undefined;
});

it('does not set queryFailure', function () {
const response = new Response(message, header, body);
expect(response.queryFailure).to.be.undefined;
});

it('does not set shardConfigStale', function () {
const response = new Response(message, header, body);
expect(response.shardConfigStale).to.be.undefined;
});

it('does not set awaitCapable', function () {
const response = new Response(message, header, body);
expect(response.awaitCapable).to.be.undefined;
});
});
});
});
});

0 comments on commit 441fc63

Please sign in to comment.