From 8df1b88877150fbc22ebf02cb17cbd90fa60e675 Mon Sep 17 00:00:00 2001 From: Derek Xu Date: Mon, 17 Apr 2023 15:58:28 +0000 Subject: [PATCH] [VM/Service] Create JSONBase64String class TEST=CI Change-Id: Ia14fcb1788a2685bef4fd61babfdd7089dd85a06 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/287801 Reviewed-by: Ben Konyi Commit-Queue: Derek Xu --- runtime/vm/json_stream.cc | 34 ++++++++++++++++++++++ runtime/vm/json_stream.h | 33 ++++++++++++++++++++++ runtime/vm/json_test.cc | 11 ++++++++ runtime/vm/json_writer.cc | 59 ++++++++++++++++++++------------------- runtime/vm/json_writer.h | 9 ++++++ 5 files changed, 118 insertions(+), 28 deletions(-) diff --git a/runtime/vm/json_stream.cc b/runtime/vm/json_stream.cc index 86ed8491c1a..f42fef32504 100644 --- a/runtime/vm/json_stream.cc +++ b/runtime/vm/json_stream.cc @@ -595,6 +595,40 @@ void JSONArray::AddValueF(const char* format, ...) const { va_end(args); } +void JSONBase64String::AppendBytes(const uint8_t* bytes, intptr_t length) { + ASSERT(bytes != nullptr); + + if (num_queued_bytes_ > 0) { + while (length > 0) { + queued_bytes_[num_queued_bytes_++] = bytes[0]; + bytes++; + length--; + if (num_queued_bytes_ == 3) { + break; + } + } + if (num_queued_bytes_ < 3) { + return; + } + stream_->AppendBytesInBase64(queued_bytes_, 3); + num_queued_bytes_ = 0; + } + + intptr_t length_mod_3 = length % 3; + intptr_t largest_multiple_of_3_less_than_or_equal_to_length = + length - length_mod_3; + if (largest_multiple_of_3_less_than_or_equal_to_length > 0) { + stream_->AppendBytesInBase64( + bytes, largest_multiple_of_3_less_than_or_equal_to_length); + } + + for (intptr_t i = 0; i < length_mod_3; ++i) { + queued_bytes_[i] = + bytes[largest_multiple_of_3_less_than_or_equal_to_length + i]; + } + num_queued_bytes_ = length_mod_3; +} + #endif // !PRODUCT } // namespace dart diff --git a/runtime/vm/json_stream.h b/runtime/vm/json_stream.h index db1ffc6afdd..f8ea65323a5 100644 --- a/runtime/vm/json_stream.h +++ b/runtime/vm/json_stream.h @@ -217,6 +217,16 @@ class JSONStream : ValueObject { writer_.CloseArray(); } + // Append the Base64 encoding of |bytes| to the stream. + // + // Beware that padding characters are added when |length| is not a multiple of + // three. Padding is only valid at the end of Base64 strings, so you must be + // careful when trying to populate a single Base64 string with multiple calls + // to this method. |JSONBase64String| should be used for that use-case, + // because it handles padding management. + void AppendBytesInBase64(const uint8_t* bytes, intptr_t length) { + writer_.AppendBytesInBase64(bytes, length); + } void PrintValueNull() { writer_.PrintValueNull(); } void PrintValueBool(bool b) { writer_.PrintValueBool(b); } void PrintValue(intptr_t i) { writer_.PrintValue(i); } @@ -350,6 +360,7 @@ class JSONStream : ValueObject { intptr_t ignore_object_depth_; friend class JSONObject; friend class JSONArray; + friend class JSONBase64String; friend class TimelineEvent; }; @@ -519,6 +530,28 @@ class JSONArray : public ValueObject { DISALLOW_COPY_AND_ASSIGN(JSONArray); }; +class JSONBase64String : public ValueObject { + public: + explicit JSONBase64String(JSONStream* stream) + : stream_(stream), queued_bytes_(), num_queued_bytes_(0) { + stream_->AppendBytes(reinterpret_cast("\""), 1); + } + ~JSONBase64String() { + stream_->AppendBytesInBase64(queued_bytes_, num_queued_bytes_); + stream_->AppendBytes(reinterpret_cast("\""), 1); + } + + void AppendBytes(const uint8_t* bytes, intptr_t length); + + private: + JSONStream* stream_; + uint8_t queued_bytes_[3]; + intptr_t num_queued_bytes_; + + DISALLOW_ALLOCATION(); + DISALLOW_COPY_AND_ASSIGN(JSONBase64String); +}; + } // namespace dart #endif // RUNTIME_VM_JSON_STREAM_H_ diff --git a/runtime/vm/json_test.cc b/runtime/vm/json_test.cc index e8eb16838bc..e01bc701762 100644 --- a/runtime/vm/json_test.cc +++ b/runtime/vm/json_test.cc @@ -91,6 +91,17 @@ TEST_CASE(JSON_JSONStream_Array) { EXPECT_STREQ("[true,false]", js.ToCString()); } +TEST_CASE(JSON_JSONStream_Base64String) { + JSONStream js; + { + JSONBase64String jsonBase64String(&js); + jsonBase64String.AppendBytes(reinterpret_cast("Hello"), 5); + jsonBase64String.AppendBytes(reinterpret_cast(", "), 2); + jsonBase64String.AppendBytes(reinterpret_cast("world!"), 6); + } + EXPECT_STREQ("\"SGVsbG8sIHdvcmxkIQ==\"", js.ToCString()); +} + TEST_CASE(JSON_JSONStream_Object) { JSONStream js; { diff --git a/runtime/vm/json_writer.cc b/runtime/vm/json_writer.cc index 41af54d05f4..f5ca5f99a79 100644 --- a/runtime/vm/json_writer.cc +++ b/runtime/vm/json_writer.cc @@ -39,6 +39,36 @@ void JSONWriter::AppendBytes(const uint8_t* buffer, intptr_t buffer_length) { buffer_.AddRaw(buffer, buffer_length); } +static const char base64_digits[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char base64_pad = '='; + +void JSONWriter::AppendBytesInBase64(const uint8_t* bytes, intptr_t length) { + ASSERT(bytes != nullptr); + intptr_t odd_bits = length % 3; + intptr_t even_bits = length - odd_bits; + for (intptr_t i = 0; i < even_bits; i += 3) { + intptr_t triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; + buffer_.AddChar(base64_digits[triplet >> 18]); + buffer_.AddChar(base64_digits[(triplet >> 12) & 63]); + buffer_.AddChar(base64_digits[(triplet >> 6) & 63]); + buffer_.AddChar(base64_digits[triplet & 63]); + } + if (odd_bits == 1) { + intptr_t triplet = bytes[even_bits] << 16; + buffer_.AddChar(base64_digits[triplet >> 18]); + buffer_.AddChar(base64_digits[(triplet >> 12) & 63]); + buffer_.AddChar(base64_pad); + buffer_.AddChar(base64_pad); + } else if (odd_bits == 2) { + intptr_t triplet = (bytes[even_bits] << 16) | (bytes[even_bits + 1] << 8); + buffer_.AddChar(base64_digits[triplet >> 18]); + buffer_.AddChar(base64_digits[(triplet >> 12) & 63]); + buffer_.AddChar(base64_digits[(triplet >> 6) & 63]); + buffer_.AddChar(base64_pad); + } +} + void JSONWriter::AppendSerializedObject(const char* serialized_object) { PrintCommaIfNeeded(); buffer_.AddString(serialized_object); @@ -126,37 +156,10 @@ void JSONWriter::PrintValue(double d) { buffer_.Printf("%s", buffer); } -static const char base64_digits[65] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static const char base64_pad = '='; - void JSONWriter::PrintValueBase64(const uint8_t* bytes, intptr_t length) { PrintCommaIfNeeded(); buffer_.AddChar('"'); - - intptr_t odd_bits = length % 3; - intptr_t even_bits = length - odd_bits; - for (intptr_t i = 0; i < even_bits; i += 3) { - intptr_t triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; - buffer_.AddChar(base64_digits[triplet >> 18]); - buffer_.AddChar(base64_digits[(triplet >> 12) & 63]); - buffer_.AddChar(base64_digits[(triplet >> 6) & 63]); - buffer_.AddChar(base64_digits[triplet & 63]); - } - if (odd_bits == 1) { - intptr_t triplet = bytes[even_bits] << 16; - buffer_.AddChar(base64_digits[triplet >> 18]); - buffer_.AddChar(base64_digits[(triplet >> 12) & 63]); - buffer_.AddChar(base64_pad); - buffer_.AddChar(base64_pad); - } else if (odd_bits == 2) { - intptr_t triplet = (bytes[even_bits] << 16) | (bytes[even_bits + 1] << 8); - buffer_.AddChar(base64_digits[triplet >> 18]); - buffer_.AddChar(base64_digits[(triplet >> 12) & 63]); - buffer_.AddChar(base64_digits[(triplet >> 6) & 63]); - buffer_.AddChar(base64_pad); - } - + AppendBytesInBase64(bytes, length); buffer_.AddChar('"'); } diff --git a/runtime/vm/json_writer.h b/runtime/vm/json_writer.h index fdf9496e1e8..37cf017d610 100644 --- a/runtime/vm/json_writer.h +++ b/runtime/vm/json_writer.h @@ -26,6 +26,15 @@ class JSONWriter : ValueObject { // Append |buffer| to the stream. void AppendBytes(const uint8_t* buffer, intptr_t buffer_length); + // Append the Base64 encoding of |bytes| to the stream. + // + // Beware that padding characters are added when |length| is not a multiple of + // three. Padding is only valid at the end of Base64 strings, so you must be + // careful when trying to populate a single Base64 string with multiple calls + // to this method. |JSONBase64String| should be used for that use-case, + // because it handles padding management. + void AppendBytesInBase64(const uint8_t* bytes, intptr_t length); + // Append |serialized_object| to the stream. void AppendSerializedObject(const char* serialized_object);