Skip to content

Commit e8ce539

Browse files
authored
CDRIVER-4598 Support optional checksum in OP_MSG (#1240)
* Add optional checksum field to OP_MSG definition * Account for optional checksum that may follow the data sections * Ensure _mongoc_rpc_get_first_document returns false on parse failure * Add regression test for optional OP_MSG checksum
1 parent 11e31e3 commit e8ce539

File tree

4 files changed

+165
-9
lines changed

4 files changed

+165
-9
lines changed

src/libmongoc/src/mongoc/mongoc-rpc-private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ typedef struct _mongoc_rpc_section_t {
7474
const uint8_t *_name; \
7575
int32_t _name##_len;
7676
#define BSON_OPTIONAL(_check, _code) _code
77+
#define CHECKSUM_FIELD(_name) uint32_t _name;
7778

7879

7980
#pragma pack(1)
@@ -129,6 +130,7 @@ BSON_STATIC_ASSERT2 (sizeof_reply_header,
129130
#undef SECTION_ARRAY_FIELD
130131
#undef BSON_OPTIONAL
131132
#undef RAW_BUFFER_FIELD
133+
#undef CHECKSUM_FIELD
132134

133135

134136
void

src/libmongoc/src/mongoc/mongoc-rpc.c

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
iov.iov_len = 4; \
4848
header->msg_len += (int32_t) iov.iov_len; \
4949
_mongoc_array_append_val (array, iov);
50+
#define CHECKSUM_FIELD(_name) // Do not include optional checksum.
5051
#define ENUM_FIELD INT32_FIELD
5152
#define INT64_FIELD(_name) \
5253
iov.iov_base = (void *) &rpc->_name; \
@@ -176,6 +177,7 @@
176177
#undef SECTION_ARRAY_FIELD
177178
#undef RAW_BUFFER_FIELD
178179
#undef BSON_OPTIONAL
180+
#undef CHECKSUM_FIELD
179181

180182

181183
#if BSON_BYTE_ORDER == BSON_BIG_ENDIAN
@@ -188,6 +190,7 @@
188190
}
189191
#define UINT8_FIELD(_name)
190192
#define INT32_FIELD(_name) rpc->_name = BSON_UINT32_FROM_LE (rpc->_name);
193+
#define CHECKSUM_FIELD(_name) rpc->_name = BSON_UINT32_FROM_LE (rpc->_name);
191194
#define ENUM_FIELD INT32_FIELD
192195
#define INT64_FIELD(_name) rpc->_name = BSON_UINT64_FROM_LE (rpc->_name);
193196
#define CSTRING_FIELD(_name)
@@ -262,6 +265,7 @@
262265
#undef SECTION_ARRAY_FIELD
263266
#undef BSON_OPTIONAL
264267
#undef RAW_BUFFER_FIELD
268+
#undef CHECKSUM_FIELD
265269

266270
#endif /* BSON_BYTE_ORDER == BSON_BIG_ENDIAN */
267271

@@ -273,7 +277,9 @@
273277
_code \
274278
}
275279
#define UINT8_FIELD(_name) printf (" " #_name " : %u\n", rpc->_name);
276-
#define INT32_FIELD(_name) printf (" " #_name " : %d\n", rpc->_name);
280+
#define INT32_FIELD(_name) printf (" " #_name " : %" PRId32 "\n", rpc->_name);
281+
#define CHECKSUM_FIELD(_name) \
282+
printf (" " #_name " : %" PRIu32 "\n", rpc->_name);
277283
#define ENUM_FIELD(_name) printf (" " #_name " : %u\n", rpc->_name);
278284
#define INT64_FIELD(_name) \
279285
printf (" " #_name " : %" PRIi64 "\n", (int64_t) rpc->_name);
@@ -408,6 +414,7 @@
408414
#undef SECTION_ARRAY_FIELD
409415
#undef BSON_OPTIONAL
410416
#undef RAW_BUFFER_FIELD
417+
#undef CHECKSUM_FIELD
411418

412419

413420
#define RPC(_name, _code) \
@@ -433,6 +440,12 @@
433440
memcpy (&rpc->_name, buf, 4); \
434441
buflen -= 4; \
435442
buf += 4;
443+
#define CHECKSUM_FIELD(_name) \
444+
if (buflen >= 4) { \
445+
memcpy (&rpc->_name, buf, 4); \
446+
buflen -= 4; \
447+
buf += 4; \
448+
}
436449
#define ENUM_FIELD INT32_FIELD
437450
#define INT64_FIELD(_name) \
438451
if (buflen < 8) { \
@@ -527,7 +540,7 @@
527540
buf += __l; \
528541
buflen -= __l; \
529542
rpc->n_##_name++; \
530-
} while (buflen);
543+
} while (buflen > 4); // Only optional checksum can come after data sections.
531544
#define RAW_BUFFER_FIELD(_name) \
532545
rpc->_name = (void *) buf; \
533546
rpc->_name##_len = (int32_t) buflen; \
@@ -561,6 +574,7 @@
561574
#undef SECTION_ARRAY_FIELD
562575
#undef BSON_OPTIONAL
563576
#undef RAW_BUFFER_FIELD
577+
#undef CHECKSUM_FIELD
564578

565579

566580
/*
@@ -1000,9 +1014,8 @@ _mongoc_rpc_get_first_document (mongoc_rpc_t *rpc, bson_t *reply)
10001014
return _mongoc_rpc_reply_get_first_msg (&rpc->msg, reply);
10011015
}
10021016

1003-
if (rpc->header.opcode == MONGOC_OPCODE_REPLY &&
1004-
_mongoc_rpc_reply_get_first (&rpc->reply, reply)) {
1005-
return true;
1017+
if (rpc->header.opcode == MONGOC_OPCODE_REPLY) {
1018+
return _mongoc_rpc_reply_get_first (&rpc->reply, reply);
10061019
}
10071020

10081021
return false;
@@ -1018,17 +1031,17 @@ _mongoc_rpc_reply_get_first_msg (mongoc_rpc_msg_t *reply_msg,
10181031

10191032
int32_t document_len;
10201033

1021-
BSON_ASSERT (0 == reply_msg->sections[0].payload_type);
1034+
if (BSON_UNLIKELY (reply_msg->sections[0].payload_type != 0)) {
1035+
return false;
1036+
}
10221037

10231038
/* As per the Wire Protocol documentation, each section has a 32 bit length
10241039
field: */
10251040
memcpy (&document_len, reply_msg->sections[0].payload.bson_document, 4);
10261041
document_len = BSON_UINT32_FROM_LE (document_len);
10271042

1028-
bson_init_static (
1043+
return bson_init_static (
10291044
bson_reply, reply_msg->sections[0].payload.bson_document, document_len);
1030-
1031-
return true;
10321045
}
10331046

10341047
bool

src/libmongoc/src/mongoc/op-msg.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ RPC(
66
INT32_FIELD(opcode)
77
ENUM_FIELD(flags)
88
SECTION_ARRAY_FIELD(sections)
9+
CHECKSUM_FIELD(checksum)
910
)

src/libmongoc/tests/test-mongoc-rpc.c

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#include <fcntl.h>
22
#include <mongoc/mongoc.h>
33
#include <mongoc/mongoc-array-private.h>
4+
#include <mongoc/mongoc-flags-private.h>
45
#include <mongoc/mongoc-rpc-private.h>
56
#include <stdio.h>
67
#include <stdlib.h>
78
#include <string.h>
89

910
#include "TestSuite.h"
11+
#include "test-conveniences.h"
1012
#include "mongoc/mongoc-cluster-private.h"
1113

1214

@@ -655,6 +657,143 @@ test_mongoc_rpc_buffer_iov (void)
655657
}
656658

657659

660+
static void
661+
test_mongoc_rpc_msg_checksum_gather (mongoc_rpc_t *rpc)
662+
{
663+
mongoc_array_t array;
664+
_mongoc_array_init (&array, sizeof (mongoc_iovec_t));
665+
666+
_mongoc_rpc_gather (rpc, &array);
667+
_mongoc_rpc_swab_to_le (rpc);
668+
669+
// OP_MSG gather should always ignore the optional checksum.
670+
ASSERT_CMPSIZE_T (array.len, ==, 7u);
671+
672+
const size_t expected_lens[] = {
673+
4u, // MsgHeader.messageLength
674+
4u, // MsgHeader.requestID
675+
4u, // MsgHeader.responseTo
676+
4u, // MsgHeader.opCode
677+
4u, // OP_MSG.flagBits
678+
1u, // OP_MSG.sections[0] Kind
679+
11u, // OP_MSG.sections[0] Payload
680+
};
681+
682+
for (size_t i = 0u; i < array.len; ++i) {
683+
const size_t expected = expected_lens[i];
684+
const size_t actual =
685+
_mongoc_array_index (&array, mongoc_iovec_t, i).iov_len;
686+
687+
ASSERT_WITH_MSG (expected == actual,
688+
"expected element %zu to have iov_len %zu, got %zu",
689+
i,
690+
expected,
691+
actual);
692+
}
693+
694+
_mongoc_array_destroy (&array);
695+
}
696+
697+
698+
static void
699+
test_mongoc_rpc_msg_checksum (void)
700+
{
701+
// OP_MSG scatter should be able to handle absence of checksum.
702+
{
703+
// clang-format off
704+
unsigned char input[] = {
705+
// OP_MSG.header
706+
0x20, 0x00, 0x00, 0x00, // MsgHeader.messageLength (0x00000020 = 32)
707+
0x01, 0x00, 0x00, 0x00, // MsgHeader.requestID (0x00000001 = 1)
708+
0x00, 0x00, 0x00, 0x00, // MsgHeader.responseTo (0x00000000 = 0)
709+
0xdd, 0x07, 0x00, 0x00, // MsgHeader.opCode (0x000007dd = 2013 (OP_MSG))
710+
711+
// OP_MSG.flagBits
712+
0x00, 0x00, 0x00, 0x00, // 0x00000000 = MONGOC_MSG_NONE (0)
713+
714+
// OP_MSG.sections
715+
0x00, // Kind 0
716+
0x0b, 0x00, 0x00, 0x00, // Section size (0x0000000b = 11)
717+
0x08, 0x68, 0x61, 0x73, 0x00, 0x00, // Boolean "has" (false)
718+
0x00, // End Byte (empty document)
719+
};
720+
// clang-format on
721+
722+
mongoc_rpc_t rpc = {._init = 0};
723+
ASSERT_WITH_MSG (_mongoc_rpc_scatter (&rpc, input, sizeof (input)),
724+
"failed to parse OP_MSG without checksum");
725+
_mongoc_rpc_swab_from_le (&rpc);
726+
727+
ASSERT_CMPSIZE_T ((size_t) rpc.msg.msg_len, ==, sizeof (input));
728+
ASSERT_CMPINT32 (rpc.msg.request_id, ==, 1);
729+
ASSERT_CMPINT32 (rpc.msg.response_to, ==, 0);
730+
ASSERT_CMPINT32 (rpc.msg.opcode, ==, MONGOC_OPCODE_MSG);
731+
ASSERT_CMPUINT32 (
732+
(uint32_t) rpc.msg.flags, ==, (uint32_t) MONGOC_MSG_NONE);
733+
ASSERT_CMPINT32 (rpc.msg.n_sections, ==, 1);
734+
ASSERT_CMPINT (rpc.msg.sections->payload_type, ==, 0);
735+
{
736+
bson_t doc;
737+
ASSERT_WITH_MSG (
738+
_mongoc_rpc_get_first_document (&rpc, &doc),
739+
"failed to parse document in OP_MSG without checksum");
740+
assert_match_bson (&doc, tmp_bson ("{'has': false}"), false);
741+
bson_destroy (&doc);
742+
}
743+
ASSERT_CMPUINT32 (rpc.msg.checksum, ==, 0u);
744+
745+
test_mongoc_rpc_msg_checksum_gather (&rpc);
746+
}
747+
748+
// OP_MSG scatter should be able to handle presence of checksum.
749+
{
750+
// clang-format off
751+
unsigned char input[] = {
752+
// OP_MSG.header
753+
0x24, 0x00, 0x00, 0x00, // MsgHeader.messageLength (0x00000024 = 36)
754+
0x01, 0x00, 0x00, 0x00, // MsgHeader.requestID (0x00000001 = 1)
755+
0x00, 0x00, 0x00, 0x00, // MsgHeader.responseTo (0x00000000 = 0)
756+
0xdd, 0x07, 0x00, 0x00, // MsgHeader.opCode (0x000007dd = 2013 (OP_MSG))
757+
758+
// OP_MSG.flagBits
759+
0x01, 0x00, 0x00, 0x00, // 0x00000001 = MONGOC_MSG_CHECKSUM_PRESENT (1)
760+
761+
// OP_MSG.sections
762+
0x00, // Kind 0
763+
0x0b, 0x00, 0x00, 0x00, // Section size (0x0000000b = 11)
764+
0x08, 0x68, 0x61, 0x73, 0x00, 0x01, // Boolean "has" (true)
765+
0x00, // End Byte (empty document)
766+
0x01, 0x02, 0x03, 0x04, // Checksum (0x04030201 = 67305985)
767+
};
768+
// clang-format on
769+
770+
mongoc_rpc_t rpc = {._init = 0};
771+
ASSERT_WITH_MSG (_mongoc_rpc_scatter (&rpc, input, sizeof (input)),
772+
"failed to parse OP_MSG with checksum");
773+
_mongoc_rpc_swab_from_le (&rpc);
774+
775+
ASSERT_CMPSIZE_T ((size_t) rpc.msg.msg_len, ==, sizeof (input));
776+
ASSERT_CMPINT32 (rpc.msg.request_id, ==, 1);
777+
ASSERT_CMPINT32 (rpc.msg.response_to, ==, 0);
778+
ASSERT_CMPINT32 (rpc.msg.opcode, ==, MONGOC_OPCODE_MSG);
779+
ASSERT_CMPUINT32 (
780+
(uint32_t) rpc.msg.flags, ==, (uint32_t) MONGOC_MSG_CHECKSUM_PRESENT);
781+
ASSERT_CMPINT32 (rpc.msg.n_sections, ==, 1);
782+
ASSERT_CMPINT (rpc.msg.sections->payload_type, ==, 0);
783+
{
784+
bson_t doc;
785+
ASSERT_WITH_MSG (_mongoc_rpc_get_first_document (&rpc, &doc),
786+
"failed to parse document in OP_MSG with checksum");
787+
assert_match_bson (&doc, tmp_bson ("{'has': true}"), false);
788+
bson_destroy (&doc);
789+
}
790+
ASSERT_CMPUINT32 (rpc.msg.checksum, ==, 67305985);
791+
792+
test_mongoc_rpc_msg_checksum_gather (&rpc);
793+
}
794+
}
795+
796+
658797
void
659798
test_rpc_install (TestSuite *suite)
660799
{
@@ -678,4 +817,5 @@ test_rpc_install (TestSuite *suite)
678817
TestSuite_Add (suite, "/Rpc/update/gather", test_mongoc_rpc_update_gather);
679818
TestSuite_Add (suite, "/Rpc/update/scatter", test_mongoc_rpc_update_scatter);
680819
TestSuite_Add (suite, "/Rpc/buffer/iov", test_mongoc_rpc_buffer_iov);
820+
TestSuite_Add (suite, "/Rpc/msg/checksum", test_mongoc_rpc_msg_checksum);
681821
}

0 commit comments

Comments
 (0)