Skip to content

Commit af95568

Browse files
authored
[De]serialize Timestamps via the remote Serializer (#1233)
1 parent 01ed7b2 commit af95568

File tree

4 files changed

+190
-0
lines changed

4 files changed

+190
-0
lines changed

Firestore/core/src/firebase/firestore/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ cc_library(
1818
SOURCES
1919
geo_point.cc
2020
timestamp.cc
21+
timestamp_internal.h
2122
DEPENDS
2223
firebase_firestore_util
2324
)

Firestore/core/src/firebase/firestore/remote/serializer.cc

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@
2525
#include <utility>
2626

2727
#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h"
28+
#include "Firestore/core/include/firebase/firestore/timestamp.h"
2829
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
30+
#include "Firestore/core/src/firebase/firestore/timestamp_internal.h"
2931
#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
3032

3133
namespace firebase {
3234
namespace firestore {
3335
namespace remote {
3436

37+
using firebase::Timestamp;
38+
using firebase::TimestampInternal;
3539
using firebase::firestore::model::DatabaseId;
3640
using firebase::firestore::model::DocumentKey;
3741
using firebase::firestore::model::FieldValue;
@@ -95,6 +99,15 @@ class Writer {
9599
*/
96100
void WriteTag(Tag tag);
97101

102+
/**
103+
* Writes a nanopb message to the output stream.
104+
*
105+
* This essentially wraps calls to nanopb's `pb_encode()` method. If we didn't
106+
* use `oneof`s in our protos, this would be the primary way of encoding
107+
* messages.
108+
*/
109+
void WriteNanopbMessage(const pb_field_t fields[], const void* src_struct);
110+
98111
void WriteSize(size_t size);
99112
void WriteNull();
100113
void WriteBool(bool bool_value);
@@ -179,6 +192,15 @@ class Reader {
179192
*/
180193
Tag ReadTag();
181194

195+
/**
196+
* Reads a nanopb message from the input stream.
197+
*
198+
* This essentially wraps calls to nanopb's pb_decode() method. If we didn't
199+
* use `oneof`s in our protos, this would be the primary way of decoding
200+
* messages.
201+
*/
202+
void ReadNanopbMessage(const pb_field_t fields[], void* dest_struct);
203+
182204
void ReadNull();
183205
bool ReadBool();
184206
int64_t ReadInteger();
@@ -299,6 +321,23 @@ Tag Reader::ReadTag() {
299321
return tag;
300322
}
301323

324+
void Writer::WriteNanopbMessage(const pb_field_t fields[],
325+
const void* src_struct) {
326+
if (!status_.ok()) return;
327+
328+
if (!pb_encode(&stream_, fields, src_struct)) {
329+
FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
330+
}
331+
}
332+
333+
void Reader::ReadNanopbMessage(const pb_field_t fields[], void* dest_struct) {
334+
if (!status_.ok()) return;
335+
336+
if (!pb_decode(&stream_, fields, dest_struct)) {
337+
status_ = Status(FirestoreErrorCode::DataLoss, PB_GET_ERROR(&stream_));
338+
}
339+
}
340+
302341
void Writer::WriteSize(size_t size) {
303342
return WriteVarint(size);
304343
}
@@ -415,6 +454,43 @@ std::string Reader::ReadString() {
415454
return result;
416455
}
417456

457+
void EncodeTimestamp(Writer* writer, const Timestamp& timestamp_value) {
458+
google_protobuf_Timestamp timestamp_proto =
459+
google_protobuf_Timestamp_init_zero;
460+
timestamp_proto.seconds = timestamp_value.seconds();
461+
timestamp_proto.nanos = timestamp_value.nanoseconds();
462+
writer->WriteNanopbMessage(google_protobuf_Timestamp_fields,
463+
&timestamp_proto);
464+
}
465+
466+
Timestamp DecodeTimestamp(Reader* reader) {
467+
google_protobuf_Timestamp timestamp_proto =
468+
google_protobuf_Timestamp_init_zero;
469+
reader->ReadNanopbMessage(google_protobuf_Timestamp_fields, &timestamp_proto);
470+
471+
// The Timestamp ctor will assert if we provide values outside the valid
472+
// range. However, since we're decoding, a single corrupt byte could cause
473+
// this to occur, so we'll verify the ranges before passing them in since we'd
474+
// rather not abort in these situations.
475+
if (timestamp_proto.seconds < TimestampInternal::Min().seconds()) {
476+
reader->set_status(Status(
477+
FirestoreErrorCode::DataLoss,
478+
"Invalid message: timestamp beyond the earliest supported date"));
479+
return {};
480+
} else if (TimestampInternal::Max().seconds() < timestamp_proto.seconds) {
481+
reader->set_status(
482+
Status(FirestoreErrorCode::DataLoss,
483+
"Invalid message: timestamp behond the latest supported date"));
484+
return {};
485+
} else if (timestamp_proto.nanos < 0 || timestamp_proto.nanos > 999999999) {
486+
reader->set_status(Status(
487+
FirestoreErrorCode::DataLoss,
488+
"Invalid message: timestamp nanos must be between 0 and 999999999"));
489+
return {};
490+
}
491+
return Timestamp{timestamp_proto.seconds, timestamp_proto.nanos};
492+
}
493+
418494
// Named '..Impl' so as to not conflict with Serializer::EncodeFieldValue.
419495
// TODO(rsgowman): Refactor to use a helper class that wraps the stream struct.
420496
// This will help with error handling, and should eliminate the issue of two
@@ -447,6 +523,14 @@ void EncodeFieldValueImpl(Writer* writer, const FieldValue& field_value) {
447523
writer->WriteString(field_value.string_value());
448524
break;
449525

526+
case FieldValue::Type::Timestamp:
527+
writer->WriteTag(
528+
{PB_WT_STRING, google_firestore_v1beta1_Value_timestamp_value_tag});
529+
writer->WriteNestedMessage([&field_value](Writer* writer) {
530+
EncodeTimestamp(writer, field_value.timestamp_value());
531+
});
532+
break;
533+
450534
case FieldValue::Type::Object:
451535
writer->WriteTag(
452536
{PB_WT_STRING, google_firestore_v1beta1_Value_map_value_tag});
@@ -477,6 +561,7 @@ FieldValue DecodeFieldValueImpl(Reader* reader) {
477561
break;
478562

479563
case google_firestore_v1beta1_Value_string_value_tag:
564+
case google_firestore_v1beta1_Value_timestamp_value_tag:
480565
case google_firestore_v1beta1_Value_map_value_tag:
481566
if (tag.wire_type != PB_WT_STRING) {
482567
reader->set_status(
@@ -517,6 +602,9 @@ FieldValue DecodeFieldValueImpl(Reader* reader) {
517602
return FieldValue::IntegerValue(reader->ReadInteger());
518603
case google_firestore_v1beta1_Value_string_value_tag:
519604
return FieldValue::StringValue(reader->ReadString());
605+
case google_firestore_v1beta1_Value_timestamp_value_tag:
606+
return FieldValue::TimestampValue(
607+
reader->ReadNestedMessage<Timestamp>(DecodeTimestamp));
520608
case google_firestore_v1beta1_Value_map_value_tag:
521609
return FieldValue::ObjectValueFromMap(DecodeObject(reader));
522610

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2018 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_TIMESTAMP_INTERNAL_H_
18+
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_TIMESTAMP_INTERNAL_H_
19+
20+
#include "Firestore/core/include/firebase/firestore/timestamp.h"
21+
22+
namespace firebase {
23+
24+
/**
25+
* Details about the Timestamp class which are useful internally, but we don't
26+
* want to expose publicly.
27+
*/
28+
class TimestampInternal {
29+
public:
30+
/**
31+
* Represents the maximum allowable time that the Timestamp class handles,
32+
* specifically 9999-12-31T23:59:59.999999999Z.
33+
*/
34+
static firebase::Timestamp Max() {
35+
return {253402300800L - 1, 999999999};
36+
}
37+
38+
/**
39+
* Represents the minimum allowable time that the Timestamp class handles,
40+
* specifically 0001-01-01T00:00:00Z.
41+
*/
42+
static firebase::Timestamp Min() {
43+
return {-62135596800L, 0};
44+
}
45+
};
46+
47+
} // namespace firebase
48+
49+
#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_TIMESTAMP_INTERNAL_H_

Firestore/core/test/firebase/firestore/remote/serializer_test.cc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,18 @@
3434

3535
#include "Firestore/Protos/cpp/google/firestore/v1beta1/document.pb.h"
3636
#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
37+
#include "Firestore/core/include/firebase/firestore/timestamp.h"
3738
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
39+
#include "Firestore/core/src/firebase/firestore/timestamp_internal.h"
3840
#include "Firestore/core/src/firebase/firestore/util/status.h"
3941
#include "Firestore/core/src/firebase/firestore/util/statusor.h"
4042
#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
4143
#include "google/protobuf/stubs/common.h"
4244
#include "google/protobuf/util/message_differencer.h"
4345
#include "gtest/gtest.h"
4446

47+
using firebase::Timestamp;
48+
using firebase::TimestampInternal;
4549
using firebase::firestore::FirestoreErrorCode;
4650
using firebase::firestore::model::DatabaseId;
4751
using firebase::firestore::model::FieldValue;
@@ -179,6 +183,15 @@ class SerializerTest : public ::testing::Test {
179183
return proto;
180184
}
181185

186+
google::firestore::v1beta1::Value ValueProto(const Timestamp& ts) {
187+
std::vector<uint8_t> bytes =
188+
EncodeFieldValue(&serializer, FieldValue::TimestampValue(ts));
189+
google::firestore::v1beta1::Value proto;
190+
bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
191+
EXPECT_TRUE(ok);
192+
return proto;
193+
}
194+
182195
private:
183196
void ExpectSerializationRoundTrip(
184197
const FieldValue& model,
@@ -259,6 +272,23 @@ TEST_F(SerializerTest, EncodesString) {
259272
}
260273
}
261274

275+
TEST_F(SerializerTest, EncodesTimestamps) {
276+
std::vector<Timestamp> cases{
277+
{}, // epoch
278+
{1234, 0},
279+
{1234, 999999999},
280+
{-1234, 0},
281+
{-1234, 999999999},
282+
TimestampInternal::Max(),
283+
TimestampInternal::Min(),
284+
};
285+
286+
for (const Timestamp& ts_value : cases) {
287+
FieldValue model = FieldValue::TimestampValue(ts_value);
288+
ExpectRoundTrip(model, ValueProto(ts_value), FieldValue::Type::Timestamp);
289+
}
290+
}
291+
262292
TEST_F(SerializerTest, EncodesEmptyMap) {
263293
FieldValue model = FieldValue::ObjectValueFromMap({});
264294

@@ -366,6 +396,28 @@ TEST_F(SerializerTest, BadStringValue) {
366396
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
367397
}
368398

399+
TEST_F(SerializerTest, BadTimestampValue_TooLarge) {
400+
std::vector<uint8_t> bytes = EncodeFieldValue(
401+
&serializer, FieldValue::TimestampValue(TimestampInternal::Max()));
402+
403+
// Add some time, which should push us above the maximum allowed timestamp.
404+
Mutate(&bytes[4], 0x82, 0x83);
405+
406+
ExpectFailedStatusDuringDecode(
407+
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
408+
}
409+
410+
TEST_F(SerializerTest, BadTimestampValue_TooSmall) {
411+
std::vector<uint8_t> bytes = EncodeFieldValue(
412+
&serializer, FieldValue::TimestampValue(TimestampInternal::Min()));
413+
414+
// Remove some time, which should push us below the minimum allowed timestamp.
415+
Mutate(&bytes[4], 0x92, 0x91);
416+
417+
ExpectFailedStatusDuringDecode(
418+
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
419+
}
420+
369421
TEST_F(SerializerTest, BadTag) {
370422
std::vector<uint8_t> bytes =
371423
EncodeFieldValue(&serializer, FieldValue::NullValue());

0 commit comments

Comments
 (0)