1 
2 /**
3  *    Copyright (C) 2018-present MongoDB, Inc.
4  *
5  *    This program is free software: you can redistribute it and/or modify
6  *    it under the terms of the Server Side Public License, version 1,
7  *    as published by MongoDB, Inc.
8  *
9  *    This program is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *    Server Side Public License for more details.
13  *
14  *    You should have received a copy of the Server Side Public License
15  *    along with this program. If not, see
16  *    <http://www.mongodb.com/licensing/server-side-public-license>.
17  *
18  *    As a special exception, the copyright holders give permission to link the
19  *    code of portions of this program with the OpenSSL library under certain
20  *    conditions as described in each individual source file and distribute
21  *    linked combinations including the program with the OpenSSL library. You
22  *    must comply with the Server Side Public License in all respects for
23  *    all of the code used other than as permitted herein. If you modify file(s)
24  *    with this exception, you may extend this exception to your version of the
25  *    file(s), but you are not obligated to do so. If you do not wish to do so,
26  *    delete this exception statement from your version. If you delete this
27  *    exception statement from all source files in the program, then also delete
28  *    it in the license file.
29  */
30 
31 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
32 
33 #include "mongo/db/pipeline/resume_token.h"
34 
35 #include <boost/optional/optional_io.hpp>
36 #include <random>
37 
38 #include "mongo/db/pipeline/document.h"
39 #include "mongo/db/pipeline/document_source_change_stream.h"
40 #include "mongo/unittest/unittest.h"
41 #include "mongo/util/log.h"
42 
43 namespace mongo {
44 
45 namespace {
46 
TEST(ResumeToken,EncodesFullTokenFromData)47 TEST(ResumeToken, EncodesFullTokenFromData) {
48     Timestamp ts(1000, 2);
49     UUID testUuid = UUID::gen();
50     Document documentKey{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 2}}}};
51 
52     ResumeTokenData resumeTokenDataIn(ts, Value(documentKey), testUuid);
53     ResumeToken token(resumeTokenDataIn);
54     ResumeTokenData tokenData = token.getData();
55     ASSERT_EQ(resumeTokenDataIn, tokenData);
56 }
57 
TEST(ResumeToken,EncodesTimestampOnlyTokenFromData)58 TEST(ResumeToken, EncodesTimestampOnlyTokenFromData) {
59     Timestamp ts(1001, 3);
60 
61     ResumeTokenData resumeTokenDataIn;
62     resumeTokenDataIn.clusterTime = ts;
63     ResumeToken token(resumeTokenDataIn);
64     ResumeTokenData tokenData = token.getData();
65     ASSERT_EQ(resumeTokenDataIn, tokenData);
66 }
67 
TEST(ResumeToken,RoundTripThroughBsonFullToken)68 TEST(ResumeToken, RoundTripThroughBsonFullToken) {
69     Timestamp ts(1000, 2);
70     UUID testUuid = UUID::gen();
71     Document documentKey{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 2}}}};
72 
73     ResumeTokenData resumeTokenDataIn(ts, Value(documentKey), testUuid);
74     auto rtToken = ResumeToken::parse(ResumeToken(resumeTokenDataIn).toBSON());
75     ResumeTokenData tokenData = rtToken.getData();
76     ASSERT_EQ(resumeTokenDataIn, tokenData);
77 }
78 
TEST(ResumeToken,RoundTripThroughBsonTimestampOnlyToken)79 TEST(ResumeToken, RoundTripThroughBsonTimestampOnlyToken) {
80     Timestamp ts(1001, 3);
81 
82     ResumeTokenData resumeTokenDataIn;
83     resumeTokenDataIn.clusterTime = ts;
84     auto rtToken = ResumeToken::parse(ResumeToken(resumeTokenDataIn).toBSON());
85     ResumeTokenData tokenData = rtToken.getData();
86     ASSERT_EQ(resumeTokenDataIn, tokenData);
87 }
88 
TEST(ResumeToken,RoundTripThroughDocumentFullToken)89 TEST(ResumeToken, RoundTripThroughDocumentFullToken) {
90     Timestamp ts(1000, 2);
91     UUID testUuid = UUID::gen();
92     Document documentKey{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 2}}}};
93 
94     ResumeTokenData resumeTokenDataIn(ts, Value(documentKey), testUuid);
95     auto rtToken = ResumeToken::parse(ResumeToken(resumeTokenDataIn).toDocument());
96     ResumeTokenData tokenData = rtToken.getData();
97     ASSERT_EQ(resumeTokenDataIn, tokenData);
98 }
99 
TEST(ResumeToken,RoundTripThroughDocumentTimestampOnlyToken)100 TEST(ResumeToken, RoundTripThroughDocumentTimestampOnlyToken) {
101     Timestamp ts(1001, 3);
102 
103     ResumeTokenData resumeTokenDataIn;
104     resumeTokenDataIn.clusterTime = ts;
105     auto rtToken = ResumeToken::parse(ResumeToken(resumeTokenDataIn).toDocument());
106     ResumeTokenData tokenData = rtToken.getData();
107     ASSERT_EQ(resumeTokenDataIn, tokenData);
108 }
109 
TEST(ResumeToken,TestMissingTypebitsOptimization)110 TEST(ResumeToken, TestMissingTypebitsOptimization) {
111     Timestamp ts(1000, 1);
112     UUID testUuid = UUID::gen();
113 
114     ResumeTokenData hasTypeBitsData(ts, Value(Document{{"_id", 1.0}}), testUuid);
115     ResumeTokenData noTypeBitsData(ResumeTokenData(ts, Value(Document{{"_id", 1}}), testUuid));
116     ResumeToken hasTypeBitsToken(hasTypeBitsData);
117     ResumeToken noTypeBitsToken(noTypeBitsData);
118     ASSERT_EQ(noTypeBitsToken, hasTypeBitsToken);
119     auto hasTypeBitsDoc = hasTypeBitsToken.toDocument();
120     auto noTypeBitsDoc = noTypeBitsToken.toDocument();
121     ASSERT_FALSE(hasTypeBitsDoc["_typeBits"].missing());
122     ASSERT_TRUE(noTypeBitsDoc["_typeBits"].missing()) << noTypeBitsDoc["_typeBits"];
123     auto rtHasTypeBitsData = ResumeToken::parse(hasTypeBitsDoc).getData();
124     auto rtNoTypeBitsData = ResumeToken::parse(noTypeBitsDoc).getData();
125     ASSERT_EQ(hasTypeBitsData, rtHasTypeBitsData);
126     ASSERT_EQ(noTypeBitsData, rtNoTypeBitsData);
127     ASSERT_EQ(BSONType::NumberDouble, rtHasTypeBitsData.documentKey["_id"].getType());
128     ASSERT_EQ(BSONType::NumberInt, rtNoTypeBitsData.documentKey["_id"].getType());
129 }
130 
131 // Tests comparison functions for tokens constructed from oplog data.
TEST(ResumeToken,CompareFromData)132 TEST(ResumeToken, CompareFromData) {
133     Timestamp ts1(1000, 1);
134     Timestamp ts2(1000, 2);
135     Timestamp ts3(1001, 1);
136     UUID testUuid = UUID::gen();
137     UUID testUuid2 = UUID::gen();
138     Document documentKey1a{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 2}}}};
139     Document documentKey1b{{"_id"_sd, "stuff"_sd},
140                            {"otherkey"_sd, Document{{"otherstuff"_sd, 2.0}}}};
141     Document documentKey2{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 3}}}};
142     Document documentKey3{{"_id"_sd, "ztuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 0}}}};
143 
144     ResumeToken token1a(ResumeTokenData(ts1, Value(documentKey1a), testUuid));
145     ResumeToken token1b(ResumeTokenData(ts1, Value(documentKey1b), testUuid));
146 
147     // Equivalent types don't matter.
148     ASSERT_EQ(token1a, token1b);
149     ASSERT_LTE(token1a, token1b);
150     ASSERT_GTE(token1a, token1b);
151 
152     // UUIDs matter, but all that really matters is they compare unequal.
153     ResumeToken tokenOtherCollection(ResumeTokenData(ts1, Value(documentKey1a), testUuid2));
154     ASSERT_NE(token1a, tokenOtherCollection);
155 
156     ResumeToken token2(ResumeTokenData(ts1, Value(documentKey2), testUuid));
157 
158     // Document keys matter.
159     ASSERT_LT(token1a, token2);
160     ASSERT_LTE(token1a, token2);
161     ASSERT_GT(token2, token1a);
162     ASSERT_GTE(token2, token1a);
163 
164     ResumeToken token3(ResumeTokenData(ts1, Value(documentKey3), testUuid));
165 
166     // Order within document keys matters.
167     ASSERT_LT(token1a, token3);
168     ASSERT_LTE(token1a, token3);
169     ASSERT_GT(token3, token1a);
170     ASSERT_GTE(token3, token1a);
171 
172     ASSERT_LT(token2, token3);
173     ASSERT_LTE(token2, token3);
174     ASSERT_GT(token3, token2);
175     ASSERT_GTE(token3, token2);
176 
177     ResumeToken token4(ResumeTokenData(ts2, Value(documentKey1a), testUuid));
178 
179     // Timestamps matter.
180     ASSERT_LT(token1a, token4);
181     ASSERT_LTE(token1a, token4);
182     ASSERT_GT(token4, token1a);
183     ASSERT_GTE(token4, token1a);
184 
185     // Timestamps matter more than document key.
186     ASSERT_LT(token3, token4);
187     ASSERT_LTE(token3, token4);
188     ASSERT_GT(token4, token3);
189     ASSERT_GTE(token4, token3);
190 
191     ResumeToken token5(ResumeTokenData(ts3, Value(documentKey1a), testUuid));
192 
193     // Time matters more than increment in timestamp
194     ASSERT_LT(token4, token5);
195     ASSERT_LTE(token4, token5);
196     ASSERT_GT(token5, token4);
197     ASSERT_GTE(token5, token4);
198 }
199 
200 // Tests comparison functions for tokens constructed from the keystring-encoded form.
TEST(ResumeToken,CompareFromEncodedData)201 TEST(ResumeToken, CompareFromEncodedData) {
202     Timestamp ts1(1000, 1);
203     Timestamp ts2(1000, 2);
204     Timestamp ts3(1001, 1);
205     UUID testUuid = UUID::gen();
206     UUID testUuid2 = UUID::gen();
207     Document documentKey1a{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 2}}}};
208     Document documentKey1b{{"_id"_sd, "stuff"_sd},
209                            {"otherkey"_sd, Document{{"otherstuff"_sd, 2.0}}}};
210     Document documentKey2{{"_id"_sd, "stuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 3}}}};
211     Document documentKey3{{"_id"_sd, "ztuff"_sd}, {"otherkey"_sd, Document{{"otherstuff"_sd, 0}}}};
212 
213     auto token1a = ResumeToken::parse(
214         ResumeToken(ResumeTokenData(ts1, Value(documentKey1a), testUuid)).toDocument());
215     auto token1b = ResumeToken::parse(
216         ResumeToken(ResumeTokenData(ts1, Value(documentKey1b), testUuid)).toDocument());
217 
218     // Equivalent types don't matter.
219     ASSERT_EQ(token1a, token1b);
220     ASSERT_LTE(token1a, token1b);
221     ASSERT_GTE(token1a, token1b);
222 
223     // UUIDs matter, but all that really matters is they compare unequal.
224     auto tokenOtherCollection = ResumeToken::parse(
225         ResumeToken(ResumeTokenData(ts1, Value(documentKey1a), testUuid2)).toDocument());
226     ASSERT_NE(token1a, tokenOtherCollection);
227 
228     auto token2 = ResumeToken::parse(
229         ResumeToken(ResumeTokenData(ts1, Value(documentKey2), testUuid)).toDocument());
230 
231     // Document keys matter.
232     ASSERT_LT(token1a, token2);
233     ASSERT_LTE(token1a, token2);
234     ASSERT_GT(token2, token1a);
235     ASSERT_GTE(token2, token1a);
236 
237     auto token3 = ResumeToken::parse(
238         ResumeToken(ResumeTokenData(ts1, Value(documentKey3), testUuid)).toDocument());
239 
240     // Order within document keys matters.
241     ASSERT_LT(token1a, token3);
242     ASSERT_LTE(token1a, token3);
243     ASSERT_GT(token3, token1a);
244     ASSERT_GTE(token3, token1a);
245 
246     ASSERT_LT(token2, token3);
247     ASSERT_LTE(token2, token3);
248     ASSERT_GT(token3, token2);
249     ASSERT_GTE(token3, token2);
250 
251     auto token4 = ResumeToken::parse(
252         ResumeToken(ResumeTokenData(ts2, Value(documentKey1a), testUuid)).toDocument());
253 
254     // Timestamps matter.
255     ASSERT_LT(token1a, token4);
256     ASSERT_LTE(token1a, token4);
257     ASSERT_GT(token4, token1a);
258     ASSERT_GTE(token4, token1a);
259 
260     // Timestamps matter more than document key.
261     ASSERT_LT(token3, token4);
262     ASSERT_LTE(token3, token4);
263     ASSERT_GT(token4, token3);
264     ASSERT_GTE(token4, token3);
265 
266     auto token5 = ResumeToken::parse(
267         ResumeToken(ResumeTokenData(ts3, Value(documentKey1a), testUuid)).toDocument());
268 
269     // Time matters more than increment in timestamp
270     ASSERT_LT(token4, token5);
271     ASSERT_LTE(token4, token5);
272     ASSERT_GT(token5, token4);
273     ASSERT_GTE(token5, token4);
274 }
275 
TEST(ResumeToken,FailsToParseForInvalidTokenFormats)276 TEST(ResumeToken, FailsToParseForInvalidTokenFormats) {
277     // Missing document.
278     ASSERT_THROWS(ResumeToken::parse(Document()), AssertionException);
279     // Missing data field.
280     ASSERT_THROWS(ResumeToken::parse(Document{{"somefield"_sd, "stuff"_sd}}), AssertionException);
281     // Wrong type data field
282     ASSERT_THROWS(ResumeToken::parse(Document{{"_data"_sd, "string"_sd}}), AssertionException);
283     ASSERT_THROWS(ResumeToken::parse(Document{{"_data"_sd, 0}}), AssertionException);
284 
285     // Valid data field, but wrong type typeBits.
286     Timestamp ts(1010, 4);
287     ResumeTokenData tokenData;
288     tokenData.clusterTime = ts;
289     auto goodTokenDoc = ResumeToken(tokenData).toDocument();
290     auto goodData = goodTokenDoc["_data"].getBinData();
291     ASSERT_THROWS(ResumeToken::parse(Document{{"_data"_sd, goodData}, {"_typeBits", "string"_sd}}),
292                   AssertionException);
293 
294     // Valid data except wrong bindata type.
295     ASSERT_THROWS(ResumeToken::parse(
296                       Document{{"_data"_sd, BSONBinData(goodData.data, goodData.length, newUUID)}}),
297                   AssertionException);
298     // Valid data, wrong typeBits bindata type.
299     ASSERT_THROWS(
300         ResumeToken::parse(Document{{"_data"_sd, goodData},
301                                     {"_typeBits", BSONBinData(goodData.data, 0, newUUID)}}),
302         AssertionException);
303 }
304 
TEST(ResumeToken,FailsToDecodeInvalidKeyStringBinData)305 TEST(ResumeToken, FailsToDecodeInvalidKeyStringBinData) {
306     Timestamp ts(1010, 4);
307     ResumeTokenData tokenData;
308     tokenData.clusterTime = ts;
309     auto goodTokenDocBinData = ResumeToken(tokenData).toDocument();
310     auto goodData = goodTokenDocBinData["_data"].getBinData();
311     const unsigned char zeroes[] = {0, 0, 0, 0, 0};
312     const unsigned char nonsense[] = {165, 85, 77, 86, 255};
313 
314     // Data of correct type, but empty.  This won't fail until we try to decode the data.
315     auto emptyToken =
316         ResumeToken::parse(Document{{"_data"_sd, BSONBinData(zeroes, 0, BinDataGeneral)}});
317     ASSERT_THROWS_CODE(emptyToken.getData(), AssertionException, 40649);
318 
319     // Data of correct type with a bunch of zeros.
320     auto zeroesToken = ResumeToken::parse(
321         Document{{"_data"_sd, BSONBinData(zeroes, sizeof(zeroes), BinDataGeneral)}});
322     ASSERT_THROWS_CODE(zeroesToken.getData(), AssertionException, 50811);
323 
324     // Data of correct type with a bunch of nonsense.
325     auto nonsenseToken = ResumeToken::parse(
326         Document{{"_data"_sd, BSONBinData(nonsense, sizeof(nonsense), BinDataGeneral)}});
327     ASSERT_THROWS_CODE(nonsenseToken.getData(), AssertionException, 50811);
328 
329     // Valid data, bad typeBits; note that an all-zeros typebits is valid so it is not tested here.
330     auto badTypeBitsToken = ResumeToken::parse(
331         Document{{"_data"_sd, goodData},
332                  {"_typeBits", BSONBinData(nonsense, sizeof(nonsense), BinDataGeneral)}});
333     ASSERT_THROWS_CODE(badTypeBitsToken.getData(), AssertionException, ErrorCodes::Overflow);
334 
335     const unsigned char invalidString[] = {
336         60,  // CType::kStringLike
337         55,  // Non-null terminated
338     };
339     auto invalidStringToken = ResumeToken::parse(
340         Document{{"_data"_sd, BSONBinData(invalidString, sizeof(invalidString), BinDataGeneral)}});
341     ASSERT_THROWS_WITH_CHECK(
342         invalidStringToken.getData(), AssertionException, [](const AssertionException& exception) {
343             ASSERT_EQ(exception.code(), 50816);
344             ASSERT_STRING_CONTAINS(exception.reason(), "Failed to find null terminator in string");
345         });
346 }
347 
348 }  // namspace
349 }  // namspace mongo
350