@@ -215,6 +215,7 @@ java_EXTRA_DIST= \
java/src/main/java/com/google/protobuf/Message.java \
java/src/main/java/com/google/protobuf/MessageLite.java \
java/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java \
+ java/src/main/java/com/google/protobuf/MessageLiteToString.java \
java/src/main/java/com/google/protobuf/MessageOrBuilder.java \
java/src/main/java/com/google/protobuf/MessageReflection.java \
java/src/main/java/com/google/protobuf/MutabilityOracle.java \
@@ -233,6 +234,7 @@ java_EXTRA_DIST= \
java/src/main/java/com/google/protobuf/SingleFieldBuilder.java \
java/src/main/java/com/google/protobuf/SmallSortedMap.java \
java/src/main/java/com/google/protobuf/TextFormat.java \
+ java/src/main/java/com/google/protobuf/TextFormatEscaper.java \
java/src/main/java/com/google/protobuf/UninitializedMessageException.java \
java/src/main/java/com/google/protobuf/UnknownFieldSet.java \
java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java \
@@ -504,6 +506,7 @@ objectivec_EXTRA_DIST= \
python_EXTRA_DIST= \
python/MANIFEST.in \
+ python/google/protobuf/internal/any_test.proto \
python/google/protobuf/internal/api_implementation.cc \
python/google/protobuf/internal/api_implementation.py \
python/google/protobuf/internal/containers.py \
@@ -540,6 +543,8 @@ python_EXTRA_DIST= \
python/google/protobuf/internal/text_format_test.py \
python/google/protobuf/internal/type_checkers.py \
python/google/protobuf/internal/unknown_fields_test.py \
+ python/google/protobuf/internal/well_known_types.py \
+ python/google/protobuf/internal/well_known_types_test.py \
python/google/protobuf/internal/wire_format.py \
python/google/protobuf/internal/wire_format_test.py \
python/google/protobuf/internal/__init__.py \
@@ -558,8 +563,6 @@ python_EXTRA_DIST= \
python/google/protobuf/pyext/extension_dict.cc \
python/google/protobuf/pyext/message.h \
python/google/protobuf/pyext/message.cc \
- python/google/protobuf/pyext/message_map_container.cc \
- python/google/protobuf/pyext/message_map_container.h \
python/google/protobuf/pyext/proto2_api_test.proto \
python/google/protobuf/pyext/python.proto \
python/google/protobuf/pyext/python_protobuf.h \
@@ -567,8 +570,6 @@ python_EXTRA_DIST= \
python/google/protobuf/pyext/repeated_composite_container.cc \
python/google/protobuf/pyext/repeated_scalar_container.h \
python/google/protobuf/pyext/repeated_scalar_container.cc \
- python/google/protobuf/pyext/scalar_map_container.cc \
- python/google/protobuf/pyext/scalar_map_container.h \
python/google/protobuf/pyext/scoped_pyobject_ptr.h \
python/google/protobuf/pyext/__init__.py \
python/google/protobuf/descriptor.py \
@@ -1,32 +1,32 @@
-# Only test one combination: "Visual Studio 12 + Win64 + Debug + DLL". We can
-# test more combinations but AppVeyor just takes too long to finish (each
-# combination takes ~15mins).
-platform:
- - Win64
-
-configuration:
- - Debug
-environment:
- matrix:
- - language: cpp
- BUILD_DLL: ON
- - language: csharp
-install:
- - ps: Start-FileDownload https://googlemock.googlecode.com/files/gmock-1.7.0.zip
- - 7z x gmock-1.7.0.zip
- - rename gmock-1.7.0 gmock
-before_build:
- - if %platform%==Win32 set generator=Visual Studio 12
- - if %platform%==Win64 set generator=Visual Studio 12 Win64
- - if %platform%==Win32 set vcplatform=Win32
- - if %platform%==Win64 set vcplatform=x64
-build_script:
- - CALL appveyor.bat
-skip_commits:
- message: /.*\[skip appveyor\].*/
+# Only test one combination: "Visual Studio 12 + Win64 + Debug + DLL". We can
+# test more combinations but AppVeyor just takes too long to finish (each
+# combination takes ~15mins).
+platform:
+ - Win64
+
+configuration:
+ - Debug
+environment:
+ matrix:
+ - language: cpp
+ BUILD_DLL: ON
+ - language: csharp
+install:
+ - ps: Start-FileDownload https://googlemock.googlecode.com/files/gmock-1.7.0.zip
+ - 7z x gmock-1.7.0.zip
+ - rename gmock-1.7.0 gmock
+before_build:
+ - if %platform%==Win32 set generator=Visual Studio 12
+ - if %platform%==Win64 set generator=Visual Studio 12 Win64
+ - if %platform%==Win32 set vcplatform=Win32
+ - if %platform%==Win64 set vcplatform=x64
+build_script:
+ - CALL appveyor.bat
+skip_commits:
+ message: /.*\[skip appveyor\].*/
@@ -6,6 +6,7 @@ mkdir include\google\protobuf\compiler\cpp
mkdir include\google\protobuf\compiler\csharp
mkdir include\google\protobuf\compiler\java
mkdir include\google\protobuf\compiler\javanano
+mkdir include\google\protobuf\compiler\js
mkdir include\google\protobuf\compiler\objectivec
mkdir include\google\protobuf\compiler\python
mkdir include\google\protobuf\compiler\ruby
@@ -26,6 +27,7 @@ copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\importer.h in
copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\java\java_generator.h include\google\protobuf\compiler\java\java_generator.h
copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\java\java_names.h include\google\protobuf\compiler\java\java_names.h
copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\javanano\javanano_generator.h include\google\protobuf\compiler\javanano\javanano_generator.h
+copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\js\js_generator.h include\google\protobuf\compiler\js\js_generator.h
copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\objectivec\objectivec_generator.h include\google\protobuf\compiler\objectivec\objectivec_generator.h
copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\objectivec\objectivec_helpers.h include\google\protobuf\compiler\objectivec\objectivec_helpers.h
copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\parser.h include\google\protobuf\compiler\parser.h
@@ -40,6 +40,7 @@ set(libprotobuf_files
${protobuf_source_dir}/src/google/protobuf/util/internal/json_objectwriter.cc
${protobuf_source_dir}/src/google/protobuf/util/internal/json_stream_parser.cc
${protobuf_source_dir}/src/google/protobuf/util/internal/object_writer.cc
+ ${protobuf_source_dir}/src/google/protobuf/util/internal/proto_writer.cc
${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectsource.cc
${protobuf_source_dir}/src/google/protobuf/util/internal/protostream_objectwriter.cc
${protobuf_source_dir}/src/google/protobuf/util/internal/type_info.cc
@@ -70,6 +70,7 @@ set(libprotoc_files
${protobuf_source_dir}/src/google/protobuf/compiler/javanano/javanano_message.cc
${protobuf_source_dir}/src/google/protobuf/compiler/javanano/javanano_message_field.cc
${protobuf_source_dir}/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc
+ ${protobuf_source_dir}/src/google/protobuf/compiler/js/js_generator.cc
${protobuf_source_dir}/src/google/protobuf/compiler/objectivec/objectivec_enum.cc
${protobuf_source_dir}/src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc
${protobuf_source_dir}/src/google/protobuf/compiler/objectivec/objectivec_extension.cc
@@ -1,9 +1,12 @@
import com.google.protobuf.conformance.Conformance;
+import com.google.protobuf.util.JsonFormat;
+import com.google.protobuf.util.JsonFormat.TypeRegistry;
import com.google.protobuf.InvalidProtocolBufferException;
class ConformanceJava {
private int testCount = 0;
+ private TypeRegistry typeRegistry;
private boolean readFromStdin(byte[] buf, int len) throws Exception {
int ofs = 0;
@@ -29,7 +32,10 @@ class ConformanceJava {
if (!readFromStdin(buf, 4)) {
return -1;
}
- return buf[0] | (buf[1] << 1) | (buf[2] << 2) | (buf[3] << 3);
+ return (buf[0] & 0xff)
+ | ((buf[1] & 0xff) << 8)
+ | ((buf[2] & 0xff) << 16)
+ | ((buf[3] & 0xff) << 24);
private void writeLittleEndianIntToStdout(int val) throws Exception {
@@ -54,7 +60,15 @@ class ConformanceJava {
break;
case JSON_PAYLOAD: {
- return Conformance.ConformanceResponse.newBuilder().setSkipped("JSON not yet supported.").build();
+ try {
+ Conformance.TestAllTypes.Builder builder = Conformance.TestAllTypes.newBuilder();
+ JsonFormat.parser().usingTypeRegistry(typeRegistry)
+ .merge(request.getJsonPayload(), builder);
+ testMessage = builder.build();
+ } catch (InvalidProtocolBufferException e) {
+ return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
+ }
+ break;
case PAYLOAD_NOT_SET: {
throw new RuntimeException("Request didn't have payload.");
@@ -73,7 +87,13 @@ class ConformanceJava {
return Conformance.ConformanceResponse.newBuilder().setProtobufPayload(testMessage.toByteString()).build();
case JSON:
+ return Conformance.ConformanceResponse.newBuilder().setJsonPayload(
+ JsonFormat.printer().usingTypeRegistry(typeRegistry).print(testMessage)).build();
+ } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
+ return Conformance.ConformanceResponse.newBuilder().setSerializeError(
+ e.getMessage()).build();
default: {
throw new RuntimeException("Unexpected request output.");
@@ -106,6 +126,8 @@ class ConformanceJava {
public void run() throws Exception {
+ typeRegistry = TypeRegistry.newBuilder().add(
+ Conformance.TestAllTypes.getDescriptor()).build();
while (doTestIo()) {
// Empty.
@@ -32,6 +32,13 @@ syntax = "proto3";
package conformance;
option java_package = "com.google.protobuf.conformance";
+import "google/protobuf/any.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/field_mask.proto";
+import "google/protobuf/struct.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
// This defines the conformance testing protocol. This protocol exists between
// the conformance test suite itself and the code being tested. For each test,
// the suite will send a ConformanceRequest message and expect a
@@ -84,6 +91,11 @@ message ConformanceResponse {
// test. Some of the test cases are intentionally invalid input.
string parse_error = 1;
+ // If the input was successfully parsed but errors occurred when
+ // serializing it to the requested output format, set the error message in
+ // this field.
+ string serialize_error = 6;
// This should be set if some other error occurred. This will always
// indicate that the test failed. The string can provide more information
// about the failure.
@@ -199,6 +211,55 @@ message TestAllTypes {
string oneof_string = 113;
bytes oneof_bytes = 114;
+ // Well-known types
+ google.protobuf.BoolValue optional_bool_wrapper = 201;
+ google.protobuf.Int32Value optional_int32_wrapper = 202;
+ google.protobuf.Int64Value optional_int64_wrapper = 203;
+ google.protobuf.UInt32Value optional_uint32_wrapper = 204;
+ google.protobuf.UInt64Value optional_uint64_wrapper = 205;
+ google.protobuf.FloatValue optional_float_wrapper = 206;
+ google.protobuf.DoubleValue optional_double_wrapper = 207;
+ google.protobuf.StringValue optional_string_wrapper = 208;
+ google.protobuf.BytesValue optional_bytes_wrapper = 209;
+ repeated google.protobuf.BoolValue repeated_bool_wrapper = 211;
+ repeated google.protobuf.Int32Value repeated_int32_wrapper = 212;
+ repeated google.protobuf.Int64Value repeated_int64_wrapper = 213;
+ repeated google.protobuf.UInt32Value repeated_uint32_wrapper = 214;
+ repeated google.protobuf.UInt64Value repeated_uint64_wrapper = 215;
+ repeated google.protobuf.FloatValue repeated_float_wrapper = 216;
+ repeated google.protobuf.DoubleValue repeated_double_wrapper = 217;
+ repeated google.protobuf.StringValue repeated_string_wrapper = 218;
+ repeated google.protobuf.BytesValue repeated_bytes_wrapper = 219;
+ google.protobuf.Duration optional_duration = 301;
+ google.protobuf.Timestamp optional_timestamp = 302;
+ google.protobuf.FieldMask optional_field_mask = 303;
+ google.protobuf.Struct optional_struct = 304;
+ google.protobuf.Any optional_any = 305;
+ google.protobuf.Value optional_value = 306;
+ repeated google.protobuf.Duration repeated_duration = 311;
+ repeated google.protobuf.Timestamp repeated_timestamp = 312;
+ repeated google.protobuf.FieldMask repeated_fieldmask = 313;
+ repeated google.protobuf.Struct repeated_struct = 324;
+ repeated google.protobuf.Any repeated_any = 315;
+ repeated google.protobuf.Value repeated_value = 316;
+ // Test field-name-to-JSON-name convention.
+ int32 fieldname1 = 401;
+ int32 field_name2 = 402;
+ int32 _field_name3 = 403;
+ int32 field__name4_ = 404;
+ int32 field0name5 = 405;
+ int32 field_0_name6 = 406;
+ int32 fieldName7 = 407;
+ int32 FieldName8 = 408;
+ int32 field_Name9 = 409;
+ int32 Field_Name10 = 410;
+ int32 FIELD_NAME11 = 411;
+ int32 FIELD_name12 = 412;
message ForeignMessage {
@@ -108,7 +108,11 @@ void DoTest(const ConformanceRequest& request, ConformanceResponse* response) {
return;
- GOOGLE_CHECK(test_message.ParseFromString(proto_binary));
+ if (!test_message.ParseFromString(proto_binary)) {
+ response->set_runtime_error(
+ "Parsing JSON generates invalid proto output.");
+ return;
@@ -132,9 +136,18 @@ void DoTest(const ConformanceRequest& request, ConformanceResponse* response) {
GOOGLE_CHECK(test_message.SerializeToString(&proto_binary));
Status status = BinaryToJsonString(type_resolver, *type_url, proto_binary,
response->mutable_json_payload());
- GOOGLE_CHECK(status.ok());
+ if (!status.ok()) {
+ response->set_serialize_error(
+ string("Failed to serialize JSON output: ") +
+ status.error_message().as_string());
+ default:
+ GOOGLE_LOG(FATAL) << "Unknown output format: "
+ << request.requested_output_format();
@@ -37,10 +37,14 @@
#include <google/protobuf/stubs/stringprintf.h>
#include <google/protobuf/text_format.h>
#include <google/protobuf/util/json_util.h>
+#include <google/protobuf/util/field_comparator.h>
#include <google/protobuf/util/message_differencer.h>
#include <google/protobuf/util/type_resolver_util.h>
#include <google/protobuf/wire_format_lite.h>
+#include "third_party/jsoncpp/value.h"
+#include "third_party/jsoncpp/reader.h"
using conformance::ConformanceRequest;
using conformance::ConformanceResponse;
using conformance::TestAllTypes;
@@ -49,6 +53,7 @@ using google::protobuf::Descriptor;
using google::protobuf::FieldDescriptor;
using google::protobuf::internal::WireFormatLite;
using google::protobuf::TextFormat;
+using google::protobuf::util::DefaultFieldComparator;
using google::protobuf::util::JsonToBinaryString;
using google::protobuf::util::MessageDifferencer;
using google::protobuf::util::NewTypeResolverForDescriptorPool;
@@ -220,7 +225,7 @@ void ConformanceTestSuite::RunTest(const string& test_name,
string serialized_response;
request.SerializeToString(&serialized_request);
- runner_->RunTest(serialized_request, &serialized_response);
+ runner_->RunTest(test_name, serialized_request, &serialized_response);
if (!response->ParseFromString(serialized_response)) {
response->Clear();
@@ -240,7 +245,9 @@ void ConformanceTestSuite::RunValidInputTest(
const string& equivalent_text_format, WireFormat requested_output) {
TestAllTypes reference_message;
GOOGLE_CHECK(
- TextFormat::ParseFromString(equivalent_text_format, &reference_message));
+ TextFormat::ParseFromString(equivalent_text_format, &reference_message))
+ << "Failed to parse data for test case: " << test_name
+ << ", data: " << equivalent_text_format;
ConformanceRequest request;
ConformanceResponse response;
@@ -254,9 +261,8 @@ void ConformanceTestSuite::RunValidInputTest(
request.set_json_payload(input);
- case conformance::UNSPECIFIED:
GOOGLE_LOG(FATAL) << "Unspecified input format";
request.set_requested_output_format(requested_output);
@@ -268,8 +274,9 @@ void ConformanceTestSuite::RunValidInputTest(
switch (response.result_case()) {
case ConformanceResponse::kParseError:
case ConformanceResponse::kRuntimeError:
+ case ConformanceResponse::kSerializeError:
ReportFailure(test_name, request, response,
- "Failed to parse valid JSON input.");
+ "Failed to parse JSON input or produce JSON output.");
case ConformanceResponse::kSkipped:
@@ -313,13 +320,20 @@ void ConformanceTestSuite::RunValidInputTest(
+ GOOGLE_LOG(FATAL) << test_name << ": unknown payload type: "
+ << response.result_case();
MessageDifferencer differencer;
+ DefaultFieldComparator field_comparator;
+ field_comparator.set_treat_nan_as_equal(true);
+ differencer.set_field_comparator(&field_comparator);
string differences;
differencer.ReportDifferencesToString(&differences);
- if (differencer.Equals(reference_message, test_message)) {
+ if (differencer.Compare(reference_message, test_message)) {
ReportSuccess(test_name);
} else {
@@ -362,13 +376,103 @@ void ConformanceTestSuite::ExpectHardParseFailureForProto(
void ConformanceTestSuite::RunValidJsonTest(
const string& test_name, const string& input_json,
const string& equivalent_text_format) {
- RunValidInputTest("JsonInput." + test_name + ".JsonOutput", input_json,
+ RunValidInputTest("JsonInput." + test_name + ".ProtobufOutput", input_json,
conformance::JSON, equivalent_text_format,
conformance::PROTOBUF);
- RunValidInputTest("JsonInput." + test_name + ".ProtobufOutput", input_json, conformance::JSON,
+ RunValidInputTest("JsonInput." + test_name + ".JsonOutput", input_json,
+ conformance::JSON, equivalent_text_format,
+ conformance::JSON);
+}
+void ConformanceTestSuite::RunValidJsonTestWithProtobufInput(
+ const string& test_name, const TestAllTypes& input,
+ const string& equivalent_text_format) {
+ RunValidInputTest("ProtobufInput." + test_name + ".JsonOutput",
+ input.SerializeAsString(), conformance::PROTOBUF,
equivalent_text_format, conformance::JSON);
+// According to proto3 JSON specification, JSON serializers follow more strict
+// rules than parsers (e.g., a serializer must serialize int32 values as JSON
+// numbers while the parser is allowed to accept them as JSON strings). This
+// method allows strict checking on a proto3 JSON serializer by inspecting
+// the JSON output directly.
+void ConformanceTestSuite::RunValidJsonTestWithValidator(
+ const string& test_name, const string& input_json,
+ const Validator& validator) {
+ ConformanceRequest request;
+ ConformanceResponse response;
+ request.set_json_payload(input_json);
+ request.set_requested_output_format(conformance::JSON);
+ string effective_test_name = "JsonInput." + test_name + ".Validator";
+ RunTest(effective_test_name, request, &response);
+ if (response.result_case() != ConformanceResponse::kJsonPayload) {
+ ReportFailure(effective_test_name, request, response,
+ "Expected JSON payload but got type %d.",
+ response.result_case());
+ Json::Reader reader;
+ Json::Value value;
+ if (!reader.parse(response.json_payload(), value)) {
+ "JSON payload cannot be parsed as valid JSON: %s",
+ reader.getFormattedErrorMessages().c_str());
+ if (!validator(value)) {
+ "JSON payload validation failed.");
+ ReportSuccess(effective_test_name);
+void ConformanceTestSuite::ExpectParseFailureForJson(
+ const string& test_name, const string& input_json) {
+ string effective_test_name = "JsonInput." + test_name;
+ // We don't expect output, but if the program erroneously accepts the protobuf
+ // we let it send its response as this. We must not leave it unspecified.
+ if (response.result_case() == ConformanceResponse::kParseError) {
+ } else {
+ "Should have failed to parse, but didn't.");
+void ConformanceTestSuite::ExpectSerializeFailureForJson(
+ const string& test_name, const string& text_format) {
+ TestAllTypes payload_message;
+ GOOGLE_CHECK(
+ TextFormat::ParseFromString(text_format, &payload_message))
+ << "Failed to parse: " << text_format;
+ request.set_protobuf_payload(payload_message.SerializeAsString());
+ string effective_test_name = test_name + ".JsonOutput";
+ if (response.result_case() == ConformanceResponse::kSerializeError) {
+ "Should have failed to serialize, but didn't.");
void ConformanceTestSuite::TestPrematureEOFForType(FieldDescriptor::Type type) {
// Incomplete values for each wire type.
static const string incompletes[6] = {
@@ -500,20 +604,1373 @@ bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
RunValidJsonTest("HelloWorld", "{\"optionalString\":\"Hello, World!\"}",
"optional_string: 'Hello, World!'");
- bool ok =
- CheckSetEmpty(expected_to_fail_,
- "These tests were listed in the failure list, but they "
- "don't exist. Remove them from the failure list") &&
+ // Test field name conventions.
+ RunValidJsonTest(
+ "FieldNameInSnakeCase",
+ R"({
+ "fieldname1": 1,
+ "fieldName2": 2,
+ "FieldName3": 3
+ })",
+ R"(
+ fieldname1: 1
+ field_name2: 2
+ _field_name3: 3
+ )");
+ "FieldNameWithNumbers",
+ "field0name5": 5,
+ "field0Name6": 6
+ field0name5: 5
+ field_0_name6: 6
+ "FieldNameWithMixedCases",
+ "fieldName7": 7,
+ "fieldName8": 8,
+ "fieldName9": 9,
+ "fieldName10": 10,
+ "fIELDNAME11": 11,
+ "fIELDName12": 12
+ fieldName7: 7
+ FieldName8: 8
+ field_Name9: 9
+ Field_Name10: 10
+ FIELD_NAME11: 11
+ FIELD_name12: 12
+ // Using the original proto field name in JSON is also allowed.
+ "OriginalProtoFieldName",
+ "field_name2": 2,
+ "_field_name3": 3,
+ "field_0_name6": 6,
+ "FieldName8": 8,
+ "field_Name9": 9,
+ "Field_Name10": 10,
+ "FIELD_NAME11": 11,
+ "FIELD_name12": 12
+ // Field names can be escaped.
+ "FieldNameEscaped",
+ R"({"fieldn\u0061me1": 1})",
+ "fieldname1: 1");
+ // Field names must be quoted (or it's not valid JSON).
+ ExpectParseFailureForJson(
+ "FieldNameNotQuoted",
+ "{fieldname1: 1}");
+ // Trailing comma is not allowed (not valid JSON).
+ "TrailingCommaInAnObject",
+ R"({"fieldname1":1,})");
+ // JSON doesn't support comments.
+ "JsonWithComments",
+ // This is a comment.
+ "fieldname1": 1
+ })");
+ // Duplicated field names are not allowed.
+ "FieldNameDuplicate",
+ "optionalNestedMessage": {a: 1},
+ "optionalNestedMessage": {}
+ "FieldNameDuplicateDifferentCasing1",
+ "optional_nested_message": {a: 1},
+ "FieldNameDuplicateDifferentCasing2",
+ "optional_nested_message": {}
+ // Serializers should use lowerCamelCase by default.
+ RunValidJsonTestWithValidator(
+ "FieldNameInLowerCamelCase",
+ [](const Json::Value& value) {
+ return value.isMember("fieldname1") &&
+ value.isMember("fieldName2") &&
+ value.isMember("FieldName3");
+ });
+ return value.isMember("field0name5") &&
+ value.isMember("field0Name6");
+ return value.isMember("fieldName7") &&
+ value.isMember("fieldName8") &&
+ value.isMember("fieldName9") &&
+ value.isMember("fieldName10") &&
+ value.isMember("fIELDNAME11") &&
+ value.isMember("fIELDName12");
+ // Integer fields.
+ "Int32FieldMaxValue",
+ R"({"optionalInt32": 2147483647})",
+ "optional_int32: 2147483647");
+ "Int32FieldMinValue",
+ R"({"optionalInt32": -2147483648})",
+ "optional_int32: -2147483648");
+ "Uint32FieldMaxValue",
+ R"({"optionalUint32": 4294967295})",
+ "optional_uint32: 4294967295");
+ "Int64FieldMaxValue",
+ R"({"optionalInt64": "9223372036854775807"})",
+ "optional_int64: 9223372036854775807");
+ "Int64FieldMinValue",
+ R"({"optionalInt64": "-9223372036854775808"})",
+ "optional_int64: -9223372036854775808");
+ "Uint64FieldMaxValue",
+ R"({"optionalUint64": "18446744073709551615"})",
+ "optional_uint64: 18446744073709551615");
+ "Int64FieldMaxValueNotQuoted",
+ R"({"optionalInt64": 9223372036854775807})",
+ "Int64FieldMinValueNotQuoted",
+ R"({"optionalInt64": -9223372036854775808})",
+ "Uint64FieldMaxValueNotQuoted",
+ R"({"optionalUint64": 18446744073709551615})",
+ // Values can be represented as JSON strings.
+ "Int32FieldStringValue",
+ R"({"optionalInt32": "2147483647"})",
+ "Int32FieldStringValueEscaped",
+ R"({"optionalInt32": "2\u003147483647"})",
+ // Parsers reject out-of-bound integer values.
+ "Int32FieldTooLarge",
+ R"({"optionalInt32": 2147483648})");
+ "Int32FieldTooSmall",
+ R"({"optionalInt32": -2147483649})");
+ "Uint32FieldTooLarge",
+ R"({"optionalUint32": 4294967296})");
+ "Int64FieldTooLarge",
+ R"({"optionalInt64": "9223372036854775808"})");
+ "Int64FieldTooSmall",
+ R"({"optionalInt64": "-9223372036854775809"})");
+ "Uint64FieldTooLarge",
+ R"({"optionalUint64": "18446744073709551616"})");
+ // Parser reject non-integer numeric values as well.
+ "Int32FieldNotInteger",
+ R"({"optionalInt32": 0.5})");
+ "Uint32FieldNotInteger",
+ R"({"optionalUint32": 0.5})");
+ "Int64FieldNotInteger",
+ R"({"optionalInt64": "0.5"})");
+ "Uint64FieldNotInteger",
+ R"({"optionalUint64": "0.5"})");
+ // Integers but represented as float values are accepted.
+ "Int32FieldFloatTrailingZero",
+ R"({"optionalInt32": 100000.000})",
+ "optional_int32: 100000");
+ "Int32FieldExponentialFormat",
+ R"({"optionalInt32": 1e5})",
+ "Int32FieldMaxFloatValue",
+ R"({"optionalInt32": 2.147483647e9})",
+ "Int32FieldMinFloatValue",
+ R"({"optionalInt32": -2.147483648e9})",
+ "Uint32FieldMaxFloatValue",
+ R"({"optionalUint32": 4.294967295e9})",
+ // Parser reject non-numeric values.
+ "Int32FieldNotNumber",
+ R"({"optionalInt32": "3x3"})");
+ "Uint32FieldNotNumber",
+ R"({"optionalUint32": "3x3"})");
+ "Int64FieldNotNumber",
+ R"({"optionalInt64": "3x3"})");
+ "Uint64FieldNotNumber",
+ R"({"optionalUint64": "3x3"})");
+ // JSON does not allow "+" on numric values.
+ "Int32FieldPlusSign",
+ R"({"optionalInt32": +1})");
+ // JSON doesn't allow leading 0s.
+ "Int32FieldLeadingZero",
+ R"({"optionalInt32": 01})");
+ "Int32FieldNegativeWithLeadingZero",
+ R"({"optionalInt32": -01})");
+ // String values must follow the same syntax rule. Specifically leading
+ // or traling spaces are not allowed.
+ "Int32FieldLeadingSpace",
+ R"({"optionalInt32": " 1"})");
+ "Int32FieldTrailingSpace",
+ R"({"optionalInt32": "1 "})");
+ // 64-bit values are serialized as strings.
+ "Int64FieldBeString",
+ R"({"optionalInt64": 1})",
+ return value["optionalInt64"].type() == Json::stringValue &&
+ value["optionalInt64"].asString() == "1";
+ "Uint64FieldBeString",
+ R"({"optionalUint64": 1})",
+ return value["optionalUint64"].type() == Json::stringValue &&
+ value["optionalUint64"].asString() == "1";
+ // Bool fields.
+ "BoolFieldTrue",
+ R"({"optionalBool":true})",
+ "optional_bool: true");
+ "BoolFieldFalse",
+ R"({"optionalBool":false})",
+ "optional_bool: false");
+ // Other forms are not allowed.
+ "BoolFieldIntegerZero",
+ R"({"optionalBool":0})");
+ "BoolFieldIntegerOne",
+ R"({"optionalBool":1})");
+ "BoolFieldCamelCaseTrue",
+ R"({"optionalBool":True})");
+ "BoolFieldCamelCaseFalse",
+ R"({"optionalBool":False})");
+ "BoolFieldAllCapitalTrue",
+ R"({"optionalBool":TRUE})");
+ "BoolFieldAllCapitalFalse",
+ R"({"optionalBool":FALSE})");
+ "BoolFieldDoubleQuotedTrue",
+ R"({"optionalBool":"true"})");
+ "BoolFieldDoubleQuotedFalse",
+ R"({"optionalBool":"false"})");
+ // Float fields.
+ "FloatFieldMinPositiveValue",
+ R"({"optionalFloat": 1.175494e-38})",
+ "optional_float: 1.175494e-38");
+ "FloatFieldMaxNegativeValue",
+ R"({"optionalFloat": -1.175494e-38})",
+ "optional_float: -1.175494e-38");
+ "FloatFieldMaxPositiveValue",
+ R"({"optionalFloat": 3.402823e+38})",
+ "optional_float: 3.402823e+38");
+ "FloatFieldMinNegativeValue",
+ // Values can be quoted.
+ "FloatFieldQuotedValue",
+ R"({"optionalFloat": "1"})",
+ "optional_float: 1");
+ // Special values.
+ "FloatFieldNan",
+ R"({"optionalFloat": "NaN"})",
+ "optional_float: nan");
+ "FloatFieldInfinity",
+ R"({"optionalFloat": "Infinity"})",
+ "optional_float: inf");
+ "FloatFieldNegativeInfinity",
+ R"({"optionalFloat": "-Infinity"})",
+ "optional_float: -inf");
+ // Non-cannonical Nan will be correctly normalized.
+ {
+ TestAllTypes message;
+ // IEEE floating-point standard 32-bit quiet NaN:
+ // 0111 1111 1xxx xxxx xxxx xxxx xxxx xxxx
+ message.set_optional_float(
+ WireFormatLite::DecodeFloat(0x7FA12345));
+ RunValidJsonTestWithProtobufInput(
+ "FloatFieldNormalizeQuietNan", message,
+ // IEEE floating-point standard 64-bit signaling NaN:
+ // 1111 1111 1xxx xxxx xxxx xxxx xxxx xxxx
+ WireFormatLite::DecodeFloat(0xFFB54321));
+ "FloatFieldNormalizeSignalingNan", message,
- CheckSetEmpty(unexpected_failing_tests_,
- "These tests failed. If they can't be fixed right now, "
- "you can add them to the failure list so the overall "
- "suite can succeed") &&
+ // Special values must be quoted.
+ "FloatFieldNanNotQuoted",
+ R"({"optionalFloat": NaN})");
+ "FloatFieldInfinityNotQuoted",
+ R"({"optionalFloat": Infinity})");
+ "FloatFieldNegativeInfinityNotQuoted",
+ R"({"optionalFloat": -Infinity})");
+ // Parsers should reject out-of-bound values.
+ "FloatFieldTooSmall",
+ R"({"optionalFloat": -3.502823e+38})");
+ "FloatFieldTooLarge",
+ R"({"optionalFloat": 3.502823e+38})");
+ // Double fields.
+ "DoubleFieldMinPositiveValue",
+ R"({"optionalDouble": 2.22507e-308})",
+ "optional_double: 2.22507e-308");
+ "DoubleFieldMaxNegativeValue",
+ R"({"optionalDouble": -2.22507e-308})",
+ "optional_double: -2.22507e-308");
+ "DoubleFieldMaxPositiveValue",
+ R"({"optionalDouble": 1.79769e+308})",
+ "optional_double: 1.79769e+308");
+ "DoubleFieldMinNegativeValue",
+ R"({"optionalDouble": -1.79769e+308})",
+ "optional_double: -1.79769e+308");
+ "DoubleFieldQuotedValue",
+ R"({"optionalDouble": "1"})",
+ "optional_double: 1");
+ // Speical values.
+ "DoubleFieldNan",
+ R"({"optionalDouble": "NaN"})",
+ "optional_double: nan");
+ "DoubleFieldInfinity",
+ R"({"optionalDouble": "Infinity"})",
+ "optional_double: inf");
+ "DoubleFieldNegativeInfinity",
+ R"({"optionalDouble": "-Infinity"})",
+ "optional_double: -inf");
+ message.set_optional_double(
+ WireFormatLite::DecodeDouble(0x7FFA123456789ABCLL));
+ "DoubleFieldNormalizeQuietNan", message,
+ WireFormatLite::DecodeDouble(0xFFFBCBA987654321LL));
+ "DoubleFieldNormalizeSignalingNan", message,
+ "DoubleFieldNanNotQuoted",
+ R"({"optionalDouble": NaN})");
+ "DoubleFieldInfinityNotQuoted",
+ R"({"optionalDouble": Infinity})");
+ "DoubleFieldNegativeInfinityNotQuoted",
+ R"({"optionalDouble": -Infinity})");
+ "DoubleFieldTooSmall",
+ R"({"optionalDouble": -1.89769e+308})");
+ "DoubleFieldTooLarge",
+ R"({"optionalDouble": +1.89769e+308})");
+ // Enum fields.
+ "EnumField",
+ R"({"optionalNestedEnum": "FOO"})",
+ "optional_nested_enum: FOO");
+ // Enum values must be represented as strings.
+ "EnumFieldNotQuoted",
+ R"({"optionalNestedEnum": FOO})");
+ // Numeric values are allowed.
+ "EnumFieldNumericValueZero",
+ R"({"optionalNestedEnum": 0})",
+ "EnumFieldNumericValueNonZero",
+ R"({"optionalNestedEnum": 1})",
+ "optional_nested_enum: BAR");
+ // Unknown enum values are represented as numeric values.
+ "EnumFieldUnknownValue",
+ R"({"optionalNestedEnum": 123})",
+ return value["optionalNestedEnum"].type() == Json::intValue &&
+ value["optionalNestedEnum"].asInt() == 123;
+ // String fields.
+ "StringField",
+ R"({"optionalString": "Hello world!"})",
+ "optional_string: \"Hello world!\"");
+ "StringFieldUnicode",
+ // Google in Chinese.
+ R"({"optionalString": "谷歌"})",
+ R"(optional_string: "谷歌")");
+ "StringFieldEscape",
+ R"({"optionalString": "\"\\\/\b\f\n\r\t"})",
+ R"(optional_string: "\"\\/\b\f\n\r\t")");
+ "StringFieldUnicodeEscape",
+ R"({"optionalString": "\u8C37\u6B4C"})",
+ "StringFieldUnicodeEscapeWithLowercaseHexLetters",
+ R"({"optionalString": "\u8c37\u6b4c"})",
+ "StringFieldSurrogatePair",
+ // The character is an emoji: grinning face with smiling eyes. 😁
+ R"({"optionalString": "\uD83D\uDE01"})",
+ R"(optional_string: "\xF0\x9F\x98\x81")");
+ // Unicode escapes must start with "\u" (lowercase u).
+ "StringFieldUppercaseEscapeLetter",
+ R"({"optionalString": "\U8C37\U6b4C"})");
+ "StringFieldInvalidEscape",
+ R"({"optionalString": "\uXXXX\u6B4C"})");
+ "StringFieldUnterminatedEscape",
+ R"({"optionalString": "\u8C3"})");
+ "StringFieldUnpairedHighSurrogate",
+ R"({"optionalString": "\uD800"})");
+ "StringFieldUnpairedLowSurrogate",
+ R"({"optionalString": "\uDC00"})");
+ "StringFieldSurrogateInWrongOrder",
+ R"({"optionalString": "\uDE01\uD83D"})");
+ "StringFieldNotAString",
+ R"({"optionalString": 12345})");
+ // Bytes fields.
+ "BytesField",
+ R"({"optionalBytes": "AQI="})",
+ R"(optional_bytes: "\x01\x02")");
+ "BytesFieldNoPadding",
+ R"({"optionalBytes": "AQI"})");
+ "BytesFieldInvalidBase64Characters",
+ R"({"optionalBytes": "-_=="})");
+ // Message fields.
+ "MessageField",
+ R"({"optionalNestedMessage": {"a": 1234}})",
+ "optional_nested_message: {a: 1234}");
+ // Oneof fields.
+ "OneofFieldDuplicate",
+ R"({"oneofUint32": 1, "oneofString": "test"})");
+ // Repeated fields.
+ "PrimitiveRepeatedField",
+ R"({"repeatedInt32": [1, 2, 3, 4]})",
+ "repeated_int32: [1, 2, 3, 4]");
+ "EnumRepeatedField",
+ R"({"repeatedNestedEnum": ["FOO", "BAR", "BAZ"]})",
+ "repeated_nested_enum: [FOO, BAR, BAZ]");
+ "StringRepeatedField",
+ R"({"repeatedString": ["Hello", "world"]})",
+ R"(repeated_string: ["Hello", "world"])");
+ "BytesRepeatedField",
+ R"({"repeatedBytes": ["AAEC", "AQI="]})",
+ R"(repeated_bytes: ["\x00\x01\x02", "\x01\x02"])");
+ "MessageRepeatedField",
+ R"({"repeatedNestedMessage": [{"a": 1234}, {"a": 5678}]})",
+ "repeated_nested_message: {a: 1234}"
+ "repeated_nested_message: {a: 5678}");
+ // Repeated field elements are of incorrect type.
+ "RepeatedFieldWrongElementTypeExpectingIntegersGotBool",
+ R"({"repeatedInt32": [1, false, 3, 4]})");
+ "RepeatedFieldWrongElementTypeExpectingIntegersGotString",
+ R"({"repeatedInt32": [1, 2, "name", 4]})");
+ "RepeatedFieldWrongElementTypeExpectingIntegersGotMessage",
+ R"({"repeatedInt32": [1, 2, 3, {"a": 4}]})");
+ "RepeatedFieldWrongElementTypeExpectingStringsGotInt",
+ R"({"repeatedString": ["1", 2, "3", "4"]})");
+ "RepeatedFieldWrongElementTypeExpectingStringsGotBool",
+ R"({"repeatedString": ["1", "2", false, "4"]})");
+ "RepeatedFieldWrongElementTypeExpectingStringsGotMessage",
+ R"({"repeatedString": ["1", 2, "3", {"a": 4}]})");
+ "RepeatedFieldWrongElementTypeExpectingMessagesGotInt",
+ R"({"repeatedNestedMessage": [{"a": 1}, 2]})");
+ "RepeatedFieldWrongElementTypeExpectingMessagesGotBool",
+ R"({"repeatedNestedMessage": [{"a": 1}, false]})");
+ "RepeatedFieldWrongElementTypeExpectingMessagesGotString",
+ R"({"repeatedNestedMessage": [{"a": 1}, "2"]})");
+ // Trailing comma in the repeated field is not allowed.
+ "RepeatedFieldTrailingComma",
+ R"({"repeatedInt32": [1, 2, 3, 4,]})");
+ // Map fields.
+ "Int32MapField",
+ R"({"mapInt32Int32": {"1": 2, "3": 4}})",
+ "map_int32_int32: {key: 1 value: 2}"
+ "map_int32_int32: {key: 3 value: 4}");
+ "Int32MapFieldKeyNotQuoted",
+ R"({"mapInt32Int32": {1: 2, 3: 4}})");
+ "Uint32MapField",
+ R"({"mapUint32Uint32": {"1": 2, "3": 4}})",
+ "map_uint32_uint32: {key: 1 value: 2}"
+ "map_uint32_uint32: {key: 3 value: 4}");
+ "Uint32MapFieldKeyNotQuoted",
+ R"({"mapUint32Uint32": {1: 2, 3: 4}})");
+ "Int64MapField",
+ R"({"mapInt64Int64": {"1": 2, "3": 4}})",
+ "map_int64_int64: {key: 1 value: 2}"
+ "map_int64_int64: {key: 3 value: 4}");
+ "Int64MapFieldKeyNotQuoted",
+ R"({"mapInt64Int64": {1: 2, 3: 4}})");
+ "Uint64MapField",
+ R"({"mapUint64Uint64": {"1": 2, "3": 4}})",
+ "map_uint64_uint64: {key: 1 value: 2}"
+ "map_uint64_uint64: {key: 3 value: 4}");
+ "Uint64MapFieldKeyNotQuoted",
+ R"({"mapUint64Uint64": {1: 2, 3: 4}})");
+ "BoolMapField",
+ R"({"mapBoolBool": {"true": true, "false": false}})",
+ "map_bool_bool: {key: true value: true}"
+ "map_bool_bool: {key: false value: false}");
+ "BoolMapFieldKeyNotQuoted",
+ R"({"mapBoolBool": {true: true, false: false}})");
+ "MessageMapField",
+ "mapStringNestedMessage": {
+ "hello": {"a": 1234},
+ "world": {"a": 5678}
+ map_string_nested_message: {
+ key: "hello"
+ value: {a: 1234}
+ key: "world"
+ value: {a: 5678}
+ // Since Map keys are represented as JSON strings, escaping should be allowed.
+ "Int32MapEscapedKey",
+ R"({"mapInt32Int32": {"\u0031": 2}})",
+ "map_int32_int32: {key: 1 value: 2}");
+ "Int64MapEscapedKey",
+ R"({"mapInt64Int64": {"\u0031": 2}})",
+ "map_int64_int64: {key: 1 value: 2}");
+ "BoolMapEscapedKey",
+ R"({"mapBoolBool": {"tr\u0075e": true}})",
+ "map_bool_bool: {key: true value: true}");
+ // "null" is accepted for all fields types.
+ "AllFieldAcceptNull",
+ "optionalInt32": null,
+ "optionalInt64": null,
+ "optionalUint32": null,
+ "optionalUint64": null,
+ "optionalBool": null,
+ "optionalString": null,
+ "optionalBytes": null,
+ "optionalNestedEnum": null,
+ "optionalNestedMessage": null,
+ "repeatedInt32": null,
+ "repeatedInt64": null,
+ "repeatedUint32": null,
+ "repeatedUint64": null,
+ "repeatedBool": null,
+ "repeatedString": null,
+ "repeatedBytes": null,
+ "repeatedNestedEnum": null,
+ "repeatedNestedMessage": null,
+ "mapInt32Int32": null,
+ "mapBoolBool": null,
+ "mapStringNestedMessage": null
+ "");
+ // Repeated field elements cannot be null.
+ "RepeatedFieldPrimitiveElementIsNull",
+ R"({"repeatedInt32": [1, null, 2]})");
+ "RepeatedFieldMessageElementIsNull",
+ R"({"repeatedNestedMessage": [{"a":1}, null, {"a":2}]})");
+ // Map field keys cannot be null.
+ "MapFieldKeyIsNull",
+ R"({"mapInt32Int32": {null: 1}})");
+ // Map field values cannot be null.
+ "MapFieldValueIsNull",
+ R"({"mapInt32Int32": {"0": null}})");
+ // Wrapper types.
+ "OptionalBoolWrapper",
+ R"({"optionalBoolWrapper": false})",
+ "optional_bool_wrapper: {value: false}");
+ "OptionalInt32Wrapper",
+ R"({"optionalInt32Wrapper": 0})",
+ "optional_int32_wrapper: {value: 0}");
+ "OptionalUint32Wrapper",
+ R"({"optionalUint32Wrapper": 0})",
+ "optional_uint32_wrapper: {value: 0}");
+ "OptionalInt64Wrapper",
+ R"({"optionalInt64Wrapper": 0})",
+ "optional_int64_wrapper: {value: 0}");
+ "OptionalUint64Wrapper",
+ R"({"optionalUint64Wrapper": 0})",
+ "optional_uint64_wrapper: {value: 0}");
+ "OptionalFloatWrapper",
+ R"({"optionalFloatWrapper": 0})",
+ "optional_float_wrapper: {value: 0}");
+ "OptionalDoubleWrapper",
+ R"({"optionalDoubleWrapper": 0})",
+ "optional_double_wrapper: {value: 0}");
+ "OptionalStringWrapper",
+ R"({"optionalStringWrapper": ""})",
+ R"(optional_string_wrapper: {value: ""})");
+ "OptionalBytesWrapper",
+ R"({"optionalBytesWrapper": ""})",
+ R"(optional_bytes_wrapper: {value: ""})");
+ "OptionalWrapperTypesWithNonDefaultValue",
+ "optionalBoolWrapper": true,
+ "optionalInt32Wrapper": 1,
+ "optionalUint32Wrapper": 1,
+ "optionalInt64Wrapper": "1",
+ "optionalUint64Wrapper": "1",
+ "optionalFloatWrapper": 1,
+ "optionalDoubleWrapper": 1,
+ "optionalStringWrapper": "1",
+ "optionalBytesWrapper": "AQI="
+ optional_bool_wrapper: {value: true}
+ optional_int32_wrapper: {value: 1}
+ optional_uint32_wrapper: {value: 1}
+ optional_int64_wrapper: {value: 1}
+ optional_uint64_wrapper: {value: 1}
+ optional_float_wrapper: {value: 1}
+ optional_double_wrapper: {value: 1}
+ optional_string_wrapper: {value: "1"}
+ optional_bytes_wrapper: {value: "\x01\x02"}
+ "RepeatedBoolWrapper",
+ R"({"repeatedBoolWrapper": [true, false]})",
+ "repeated_bool_wrapper: {value: true}"
+ "repeated_bool_wrapper: {value: false}");
+ "RepeatedInt32Wrapper",
+ R"({"repeatedInt32Wrapper": [0, 1]})",
+ "repeated_int32_wrapper: {value: 0}"
+ "repeated_int32_wrapper: {value: 1}");
+ "RepeatedUint32Wrapper",
+ R"({"repeatedUint32Wrapper": [0, 1]})",
+ "repeated_uint32_wrapper: {value: 0}"
+ "repeated_uint32_wrapper: {value: 1}");
+ "RepeatedInt64Wrapper",
+ R"({"repeatedInt64Wrapper": [0, 1]})",
+ "repeated_int64_wrapper: {value: 0}"
+ "repeated_int64_wrapper: {value: 1}");
+ "RepeatedUint64Wrapper",
+ R"({"repeatedUint64Wrapper": [0, 1]})",
+ "repeated_uint64_wrapper: {value: 0}"
+ "repeated_uint64_wrapper: {value: 1}");
+ "RepeatedFloatWrapper",
+ R"({"repeatedFloatWrapper": [0, 1]})",
+ "repeated_float_wrapper: {value: 0}"
+ "repeated_float_wrapper: {value: 1}");
+ "RepeatedDoubleWrapper",
+ R"({"repeatedDoubleWrapper": [0, 1]})",
+ "repeated_double_wrapper: {value: 0}"
+ "repeated_double_wrapper: {value: 1}");
+ "RepeatedStringWrapper",
+ R"({"repeatedStringWrapper": ["", "AQI="]})",
+ repeated_string_wrapper: {value: ""}
+ repeated_string_wrapper: {value: "AQI="}
+ "RepeatedBytesWrapper",
+ R"({"repeatedBytesWrapper": ["", "AQI="]})",
+ repeated_bytes_wrapper: {value: ""}
+ repeated_bytes_wrapper: {value: "\x01\x02"}
+ "WrapperTypesWithNullValue",
+ "optionalBoolWrapper": null,
+ "optionalInt32Wrapper": null,
+ "optionalUint32Wrapper": null,
+ "optionalInt64Wrapper": null,
+ "optionalUint64Wrapper": null,
+ "optionalFloatWrapper": null,
+ "optionalDoubleWrapper": null,
+ "optionalStringWrapper": null,
+ "optionalBytesWrapper": null,
+ "repeatedBoolWrapper": null,
+ "repeatedInt32Wrapper": null,
+ "repeatedUint32Wrapper": null,
+ "repeatedInt64Wrapper": null,
+ "repeatedUint64Wrapper": null,
+ "repeatedFloatWrapper": null,
+ "repeatedDoubleWrapper": null,
+ "repeatedStringWrapper": null,
+ "repeatedBytesWrapper": null
+ // Duration
+ "DurationMinValue",
+ R"({"optionalDuration": "-315576000000.999999999s"})",
+ "optional_duration: {seconds: -315576000000 nanos: -999999999}");
+ "DurationMaxValue",
+ R"({"optionalDuration": "315576000000.999999999s"})",
+ "optional_duration: {seconds: 315576000000 nanos: 999999999}");
+ "DurationRepeatedValue",
+ R"({"repeatedDuration": ["1.5s", "-1.5s"]})",
+ "repeated_duration: {seconds: 1 nanos: 500000000}"
+ "repeated_duration: {seconds: -1 nanos: -500000000}");
+ "DurationMissingS",
+ R"({"optionalDuration": "1"})");
+ "DurationJsonInputTooSmall",
+ R"({"optionalDuration": "-315576000001.000000000s"})");
+ "DurationJsonInputTooLarge",
+ R"({"optionalDuration": "315576000001.000000000s"})");
+ ExpectSerializeFailureForJson(
+ "DurationProtoInputTooSmall",
+ "optional_duration: {seconds: -315576000001 nanos: 0}");
+ "DurationProtoInputTooLarge",
+ "optional_duration: {seconds: 315576000001 nanos: 0}");
+ "DurationHasZeroFractionalDigit",
+ R"({"optionalDuration": "1.000000000s"})",
+ return value["optionalDuration"].asString() == "1s";
+ "DurationHas3FractionalDigits",
+ R"({"optionalDuration": "1.010000000s"})",
+ return value["optionalDuration"].asString() == "1.010s";
+ "DurationHas6FractionalDigits",
+ R"({"optionalDuration": "1.000010000s"})",
+ return value["optionalDuration"].asString() == "1.000010s";
+ "DurationHas9FractionalDigits",
+ R"({"optionalDuration": "1.000000010s"})",
+ return value["optionalDuration"].asString() == "1.000000010s";
+ // Timestamp
+ "TimestampMinValue",
+ R"({"optionalTimestamp": "0001-01-01T00:00:00Z"})",
+ "optional_timestamp: {seconds: -62135596800}");
+ "TimestampMaxValue",
+ R"({"optionalTimestamp": "9999-12-31T23:59:59.999999999Z"})",
+ "optional_timestamp: {seconds: 253402300799 nanos: 999999999}");
+ "TimestampRepeatedValue",
+ "repeatedTimestamp": [
+ "0001-01-01T00:00:00Z",
+ "9999-12-31T23:59:59.999999999Z"
+ ]
+ "repeated_timestamp: {seconds: -62135596800}"
+ "repeated_timestamp: {seconds: 253402300799 nanos: 999999999}");
+ "TimestampWithPositiveOffset",
+ R"({"optionalTimestamp": "1970-01-01T08:00:00+08:00"})",
+ "optional_timestamp: {seconds: 0}");
+ "TimestampWithNegativeOffset",
+ R"({"optionalTimestamp": "1969-12-31T16:00:00-08:00"})",
+ "TimestampJsonInputTooSmall",
+ R"({"optionalTimestamp": "0000-01-01T00:00:00Z"})");
+ "TimestampJsonInputTooLarge",
+ R"({"optionalTimestamp": "10000-01-01T00:00:00Z"})");
+ "TimestampJsonInputMissingZ",
+ R"({"optionalTimestamp": "0001-01-01T00:00:00"})");
+ "TimestampJsonInputMissingT",
+ R"({"optionalTimestamp": "0001-01-01 00:00:00Z"})");
+ "TimestampJsonInputLowercaseZ",
+ R"({"optionalTimestamp": "0001-01-01T00:00:00z"})");
+ "TimestampJsonInputLowercaseT",
+ R"({"optionalTimestamp": "0001-01-01t00:00:00Z"})");
+ "TimestampProtoInputTooSmall",
+ "optional_timestamp: {seconds: -62135596801}");
+ "TimestampProtoInputTooLarge",
+ "optional_timestamp: {seconds: 253402300800}");
+ "TimestampZeroNormalized",
+ return value["optionalTimestamp"].asString() ==
+ "1970-01-01T00:00:00Z";
+ "TimestampHasZeroFractionalDigit",
+ R"({"optionalTimestamp": "1970-01-01T00:00:00.000000000Z"})",
+ "TimestampHas3FractionalDigits",
+ R"({"optionalTimestamp": "1970-01-01T00:00:00.010000000Z"})",
+ "1970-01-01T00:00:00.010Z";
+ "TimestampHas6FractionalDigits",
+ R"({"optionalTimestamp": "1970-01-01T00:00:00.000010000Z"})",
+ "1970-01-01T00:00:00.000010Z";
+ "TimestampHas9FractionalDigits",
+ R"({"optionalTimestamp": "1970-01-01T00:00:00.000000010Z"})",
+ "1970-01-01T00:00:00.000000010Z";
+ // FieldMask
+ "FieldMask",
+ R"({"optionalFieldMask": "foo,barBaz"})",
+ R"(optional_field_mask: {paths: "foo" paths: "bar_baz"})");
+ "FieldMaskInvalidCharacter",
+ R"({"optionalFieldMask": "foo,bar_bar"})");
+ "FieldMaskPathsDontRoundTrip",
+ R"(optional_field_mask: {paths: "fooBar"})");
+ "FieldMaskNumbersDontRoundTrip",
+ R"(optional_field_mask: {paths: "foo_3_bar"})");
+ "FieldMaskTooManyUnderscore",
+ R"(optional_field_mask: {paths: "foo__bar"})");
+ // Struct
+ "Struct",
+ "optionalStruct": {
+ "nullValue": null,
+ "intValue": 1234,
+ "boolValue": true,
+ "doubleValue": 1234.5678,
+ "stringValue": "Hello world!",
+ "listValue": [1234, "5678"],
+ "objectValue": {
+ "value": 0
+ optional_struct: {
+ fields: {
+ key: "nullValue"
+ value: {null_value: NULL_VALUE}
+ key: "intValue"
+ value: {number_value: 1234}
+ key: "boolValue"
+ value: {bool_value: true}
+ key: "doubleValue"
+ value: {number_value: 1234.5678}
+ key: "stringValue"
+ value: {string_value: "Hello world!"}
+ key: "listValue"
+ value: {
+ list_value: {
+ values: {
+ number_value: 1234
+ string_value: "5678"
+ key: "objectValue"
+ struct_value: {
+ key: "value"
+ number_value: 0
+ // Value
+ "ValueAcceptInteger",
+ R"({"optionalValue": 1})",
+ "optional_value: { number_value: 1}");
+ "ValueAcceptFloat",
+ R"({"optionalValue": 1.5})",
+ "optional_value: { number_value: 1.5}");
+ "ValueAcceptBool",
+ R"({"optionalValue": false})",
+ "optional_value: { bool_value: false}");
+ "ValueAcceptNull",
+ R"({"optionalValue": null})",
+ "optional_value: { null_value: NULL_VALUE}");
+ "ValueAcceptString",
+ R"({"optionalValue": "hello"})",
+ R"(optional_value: { string_value: "hello"})");
+ "ValueAcceptList",
+ R"({"optionalValue": [0, "hello"]})",
+ optional_value: {
+ string_value: "hello"
+ "ValueAcceptObject",
+ R"({"optionalValue": {"value": 1}})",
+ number_value: 1
+ // Any
+ "Any",
+ "optionalAny": {
+ "@type": "type.googleapis.com/conformance.TestAllTypes",
+ "optionalInt32": 12345
+ optional_any: {
+ [type.googleapis.com/conformance.TestAllTypes] {
+ optional_int32: 12345
+ "AnyNested",
+ "@type": "type.googleapis.com/google.protobuf.Any",
+ "value": {
+ [type.googleapis.com/google.protobuf.Any] {
+ // The special "@type" tag is not required to appear first.
+ "AnyUnorderedTypeTag",
+ "optionalInt32": 12345,
+ "@type": "type.googleapis.com/conformance.TestAllTypes"
+ // Well-known types in Any.
+ "AnyWithInt32ValueWrapper",
+ "@type": "type.googleapis.com/google.protobuf.Int32Value",
+ "value": 12345
+ [type.googleapis.com/google.protobuf.Int32Value] {
+ value: 12345
+ "AnyWithDuration",
+ "@type": "type.googleapis.com/google.protobuf.Duration",
+ "value": "1.5s"
+ [type.googleapis.com/google.protobuf.Duration] {
+ seconds: 1
+ nanos: 500000000
+ "AnyWithTimestamp",
+ "@type": "type.googleapis.com/google.protobuf.Timestamp",
+ "value": "1970-01-01T00:00:00Z"
+ [type.googleapis.com/google.protobuf.Timestamp] {
+ seconds: 0
+ nanos: 0
+ "AnyWithFieldMask",
+ "@type": "type.googleapis.com/google.protobuf.FieldMask",
+ "value": "foo,barBaz"
+ [type.googleapis.com/google.protobuf.FieldMask] {
+ paths: ["foo", "bar_baz"]
+ "AnyWithStruct",
+ "@type": "type.googleapis.com/google.protobuf.Struct",
+ "foo": 1
+ [type.googleapis.com/google.protobuf.Struct] {
+ key: "foo"
+ "AnyWithValueForJsonObject",
+ "@type": "type.googleapis.com/google.protobuf.Value",
+ [type.googleapis.com/google.protobuf.Value] {
+ "AnyWithValueForInteger",
+ "value": 1
+ bool ok = true;
+ if (!CheckSetEmpty(expected_to_fail_,
+ "These tests were listed in the failure list, but they "
+ "don't exist. Remove them from the failure list")) {
+ ok = false;
+ if (!CheckSetEmpty(unexpected_failing_tests_,
+ "These tests failed. If they can't be fixed right now, "
+ "you can add them to the failure list so the overall "
+ "suite can succeed")) {
- CheckSetEmpty(unexpected_succeeding_tests_,
- "These tests succeeded, even though they were listed in "
- "the failure list. Remove them from the failure list");
+ // Sometimes the testee may be fixed before we update the failure list (e.g.,
+ // the testee is from a different component). We warn about this case but
+ // don't consider it an overall test failure.
+ CheckSetEmpty(unexpected_succeeding_tests_,
+ "These tests succeeded, even though they were listed in "
+ "the failure list. Remove them from the failure list");
+ CheckSetEmpty(skipped_,
+ "These tests were skipped (probably because support for some "
+ "features is not implemented)");
if (verbose_) {
CheckSetEmpty(skipped_,
"These tests were skipped (probably because support for some "
@@ -38,14 +38,19 @@
#ifndef CONFORMANCE_CONFORMANCE_TEST_H
#define CONFORMANCE_CONFORMANCE_TEST_H
+#include <functional>
#include <string>
#include <google/protobuf/stubs/common.h>
#include <google/protobuf/util/type_resolver.h>
namespace conformance {
class ConformanceRequest;
class ConformanceResponse;
+class TestAllTypes;
} // namespace conformance
namespace google {
@@ -53,6 +58,8 @@ namespace protobuf {
class ConformanceTestRunner {
public:
+ virtual ~ConformanceTestRunner() {}
// Call to run a single conformance test.
//
// "input" is a serialized conformance.ConformanceRequest.
@@ -60,7 +67,9 @@ class ConformanceTestRunner {
// If there is any error in running the test itself, set "runtime_error" in
// the response.
- virtual void RunTest(const std::string& input, std::string* output) = 0;
+ virtual void RunTest(const std::string& test_name,
+ const std::string& input,
+ std::string* output) = 0;
};
// Class representing the test suite itself. To run it, implement your own
@@ -118,6 +127,18 @@ class ConformanceTestSuite {
conformance::WireFormat requested_output);
void RunValidJsonTest(const string& test_name, const string& input_json,
const string& equivalent_text_format);
+ void RunValidJsonTestWithProtobufInput(const string& test_name,
+ const conformance::TestAllTypes& input,
+ const string& equivalent_text_format);
+ typedef std::function<bool(const Json::Value&)> Validator;
+ void RunValidJsonTestWithValidator(const string& test_name,
+ const string& input_json,
+ const Validator& validator);
+ void ExpectParseFailureForJson(const string& test_name,
+ const string& input_json);
+ void ExpectSerializeFailureForJson(const string& test_name,
+ const string& text_format);
void ExpectParseFailureForProto(const std::string& proto,
const std::string& test_name);
void ExpectHardParseFailureForProto(const std::string& proto,
@@ -53,6 +53,7 @@
// 3. testee sends 4-byte length M (little endian)
// 4. testee sends M bytes representing a ConformanceResponse proto
+#include <algorithm>
#include <errno.h>
#include <unistd.h>
#include <fstream>
@@ -80,13 +81,19 @@ using std::vector;
class ForkPipeRunner : public google::protobuf::ConformanceTestRunner {
ForkPipeRunner(const std::string &executable)
- : executable_(executable), running_(false) {}
+ : running_(false), executable_(executable) {}
- void RunTest(const std::string& request, std::string* response) {
+ virtual ~ForkPipeRunner() {}
+ void RunTest(const std::string& test_name,
+ const std::string& request,
+ std::string* response) {
if (!running_) {
SpawnTestProgram();
+ current_test_name_ = test_name;
uint32_t len = request.size();
CheckedWrite(write_fd_, &len, sizeof(uint32_t));
CheckedWrite(write_fd_, request.c_str(), request.size());
@@ -158,7 +165,9 @@ class ForkPipeRunner : public google::protobuf::ConformanceTestRunner {
void CheckedWrite(int fd, const void *buf, size_t len) {
if (write(fd, buf, len) != len) {
- GOOGLE_LOG(FATAL) << "Error writing to test program: " << strerror(errno);
+ GOOGLE_LOG(FATAL) << current_test_name_
+ << ": error writing to test program: "
+ << strerror(errno);
@@ -168,9 +177,12 @@ class ForkPipeRunner : public google::protobuf::ConformanceTestRunner {
ssize_t bytes_read = read(fd, (char*)buf + ofs, len);
if (bytes_read == 0) {
- GOOGLE_LOG(FATAL) << "Unexpected EOF from test program";
+ << ": unexpected EOF from test program";
} else if (bytes_read < 0) {
- GOOGLE_LOG(FATAL) << "Error reading from test program: " << strerror(errno);
+ << ": error reading from test program: "
len -= bytes_read;
@@ -182,6 +194,7 @@ class ForkPipeRunner : public google::protobuf::ConformanceTestRunner {
int read_fd_;
bool running_;
std::string executable_;
+ std::string current_test_name_;
void UsageError() {
@@ -223,7 +236,6 @@ void ParseFailureList(const char *filename, vector<string>* failure_list) {
int main(int argc, char *argv[]) {
- int arg = 1;
char *program;
google::protobuf::ConformanceTestSuite suite;
@@ -7,6 +7,92 @@
# TODO(haberman): insert links to corresponding bugs tracking the issue.
# Should we use GitHub issues or the Google-internal bug tracker?
+FieldMaskNumbersDontRoundTrip.JsonOutput
+FieldMaskPathsDontRoundTrip.JsonOutput
+FieldMaskTooManyUnderscore.JsonOutput
+JsonInput.AnyUnorderedTypeTag.JsonOutput
+JsonInput.AnyUnorderedTypeTag.ProtobufOutput
+JsonInput.AnyWithValueForInteger.JsonOutput
+JsonInput.AnyWithValueForInteger.ProtobufOutput
+JsonInput.AnyWithValueForJsonObject.JsonOutput
+JsonInput.AnyWithValueForJsonObject.ProtobufOutput
+JsonInput.BoolFieldDoubleQuotedFalse
+JsonInput.BoolFieldDoubleQuotedTrue
+JsonInput.BoolFieldIntegerOne
+JsonInput.BoolFieldIntegerZero
+JsonInput.BytesFieldInvalidBase64Characters
+JsonInput.BytesFieldNoPadding
+JsonInput.DoubleFieldTooSmall
+JsonInput.DurationHasZeroFractionalDigit.Validator
+JsonInput.DurationJsonInputTooLarge
+JsonInput.DurationJsonInputTooSmall
+JsonInput.DurationMissingS
+JsonInput.EnumFieldUnknownValue.Validator
+JsonInput.FieldMaskInvalidCharacter
+JsonInput.FieldNameDuplicate
+JsonInput.FieldNameDuplicateDifferentCasing1
+JsonInput.FieldNameDuplicateDifferentCasing2
+JsonInput.FieldNameInLowerCamelCase.Validator
+JsonInput.FieldNameInSnakeCase.JsonOutput
+JsonInput.FieldNameInSnakeCase.ProtobufOutput
+JsonInput.FieldNameNotQuoted
+JsonInput.FieldNameWithMixedCases.JsonOutput
+JsonInput.FieldNameWithMixedCases.ProtobufOutput
+JsonInput.FieldNameWithMixedCases.Validator
+JsonInput.FloatFieldTooLarge
+JsonInput.FloatFieldTooSmall
+JsonInput.Int32FieldLeadingSpace
+JsonInput.Int32FieldLeadingZero
+JsonInput.Int32FieldMinFloatValue.JsonOutput
+JsonInput.Int32FieldMinFloatValue.ProtobufOutput
+JsonInput.Int32FieldMinValue.JsonOutput
+JsonInput.Int32FieldMinValue.ProtobufOutput
+JsonInput.Int32FieldNegativeWithLeadingZero
+JsonInput.Int32FieldNotInteger
+JsonInput.Int32FieldNotNumber
+JsonInput.Int32FieldTooLarge
+JsonInput.Int32FieldTooSmall
+JsonInput.Int32FieldTrailingSpace
+JsonInput.Int64FieldNotInteger
+JsonInput.Int64FieldNotNumber
+JsonInput.Int64FieldTooLarge
+JsonInput.Int64FieldTooSmall
+JsonInput.MapFieldValueIsNull
+JsonInput.OneofFieldDuplicate
+JsonInput.RepeatedFieldMessageElementIsNull
+JsonInput.RepeatedFieldPrimitiveElementIsNull
+JsonInput.RepeatedFieldTrailingComma
+JsonInput.RepeatedFieldWrongElementTypeExpectingIntegersGotBool
+JsonInput.RepeatedFieldWrongElementTypeExpectingIntegersGotMessage
+JsonInput.RepeatedFieldWrongElementTypeExpectingIntegersGotString
+JsonInput.RepeatedFieldWrongElementTypeExpectingMessagesGotBool
+JsonInput.RepeatedFieldWrongElementTypeExpectingMessagesGotInt
+JsonInput.RepeatedFieldWrongElementTypeExpectingMessagesGotString
+JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotBool
+JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotInt
+JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotMessage
+JsonInput.StringFieldNotAString
+JsonInput.StringFieldSurrogateInWrongOrder
+JsonInput.StringFieldSurrogatePair.JsonOutput
+JsonInput.StringFieldSurrogatePair.ProtobufOutput
+JsonInput.StringFieldUnpairedHighSurrogate
+JsonInput.StringFieldUnpairedLowSurrogate
+JsonInput.StringFieldUppercaseEscapeLetter
+JsonInput.TimestampJsonInputLowercaseT
+JsonInput.TimestampJsonInputLowercaseZ
+JsonInput.TimestampJsonInputMissingT
+JsonInput.TimestampJsonInputMissingZ
+JsonInput.TimestampJsonInputTooLarge
+JsonInput.TimestampJsonInputTooSmall
+JsonInput.TrailingCommaInAnObject
+JsonInput.Uint32FieldNotInteger
+JsonInput.Uint32FieldNotNumber
+JsonInput.Uint32FieldTooLarge
+JsonInput.Uint64FieldNotInteger
+JsonInput.Uint64FieldNotNumber
+JsonInput.Uint64FieldTooLarge
+JsonInput.WrapperTypesWithNullValue.JsonOutput
+JsonInput.WrapperTypesWithNullValue.ProtobufOutput
ProtobufInput.PrematureEofBeforeKnownRepeatedValue.MESSAGE
ProtobufInput.PrematureEofInDelimitedDataForKnownNonRepeatedValue.MESSAGE
ProtobufInput.PrematureEofInDelimitedDataForKnownRepeatedValue.MESSAGE
@@ -19,3 +105,5 @@ ProtobufInput.PrematureEofInPackedField.SINT64
ProtobufInput.PrematureEofInPackedField.UINT32
ProtobufInput.PrematureEofInPackedField.UINT64
ProtobufInput.PrematureEofInsideKnownRepeatedValue.MESSAGE
+TimestampProtoInputTooLarge.JsonOutput
+TimestampProtoInputTooSmall.JsonOutput
@@ -0,0 +1,74 @@
+# This is the list of conformance tests that are known to fail for the Java
+# implementation right now. These should be fixed.
+#
+# By listing them here we can keep tabs on which ones are failing and be sure
+# that we don't introduce regressions in other tests.
+JsonInput.AnyWithFieldMask.ProtobufOutput
+JsonInput.BoolFieldAllCapitalFalse
+JsonInput.BoolFieldAllCapitalTrue
+JsonInput.BoolFieldCamelCaseFalse
+JsonInput.BoolFieldCamelCaseTrue
+JsonInput.BoolMapFieldKeyNotQuoted
+JsonInput.DoubleFieldInfinityNotQuoted
+JsonInput.DoubleFieldNanNotQuoted
+JsonInput.DoubleFieldNegativeInfinityNotQuoted
+JsonInput.EnumFieldNotQuoted
+JsonInput.EnumFieldNumericValueNonZero.JsonOutput
+JsonInput.EnumFieldNumericValueNonZero.ProtobufOutput
+JsonInput.EnumFieldNumericValueZero.JsonOutput
+JsonInput.EnumFieldNumericValueZero.ProtobufOutput
+JsonInput.FieldMask.ProtobufOutput
+JsonInput.FloatFieldInfinityNotQuoted
+JsonInput.FloatFieldNanNotQuoted
+JsonInput.FloatFieldNegativeInfinityNotQuoted
+JsonInput.Int32FieldExponentialFormat.JsonOutput
+JsonInput.Int32FieldExponentialFormat.ProtobufOutput
+JsonInput.Int32FieldFloatTrailingZero.JsonOutput
+JsonInput.Int32FieldFloatTrailingZero.ProtobufOutput
+JsonInput.Int32FieldMaxFloatValue.JsonOutput
+JsonInput.Int32FieldMaxFloatValue.ProtobufOutput
+JsonInput.Int32FieldPlusSign
+JsonInput.Int32MapFieldKeyNotQuoted
+JsonInput.Int64MapFieldKeyNotQuoted
+JsonInput.JsonWithComments
+JsonInput.OriginalProtoFieldName.JsonOutput
+JsonInput.Uint32FieldMaxFloatValue.JsonOutput
+JsonInput.Uint32FieldMaxFloatValue.ProtobufOutput
+JsonInput.Uint32MapFieldKeyNotQuoted
+JsonInput.Uint64MapFieldKeyNotQuoted
+JsonInput.ValueAcceptNull.JsonOutput
+JsonInput.ValueAcceptNull.ProtobufOutput
@@ -295,15 +295,19 @@
<include>**/MapFieldLite.java</include>
<include>**/MessageLite.java</include>
<include>**/MessageLiteOrBuilder.java</include>
+ <include>**/MessageLiteToString.java</include>
<include>**/MutabilityOracle.java</include>
+ <include>**/NioByteString.java</include>
<include>**/Parser.java</include>
<include>**/ProtobufArrayList.java</include>
<include>**/ProtocolStringList.java</include>
<include>**/RopeByteString.java</include>
<include>**/SmallSortedMap.java</include>
+ <include>**/TextFormatEscaper.java</include>
<include>**/UninitializedMessageException.java</include>
<include>**/UnknownFieldSetLite.java</include>
<include>**/UnmodifiableLazyStringList.java</include>
+ <include>**/UnsafeByteStrings.java</include>
<include>**/Utf8.java</include>
<include>**/WireFormat.java</include>
</includes>
@@ -316,6 +320,7 @@
<testInclude>**/LazyMessageLiteTest.java</testInclude>
<testInclude>**/LiteTest.java</testInclude>
<testInclude>**/LongArrayListTest.java</testInclude>
+ <testInclude>**/NioByteStringTest.java</testInclude>
<testInclude>**/ProtobufArrayListTest.java</testInclude>
<testInclude>**/UnknownFieldSetLiteTest.java</testInclude>
</testIncludes>
@@ -30,6 +30,7 @@
package com.google.protobuf;
+import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import com.google.protobuf.Internal.EnumLite;
@@ -161,10 +162,18 @@ public abstract class AbstractMessage extends AbstractMessageLite
Descriptors.Descriptor descriptor = entry.getDescriptorForType();
Descriptors.FieldDescriptor key = descriptor.findFieldByName("key");
Descriptors.FieldDescriptor value = descriptor.findFieldByName("value");
- result.put(entry.getField(key), entry.getField(value));
+ Object fieldValue = entry.getField(value);
+ if (fieldValue instanceof EnumValueDescriptor) {
+ fieldValue = ((EnumValueDescriptor) fieldValue).getNumber();
+ result.put(entry.getField(key), fieldValue);
while (iterator.hasNext()) {
entry = (Message) iterator.next();
+ fieldValue = entry.getField(value);
return result;
@@ -68,10 +68,17 @@ final class BooleanArrayList
private int size;
/**
- * Constructs a new mutable {@code BooleanArrayList}.
+ * Constructs a new mutable {@code BooleanArrayList} with default capacity.
*/
BooleanArrayList() {
- array = new boolean[DEFAULT_CAPACITY];
+ this(DEFAULT_CAPACITY);
+ /**
+ * Constructs a new mutable {@code BooleanArrayList} with the provided capacity.
+ */
+ BooleanArrayList(int capacity) {
+ array = new boolean[capacity];
size = 0;
@@ -33,7 +33,6 @@ package com.google.protobuf;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
-import java.util.NoSuchElementException;
* This class is used to represent the substring of a {@link ByteString} over a
@@ -47,7 +46,7 @@ import java.util.NoSuchElementException;
*
* @author carlanton@google.com (Carl Haverl)
-class BoundedByteString extends LiteralByteString {
+final class BoundedByteString extends LiteralByteString {
private final int bytesOffset;
private final int bytesLength;
@@ -65,16 +64,7 @@ class BoundedByteString extends LiteralByteString {
BoundedByteString(byte[] bytes, int offset, int length) {
super(bytes);
- if (offset < 0) {
- throw new IllegalArgumentException("Offset too small: " + offset);
- }
- if (length < 0) {
- throw new IllegalArgumentException("Length too small: " + offset);
- if ((long) offset + length > bytes.length) {
- throw new IllegalArgumentException(
- "Offset+Length too large: " + offset + "+" + length);
+ checkRange(offset, offset + length, bytes.length);
this.bytesOffset = offset;
this.bytesLength = length;
@@ -94,14 +84,7 @@ class BoundedByteString extends LiteralByteString {
public byte byteAt(int index) {
// We must check the index ourselves as we cannot rely on Java array index
// checking for substrings.
- if (index < 0) {
- throw new ArrayIndexOutOfBoundsException("Index too small: " + index);
- if (index >= size()) {
- throw new ArrayIndexOutOfBoundsException(
- "Index too large: " + index + ", " + size());
+ checkIndex(index, size());
return bytes[bytesOffset + index];
@@ -119,8 +102,8 @@ class BoundedByteString extends LiteralByteString {
// ByteString -> byte[]
@Override
- protected void copyToInternal(byte[] target, int sourceOffset,
- int targetOffset, int numberToCopy) {
+ protected void copyToInternal(byte[] target, int sourceOffset, int targetOffset,
+ int numberToCopy) {
System.arraycopy(bytes, getOffsetIntoBytes() + sourceOffset, target,
targetOffset, numberToCopy);
@@ -134,47 +117,8 @@ class BoundedByteString extends LiteralByteString {
return new LiteralByteString(toByteArray());
- private void readObject(ObjectInputStream in) throws IOException {
+ private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException {
throw new InvalidObjectException(
"BoundedByteStream instances are not to be serialized directly");
- // =================================================================
- // ByteIterator
- @Override
- public ByteIterator iterator() {
- return new BoundedByteIterator();
- private class BoundedByteIterator implements ByteIterator {
- private int position;
- private final int limit;
- private BoundedByteIterator() {
- position = getOffsetIntoBytes();
- limit = position + size();
- public boolean hasNext() {
- return (position < limit);
- public Byte next() {
- // Boxing calls Byte.valueOf(byte), which does not instantiate.
- return nextByte();
- public byte nextByte() {
- if (position >= limit) {
- throw new NoSuchElementException();
- return bytes[position++];
- public void remove() {
- throw new UnsupportedOperationException();
@@ -83,6 +83,13 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
public static final ByteString EMPTY = new LiteralByteString(new byte[0]);
+ * Cached hash value. Intentionally accessed via a data race, which
+ * is safe because of the Java Memory Model's "no out-of-thin-air values"
+ * guarantees for ints. A value of 0 implies that the hash has not been set.
+ private int hash = 0;
// This constructor is here to prevent subclassing outside of this package,
ByteString() {}
@@ -105,7 +112,38 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @return the iterator
- public abstract ByteIterator iterator();
+ @Override
+ public final ByteIterator iterator() {
+ return new ByteIterator() {
+ private int position = 0;
+ private final int limit = size();
+ public boolean hasNext() {
+ return position < limit;
+ public Byte next() {
+ // Boxing calls Byte.valueOf(byte), which does not instantiate.
+ return nextByte();
+ public byte nextByte() {
+ return byteAt(position++);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new NoSuchElementException(e.getMessage());
+ public void remove() {
+ throw new UnsupportedOperationException();
+ };
* This interface extends {@code Iterator<Byte>}, so that we can return an
@@ -134,7 +172,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @return true if this is zero bytes long
- public boolean isEmpty() {
+ public final boolean isEmpty() {
return size() == 0;
@@ -150,7 +188,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @throws IndexOutOfBoundsException if {@code beginIndex < 0} or
* {@code beginIndex > size()}.
- public ByteString substring(int beginIndex) {
+ public final ByteString substring(int beginIndex) {
return substring(beginIndex, size());
@@ -175,7 +213,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* argument is a prefix of the byte sequence represented by
* this string; <code>false</code> otherwise.
- public boolean startsWith(ByteString prefix) {
+ public final boolean startsWith(ByteString prefix) {
return size() >= prefix.size() &&
substring(0, prefix.size()).equals(prefix);
@@ -189,7 +227,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* argument is a suffix of the byte sequence represented by
- public boolean endsWith(ByteString suffix) {
+ public final boolean endsWith(ByteString suffix) {
return size() >= suffix.size() &&
substring(size() - suffix.size()).equals(suffix);
@@ -309,8 +347,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
public static ByteString readFrom(InputStream streamToDrain)
throws IOException {
- return readFrom(
- streamToDrain, MIN_READ_FROM_CHUNK_SIZE, MAX_READ_FROM_CHUNK_SIZE);
+ return readFrom(streamToDrain, MIN_READ_FROM_CHUNK_SIZE, MAX_READ_FROM_CHUNK_SIZE);
@@ -383,10 +420,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
if (bytesRead == 0) {
return null;
- } else {
- // Always make a copy since InputStream could steal a reference to buf.
- return ByteString.copyFrom(buf, 0, bytesRead);
+ // Always make a copy since InputStream could steal a reference to buf.
+ return ByteString.copyFrom(buf, 0, bytesRead);
// =================================================================
@@ -402,12 +439,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @param other string to concatenate
* @return a new {@code ByteString} instance
- public ByteString concat(ByteString other) {
- int thisSize = size();
- int otherSize = other.size();
- if ((long) thisSize + otherSize >= Integer.MAX_VALUE) {
+ public final ByteString concat(ByteString other) {
+ if (Integer.MAX_VALUE - size() < other.size()) {
throw new IllegalArgumentException("ByteString would be too long: " +
- thisSize + "+" + otherSize);
+ size() + "+" + other.size());
return RopeByteString.concatenate(this, other);
@@ -426,29 +461,29 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @return new {@code ByteString}
public static ByteString copyFrom(Iterable<ByteString> byteStrings) {
- Collection<ByteString> collection;
+ // Determine the size;
+ final int size;
if (!(byteStrings instanceof Collection)) {
- collection = new ArrayList<ByteString>();
- for (ByteString byteString : byteStrings) {
- collection.add(byteString);
+ int tempSize = 0;
+ for (Iterator<ByteString> iter = byteStrings.iterator(); iter.hasNext();
+ iter.next(), ++tempSize) {
+ size = tempSize;
- collection = (Collection<ByteString>) byteStrings;
+ size = ((Collection<ByteString>) byteStrings).size();
- ByteString result;
- if (collection.isEmpty()) {
- result = EMPTY;
- result = balancedConcat(collection.iterator(), collection.size());
+ if (size == 0) {
+ return EMPTY;
- return result;
+ return balancedConcat(byteStrings.iterator(), size);
// Internal function used by copyFrom(Iterable<ByteString>).
// Create a balanced concatenation of the next "length" elements from the
// iterable.
- private static ByteString balancedConcat(Iterator<ByteString> iterator,
- int length) {
+ private static ByteString balancedConcat(Iterator<ByteString> iterator, int length) {
assert length >= 1;
ByteString result;
if (length == 1) {
@@ -486,25 +521,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @throws IndexOutOfBoundsException if an offset or size is negative or too
* large
- public void copyTo(byte[] target, int sourceOffset, int targetOffset,
+ public final void copyTo(byte[] target, int sourceOffset, int targetOffset,
int numberToCopy) {
- if (sourceOffset < 0) {
- throw new IndexOutOfBoundsException("Source offset < 0: " + sourceOffset);
- if (targetOffset < 0) {
- throw new IndexOutOfBoundsException("Target offset < 0: " + targetOffset);
- if (numberToCopy < 0) {
- throw new IndexOutOfBoundsException("Length < 0: " + numberToCopy);
- if (sourceOffset + numberToCopy > size()) {
- throw new IndexOutOfBoundsException(
- "Source end offset < 0: " + (sourceOffset + numberToCopy));
- if (targetOffset + numberToCopy > target.length) {
- "Target end offset < 0: " + (targetOffset + numberToCopy));
+ checkRange(sourceOffset, sourceOffset + numberToCopy, size());
+ checkRange(targetOffset, targetOffset + numberToCopy, target.length);
if (numberToCopy > 0) {
copyToInternal(target, sourceOffset, targetOffset, numberToCopy);
@@ -534,8 +554,8 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @return copied bytes
- public byte[] toByteArray() {
- int size = size();
+ public final byte[] toByteArray() {
+ final int size = size();
if (size == 0) {
return Internal.EMPTY_BYTE_ARRAY;
@@ -548,6 +568,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* Writes the complete contents of this byte string to
* the specified output stream argument.
+ * <p>It is assumed that the {@link OutputStream} will not modify the contents passed it
+ * it. It may be possible for a malicious {@link OutputStream} to corrupt
+ * the data underlying the {@link ByteString}.
+ *
* @param out the output stream to which to write the data.
* @throws IOException if an I/O error occurs.
@@ -563,30 +587,20 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
- void writeTo(OutputStream out, int sourceOffset, int numberToWrite)
+ final void writeTo(OutputStream out, int sourceOffset, int numberToWrite)
- if (numberToWrite < 0) {
- throw new IndexOutOfBoundsException("Length < 0: " + numberToWrite);
- if (sourceOffset + numberToWrite > size()) {
- "Source end offset exceeded: " + (sourceOffset + numberToWrite));
+ checkRange(sourceOffset, sourceOffset + numberToWrite, size());
if (numberToWrite > 0) {
writeToInternal(out, sourceOffset, numberToWrite);
* Internal version of {@link #writeTo(OutputStream,int,int)} that assumes
* all error checking has already been done.
- abstract void writeToInternal(OutputStream out, int sourceOffset,
- int numberToWrite) throws IOException;
+ abstract void writeToInternal(OutputStream out, int sourceOffset, int numberToWrite)
+ throws IOException;
* Constructs a read-only {@code java.nio.ByteBuffer} whose content
@@ -618,7 +632,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @return new string
* @throws UnsupportedEncodingException if charset isn't recognized
- public String toString(String charsetName)
+ public final String toString(String charsetName)
throws UnsupportedEncodingException {
try {
return toString(Charset.forName(charsetName));
@@ -636,7 +650,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @param charset encode using this charset
- public String toString(Charset charset) {
+ public final String toString(Charset charset) {
return size() == 0 ? "" : toStringInternal(charset);
@@ -657,7 +671,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @return new string using UTF-8 encoding
- public String toStringUtf8() {
+ public final String toStringUtf8() {
return toString(Internal.UTF_8);
@@ -716,13 +730,51 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
public abstract boolean equals(Object o);
- * Return a non-zero hashCode depending only on the sequence of bytes
- * in this ByteString.
+ * Base class for leaf {@link ByteString}s (i.e. non-ropes).
+ abstract static class LeafByteString extends ByteString {
+ protected final int getTreeDepth() {
+ return 0;
+ protected final boolean isBalanced() {
+ return true;
+ * Check equality of the substring of given length of this object starting at
+ * zero with another {@code ByteString} substring starting at offset.
+ * @param other what to compare a substring in
+ * @param offset offset into other
+ * @param length number of bytes to compare
+ * @return true for equality of substrings, else false.
+ abstract boolean equalsRange(ByteString other, int offset, int length);
+ * Compute the hashCode using the traditional algorithm from {@link
+ * ByteString}.
- * @return hashCode value for this object
+ * @return hashCode value
- public abstract int hashCode();
+ public final int hashCode() {
+ int h = hash;
+ if (h == 0) {
+ int size = size();
+ h = partialHash(size, 0, size);
+ h = 1;
+ hash = h;
+ return h;
// Input stream
@@ -1034,7 +1086,9 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @return value of cached hash code or 0 if not computed yet
- protected abstract int peekCachedHashCode();
+ protected final int peekCachedHashCode() {
+ return hash;
* Compute the hash across the value bytes starting with the given hash, and
@@ -1049,8 +1103,49 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
protected abstract int partialHash(int h, int offset, int length);
+ * Checks that the given index falls within the specified array size.
+ * @param index the index position to be tested
+ * @param size the length of the array
+ * @throws ArrayIndexOutOfBoundsException if the index does not fall within the array.
+ static void checkIndex(int index, int size) {
+ if ((index | (size - (index + 1))) < 0) {
+ if (index < 0) {
+ throw new ArrayIndexOutOfBoundsException("Index < 0: " + index);
+ throw new ArrayIndexOutOfBoundsException("Index > length: " + index + ", " + size);
+ * Checks that the given range falls within the bounds of an array
+ * @param startIndex the start index of the range (inclusive)
+ * @param endIndex the end index of the range (exclusive)
+ * @param size the size of the array.
+ * @return the length of the range.
+ * @throws ArrayIndexOutOfBoundsException some or all of the range falls outside of the array.
+ static int checkRange(int startIndex, int endIndex, int size) {
+ final int length = endIndex - startIndex;
+ if ((startIndex | endIndex | length | (size - endIndex)) < 0) {
+ if (startIndex < 0) {
+ throw new IndexOutOfBoundsException("Beginning index: " + startIndex + " < 0");
+ if (endIndex < startIndex) {
+ throw new IndexOutOfBoundsException(
+ "Beginning index larger than ending index: " + startIndex + ", " + endIndex);
+ // endIndex >= size
+ throw new IndexOutOfBoundsException("End index: " + endIndex + " >= " + size);
+ return length;
- public String toString() {
+ public final String toString() {
return String.format("<ByteString@%s size=%d>",
Integer.toHexString(System.identityHashCode(this)), size());
@@ -1055,20 +1055,6 @@ public final class CodedInputStream {
private RefillCallback refillCallback = null;
- /**
- * Ensures that at least {@code n} bytes are available in the buffer, reading
- * more bytes from the input if necessary to make it so. Caller must ensure
- * that the requested space is less than BUFFER_SIZE.
- *
- * @throws InvalidProtocolBufferException The end of the stream or the current
- * limit was reached.
- */
- private void ensureAvailable(int n) throws IOException {
- if (bufferSize - bufferPos < n) {
- refillBuffer(n);
* Reads more bytes from the input, making at least {@code n} bytes available
* in the buffer. Caller must ensure that the requested space is not yet
@@ -1180,86 +1166,97 @@ public final class CodedInputStream {
- if (totalBytesRetired + bufferPos + size > currentLimit) {
+ // Verify that the message size so far has not exceeded sizeLimit.
+ int currentMessageSize = totalBytesRetired + bufferPos + size;
+ if (currentMessageSize > sizeLimit) {
+ throw InvalidProtocolBufferException.sizeLimitExceeded();
+ // Verify that the message size so far has not exceeded currentLimit.
+ if (currentMessageSize > currentLimit) {
// Read to the end of the stream anyway.
skipRawBytes(currentLimit - totalBytesRetired - bufferPos);
- // Then fail.
throw InvalidProtocolBufferException.truncatedMessage();
- if (size < BUFFER_SIZE) {
- // Reading more bytes than are in the buffer, but not an excessive number
- // of bytes. We can safely allocate the resulting array ahead of time.
+ // We need the input stream to proceed.
+ if (input == null) {
+ throw InvalidProtocolBufferException.truncatedMessage();
+ final int originalBufferPos = bufferPos;
+ final int bufferedBytes = bufferSize - bufferPos;
+ // Mark the current buffer consumed.
+ totalBytesRetired += bufferSize;
+ bufferPos = 0;
+ bufferSize = 0;
- // First copy what we have.
+ // Determine the number of bytes we need to read from the input stream.
+ int sizeLeft = size - bufferedBytes;
+ // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE.
+ if (sizeLeft < BUFFER_SIZE || sizeLeft <= input.available()) {
+ // Either the bytes we need are known to be available, or the required buffer is
+ // within an allowed threshold - go ahead and allocate the buffer now.
final byte[] bytes = new byte[size];
- int pos = bufferSize - bufferPos;
- System.arraycopy(buffer, bufferPos, bytes, 0, pos);
- bufferPos = bufferSize;
- // We want to refill the buffer and then copy from the buffer into our
- // byte array rather than reading directly into our byte array because
- // the input may be unbuffered.
- ensureAvailable(size - pos);
- System.arraycopy(buffer, 0, bytes, pos, size - pos);
- bufferPos = size - pos;
+ // Copy all of the buffered bytes to the result buffer.
+ System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes);
- return bytes;
- // The size is very large. For security reasons, we can't allocate the
- // entire byte array yet. The size comes directly from the input, so a
- // maliciously-crafted message could provide a bogus very large size in
- // order to trick the app into allocating a lot of memory. We avoid this
- // by allocating and reading only a small chunk at a time, so that the
- // malicious message must actually *be* extremely large to cause
- // problems. Meanwhile, we limit the allowed size of a message elsewhere.
- // Remember the buffer markers since we'll have to copy the bytes out of
- // it later.
- final int originalBufferPos = bufferPos;
- final int originalBufferSize = bufferSize;
- // Mark the current buffer consumed.
- totalBytesRetired += bufferSize;
- bufferPos = 0;
- bufferSize = 0;
- // Read all the rest of the bytes we need.
- int sizeLeft = size - (originalBufferSize - originalBufferPos);
- final List<byte[]> chunks = new ArrayList<byte[]>();
- while (sizeLeft > 0) {
- final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)];
- int pos = 0;
- while (pos < chunk.length) {
- final int n = (input == null) ? -1 :
- input.read(chunk, pos, chunk.length - pos);
- if (n == -1) {
- throw InvalidProtocolBufferException.truncatedMessage();
- totalBytesRetired += n;
- pos += n;
+ // Fill the remaining bytes from the input stream.
+ int pos = bufferedBytes;
+ while (pos < bytes.length) {
+ int n = input.read(bytes, pos, size - pos);
+ if (n == -1) {
- sizeLeft -= chunk.length;
- chunks.add(chunk);
+ totalBytesRetired += n;
+ pos += n;
- // OK, got everything. Now concatenate it all into one buffer.
- final byte[] bytes = new byte[size];
- // Start by copying the leftover bytes from this.buffer.
- int pos = originalBufferSize - originalBufferPos;
- System.arraycopy(buffer, originalBufferPos, bytes, 0, pos);
+ return bytes;
- // And now all the chunks.
- for (final byte[] chunk : chunks) {
- System.arraycopy(chunk, 0, bytes, pos, chunk.length);
- pos += chunk.length;
+ // The size is very large. For security reasons, we can't allocate the
+ // entire byte array yet. The size comes directly from the input, so a
+ // maliciously-crafted message could provide a bogus very large size in
+ // order to trick the app into allocating a lot of memory. We avoid this
+ // by allocating and reading only a small chunk at a time, so that the
+ // malicious message must actually *be* extremely large to cause
+ // problems. Meanwhile, we limit the allowed size of a message elsewhere.
+ final List<byte[]> chunks = new ArrayList<byte[]>();
+ while (sizeLeft > 0) {
+ final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)];
+ int pos = 0;
+ while (pos < chunk.length) {
+ final int n = input.read(chunk, pos, chunk.length - pos);
+ sizeLeft -= chunk.length;
+ chunks.add(chunk);
- // Done.
+ // OK, got everything. Now concatenate it all into one buffer.
+ final byte[] bytes = new byte[size];
+ // Start by copying the leftover bytes from this.buffer.
+ // And now all the chunks.
+ for (final byte[] chunk : chunks) {
+ System.arraycopy(chunk, 0, bytes, pos, chunk.length);
+ pos += chunk.length;
+ // Done.
@@ -53,7 +53,7 @@ import java.util.logging.Logger;
* @author kneton@google.com Kenton Varda
public final class CodedOutputStream {
private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName());
// TODO(dweis): Consider migrating to a ByteBuffer.
@@ -243,19 +243,6 @@ public final class CodedOutputStream {
- * Write a group represented by an {@link UnknownFieldSet}.
- * @deprecated UnknownFieldSet now implements MessageLite, so you can just
- * call {@link #writeGroup}.
- @Deprecated
- public void writeUnknownGroup(final int fieldNumber,
- final MessageLite value)
- throws IOException {
- writeGroup(fieldNumber, value);
/** Write an embedded message field, including tag, to the stream. */
public void writeMessage(final int fieldNumber, final MessageLite value)
@@ -428,7 +415,7 @@ public final class CodedOutputStream {
efficientWriteStringNoTag(value);
} catch (UnpairedSurrogateException e) {
- logger.log(Level.WARNING,
+ logger.log(Level.WARNING,
"Converting ill-formed UTF-16. Your Protocol Buffer will not round trip correctly!", e);
inefficientWriteStringNoTag(value);
@@ -449,10 +436,10 @@ public final class CodedOutputStream {
* Write a {@code string} field to the stream efficiently. If the {@code string} is malformed,
* this method rolls back its changes and throws an {@link UnpairedSurrogateException} with the
* intent that the caller will catch and retry with {@link #inefficientWriteStringNoTag(String)}.
* @param value the string to write to the stream
- * @throws UnpairedSurrogateException when {@code value} is ill-formed UTF-16.
+ * @throws UnpairedSurrogateException when {@code value} is ill-formed UTF-16.
private void efficientWriteStringNoTag(final String value) throws IOException {
// UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()),
@@ -510,18 +497,6 @@ public final class CodedOutputStream {
- * call {@link #writeGroupNoTag}.
- public void writeUnknownGroupNoTag(final MessageLite value)
- writeGroupNoTag(value);
/** Write an embedded message field to the stream. */
public void writeMessageNoTag(final MessageLite value) throws IOException {
writeRawVarint32(value.getSerializedSize());
@@ -684,20 +659,6 @@ public final class CodedOutputStream {
return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value);
- * Compute the number of bytes that would be needed to encode a
- * {@code group} field represented by an {@code UnknownFieldSet}, including
- * tag.
- * call {@link #computeGroupSize}.
- public static int computeUnknownGroupSize(final int fieldNumber,
- final MessageLite value) {
- return computeGroupSize(fieldNumber, value);
* Compute the number of bytes that would be needed to encode an
* embedded message field, including tag.
@@ -926,19 +887,6 @@ public final class CodedOutputStream {
return value.getSerializedSize();
- * call {@link #computeUnknownGroupSizeNoTag}.
- public static int computeUnknownGroupSizeNoTag(final MessageLite value) {
- return computeGroupSizeNoTag(value);
* Compute the number of bytes that would be needed to encode an embedded
* message field.
@@ -1295,10 +1243,10 @@ public final class CodedOutputStream {
* negative.
public static int computeRawVarint32Size(final int value) {
- if ((value & (0xffffffff << 7)) == 0) return 1;
- if ((value & (0xffffffff << 14)) == 0) return 2;
- if ((value & (0xffffffff << 21)) == 0) return 3;
- if ((value & (0xffffffff << 28)) == 0) return 4;
+ if ((value & (~0 << 7)) == 0) return 1;
+ if ((value & (~0 << 14)) == 0) return 2;
+ if ((value & (~0 << 21)) == 0) return 3;
+ if ((value & (~0 << 28)) == 0) return 4;
return 5;
@@ -1316,17 +1264,16 @@ public final class CodedOutputStream {
/** Compute the number of bytes that would be needed to encode a varint. */
- public static int computeRawVarint64Size(final long value) {
- if ((value & (0xffffffffffffffffL << 7)) == 0) return 1;
- if ((value & (0xffffffffffffffffL << 14)) == 0) return 2;
- if ((value & (0xffffffffffffffffL << 21)) == 0) return 3;
- if ((value & (0xffffffffffffffffL << 28)) == 0) return 4;
- if ((value & (0xffffffffffffffffL << 35)) == 0) return 5;
- if ((value & (0xffffffffffffffffL << 42)) == 0) return 6;
- if ((value & (0xffffffffffffffffL << 49)) == 0) return 7;
- if ((value & (0xffffffffffffffffL << 56)) == 0) return 8;
- if ((value & (0xffffffffffffffffL << 63)) == 0) return 9;
- return 10;
+ public static int computeRawVarint64Size(long value) {
+ // handle two popular special cases up front ...
+ if ((value & (~0L << 7)) == 0L) return 1;
+ if (value < 0L) return 10;
+ // ... leaving us with 8 remaining, which we can divide and conquer
+ int n = 2;
+ if ((value & (~0L << 35)) != 0L) { n += 4; value >>>= 28; }
+ if ((value & (~0L << 21)) != 0L) { n += 2; value >>>= 14; }
+ if ((value & (~0L << 14)) != 0L) { n += 1; }
+ return n;
/** Write a little-endian 32-bit integer. */
@@ -889,6 +889,11 @@ public final class Descriptors {
public String getFullName() { return fullName; }
+ /** Get the JSON name of this field. */
+ public String getJsonName() {
+ return jsonName;
* Get the field's java type. This is just for convenience. Every
* {@code FieldDescriptorProto.Type} maps to exactly one Java type.
@@ -1079,6 +1084,7 @@ public final class Descriptors {
private FieldDescriptorProto proto;
private final String fullName;
+ private final String jsonName;
private final FileDescriptor file;
private final Descriptor extensionScope;
@@ -1157,6 +1163,38 @@ public final class Descriptors {
private final Object defaultDefault;
+ // TODO(xiaofeng): Implement it consistently across different languages. See b/24751348.
+ private static String fieldNameToLowerCamelCase(String name) {
+ StringBuilder result = new StringBuilder(name.length());
+ boolean isNextUpperCase = false;
+ for (int i = 0; i < name.length(); i++) {
+ Character ch = name.charAt(i);
+ if (Character.isLowerCase(ch)) {
+ if (isNextUpperCase) {
+ result.append(Character.toUpperCase(ch));
+ result.append(ch);
+ isNextUpperCase = false;
+ } else if (Character.isUpperCase(ch)) {
+ if (i == 0) {
+ // Force first letter to lower-case.
+ result.append(Character.toLowerCase(ch));
+ // Capital letters after the first are left as-is.
+ } else if (Character.isDigit(ch)) {
+ isNextUpperCase = true;
+ return result.toString();
private FieldDescriptor(final FieldDescriptorProto proto,
final FileDescriptor file,
final Descriptor parent,
@@ -1167,6 +1205,11 @@ public final class Descriptors {
this.proto = proto;
fullName = computeFullName(file, parent, proto.getName());
this.file = file;
+ if (proto.hasJsonName()) {
+ jsonName = proto.getJsonName();
+ jsonName = fieldNameToLowerCamelCase(proto.getName());
if (proto.hasType()) {
type = Type.valueOf(proto.getType());
@@ -68,10 +68,17 @@ final class DoubleArrayList
- * Constructs a new mutable {@code DoubleArrayList}.
+ * Constructs a new mutable {@code DoubleArrayList} with default capacity.
DoubleArrayList() {
- array = new double[DEFAULT_CAPACITY];
+ * Constructs a new mutable {@code DoubleArrayList} with the provided capacity.
+ DoubleArrayList(int capacity) {
+ array = new double[capacity];
@@ -67,10 +67,17 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL
- * Constructs a new mutable {@code FloatArrayList}.
+ * Constructs a new mutable {@code FloatArrayList} with default capacity.
FloatArrayList() {
- array = new float[DEFAULT_CAPACITY];
+ * Constructs a new mutable {@code FloatArrayList} with the provided capacity.
+ FloatArrayList(int capacity) {
+ array = new float[capacity];
@@ -102,6 +102,11 @@ public abstract class GeneratedMessageLite<
* @return {@code true} unless the tag is an end-group tag.
protected boolean parseUnknownField(int tag, CodedInputStream input) throws IOException {
+ // This will avoid the allocation of unknown fields when a group tag is encountered.
+ if (WireFormat.getTagWireType(tag) == WireFormat.WIRETYPE_END_GROUP) {
+ return false;
ensureUnknownFieldsInitialized();
return unknownFields.mergeFieldFrom(tag, input);
@@ -1173,6 +1178,10 @@ public abstract class GeneratedMessageLite<
return new IntArrayList();
+ protected static IntList newIntListWithCapacity(int capacity) {
+ return new IntArrayList(capacity);
protected static IntList newIntList(List<Integer> toCopy) {
return new IntArrayList(toCopy);
@@ -1180,10 +1189,14 @@ public abstract class GeneratedMessageLite<
protected static IntList emptyIntList() {
return IntArrayList.emptyList();
protected static LongList newLongList() {
return new LongArrayList();
+ protected static LongList newLongListWithCapacity(int capacity) {
+ return new LongArrayList(capacity);
protected static LongList newLongList(List<Long> toCopy) {
return new LongArrayList(toCopy);
@@ -1197,6 +1210,10 @@ public abstract class GeneratedMessageLite<
return new FloatArrayList();
+ protected static FloatList newFloatListWithCapacity(int capacity) {
+ return new FloatArrayList(capacity);
protected static FloatList newFloatList(List<Float> toCopy) {
return new FloatArrayList(toCopy);
@@ -1209,6 +1226,10 @@ public abstract class GeneratedMessageLite<
return new DoubleArrayList();
+ protected static DoubleList newDoubleListWithCapacity(int capacity) {
+ return new DoubleArrayList(capacity);
protected static DoubleList newDoubleList(List<Double> toCopy) {
return new DoubleArrayList(toCopy);
@@ -1221,6 +1242,10 @@ public abstract class GeneratedMessageLite<
return new BooleanArrayList();
+ protected static BooleanList newBooleanListWithCapacity(int capacity) {
+ return new BooleanArrayList(capacity);
protected static BooleanList newBooleanList(List<Boolean> toCopy) {
return new BooleanArrayList(toCopy);
@@ -1237,6 +1262,10 @@ public abstract class GeneratedMessageLite<
return new ProtobufArrayList<E>(toCopy);
+ protected static <E> ProtobufList<E> newProtobufListWithCapacity(int capacity) {
+ return new ProtobufArrayList<E>(capacity);
protected static <E> ProtobufList<E> emptyProtobufList() {
return ProtobufArrayList.emptyList();
@@ -67,10 +67,17 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis
- * Constructs a new mutable {@code IntArrayList}.
+ * Constructs a new mutable {@code IntArrayList} with default capacity.
IntArrayList() {
- array = new int[DEFAULT_CAPACITY];
+ * Constructs a new mutable {@code IntArrayList} with the provided capacity.
+ IntArrayList(int capacity) {
+ array = new int[capacity];
@@ -36,9 +36,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
* This class implements a {@link com.google.protobuf.ByteString} backed by a
@@ -49,8 +48,7 @@ import java.util.NoSuchElementException;
-class LiteralByteString extends ByteString {
+class LiteralByteString extends ByteString.LeafByteString {
private static final long serialVersionUID = 1L;
protected final byte[] bytes;
@@ -82,77 +80,56 @@ class LiteralByteString extends ByteString {
// ByteString -> substring
- public ByteString substring(int beginIndex, int endIndex) {
- if (beginIndex < 0) {
- "Beginning index: " + beginIndex + " < 0");
- if (endIndex > size()) {
- throw new IndexOutOfBoundsException("End index: " + endIndex + " > " +
- size());
- int substringLength = endIndex - beginIndex;
- if (substringLength < 0) {
- "Beginning index larger than ending index: " + beginIndex + ", "
- + endIndex);
+ public final ByteString substring(int beginIndex, int endIndex) {
+ final int length = checkRange(beginIndex, endIndex, size());
- if (substringLength == 0) {
- result = ByteString.EMPTY;
- result = new BoundedByteString(bytes, getOffsetIntoBytes() + beginIndex,
- substringLength);
+ if (length == 0) {
+ return ByteString.EMPTY;
+ return new BoundedByteString(bytes, getOffsetIntoBytes() + beginIndex, length);
+ protected void copyToInternal(
+ byte[] target, int sourceOffset, int targetOffset, int numberToCopy) {
// Optimized form, not for subclasses, since we don't call
// getOffsetIntoBytes() or check the 'numberToCopy' parameter.
+ // TODO(nathanmittler): Is not calling getOffsetIntoBytes really saving that much?
System.arraycopy(bytes, sourceOffset, target, targetOffset, numberToCopy);
- public void copyTo(ByteBuffer target) {
- target.put(bytes, getOffsetIntoBytes(), size()); // Copies bytes
+ public final void copyTo(ByteBuffer target) {
+ target.put(bytes, getOffsetIntoBytes(), size()); // Copies bytes
+ public final ByteBuffer asReadOnlyByteBuffer() {
+ return ByteBuffer.wrap(bytes, getOffsetIntoBytes(), size()).asReadOnlyBuffer();
- public ByteBuffer asReadOnlyByteBuffer() {
- ByteBuffer byteBuffer =
- ByteBuffer.wrap(bytes, getOffsetIntoBytes(), size());
- return byteBuffer.asReadOnlyBuffer();
+ public final List<ByteBuffer> asReadOnlyByteBufferList() {
+ return Collections.singletonList(asReadOnlyByteBuffer());
- public List<ByteBuffer> asReadOnlyByteBufferList() {
- // Return the ByteBuffer generated by asReadOnlyByteBuffer() as a singleton
- List<ByteBuffer> result = new ArrayList<ByteBuffer>(1);
- result.add(asReadOnlyByteBuffer());
- public void writeTo(OutputStream outputStream) throws IOException {
+ public final void writeTo(OutputStream outputStream) throws IOException {
outputStream.write(toByteArray());
- void writeToInternal(OutputStream outputStream, int sourceOffset,
- int numberToWrite) throws IOException {
- outputStream.write(bytes, getOffsetIntoBytes() + sourceOffset,
- numberToWrite);
+ final void writeToInternal(OutputStream outputStream, int sourceOffset, int numberToWrite)
+ throws IOException {
+ outputStream.write(bytes, getOffsetIntoBytes() + sourceOffset, numberToWrite);
- protected String toStringInternal(Charset charset) {
+ protected final String toStringInternal(Charset charset) {
return new String(bytes, getOffsetIntoBytes(), size(), charset);
@@ -160,13 +137,13 @@ class LiteralByteString extends ByteString {
// UTF-8 decoding
- public boolean isValidUtf8() {
+ public final boolean isValidUtf8() {
int offset = getOffsetIntoBytes();
return Utf8.isValidUtf8(bytes, offset, offset + size());
- protected int partialIsValidUtf8(int state, int offset, int length) {
+ protected final int partialIsValidUtf8(int state, int offset, int length) {
int index = getOffsetIntoBytes() + offset;
return Utf8.partialIsValidUtf8(state, bytes, index, index + length);
@@ -175,7 +152,7 @@ class LiteralByteString extends ByteString {
// equals() and hashCode()
- public boolean equals(Object other) {
+ public final boolean equals(Object other) {
if (other == this) {
return true;
@@ -194,19 +171,16 @@ class LiteralByteString extends ByteString {
LiteralByteString otherAsLiteral = (LiteralByteString) other;
// If we know the hash codes and they are not equal, we know the byte
// strings are not equal.
- if (hash != 0
- && otherAsLiteral.hash != 0
- && hash != otherAsLiteral.hash) {
+ int thisHash = peekCachedHashCode();
+ int thatHash = otherAsLiteral.peekCachedHashCode();
+ if (thisHash != 0 && thatHash != 0 && thisHash != thatHash) {
return false;
return equalsRange((LiteralByteString) other, 0, size());
- } else if (other instanceof RopeByteString) {
- return other.equals(this);
- "Has a new type of ByteString been created? Found "
- + other.getClass());
+ // RopeByteString and NioByteString.
+ return other.equals(this);
@@ -219,65 +193,36 @@ class LiteralByteString extends ByteString {
* @param length number of bytes to compare
* @return true for equality of substrings, else false.
- boolean equalsRange(LiteralByteString other, int offset, int length) {
+ final boolean equalsRange(ByteString other, int offset, int length) {
if (length > other.size()) {
- "Length too large: " + length + size());
+ throw new IllegalArgumentException("Length too large: " + length + size());
if (offset + length > other.size()) {
throw new IllegalArgumentException(
- "Ran off end of other: " + offset + ", " + length + ", " +
- other.size());
+ "Ran off end of other: " + offset + ", " + length + ", " + other.size());
- byte[] thisBytes = bytes;
- byte[] otherBytes = other.bytes;
- int thisLimit = getOffsetIntoBytes() + length;
- for (int thisIndex = getOffsetIntoBytes(), otherIndex =
- other.getOffsetIntoBytes() + offset;
- (thisIndex < thisLimit); ++thisIndex, ++otherIndex) {
- if (thisBytes[thisIndex] != otherBytes[otherIndex]) {
- return false;
- return true;
- * Cached hash value. Intentionally accessed via a data race, which
- * is safe because of the Java Memory Model's "no out-of-thin-air values"
- * guarantees for ints.
- private int hash = 0;
- * Compute the hashCode using the traditional algorithm from {@link
- * ByteString}.
- * @return hashCode value
- public int hashCode() {
- int h = hash;
- if (h == 0) {
- h = partialHash(size, 0, size);
- h = 1;
+ if (other instanceof LiteralByteString) {
+ LiteralByteString lbsOther = (LiteralByteString) other;
+ byte[] thisBytes = bytes;
+ byte[] otherBytes = lbsOther.bytes;
+ int thisLimit = getOffsetIntoBytes() + length;
+ for (
+ int thisIndex = getOffsetIntoBytes(), otherIndex = lbsOther.getOffsetIntoBytes() + offset;
+ (thisIndex < thisLimit); ++thisIndex, ++otherIndex) {
+ if (thisBytes[thisIndex] != otherBytes[otherIndex]) {
- hash = h;
- return h;
- protected int peekCachedHashCode() {
- return hash;
+ return other.substring(offset, offset + length).equals(substring(0, length));
- protected int partialHash(int h, int offset, int length) {
+ protected final int partialHash(int h, int offset, int length) {
return hashCode(h, bytes, getOffsetIntoBytes() + offset, length);
@@ -297,70 +242,20 @@ class LiteralByteString extends ByteString {
- public InputStream newInput() {
- return new ByteArrayInputStream(bytes, getOffsetIntoBytes(),
- size()); // No copy
+ public final InputStream newInput() {
+ return new ByteArrayInputStream(bytes, getOffsetIntoBytes(), size()); // No copy
- public CodedInputStream newCodedInput() {
+ public final CodedInputStream newCodedInput() {
// We trust CodedInputStream not to modify the bytes, or to give anyone
// else access to them.
return CodedInputStream.newInstance(this);
- return new LiteralByteIterator();
- private class LiteralByteIterator implements ByteIterator {
- private LiteralByteIterator() {
- position = 0;
- limit = size();
- try {
- } catch (ArrayIndexOutOfBoundsException e) {
- throw new NoSuchElementException(e.getMessage());
// Internal methods
- protected int getTreeDepth() {
- return 0;
- protected boolean isBalanced() {
* Offset into {@code bytes[]} to use, non-zero for substrings.
@@ -67,10 +67,17 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList
- * Constructs a new mutable {@code LongArrayList}.
+ * Constructs a new mutable {@code LongArrayList} with default capacity.
LongArrayList() {
- array = new long[DEFAULT_CAPACITY];
+ * Constructs a new mutable {@code LongArrayList} with the provided capacity.
+ LongArrayList(int capacity) {
+ array = new long[capacity];
@@ -30,6 +30,8 @@
+import com.google.protobuf.Internal.EnumLite;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -44,7 +46,7 @@ import java.util.Set;
* This class is a protobuf implementation detail. Users shouldn't use this
* class directly.
-public class MapFieldLite<K, V> implements MutabilityOracle {
+public final class MapFieldLite<K, V> implements MutabilityOracle {
private MutatabilityAwareMap<K, V> mapData;
private boolean isMutable;
@@ -136,8 +138,9 @@ public class MapFieldLite<K, V> implements MutabilityOracle {
if (a instanceof byte[]) {
return LiteralByteString.hashCode((byte[]) a);
- if (a instanceof Internal.EnumLite) {
- return Internal.hashEnum((Internal.EnumLite) a);
+ // Enums should be stored as integers internally.
+ if (a instanceof EnumLite) {
return a.hashCode();
@@ -0,0 +1,309 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+package com.google.protobuf;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.InvalidMarkException;
+import java.nio.channels.Channels;
+import java.nio.charset.Charset;
+import java.util.List;
+/**
+ * A {@link ByteString} that wraps around a {@link ByteBuffer}.
+final class NioByteString extends ByteString.LeafByteString {
+ private final ByteBuffer buffer;
+ NioByteString(ByteBuffer buffer) {
+ if (buffer == null) {
+ throw new NullPointerException("buffer");
+ this.buffer = buffer.slice();
+ // =================================================================
+ // Serializable
+ * Magic method that lets us override serialization behavior.
+ private Object writeReplace() {
+ return ByteString.copyFrom(buffer.slice());
+ * Magic method that lets us override deserialization behavior.
+ throw new InvalidObjectException("NioByteString instances are not to be serialized directly");
+ public byte byteAt(int index) {
+ return buffer.get(index);
+ throw e;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ArrayIndexOutOfBoundsException(e.getMessage());
+ public int size() {
+ return buffer.remaining();
+ public ByteString substring(int beginIndex, int endIndex) {
+ ByteBuffer slice = slice(beginIndex, endIndex);
+ return new NioByteString(slice);
+ ByteBuffer slice = buffer.slice();
+ slice.position(sourceOffset);
+ slice.get(target, targetOffset, numberToCopy);
+ public void copyTo(ByteBuffer target) {
+ target.put(buffer.slice());
+ public void writeTo(OutputStream out) throws IOException {
+ writeToInternal(out, buffer.position(), buffer.remaining());
+ boolean equalsRange(ByteString other, int offset, int length) {
+ return substring(0, length).equals(other.substring(offset, offset + length));
+ void writeToInternal(OutputStream out, int sourceOffset, int numberToWrite) throws IOException {
+ if (buffer.hasArray()) {
+ // Optimized write for array-backed buffers.
+ // Note that we're taking the risk that a malicious OutputStream could modify the array.
+ int bufferOffset = buffer.arrayOffset() + buffer.position() + sourceOffset;
+ out.write(buffer.array(), bufferOffset, numberToWrite);
+ // Slow path
+ if (out instanceof FileOutputStream || numberToWrite >= 8192) {
+ // Use a channel to write out the ByteBuffer.
+ Channels.newChannel(out).write(slice(sourceOffset, sourceOffset + numberToWrite));
+ // Just copy the data to an array and write it.
+ out.write(toByteArray());
+ public ByteBuffer asReadOnlyByteBuffer() {
+ return buffer.asReadOnlyBuffer();
+ public List<ByteBuffer> asReadOnlyByteBufferList() {
+ protected String toStringInternal(Charset charset) {
+ byte[] bytes;
+ int offset;
+ bytes = buffer.array();
+ offset = buffer.arrayOffset() + buffer.position();
+ bytes = toByteArray();
+ offset = 0;
+ return new String(bytes, offset, size(), charset);
+ public boolean isValidUtf8() {
+ // TODO(nathanmittler): add a ByteBuffer fork for Utf8.isValidUtf8 to avoid the copy
+ int startIndex;
+ startIndex = buffer.arrayOffset() + buffer.position();
+ startIndex = 0;
+ return Utf8.isValidUtf8(bytes, startIndex, startIndex + size());
+ protected int partialIsValidUtf8(int state, int offset, int length) {
+ // TODO(nathanmittler): TODO add a ByteBuffer fork for Utf8.partialIsValidUtf8 to avoid the copy
+ return Utf8.partialIsValidUtf8(state, bytes, startIndex, startIndex + size());
+ public boolean equals(Object other) {
+ if (other == this) {
+ if (!(other instanceof ByteString)) {
+ ByteString otherString = ((ByteString) other);
+ if (size() != otherString.size()) {
+ if (size() == 0) {
+ if (other instanceof NioByteString) {
+ return buffer.equals(((NioByteString) other).buffer);
+ if (other instanceof RopeByteString) {
+ return buffer.equals(otherString.asReadOnlyByteBuffer());
+ protected int partialHash(int h, int offset, int length) {
+ for (int i = offset; i < offset + length; i++) {
+ h = h * 31 + buffer.get(i);
+ public InputStream newInput() {
+ return new InputStream() {
+ private final ByteBuffer buf = buffer.slice();
+ public void mark(int readlimit) {
+ buf.mark();
+ public boolean markSupported() {
+ public void reset() throws IOException {
+ buf.reset();
+ } catch (InvalidMarkException e) {
+ throw new IOException(e);
+ public int available() throws IOException {
+ return buf.remaining();
+ public int read() throws IOException {
+ if (!buf.hasRemaining()) {
+ return -1;
+ return buf.get() & 0xFF;
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ len = Math.min(len, buf.remaining());
+ buf.get(bytes, off, len);
+ return len;
+ public CodedInputStream newCodedInput() {
+ return CodedInputStream.newInstance(buffer);
+ * Creates a slice of a range of this buffer.
+ * @param beginIndex the beginning index of the slice (inclusive).
+ * @param endIndex the end index of the slice (exclusive).
+ * @return the requested slice.
+ private ByteBuffer slice(int beginIndex, int endIndex) {
+ if (beginIndex < buffer.position() || endIndex > buffer.limit() || beginIndex > endIndex) {
+ throw new IllegalArgumentException(
+ String.format("Invalid indices [%d, %d]", beginIndex, endIndex));
+ slice.position(beginIndex - buffer.position());
+ slice.limit(endIndex - buffer.position());
+ return slice;
@@ -60,6 +60,10 @@ class ProtobufArrayList<E> extends AbstractProtobufList<E> {
list = new ArrayList<E>(toCopy);
+ ProtobufArrayList(int capacity) {
+ list = new ArrayList<E>(capacity);
public void add(int index, E element) {
ensureIsMutable();
@@ -69,7 +69,7 @@ import java.util.Stack;
-class RopeByteString extends ByteString {
+final class RopeByteString extends ByteString {
* BAP95. Let Fn be the nth Fibonacci number. A {@link RopeByteString} of
@@ -151,21 +151,24 @@ class RopeByteString extends ByteString {
* @return concatenation representing the same sequence as the given strings
static ByteString concatenate(ByteString left, ByteString right) {
- RopeByteString leftRope =
- (left instanceof RopeByteString) ? (RopeByteString) left : null;
if (right.size() == 0) {
- result = left;
- } else if (left.size() == 0) {
- result = right;
- int newLength = left.size() + right.size();
- if (newLength < ByteString.CONCATENATE_BY_COPY_SIZE) {
- // Optimization from BAP95: For short (leaves in paper, but just short
- // here) total length, do a copy of data to a new leaf.
- result = concatenateBytes(left, right);
- } else if (leftRope != null
- && leftRope.right.size() + right.size() < CONCATENATE_BY_COPY_SIZE) {
+ return left;
+ if (left.size() == 0) {
+ return right;
+ final int newLength = left.size() + right.size();
+ if (newLength < ByteString.CONCATENATE_BY_COPY_SIZE) {
+ // Optimization from BAP95: For short (leaves in paper, but just short
+ // here) total length, do a copy of data to a new leaf.
+ return concatenateBytes(left, right);
+ if (left instanceof RopeByteString) {
+ final RopeByteString leftRope = (RopeByteString) left;
+ if (leftRope.right.size() + right.size() < CONCATENATE_BY_COPY_SIZE) {
// Optimization from BAP95: As an optimization of the case where the
// ByteString is constructed by repeated concatenate, recognize the case
// where a short string is concatenated to a left-hand node whose
@@ -177,9 +180,10 @@ class RopeByteString extends ByteString {
// new parent node so that the depth of the result is the same as the
// given left tree.
ByteString newRight = concatenateBytes(leftRope.right, right);
- result = new RopeByteString(leftRope.left, newRight);
- && leftRope.left.getTreeDepth() > leftRope.right.getTreeDepth()
+ return new RopeByteString(leftRope.left, newRight);
+ if (leftRope.left.getTreeDepth() > leftRope.right.getTreeDepth()
&& leftRope.getTreeDepth() > right.getTreeDepth()) {
// Typically for concatenate-built strings the left-side is deeper than
// the right. This is our final attempt to concatenate without
@@ -187,20 +191,18 @@ class RopeByteString extends ByteString {
// is yet another optimization for building the string by repeatedly
// concatenating on the right.
ByteString newRight = new RopeByteString(leftRope.right, right);
- // Fine, we'll add a node and increase the tree depth--unless we
- // rebalance ;^)
- int newDepth = Math.max(left.getTreeDepth(), right.getTreeDepth()) + 1;
- if (newLength >= minLengthByDepth[newDepth]) {
- // The tree is shallow enough, so don't rebalance
- result = new RopeByteString(left, right);
- result = new Balancer().balance(left, right);
+ // Fine, we'll add a node and increase the tree depth--unless we rebalance ;^)
+ int newDepth = Math.max(left.getTreeDepth(), right.getTreeDepth()) + 1;
+ if (newLength >= minLengthByDepth[newDepth]) {
+ // The tree is shallow enough, so don't rebalance
+ return new RopeByteString(left, right);
+ return new Balancer().balance(left, right);
@@ -248,22 +250,14 @@ class RopeByteString extends ByteString {
- throw new ArrayIndexOutOfBoundsException("Index < 0: " + index);
- if (index > totalLength) {
- "Index > length: " + index + ", " + totalLength);
+ checkIndex(index, totalLength);
- byte result;
// Find the relevant piece by recursive descent
if (index < leftLength) {
- result = left.byteAt(index);
- result = right.byteAt(index - leftLength);
+ return left.byteAt(index);
+ return right.byteAt(index - leftLength);
@@ -309,48 +303,36 @@ class RopeByteString extends ByteString {
public ByteString substring(int beginIndex, int endIndex) {
+ final int length = checkRange(beginIndex, endIndex, totalLength);
+ // Empty substring
- if (endIndex > totalLength) {
- "End index: " + endIndex + " > " + totalLength);
+ if (length == totalLength) {
+ // The whole string
+ return this;
+ // Proper substring
+ if (endIndex <= leftLength) {
+ // Substring on the left
+ return left.substring(beginIndex, endIndex);
- // Empty substring
- } else if (substringLength == totalLength) {
- // The whole string
- result = this;
- // Proper substring
- if (endIndex <= leftLength) {
- // Substring on the left
- result = left.substring(beginIndex, endIndex);
- } else if (beginIndex >= leftLength) {
- // Substring on the right
- result = right
- .substring(beginIndex - leftLength, endIndex - leftLength);
- // Split substring
- ByteString leftSub = left.substring(beginIndex);
- ByteString rightSub = right.substring(0, endIndex - leftLength);
- // Intentionally not rebalancing, since in many cases these two
- // substrings will already be less deep than the top-level
- // RopeByteString we're taking a substring of.
- result = new RopeByteString(leftSub, rightSub);
+ if (beginIndex >= leftLength) {
+ // Substring on the right
+ return right.substring(beginIndex - leftLength, endIndex - leftLength);
+ // Split substring
+ ByteString leftSub = left.substring(beginIndex);
+ ByteString rightSub = right.substring(0, endIndex - leftLength);
+ // Intentionally not rebalancing, since in many cases these two
+ // substrings will already be less deep than the top-level
+ // RopeByteString we're taking a substring of.
+ return new RopeByteString(leftSub, rightSub);
@@ -391,7 +373,7 @@ class RopeByteString extends ByteString {
List<ByteBuffer> result = new ArrayList<ByteBuffer>();
PieceIterator pieces = new PieceIterator(this);
while (pieces.hasNext()) {
- LiteralByteString byteString = pieces.next();
+ LeafByteString byteString = pieces.next();
result.add(byteString.asReadOnlyByteBuffer());
@@ -471,11 +453,10 @@ class RopeByteString extends ByteString {
// hashCode if it's already computed. It's arguable we should compute the
// hashCode here, and if we're going to be testing a bunch of byteStrings,
// it might even make sense.
- if (hash != 0) {
- int cachedOtherHash = otherByteString.peekCachedHashCode();
- if (cachedOtherHash != 0 && hash != cachedOtherHash) {
+ int thatHash = otherByteString.peekCachedHashCode();
return equalsFragments(otherByteString);
@@ -492,12 +473,12 @@ class RopeByteString extends ByteString {
private boolean equalsFragments(ByteString other) {
int thisOffset = 0;
- Iterator<LiteralByteString> thisIter = new PieceIterator(this);
- LiteralByteString thisString = thisIter.next();
+ Iterator<LeafByteString> thisIter = new PieceIterator(this);
+ LeafByteString thisString = thisIter.next();
int thatOffset = 0;
- Iterator<LiteralByteString> thatIter = new PieceIterator(other);
- LiteralByteString thatString = thatIter.next();
+ Iterator<LeafByteString> thatIter = new PieceIterator(other);
+ LeafByteString thatString = thatIter.next();
int pos = 0;
while (true) {
@@ -536,33 +517,6 @@ class RopeByteString extends ByteString {
- * Cached hash value. Intentionally accessed via a data race, which is safe
- * because of the Java Memory Model's "no out-of-thin-air values" guarantees
- * for ints.
- h = totalLength;
- h = partialHash(h, 0, totalLength);
protected int partialHash(int h, int offset, int length) {
int toIndex = offset + length;
@@ -714,34 +668,34 @@ class RopeByteString extends ByteString {
* <p>This iterator is used to implement
* {@link RopeByteString#equalsFragments(ByteString)}.
- private static class PieceIterator implements Iterator<LiteralByteString> {
+ private static class PieceIterator implements Iterator<LeafByteString> {
private final Stack<RopeByteString> breadCrumbs =
new Stack<RopeByteString>();
- private LiteralByteString next;
+ private LeafByteString next;
private PieceIterator(ByteString root) {
next = getLeafByLeft(root);
- private LiteralByteString getLeafByLeft(ByteString root) {
+ private LeafByteString getLeafByLeft(ByteString root) {
ByteString pos = root;
while (pos instanceof RopeByteString) {
RopeByteString rbs = (RopeByteString) pos;
breadCrumbs.push(rbs);
pos = rbs.left;
- return (LiteralByteString) pos;
+ return (LeafByteString) pos;
- private LiteralByteString getNextNonEmptyLeaf() {
+ private LeafByteString getNextNonEmptyLeaf() {
// Almost always, we go through this loop exactly once. However, if
// we discover an empty string in the rope, we toss it and try again.
if (breadCrumbs.isEmpty()) {
- LiteralByteString result = getLeafByLeft(breadCrumbs.pop().right);
+ LeafByteString result = getLeafByLeft(breadCrumbs.pop().right);
if (!result.isEmpty()) {
@@ -749,6 +703,7 @@ class RopeByteString extends ByteString {
public boolean hasNext() {
return next != null;
@@ -758,15 +713,17 @@ class RopeByteString extends ByteString {
* @return next non-empty LiteralByteString or {@code null}
- public LiteralByteString next() {
+ public LeafByteString next() {
if (next == null) {
throw new NoSuchElementException();
- LiteralByteString result = next;
+ LeafByteString result = next;
next = getNextNonEmptyLeaf();
public void remove() {
throw new UnsupportedOperationException();
@@ -781,52 +738,11 @@ class RopeByteString extends ByteString {
"RopeByteStream instances are not to be serialized directly");
- return new RopeByteIterator();
- private class RopeByteIterator implements ByteString.ByteIterator {
- private final PieceIterator pieces;
- private ByteIterator bytes;
- int bytesRemaining;
- private RopeByteIterator() {
- pieces = new PieceIterator(RopeByteString.this);
- bytes = pieces.next().iterator();
- bytesRemaining = size();
- return (bytesRemaining > 0);
- return nextByte(); // Does not instantiate a Byte
- if (!bytes.hasNext()) {
- --bytesRemaining;
- return bytes.nextByte();
* This class is the {@link RopeByteString} equivalent for
* {@link ByteArrayInputStream}.
@@ -835,7 +751,7 @@ class RopeByteString extends ByteString {
// Iterates through the pieces of the rope
private PieceIterator pieceIterator;
// The current piece
- private LiteralByteString currentPiece;
+ private LeafByteString currentPiece;
// The size of the current piece
private int currentPieceSize;
// The index of the next byte to read in the current piece
@@ -872,7 +788,7 @@ class RopeByteString extends ByteString {
* Internal implementation of read and skip. If b != null, then read the
* next {@code length} bytes into the buffer {@code b} at
- * offset {@code offset}. If b == null, then skip the next {@code length)
+ * offset {@code offset}. If b == null, then skip the next {@code length}
* bytes.
* <p>
* This method assumes that all error checking has already happened.
@@ -1074,6 +1074,18 @@ public final class TextFormat {
private ParseException floatParseException(final NumberFormatException e) {
return parseException("Couldn't parse number: " + e.getMessage());
+ * Returns a {@link UnknownFieldParseException} with the line and column
+ * numbers of the previous token in the description, and the unknown field
+ * name, suitable for throwing.
+ public UnknownFieldParseException unknownFieldParseExceptionPreviousToken(
+ final String unknownField, final String description) {
+ // Note: People generally prefer one-based line and column numbers.
+ return new UnknownFieldParseException(
+ previousLine + 1, previousColumn + 1, unknownField, description);
/** Thrown when parsing an invalid text format message. */
@@ -1121,6 +1133,45 @@ public final class TextFormat {
return column;
+ * Thrown when encountering an unknown field while parsing
+ * a text format message.
+ public static class UnknownFieldParseException extends ParseException {
+ private final String unknownField;
+ * Create a new instance, with -1 as the line and column numbers, and an
+ * empty unknown field name.
+ public UnknownFieldParseException(final String message) {
+ this(-1, -1, "", message);
+ * Create a new instance
+ * @param line the line number where the parse error occurred,
+ * using 1-offset.
+ * @param column the column number where the parser error occurred,
+ * @param unknownField the name of the unknown field found while parsing.
+ public UnknownFieldParseException(final int line, final int column,
+ final String unknownField, final String message) {
+ super(line, column, message);
+ this.unknownField = unknownField;
+ * Return the name of the unknown field encountered while parsing the
+ * protocol buffer string.
+ public String getUnknownField() {
+ return unknownField;
private static final Parser PARSER = Parser.newBuilder().build();
@@ -1388,7 +1439,8 @@ public final class TextFormat {
if (field == null) {
if (!allowUnknownFields) {
- throw tokenizer.parseExceptionPreviousToken(
+ throw tokenizer.unknownFieldParseExceptionPreviousToken(
+ name,
"Message type \"" + type.getFullName()
+ "\" has no field named \"" + name + "\".");
@@ -0,0 +1,55 @@
+ * Provides unsafe factory methods for {@link ByteString} instances.
+ * <p><strong>DISCLAIMER:</strong> The methods in this class should only be called if it is
+ * guaranteed that the the buffer backing the {@link ByteString} will never change! Mutation of a
+ * {@link ByteString} can lead to unexpected and undesirable consequences in your application,
+ * and will likely be difficult to debug. Proceed with caution!
+public final class UnsafeByteStrings {
+ private UnsafeByteStrings() {}
+ * An unsafe operation that returns a {@link ByteString} that is backed by the provided buffer.
+ * @param buffer the Java NIO buffer to be wrapped.
+ * @return a {@link ByteString} backed by the provided buffer.
+ public static ByteString unsafeWrap(ByteBuffer buffer) {
+ return new NioByteString(buffer);
@@ -310,10 +310,6 @@ public class BooleanArrayListTest extends TestCase {
private void assertImmutable(BooleanArrayList list) {
- if (list.contains(1)) {
- throw new RuntimeException("Cannot test the immutability of lists that contain 1.");
list.add(false);
fail();
@@ -413,7 +409,7 @@ public class BooleanArrayListTest extends TestCase {
- list.removeAll(Collections.singleton(1));
+ list.removeAll(Collections.singleton(Boolean.TRUE));
} catch (UnsupportedOperationException e) {
// expected
@@ -434,7 +430,7 @@ public class BooleanArrayListTest extends TestCase {
- list.retainAll(Collections.singleton(1));
+ list.retainAll(Collections.singleton(Boolean.TRUE));
@@ -73,7 +73,7 @@ public class BoundedByteStringTest extends LiteralByteStringTest {
- public void testCharsetToString() throws UnsupportedEncodingException {
+ public void testCharsetToString() {
String testString = "I love unicode \u1234\u5678 characters";
LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8));
ByteString chopped = unicode.substring(2, unicode.size() - 6);
@@ -39,7 +39,6 @@ import java.io.ByteArrayOutputStream;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
@@ -129,7 +128,7 @@ public class ByteStringTest extends TestCase {
isArrayRange(byteString.toByteArray(), bytes, 500, bytes.length - 500));
- public void testCopyFrom_StringEncoding() throws UnsupportedEncodingException {
+ public void testCopyFrom_StringEncoding() {
ByteString byteString = ByteString.copyFrom(testString, UTF_16);
byte[] testBytes = testString.getBytes(UTF_16);
@@ -137,7 +136,7 @@ public class ByteStringTest extends TestCase {
isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
- public void testCopyFrom_Utf8() throws UnsupportedEncodingException {
+ public void testCopyFrom_Utf8() {
ByteString byteString = ByteString.copyFromUtf8(testString);
byte[] testBytes = testString.getBytes(Internal.UTF_8);
@@ -154,6 +153,7 @@ public class ByteStringTest extends TestCase {
// Call copyFrom on an iteration that's not a collection
ByteString byteStringAlt = ByteString.copyFrom(new Iterable<ByteString>() {
public Iterator<ByteString> iterator() {
return pieces.iterator();
@@ -399,7 +399,7 @@ public class ByteStringTest extends TestCase {
- public void testToStringUtf8() throws UnsupportedEncodingException {
+ public void testToStringUtf8() {
ByteString byteString = ByteString.copyFrom(testBytes);
@@ -419,7 +419,7 @@ public class ByteStringTest extends TestCase {
// Test newOutput() using a variety of buffer sizes and a variety of (fixed)
// write sizes
- public void testNewOutput_ArrayWrite() throws IOException {
+ public void testNewOutput_ArrayWrite() {
byte[] bytes = getTestBytes();
int length = bytes.length;
int[] bufferSizes = {128, 256, length / 2, length - 1, length, length + 1,
@@ -442,7 +442,7 @@ public class ByteStringTest extends TestCase {
// Test newOutput() using a variety of buffer sizes, but writing all the
// characters using write(byte);
- public void testNewOutput_WriteChar() throws IOException {
+ public void testNewOutput_WriteChar() {
int[] bufferSizes = {0, 1, 128, 256, length / 2,
@@ -461,7 +461,7 @@ public class ByteStringTest extends TestCase {
// Test newOutput() in which we write the bytes using a variety of methods
// and sizes, and in which we repeatedly call toByteString() in the middle.
- public void testNewOutput_Mixed() throws IOException {
+ public void testNewOutput_Mixed() {
Random rng = new Random(1);
@@ -494,7 +494,7 @@ public class ByteStringTest extends TestCase {
- public void testNewOutputEmpty() throws IOException {
+ public void testNewOutputEmpty() {
// Make sure newOutput() correctly builds empty byte strings
ByteString byteString = ByteString.newOutput().toByteString();
assertEquals(ByteString.EMPTY, byteString);
@@ -37,6 +37,7 @@ import protobuf_unittest.UnittestProto.TestSparseEnum;
import junit.framework.TestCase;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -80,8 +81,8 @@ public class CodedOutputStreamTest extends TestCase {
* checks that the result matches the given bytes.
private void assertWriteVarint(byte[] data, long value) throws Exception {
- // Only do 32-bit write if the value fits in 32 bits.
- if ((value >>> 32) == 0) {
+ // Only test 32-bit write if the value fits into an int.
+ if (value == (int) value) {
ByteArrayOutputStream rawOutput = new ByteArrayOutputStream();
CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
output.writeRawVarint32((int) value);
@@ -107,8 +108,8 @@ public class CodedOutputStreamTest extends TestCase {
// Try different block sizes.
for (int blockSize = 1; blockSize <= 16; blockSize *= 2) {
CodedOutputStream output =
CodedOutputStream.newInstance(rawOutput, blockSize);
@@ -128,6 +129,42 @@ public class CodedOutputStreamTest extends TestCase {
+ private void assertVarintRoundTrip(long value) throws Exception {
+ ByteArrayOutputStream rawOutput = new ByteArrayOutputStream();
+ CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
+ output.writeRawVarint64(value);
+ output.flush();
+ byte[] bytes = rawOutput.toByteArray();
+ assertEquals(bytes.length, CodedOutputStream.computeRawVarint64Size(value));
+ CodedInputStream input = CodedInputStream.newInstance(new ByteArrayInputStream(bytes));
+ assertEquals(value, input.readRawVarint64());
+ output.writeRawVarint32((int) value);
+ assertEquals(bytes.length, CodedOutputStream.computeRawVarint32Size((int) value));
+ assertEquals(value, input.readRawVarint32());
+ /** Checks that invariants are maintained for varint round trip input and output. */
+ public void testVarintRoundTrips() throws Exception {
+ assertVarintRoundTrip(0L);
+ for (int bits = 0; bits < 64; bits++) {
+ long value = 1L << bits;
+ assertVarintRoundTrip(value);
+ assertVarintRoundTrip(value + 1);
+ assertVarintRoundTrip(value - 1);
+ assertVarintRoundTrip(-value);
/** Tests writeRawVarint32() and writeRawVarint64(). */
public void testWriteVarint() throws Exception {
assertWriteVarint(bytes(0x00), 0);
@@ -274,6 +274,15 @@ public class DescriptorsTest extends TestCase {
assertFalse(repeatedField.isRequired());
assertTrue(repeatedField.isRepeated());
+ public void testFieldDescriptorJsonName() throws Exception {
+ FieldDescriptor requiredField = TestRequired.getDescriptor().findFieldByName("a");
+ FieldDescriptor optionalField = TestAllTypes.getDescriptor().findFieldByName("optional_int32");
+ FieldDescriptor repeatedField = TestAllTypes.getDescriptor().findFieldByName("repeated_int32");
+ assertEquals("a", requiredField.getJsonName());
+ assertEquals("optionalInt32", optionalField.getJsonName());
+ assertEquals("repeatedInt32", repeatedField.getJsonName());
public void testFieldDescriptorDefault() throws Exception {
Descriptor d = TestAllTypes.getDescriptor();
@@ -310,7 +310,7 @@ public class DoubleArrayListTest extends TestCase {
private void assertImmutable(DoubleArrayList list) {
+ if (list.contains(1D)) {
throw new RuntimeException("Cannot test the immutability of lists that contain 1.");
@@ -413,7 +413,7 @@ public class DoubleArrayListTest extends TestCase {
+ list.removeAll(Collections.singleton(1D));
@@ -434,7 +434,7 @@ public class DoubleArrayListTest extends TestCase {
+ list.retainAll(Collections.singleton(1D));
@@ -310,7 +310,7 @@ public class FloatArrayListTest extends TestCase {
private void assertImmutable(FloatArrayList list) {
+ if (list.contains(1F)) {
@@ -413,7 +413,7 @@ public class FloatArrayListTest extends TestCase {
+ list.removeAll(Collections.singleton(1F));
@@ -434,7 +434,7 @@ public class FloatArrayListTest extends TestCase {
+ list.retainAll(Collections.singleton(1F));
@@ -355,7 +355,7 @@ public class LiteralByteStringTest extends TestCase {
assertEquals(classUnderTest + " unicode must match", testString, roundTripString);
String roundTripString = unicode.toString(Internal.UTF_8);
@@ -310,7 +310,7 @@ public class LongArrayListTest extends TestCase {
private void assertImmutable(LongArrayList list) {
+ if (list.contains(1L)) {
@@ -413,7 +413,7 @@ public class LongArrayListTest extends TestCase {
+ list.removeAll(Collections.singleton(1L));
@@ -434,7 +434,7 @@ public class LongArrayListTest extends TestCase {
+ list.retainAll(Collections.singleton(1L));
@@ -0,0 +1,546 @@
+import static com.google.protobuf.Internal.UTF_8;
+import junit.framework.TestCase;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.ObjectOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.BufferOverflowException;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+ * Tests for {@link NioByteString}.
+public class NioByteStringTest extends TestCase {
+ private static final ByteString EMPTY = UnsafeByteStrings.unsafeWrap(
+ ByteBuffer.wrap(new byte[0]));
+ private static final String CLASSNAME = NioByteString.class.getSimpleName();
+ private static final byte[] BYTES = ByteStringTest.getTestBytes(1234, 11337766L);
+ private static final int EXPECTED_HASH = new LiteralByteString(BYTES).hashCode();
+ private static final ByteBuffer BUFFER = ByteBuffer.wrap(BYTES.clone());
+ private static final ByteString TEST_STRING = UnsafeByteStrings.unsafeWrap(BUFFER);
+ public void testExpectedType() {
+ String actualClassName = getActualClassName(TEST_STRING);
+ assertEquals(CLASSNAME + " should match type exactly", CLASSNAME, actualClassName);
+ protected String getActualClassName(Object object) {
+ String actualClassName = object.getClass().getName();
+ actualClassName = actualClassName.substring(actualClassName.lastIndexOf('.') + 1);
+ return actualClassName;
+ public void testByteAt() {
+ boolean stillEqual = true;
+ for (int i = 0; stillEqual && i < BYTES.length; ++i) {
+ stillEqual = (BYTES[i] == TEST_STRING.byteAt(i));
+ assertTrue(CLASSNAME + " must capture the right bytes", stillEqual);
+ public void testByteIterator() {
+ ByteString.ByteIterator iter = TEST_STRING.iterator();
+ stillEqual = (iter.hasNext() && BYTES[i] == iter.nextByte());
+ assertFalse(CLASSNAME + " must have exhausted the itertor", iter.hasNext());
+ iter.nextByte();
+ fail("Should have thrown an exception.");
+ } catch (NoSuchElementException e) {
+ // This is success
+ public void testByteIterable() {
+ int j = 0;
+ for (byte quantum : TEST_STRING) {
+ stillEqual = (BYTES[j] == quantum);
+ ++j;
+ assertTrue(CLASSNAME + " must capture the right bytes as Bytes", stillEqual);
+ assertEquals(CLASSNAME + " iterable character count", BYTES.length, j);
+ public void testSize() {
+ assertEquals(CLASSNAME + " must have the expected size", BYTES.length,
+ TEST_STRING.size());
+ public void testGetTreeDepth() {
+ assertEquals(CLASSNAME + " must have depth 0", 0, TEST_STRING.getTreeDepth());
+ public void testIsBalanced() {
+ assertTrue(CLASSNAME + " is technically balanced", TEST_STRING.isBalanced());
+ public void testCopyTo_ByteArrayOffsetLength() {
+ int destinationOffset = 50;
+ int length = 100;
+ byte[] destination = new byte[destinationOffset + length];
+ int sourceOffset = 213;
+ TEST_STRING.copyTo(destination, sourceOffset, destinationOffset, length);
+ for (int i = 0; stillEqual && i < length; ++i) {
+ stillEqual = BYTES[i + sourceOffset] == destination[i + destinationOffset];
+ assertTrue(CLASSNAME + ".copyTo(4 arg) must give the expected bytes", stillEqual);
+ public void testCopyTo_ByteArrayOffsetLengthErrors() {
+ // Copy one too many bytes
+ TEST_STRING.copyTo(destination, TEST_STRING.size() + 1 - length,
+ destinationOffset, length);
+ fail("Should have thrown an exception when copying too many bytes of a "
+ + CLASSNAME);
+ } catch (IndexOutOfBoundsException expected) {
+ // Copy with illegal negative sourceOffset
+ TEST_STRING.copyTo(destination, -1, destinationOffset, length);
+ fail("Should have thrown an exception when given a negative sourceOffset in "
+ // Copy with illegal negative destinationOffset
+ TEST_STRING.copyTo(destination, 0, -1, length);
+ fail("Should have thrown an exception when given a negative destinationOffset in "
+ // Copy with illegal negative size
+ TEST_STRING.copyTo(destination, 0, 0, -1);
+ fail("Should have thrown an exception when given a negative size in "
+ // Copy with illegal too-large sourceOffset
+ TEST_STRING.copyTo(destination, 2 * TEST_STRING.size(), 0, length);
+ fail("Should have thrown an exception when the destinationOffset is too large in "
+ // Copy with illegal too-large destinationOffset
+ TEST_STRING.copyTo(destination, 0, 2 * destination.length, length);
+ public void testCopyTo_ByteBuffer() {
+ // Same length.
+ ByteBuffer myBuffer = ByteBuffer.allocate(BYTES.length);
+ TEST_STRING.copyTo(myBuffer);
+ myBuffer.flip();
+ assertEquals(CLASSNAME + ".copyTo(ByteBuffer) must give back the same bytes",
+ BUFFER, myBuffer);
+ // Target buffer bigger than required.
+ myBuffer = ByteBuffer.allocate(TEST_STRING.size() + 1);
+ assertEquals(BUFFER, myBuffer);
+ // Target buffer has no space.
+ myBuffer = ByteBuffer.allocate(0);
+ fail("Should have thrown an exception when target ByteBuffer has insufficient capacity");
+ } catch (BufferOverflowException e) {
+ // Expected.
+ // Target buffer too small.
+ myBuffer = ByteBuffer.allocate(1);
+ public void testMarkSupported() {
+ InputStream stream = TEST_STRING.newInput();
+ assertTrue(CLASSNAME + ".newInput() must support marking", stream.markSupported());
+ public void testMarkAndReset() throws IOException {
+ int fraction = TEST_STRING.size() / 3;
+ stream.mark(TEST_STRING.size()); // First, mark() the end.
+ skipFully(stream, fraction); // Skip a large fraction, but not all.
+ assertEquals(
+ CLASSNAME + ": after skipping to the 'middle', half the bytes are available",
+ (TEST_STRING.size() - fraction), stream.available());
+ stream.reset();
+ CLASSNAME + ": after resetting, all bytes are available",
+ TEST_STRING.size(), stream.available());
+ skipFully(stream, TEST_STRING.size()); // Skip to the end.
+ CLASSNAME + ": after skipping to the end, no more bytes are available",
+ 0, stream.available());
+ * Discards {@code n} bytes of data from the input stream. This method
+ * will block until the full amount has been skipped. Does not close the
+ * stream.
+ * <p>Copied from com.google.common.io.ByteStreams to avoid adding dependency.
+ * @param in the input stream to read from
+ * @param n the number of bytes to skip
+ * @throws EOFException if this stream reaches the end before skipping all
+ * the bytes
+ * @throws IOException if an I/O error occurs, or the stream does not
+ * support skipping
+ static void skipFully(InputStream in, long n) throws IOException {
+ long toSkip = n;
+ while (n > 0) {
+ long amt = in.skip(n);
+ if (amt == 0) {
+ // Force a blocking read to avoid infinite loop
+ if (in.read() == -1) {
+ long skipped = toSkip - n;
+ throw new EOFException("reached end of stream after skipping "
+ + skipped + " bytes; " + toSkip + " bytes expected");
+ n--;
+ n -= amt;
+ public void testAsReadOnlyByteBuffer() {
+ ByteBuffer byteBuffer = TEST_STRING.asReadOnlyByteBuffer();
+ byte[] roundTripBytes = new byte[BYTES.length];
+ assertTrue(byteBuffer.remaining() == BYTES.length);
+ assertTrue(byteBuffer.isReadOnly());
+ byteBuffer.get(roundTripBytes);
+ assertTrue(CLASSNAME + ".asReadOnlyByteBuffer() must give back the same bytes",
+ Arrays.equals(BYTES, roundTripBytes));
+ public void testAsReadOnlyByteBufferList() {
+ List<ByteBuffer> byteBuffers = TEST_STRING.asReadOnlyByteBufferList();
+ int bytesSeen = 0;
+ for (ByteBuffer byteBuffer : byteBuffers) {
+ int thisLength = byteBuffer.remaining();
+ assertTrue(bytesSeen + thisLength <= BYTES.length);
+ byteBuffer.get(roundTripBytes, bytesSeen, thisLength);
+ bytesSeen += thisLength;
+ assertTrue(bytesSeen == BYTES.length);
+ assertTrue(CLASSNAME + ".asReadOnlyByteBufferTest() must give back the same bytes",
+ public void testToByteArray() {
+ byte[] roundTripBytes = TEST_STRING.toByteArray();
+ assertTrue(CLASSNAME + ".toByteArray() must give back the same bytes",
+ public void testWriteTo() throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ TEST_STRING.writeTo(bos);
+ byte[] roundTripBytes = bos.toByteArray();
+ assertTrue(CLASSNAME + ".writeTo() must give back the same bytes",
+ public void testNewOutput() throws IOException {
+ ByteString.Output output = ByteString.newOutput();
+ TEST_STRING.writeTo(output);
+ assertEquals("Output Size returns correct result",
+ output.size(), TEST_STRING.size());
+ output.writeTo(bos);
+ assertTrue("Output.writeTo() must give back the same bytes",
+ Arrays.equals(BYTES, bos.toByteArray()));
+ // write the output stream to itself! This should cause it to double
+ output.writeTo(output);
+ assertEquals("Writing an output stream to itself is successful",
+ TEST_STRING.concat(TEST_STRING), output.toByteString());
+ output.reset();
+ assertEquals("Output.reset() resets the output", 0, output.size());
+ assertEquals("Output.reset() resets the output",
+ EMPTY, output.toByteString());
+ public void testToString() {
+ String testString = "I love unicode \u1234\u5678 characters";
+ ByteString unicode = forString(testString);
+ String roundTripString = unicode.toString(UTF_8);
+ assertEquals(CLASSNAME + " unicode must match", testString, roundTripString);
+ public void testToString_returnsCanonicalEmptyString() {
+ assertSame(CLASSNAME + " must be the same string references",
+ EMPTY.toString(UTF_8),
+ UnsafeByteStrings.unsafeWrap(ByteBuffer.wrap(new byte[0])).toString(UTF_8));
+ public void testToString_raisesException() {
+ EMPTY.toString("invalid");
+ } catch (UnsupportedEncodingException expected) {
+ TEST_STRING.toString("invalid");
+ public void testEquals() {
+ assertEquals(CLASSNAME + " must not equal null", false, TEST_STRING.equals(null));
+ assertEquals(CLASSNAME + " must equal self", TEST_STRING, TEST_STRING);
+ assertFalse(CLASSNAME + " must not equal the empty string",
+ TEST_STRING.equals(EMPTY));
+ assertEquals(CLASSNAME + " empty strings must be equal",
+ EMPTY, TEST_STRING.substring(55, 55));
+ assertEquals(CLASSNAME + " must equal another string with the same value",
+ TEST_STRING, UnsafeByteStrings.unsafeWrap(BUFFER));
+ byte[] mungedBytes = mungedBytes();
+ assertFalse(CLASSNAME + " must not equal every string with the same length",
+ TEST_STRING.equals(UnsafeByteStrings.unsafeWrap(ByteBuffer.wrap(mungedBytes))));
+ public void testEqualsLiteralByteString() {
+ ByteString literal = ByteString.copyFrom(BYTES);
+ assertEquals(CLASSNAME + " must equal LiteralByteString with same value", literal,
+ TEST_STRING);
+ assertEquals(CLASSNAME + " must equal LiteralByteString with same value", TEST_STRING,
+ literal);
+ TEST_STRING.equals(ByteString.EMPTY));
+ ByteString.EMPTY, TEST_STRING.substring(55, 55));
+ literal = ByteString.copyFrom(mungedBytes());
+ assertFalse(CLASSNAME + " must not equal every LiteralByteString with the same length",
+ TEST_STRING.equals(literal));
+ literal.equals(TEST_STRING));
+ public void testEqualsRopeByteString() {
+ ByteString p1 = ByteString.copyFrom(BYTES, 0, 5);
+ ByteString p2 = ByteString.copyFrom(BYTES, 5, BYTES.length - 5);
+ ByteString rope = p1.concat(p2);
+ assertEquals(CLASSNAME + " must equal RopeByteString with same value", rope,
+ assertEquals(CLASSNAME + " must equal RopeByteString with same value", TEST_STRING,
+ rope);
+ TEST_STRING.equals(ByteString.EMPTY.concat(ByteString.EMPTY)));
+ ByteString.EMPTY.concat(ByteString.EMPTY), TEST_STRING.substring(55, 55));
+ p1 = ByteString.copyFrom(mungedBytes, 0, 5);
+ p2 = ByteString.copyFrom(mungedBytes, 5, mungedBytes.length - 5);
+ rope = p1.concat(p2);
+ assertFalse(CLASSNAME + " must not equal every RopeByteString with the same length",
+ TEST_STRING.equals(rope));
+ rope.equals(TEST_STRING));
+ private byte[] mungedBytes() {
+ byte[] mungedBytes = new byte[BYTES.length];
+ System.arraycopy(BYTES, 0, mungedBytes, 0, BYTES.length);
+ mungedBytes[mungedBytes.length - 5] = (byte) (mungedBytes[mungedBytes.length - 5] ^ 0xFF);
+ return mungedBytes;
+ public void testHashCode() {
+ int hash = TEST_STRING.hashCode();
+ assertEquals(CLASSNAME + " must have expected hashCode", EXPECTED_HASH, hash);
+ public void testPeekCachedHashCode() {
+ ByteString newString = UnsafeByteStrings.unsafeWrap(BUFFER);
+ assertEquals(CLASSNAME + ".peekCachedHashCode() should return zero at first", 0,
+ newString.peekCachedHashCode());
+ newString.hashCode();
+ assertEquals(CLASSNAME + ".peekCachedHashCode should return zero at first",
+ EXPECTED_HASH, newString.peekCachedHashCode());
+ public void testPartialHash() {
+ // partialHash() is more strenuously tested elsewhere by testing hashes of substrings.
+ // This test would fail if the expected hash were 1. It's not.
+ int hash = TEST_STRING.partialHash(TEST_STRING.size(), 0, TEST_STRING.size());
+ assertEquals(CLASSNAME + ".partialHash() must yield expected hashCode",
+ EXPECTED_HASH, hash);
+ public void testNewInput() throws IOException {
+ InputStream input = TEST_STRING.newInput();
+ assertEquals("InputStream.available() returns correct value",
+ TEST_STRING.size(), input.available());
+ for (byte referenceByte : BYTES) {
+ int expectedInt = (referenceByte & 0xFF);
+ stillEqual = (expectedInt == input.read());
+ 0, input.available());
+ assertTrue(CLASSNAME + " must give the same bytes from the InputStream", stillEqual);
+ assertEquals(CLASSNAME + " InputStream must now be exhausted", -1, input.read());
+ public void testNewInput_skip() throws IOException {
+ int stringSize = TEST_STRING.size();
+ int nearEndIndex = stringSize * 2 / 3;
+ long skipped1 = input.skip(nearEndIndex);
+ assertEquals("InputStream.skip()", skipped1, nearEndIndex);
+ assertEquals("InputStream.available()",
+ stringSize - skipped1, input.available());
+ assertTrue("InputStream.mark() is available", input.markSupported());
+ input.mark(0);
+ assertEquals("InputStream.skip(), read()",
+ TEST_STRING.byteAt(nearEndIndex) & 0xFF, input.read());
+ stringSize - skipped1 - 1, input.available());
+ long skipped2 = input.skip(stringSize);
+ assertEquals("InputStream.skip() incomplete",
+ skipped2, stringSize - skipped1 - 1);
+ assertEquals("InputStream.skip(), no more input", 0, input.available());
+ assertEquals("InputStream.skip(), no more input", -1, input.read());
+ input.reset();
+ assertEquals("InputStream.reset() succeded",
+ assertEquals("InputStream.reset(), read()",
+ public void testNewCodedInput() throws IOException {
+ CodedInputStream cis = TEST_STRING.newCodedInput();
+ byte[] roundTripBytes = cis.readRawBytes(BYTES.length);
+ assertTrue(CLASSNAME + " must give the same bytes back from the CodedInputStream",
+ assertTrue(CLASSNAME + " CodedInputStream must now be exhausted", cis.isAtEnd());
+ * Make sure we keep things simple when concatenating with empty. See also
+ * {@link ByteStringTest#testConcat_empty()}.
+ public void testConcat_empty() {
+ assertSame(CLASSNAME + " concatenated with empty must give " + CLASSNAME,
+ TEST_STRING.concat(EMPTY), TEST_STRING);
+ assertSame("empty concatenated with " + CLASSNAME + " must give " + CLASSNAME,
+ EMPTY.concat(TEST_STRING), TEST_STRING);
+ public void testJavaSerialization() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(out);
+ oos.writeObject(TEST_STRING);
+ oos.close();
+ byte[] pickled = out.toByteArray();
+ InputStream in = new ByteArrayInputStream(pickled);
+ ObjectInputStream ois = new ObjectInputStream(in);
+ Object o = ois.readObject();
+ assertTrue("Didn't get a ByteString back", o instanceof ByteString);
+ assertEquals("Should get an equal ByteString back", TEST_STRING, o);
+ private static ByteString forString(String str) {
+ return UnsafeByteStrings.unsafeWrap(ByteBuffer.wrap(str.getBytes(UTF_8)));
@@ -243,7 +243,7 @@ public class ProtobufArrayListTest extends TestCase {
- list.removeAll(Collections.<Double>emptyList());
+ list.removeAll(Collections.emptyList());
@@ -264,7 +264,7 @@ public class ProtobufArrayListTest extends TestCase {
- list.retainAll(Collections.<Double>emptyList());
+ list.retainAll(Collections.emptyList());
@@ -96,7 +96,7 @@ public class RopeByteStringSubstringTest extends LiteralByteStringTest {
String sourceString = "I love unicode \u1234\u5678 characters";
ByteString sourceByteString = ByteString.copyFromUtf8(sourceString);
int copies = 250;
@@ -299,6 +299,16 @@ public final class TestUtil {
return builder;
+ * Get a {@code TestAllTypesLite.Builder} with all fields set as they would be by
+ * {@link #setAllFields(TestAllTypesLite.Builder)}.
+ public static TestAllTypesLite.Builder getAllLiteSetBuilder() {
+ TestAllTypesLite.Builder builder = TestAllTypesLite.newBuilder();
+ setAllFields(builder);
+ return builder;
* Get a {@code TestAllExtensions} with all fields set as they would be by
* {@link #setAllExtensions(TestAllExtensions.Builder)}.
@@ -339,6 +349,150 @@ public final class TestUtil {
setPackedExtensions(builder);
return builder.build();
+ * Set every field of {@code builder} to the values expected by
+ * {@code assertAllFieldsSet()}.
+ public static void setAllFields(TestAllTypesLite.Builder builder) {
+ builder.setOptionalInt32 (101);
+ builder.setOptionalInt64 (102);
+ builder.setOptionalUint32 (103);
+ builder.setOptionalUint64 (104);
+ builder.setOptionalSint32 (105);
+ builder.setOptionalSint64 (106);
+ builder.setOptionalFixed32 (107);
+ builder.setOptionalFixed64 (108);
+ builder.setOptionalSfixed32(109);
+ builder.setOptionalSfixed64(110);
+ builder.setOptionalFloat (111);
+ builder.setOptionalDouble (112);
+ builder.setOptionalBool (true);
+ builder.setOptionalString ("115");
+ builder.setOptionalBytes (toBytes("116"));
+ builder.setOptionalGroup(
+ TestAllTypesLite.OptionalGroup.newBuilder().setA(117).build());
+ builder.setOptionalNestedMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(118).build());
+ builder.setOptionalForeignMessage(
+ ForeignMessageLite.newBuilder().setC(119).build());
+ builder.setOptionalImportMessage(
+ ImportMessageLite.newBuilder().setD(120).build());
+ builder.setOptionalPublicImportMessage(
+ PublicImportMessageLite.newBuilder().setE(126).build());
+ builder.setOptionalLazyMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(127).build());
+ builder.setOptionalNestedEnum (TestAllTypesLite.NestedEnum.BAZ);
+ builder.setOptionalForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAZ);
+ builder.setOptionalImportEnum (ImportEnumLite.IMPORT_LITE_BAZ);
+ builder.setOptionalStringPiece("124");
+ builder.setOptionalCord("125");
+ // -----------------------------------------------------------------
+ builder.addRepeatedInt32 (201);
+ builder.addRepeatedInt64 (202);
+ builder.addRepeatedUint32 (203);
+ builder.addRepeatedUint64 (204);
+ builder.addRepeatedSint32 (205);
+ builder.addRepeatedSint64 (206);
+ builder.addRepeatedFixed32 (207);
+ builder.addRepeatedFixed64 (208);
+ builder.addRepeatedSfixed32(209);
+ builder.addRepeatedSfixed64(210);
+ builder.addRepeatedFloat (211);
+ builder.addRepeatedDouble (212);
+ builder.addRepeatedBool (true);
+ builder.addRepeatedString ("215");
+ builder.addRepeatedBytes (toBytes("216"));
+ builder.addRepeatedGroup(
+ TestAllTypesLite.RepeatedGroup.newBuilder().setA(217).build());
+ builder.addRepeatedNestedMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(218).build());
+ builder.addRepeatedForeignMessage(
+ ForeignMessageLite.newBuilder().setC(219).build());
+ builder.addRepeatedImportMessage(
+ ImportMessageLite.newBuilder().setD(220).build());
+ builder.addRepeatedLazyMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(227).build());
+ builder.addRepeatedNestedEnum (TestAllTypesLite.NestedEnum.BAR);
+ builder.addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR);
+ builder.addRepeatedImportEnum (ImportEnumLite.IMPORT_LITE_BAR);
+ builder.addRepeatedStringPiece("224");
+ builder.addRepeatedCord("225");
+ // Add a second one of each field.
+ builder.addRepeatedInt32 (301);
+ builder.addRepeatedInt64 (302);
+ builder.addRepeatedUint32 (303);
+ builder.addRepeatedUint64 (304);
+ builder.addRepeatedSint32 (305);
+ builder.addRepeatedSint64 (306);
+ builder.addRepeatedFixed32 (307);
+ builder.addRepeatedFixed64 (308);
+ builder.addRepeatedSfixed32(309);
+ builder.addRepeatedSfixed64(310);
+ builder.addRepeatedFloat (311);
+ builder.addRepeatedDouble (312);
+ builder.addRepeatedBool (false);
+ builder.addRepeatedString ("315");
+ builder.addRepeatedBytes (toBytes("316"));
+ TestAllTypesLite.RepeatedGroup.newBuilder().setA(317).build());
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(318).build());
+ ForeignMessageLite.newBuilder().setC(319).build());
+ ImportMessageLite.newBuilder().setD(320).build());
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(327).build());
+ builder.addRepeatedNestedEnum (TestAllTypesLite.NestedEnum.BAZ);
+ builder.addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAZ);
+ builder.addRepeatedImportEnum (ImportEnumLite.IMPORT_LITE_BAZ);
+ builder.addRepeatedStringPiece("324");
+ builder.addRepeatedCord("325");
+ builder.setDefaultInt32 (401);
+ builder.setDefaultInt64 (402);
+ builder.setDefaultUint32 (403);
+ builder.setDefaultUint64 (404);
+ builder.setDefaultSint32 (405);
+ builder.setDefaultSint64 (406);
+ builder.setDefaultFixed32 (407);
+ builder.setDefaultFixed64 (408);
+ builder.setDefaultSfixed32(409);
+ builder.setDefaultSfixed64(410);
+ builder.setDefaultFloat (411);
+ builder.setDefaultDouble (412);
+ builder.setDefaultBool (false);
+ builder.setDefaultString ("415");
+ builder.setDefaultBytes (toBytes("416"));
+ builder.setDefaultNestedEnum (TestAllTypesLite.NestedEnum.FOO);
+ builder.setDefaultForeignEnum(ForeignEnumLite.FOREIGN_LITE_FOO);
+ builder.setDefaultImportEnum (ImportEnumLite.IMPORT_LITE_FOO);
+ builder.setDefaultStringPiece("424");
+ builder.setDefaultCord("425");
+ builder.setOneofUint32(601);
+ builder.setOneofNestedMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(602).build());
+ builder.setOneofString("603");
+ builder.setOneofBytes(toBytes("604"));
* Set every field of {@code message} to the values expected by
@@ -36,7 +36,6 @@ option java_package = "map_test";
option java_outer_classname = "MapTestProto";
option java_generate_equals_and_hash = true;
message TestMap {
message MessageValue {
int32 value = 1;
@@ -45,7 +45,6 @@ option java_package = "com.google.protobuf";
option java_outer_classname = "TestBadIdentifiersProto";
message TestMessage {
optional string cached_size = 1;
optional string serialized_size = 2;
@@ -30,6 +30,9 @@
package com.google.protobuf.util;
+import static com.google.common.base.Preconditions.checkArgument;
+import com.google.common.primitives.Ints;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.FieldMask;
@@ -37,7 +40,6 @@ import com.google.protobuf.Internal;
import com.google.protobuf.Message;
-import java.util.List;
* Utility helper functions to work with {@link com.google.protobuf.FieldMask}.
@@ -53,6 +55,7 @@ public class FieldMaskUtil {
* Converts a FieldMask to a string.
public static String toString(FieldMask fieldMask) {
+ // TODO(xiaofeng): Consider using com.google.common.base.Joiner here instead.
StringBuilder result = new StringBuilder();
boolean first = true;
for (String value : fieldMask.getPathsList()) {
@@ -74,6 +77,7 @@ public class FieldMaskUtil {
* Parses from a string to a FieldMask.
public static FieldMask fromString(String value) {
+ // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead.
return fromStringList(
null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX)));
@@ -83,8 +87,8 @@ public class FieldMaskUtil {
* @throws IllegalArgumentException if any of the field path is invalid.
- public static FieldMask fromString(Class<? extends Message> type, String value)
- throws IllegalArgumentException {
+ public static FieldMask fromString(Class<? extends Message> type, String value) {
type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX)));
@@ -94,9 +98,9 @@ public class FieldMaskUtil {
* @throws IllegalArgumentException if any of the field path is not valid.
+ // TODO(xiaofeng): Consider renaming fromStrings()
public static FieldMask fromStringList(
- Class<? extends Message> type, List<String> paths)
+ Class<? extends Message> type, Iterable<String> paths) {
FieldMask.Builder builder = FieldMask.newBuilder();
for (String path : paths) {
if (path.isEmpty()) {
@@ -112,16 +116,75 @@ public class FieldMaskUtil {
+ * Constructs a FieldMask from the passed field numbers.
+ * @throws IllegalArugmentException if any of the fields are invalid for the message.
+ public static FieldMask fromFieldNumbers(Class<? extends Message> type, int... fieldNumbers) {
+ return fromFieldNumbers(type, Ints.asList(fieldNumbers));
+ public static FieldMask fromFieldNumbers(
+ Class<? extends Message> type, Iterable<Integer> fieldNumbers) {
+ Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForType();
+ FieldMask.Builder builder = FieldMask.newBuilder();
+ for (Integer fieldNumber : fieldNumbers) {
+ FieldDescriptor field = descriptor.findFieldByNumber(fieldNumber);
+ checkArgument(
+ field != null,
+ String.format("%s is not a valid field number for %s.", fieldNumber, type));
+ builder.addPaths(field.getName());
+ return builder.build();
+ * Checks whether paths in a given fields mask are valid.
+ public static boolean isValid(Class<? extends Message> type, FieldMask fieldMask) {
+ Descriptor descriptor =
+ Internal.getDefaultInstance(type).getDescriptorForType();
+ return isValid(descriptor, fieldMask);
+ public static boolean isValid(Descriptor descriptor, FieldMask fieldMask) {
+ for (String path : fieldMask.getPathsList()) {
+ if (!isValid(descriptor, path)) {
* Checks whether a given field path is valid.
public static boolean isValid(Class<? extends Message> type, String path) {
+ return isValid(descriptor, path);
+ public static boolean isValid(Descriptor descriptor, String path) {
String[] parts = path.split(FIELD_SEPARATOR_REGEX);
if (parts.length == 0) {
- Descriptor descriptor =
- Internal.getDefaultInstance(type).getDescriptorForType();
for (String name : parts) {
if (descriptor == null) {
@@ -171,7 +234,7 @@ public class FieldMaskUtil {
* Options to customize merging behavior.
- public static class MergeOptions {
+ public static final class MergeOptions {
private boolean replaceMessageFields = false;
private boolean replaceRepeatedFields = false;
@@ -78,6 +78,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.logging.Logger;
@@ -99,7 +100,7 @@ public class JsonFormat {
* Creates a {@link Printer} with default configurations.
public static Printer printer() {
- return new Printer(TypeRegistry.getEmptyTypeRegistry());
+ return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false);
@@ -107,9 +108,16 @@ public class JsonFormat {
public static class Printer {
private final TypeRegistry registry;
- private Printer(TypeRegistry registry) {
+ private final boolean includingDefaultValueFields;
+ private final boolean preservingProtoFieldNames;
+ private Printer(
+ TypeRegistry registry,
+ boolean includingDefaultValueFields,
+ boolean preservingProtoFieldNames) {
this.registry = registry;
+ this.includingDefaultValueFields = includingDefaultValueFields;
+ this.preservingProtoFieldNames = preservingProtoFieldNames;
@@ -122,7 +130,27 @@ public class JsonFormat {
if (this.registry != TypeRegistry.getEmptyTypeRegistry()) {
throw new IllegalArgumentException("Only one registry is allowed.");
- return new Printer(registry);
+ return new Printer(registry, includingDefaultValueFields, preservingProtoFieldNames);
+ * Creates a new {@link Printer} that will also print fields set to their
+ * defaults. Empty repeated fields and map fields will be printed as well.
+ * The new Printer clones all other configurations from the current
+ * {@link Printer}.
+ public Printer includingDefaultValueFields() {
+ return new Printer(registry, true, preservingProtoFieldNames);
+ * Creates a new {@link Printer} that is configured to use the original proto
+ * field names as defined in the .proto file rather than converting them to
+ * lowerCamelCase. The new Printer clones all other configurations from the
+ * current {@link Printer}.
+ public Printer preservingProtoFieldNames() {
+ return new Printer(registry, includingDefaultValueFields, true);
@@ -136,7 +164,8 @@ public class JsonFormat {
// TODO(xiaofeng): Investigate the allocation overhead and optimize for
// mobile.
- new PrinterImpl(registry, output).print(message);
+ new PrinterImpl(registry, includingDefaultValueFields, preservingProtoFieldNames, output)
+ .print(message);
@@ -298,7 +327,7 @@ public class JsonFormat {
private void addFile(FileDescriptor file) {
// Skip the file if it's already added.
- if (files.contains(file.getName())) {
+ if (!files.add(file.getFullName())) {
for (FileDescriptor dependency : file.getDependencies()) {
@@ -397,6 +426,8 @@ public class JsonFormat {
private static final class PrinterImpl {
private final TextGenerator generator;
// We use Gson to help handle string escapes.
private final Gson gson;
@@ -405,8 +436,14 @@ public class JsonFormat {
private static final Gson DEFAULT_GSON = new Gson();
- PrinterImpl(TypeRegistry registry, Appendable jsonOutput) {
+ PrinterImpl(
+ boolean preservingProtoFieldNames,
+ Appendable jsonOutput) {
this.generator = new TextGenerator(jsonOutput);
this.gson = GsonHolder.DEFAULT_GSON;
@@ -647,13 +684,23 @@ public class JsonFormat {
generator.print("\"@type\": " + gson.toJson(typeUrl));
printedField = true;
- for (Map.Entry<FieldDescriptor, Object> field
- : message.getAllFields().entrySet()) {
- // Skip unknown enum fields.
- if (field.getValue() instanceof EnumValueDescriptor
- && ((EnumValueDescriptor) field.getValue()).getIndex() == -1) {
- continue;
+ Map<FieldDescriptor, Object> fieldsToPrint = null;
+ if (includingDefaultValueFields) {
+ fieldsToPrint = new TreeMap<FieldDescriptor, Object>();
+ for (FieldDescriptor field : message.getDescriptorForType().getFields()) {
+ if (field.isOptional()
+ && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE
+ && !message.hasField(field)) {
+ // Always skip empty optional message fields. If not we will recurse indefinitely if
+ // a message has itself as a sub-field.
+ continue;
+ fieldsToPrint.put(field, message.getField(field));
+ fieldsToPrint = message.getAllFields();
+ for (Map.Entry<FieldDescriptor, Object> field : fieldsToPrint.entrySet()) {
if (printedField) {
// Add line-endings for the previous field.
generator.print(",\n");
@@ -673,7 +720,11 @@ public class JsonFormat {
private void printField(FieldDescriptor field, Object value)
- generator.print("\"" + fieldNameToCamelName(field.getName()) + "\": ");
+ if (preservingProtoFieldNames) {
+ generator.print("\"" + field.getName() + "\": ");
+ generator.print("\"" + field.getJsonName() + "\": ");
if (field.isMapField()) {
printMapFieldValue(field, value);
} else if (field.isRepeated()) {
@@ -689,11 +740,6 @@ public class JsonFormat {
generator.print("[");
boolean printedElement = false;
for (Object element : (List) value) {
- // Skip unknown enum entries.
- if (element instanceof EnumValueDescriptor
- && ((EnumValueDescriptor) element).getIndex() == -1) {
if (printedElement) {
generator.print(", ");
@@ -720,11 +766,6 @@ public class JsonFormat {
Message entry = (Message) element;
Object entryKey = entry.getField(keyField);
Object entryValue = entry.getField(valueField);
- if (entryValue instanceof EnumValueDescriptor
- && ((EnumValueDescriptor) entryValue).getIndex() == -1) {
@@ -871,8 +912,13 @@ public class JsonFormat {
generator.print("\"");
- generator.print(
- "\"" + ((EnumValueDescriptor) value).getName() + "\"");
+ if (((EnumValueDescriptor) value).getIndex() == -1) {
+ generator.print(
+ String.valueOf(((EnumValueDescriptor) value).getNumber()));
+ "\"" + ((EnumValueDescriptor) value).getName() + "\"");
@@ -916,38 +962,6 @@ public class JsonFormat {
return parts[1];
- private static String fieldNameToCamelName(String name) {
- StringBuilder result = new StringBuilder(name.length());
- boolean isNextUpperCase = false;
- for (int i = 0; i < name.length(); i++) {
- Character ch = name.charAt(i);
- if (Character.isLowerCase(ch)) {
- if (isNextUpperCase) {
- result.append(Character.toUpperCase(ch));
- result.append(ch);
- isNextUpperCase = false;
- } else if (Character.isUpperCase(ch)) {
- if (i == 0 && !isNextUpperCase) {
- // Force first letter to lower-case unless explicitly told to
- // capitalize it.
- result.append(Character.toLowerCase(ch));
- // Capital letters after the first are left as-is.
- } else if (Character.isDigit(ch)) {
- isNextUpperCase = true;
- return result.toString();
private static class ParserImpl {
@@ -1085,7 +1099,8 @@ public class JsonFormat {
Map<String, FieldDescriptor> fieldNameMap =
new HashMap<String, FieldDescriptor>();
for (FieldDescriptor field : descriptor.getFields()) {
- fieldNameMap.put(fieldNameToCamelName(field.getName()), field);
+ fieldNameMap.put(field.getName(), field);
+ fieldNameMap.put(field.getJsonName(), field);
fieldNameMaps.put(descriptor, fieldNameMap);
return fieldNameMap;
@@ -1244,7 +1259,25 @@ public class JsonFormat {
private void mergeField(FieldDescriptor field, JsonElement json,
Message.Builder builder) throws InvalidProtocolBufferException {
- if (json instanceof JsonNull) {
+ if (field.isRepeated()) {
+ if (builder.getRepeatedFieldCount(field) > 0) {
+ throw new InvalidProtocolBufferException(
+ "Field " + field.getFullName() + " has already been set.");
+ if (builder.hasField(field)) {
+ if (field.getContainingOneof() != null
+ && builder.getOneofFieldDescriptor(field.getContainingOneof()) != null) {
+ FieldDescriptor other = builder.getOneofFieldDescriptor(field.getContainingOneof());
+ "Cannot set field " + field.getFullName() + " because another field "
+ + other.getFullName() + " belonging to the same oneof has already been set ");
+ if (field.isRepeated() && json instanceof JsonNull) {
// We allow "null" as value for all field types and treat it as if the
// field is not present.
@@ -1282,7 +1315,8 @@ public class JsonFormat {
Object value = parseFieldValue(
valueField, entry.getValue(), entryBuilder);
if (value == null) {
- value = getDefaultValue(valueField, entryBuilder);
+ "Map value cannot be null.");
entryBuilder.setField(keyField, key);
entryBuilder.setField(valueField, value);
@@ -1341,7 +1375,8 @@ public class JsonFormat {
for (int i = 0; i < array.size(); ++i) {
Object value = parseFieldValue(field, array.get(i), builder);
- value = getDefaultValue(field, builder);
+ "Repeated field elements cannot be null");
builder.addRepeatedField(field, value);
@@ -1351,6 +1386,15 @@ public class JsonFormat {
throws InvalidProtocolBufferException {
return Integer.parseInt(json.getAsString());
+ } catch (Exception e) {
+ // Fall through.
+ // JSON doesn't distinguish between integer values and floating point values so "1" and
+ // "1.000" are treated as equal in JSON. For this reason we accept floating point values for
+ // integer fields as well as long as it actually is an integer (i.e., round(value) == value).
+ BigDecimal value = new BigDecimal(json.getAsString());
+ return value.intValueExact();
} catch (Exception e) {
throw new InvalidProtocolBufferException("Not an int32 value: " + json);
@@ -1361,7 +1405,16 @@ public class JsonFormat {
return Long.parseLong(json.getAsString());
- throw new InvalidProtocolBufferException("Not an int64 value: " + json);
+ return value.longValueExact();
+ throw new InvalidProtocolBufferException("Not an int32 value: " + json);
@@ -1376,6 +1429,21 @@ public class JsonFormat {
return (int) result;
} catch (InvalidProtocolBufferException e) {
throw e;
+ BigDecimal decimalValue = new BigDecimal(json.getAsString());
+ BigInteger value = decimalValue.toBigIntegerExact();
+ if (value.signum() < 0 || value.compareTo(new BigInteger("FFFFFFFF", 16)) > 0) {
+ throw new InvalidProtocolBufferException("Out of range uint32 value: " + json);
+ return value.intValue();
throw new InvalidProtocolBufferException(
"Not an uint32 value: " + json);
@@ -1388,7 +1456,8 @@ public class JsonFormat {
private long parseUint64(JsonElement json)
- BigInteger value = new BigInteger(json.getAsString());
if (value.compareTo(BigInteger.ZERO) < 0
|| value.compareTo(MAX_UINT64) > 0) {
@@ -1488,7 +1557,12 @@ public class JsonFormat {
return json.getAsString();
- private ByteString parseBytes(JsonElement json) {
+ private ByteString parseBytes(JsonElement json) throws InvalidProtocolBufferException {
+ String encoded = json.getAsString();
+ if (encoded.length() % 4 != 0) {
+ "Bytes field is not encoded in standard BASE64 with paddings: " + encoded);
return ByteString.copyFrom(
BaseEncoding.base64().decode(json.getAsString()));
@@ -1498,9 +1572,25 @@ public class JsonFormat {
String value = json.getAsString();
EnumValueDescriptor result = enumDescriptor.findValueByName(value);
if (result == null) {
- throw new InvalidProtocolBufferException(
- "Invalid enum value: " + value + " for enum type: "
- + enumDescriptor.getFullName());
+ // Try to interpret the value as a number.
+ int numericValue = parseInt32(json);
+ if (enumDescriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3) {
+ result = enumDescriptor.findValueByNumberCreatingIfUnknown(numericValue);
+ result = enumDescriptor.findValueByNumber(numericValue);
+ // Fall through. This exception is about invalid int32 value we get from parseInt32() but
+ // that's not the exception we want the user to see. Since result == null, we will throw
+ // an exception later.
+ if (result == null) {
+ "Invalid enum value: " + value + " for enum type: "
+ + enumDescriptor.getFullName());
@@ -58,8 +58,12 @@ public class TimeUtil {
private static final long MILLIS_PER_SECOND = 1000;
private static final long MICROS_PER_SECOND = 1000000;
- private static final SimpleDateFormat timestampFormat =
- createTimestampFormat();
+ private static final ThreadLocal<SimpleDateFormat> timestampFormat =
+ new ThreadLocal<SimpleDateFormat>() {
+ protected SimpleDateFormat initialValue() {
+ return createTimestampFormat();
private static SimpleDateFormat createTimestampFormat() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
@@ -96,7 +100,7 @@ public class TimeUtil {
throw new IllegalArgumentException("Timestamp is out of range.");
Date date = new Date(timestamp.getSeconds() * MILLIS_PER_SECOND);
- result.append(timestampFormat.format(date));
+ result.append(timestampFormat.get().format(date));
// Format the nanos part.
if (timestamp.getNanos() < 0 || timestamp.getNanos() >= NANOS_PER_SECOND) {
throw new IllegalArgumentException("Timestamp has invalid nanos value.");
@@ -147,7 +151,7 @@ public class TimeUtil {
secondValue = timeValue.substring(0, pointPosition);
nanoValue = timeValue.substring(pointPosition + 1);
- Date date = timestampFormat.parse(secondValue);
+ Date date = timestampFormat.get().parse(secondValue);
long seconds = date.getTime() / MILLIS_PER_SECOND;
int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue);
// Parse timezone offsets.
@@ -52,6 +52,21 @@ public class FieldMaskUtilTest extends TestCase {
assertFalse(FieldMaskUtil.isValid(
NestedTestAllTypes.class, "payload.nonexist"));
+ assertTrue(FieldMaskUtil.isValid(
+ NestedTestAllTypes.class, FieldMaskUtil.fromString("payload")));
+ assertFalse(FieldMaskUtil.isValid(
+ NestedTestAllTypes.class, FieldMaskUtil.fromString("nonexist")));
+ NestedTestAllTypes.class, FieldMaskUtil.fromString("payload,nonexist")));
+ assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.getDescriptor(), "payload"));
+ assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.getDescriptor(), "nonexist"));
+ NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("payload")));
+ NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("nonexist")));
assertTrue(FieldMaskUtil.isValid(
NestedTestAllTypes.class, "payload.optional_nested_message.bb"));
// Repeated fields cannot have sub-paths.
@@ -74,7 +89,7 @@ public class FieldMaskUtilTest extends TestCase {
addPaths("bar").addPaths("").build();
assertEquals("foo,bar", FieldMaskUtil.toString(mask));
public void testFromString() throws Exception {
FieldMask mask = FieldMaskUtil.fromString("");
assertEquals(0, mask.getPathsCount());
@@ -85,16 +100,16 @@ public class FieldMaskUtilTest extends TestCase {
assertEquals(2, mask.getPathsCount());
assertEquals("foo", mask.getPaths(0));
assertEquals("bar.baz", mask.getPaths(1));
// Empty field paths are ignore.
mask = FieldMaskUtil.fromString(",foo,,bar,");
assertEquals("bar", mask.getPaths(1));
// Check whether the field paths are valid if a class parameter is provided.
mask = FieldMaskUtil.fromString(NestedTestAllTypes.class, ",payload");
mask = FieldMaskUtil.fromString(
NestedTestAllTypes.class, "payload,nonexist");
@@ -103,6 +118,31 @@ public class FieldMaskUtilTest extends TestCase {
// Expected.
+ public void testFromFieldNumbers() throws Exception {
+ FieldMask mask = FieldMaskUtil.fromFieldNumbers(TestAllTypes.class);
+ assertEquals(0, mask.getPathsCount());
+ mask =
+ FieldMaskUtil.fromFieldNumbers(
+ TestAllTypes.class, TestAllTypes.OPTIONAL_INT32_FIELD_NUMBER);
+ assertEquals(1, mask.getPathsCount());
+ assertEquals("optional_int32", mask.getPaths(0));
+ TestAllTypes.class,
+ TestAllTypes.OPTIONAL_INT32_FIELD_NUMBER,
+ TestAllTypes.OPTIONAL_INT64_FIELD_NUMBER);
+ assertEquals(2, mask.getPathsCount());
+ assertEquals("optional_int64", mask.getPaths(1));
+ int invalidFieldNumber = 1000;
+ mask = FieldMaskUtil.fromFieldNumbers(TestAllTypes.class, invalidFieldNumber);
+ fail("Exception is expected.");
+ } catch (IllegalArgumentException expected) {
public void testUnion() throws Exception {
// Only test a simple case here and expect
@@ -51,9 +51,11 @@ import com.google.protobuf.util.JsonTestProto.TestAllTypes;
import com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedEnum;
import com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedMessage;
import com.google.protobuf.util.JsonTestProto.TestAny;
+import com.google.protobuf.util.JsonTestProto.TestCustomJsonName;
import com.google.protobuf.util.JsonTestProto.TestDuration;
import com.google.protobuf.util.JsonTestProto.TestFieldMask;
import com.google.protobuf.util.JsonTestProto.TestMap;
+import com.google.protobuf.util.JsonTestProto.TestOneof;
import com.google.protobuf.util.JsonTestProto.TestStruct;
import com.google.protobuf.util.JsonTestProto.TestTimestamp;
import com.google.protobuf.util.JsonTestProto.TestWrappers;
@@ -196,9 +198,6 @@ public class JsonFormatTest extends TestCase {
public void testUnknownEnumValues() throws Exception {
- // Unknown enum values will be dropped.
- // TODO(xiaofeng): We may want to revisit this (whether we should omit
- // unknown enum values).
TestAllTypes message = TestAllTypes.newBuilder()
.setOptionalNestedEnumValue(12345)
.addRepeatedNestedEnumValue(12345)
@@ -206,8 +205,10 @@ public class JsonFormatTest extends TestCase {
.build();
assertEquals(
"{\n"
- + " \"repeatedNestedEnum\": [\"FOO\"]\n"
+ + " \"optionalNestedEnum\": 12345,\n"
+ + " \"repeatedNestedEnum\": [12345, \"FOO\"]\n"
+ "}", toJsonString(message));
+ assertRoundTripEquals(message);
TestMap.Builder mapBuilder = TestMap.newBuilder();
mapBuilder.getMutableInt32ToEnumMapValue().put(1, 0);
@@ -216,9 +217,11 @@ public class JsonFormatTest extends TestCase {
+ " \"int32ToEnumMap\": {\n"
- + " \"1\": \"FOO\"\n"
+ + " \"1\": \"FOO\",\n"
+ + " \"2\": 12345\n"
+ " }\n"
+ "}", toJsonString(mapMessage));
+ assertRoundTripEquals(mapMessage);
public void testSpecialFloatValues() throws Exception {
@@ -263,6 +266,35 @@ public class JsonFormatTest extends TestCase {
assertEquals(true, message.getOptionalBool());
+ public void testParserAcceptFloatingPointValueForIntegerField() throws Exception {
+ // Test that numeric values like "1.000", "1e5" will also be accepted.
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ mergeFromJson(
+ "{\n"
+ + " \"repeatedInt32\": [1.000, 1e5, \"1.000\", \"1e5\"],\n"
+ + " \"repeatedUint32\": [1.000, 1e5, \"1.000\", \"1e5\"],\n"
+ + " \"repeatedInt64\": [1.000, 1e5, \"1.000\", \"1e5\"],\n"
+ + " \"repeatedUint64\": [1.000, 1e5, \"1.000\", \"1e5\"]\n"
+ + "}", builder);
+ int[] expectedValues = new int[]{1, 100000, 1, 100000};
+ assertEquals(4, builder.getRepeatedInt32Count());
+ assertEquals(4, builder.getRepeatedUint32Count());
+ assertEquals(4, builder.getRepeatedInt64Count());
+ assertEquals(4, builder.getRepeatedUint64Count());
+ for (int i = 0; i < 4; ++i) {
+ assertEquals(expectedValues[i], builder.getRepeatedInt32(i));
+ assertEquals(expectedValues[i], builder.getRepeatedUint32(i));
+ assertEquals(expectedValues[i], builder.getRepeatedInt64(i));
+ assertEquals(expectedValues[i], builder.getRepeatedUint64(i));
+ // Non-integers will still be rejected.
+ assertRejects("optionalInt32", "1.5");
+ assertRejects("optionalUint32", "1.5");
+ assertRejects("optionalInt64", "1.5");
+ assertRejects("optionalUint64", "1.5");
private void assertRejects(String name, String value) {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
@@ -285,6 +317,7 @@ public class JsonFormatTest extends TestCase {
// Both numeric form and string form are accepted.
mergeFromJson("{\"" + name + "\":" + value + "}", builder);
+ builder.clear();
mergeFromJson("{\"" + name + "\":\"" + value + "\"}", builder);
@@ -370,84 +403,74 @@ public class JsonFormatTest extends TestCase {
TestAllTypes message = builder.build();
assertEquals(TestAllTypes.getDefaultInstance(), message);
- // Repeated field elements can also be null.
- builder = TestAllTypes.newBuilder();
- mergeFromJson(
- "{\n"
- + " \"repeatedInt32\": [null, null],\n"
- + " \"repeatedInt64\": [null, null],\n"
- + " \"repeatedUint32\": [null, null],\n"
- + " \"repeatedUint64\": [null, null],\n"
- + " \"repeatedSint32\": [null, null],\n"
- + " \"repeatedSint64\": [null, null],\n"
- + " \"repeatedFixed32\": [null, null],\n"
- + " \"repeatedFixed64\": [null, null],\n"
- + " \"repeatedSfixed32\": [null, null],\n"
- + " \"repeatedSfixed64\": [null, null],\n"
- + " \"repeatedFloat\": [null, null],\n"
- + " \"repeatedDouble\": [null, null],\n"
- + " \"repeatedBool\": [null, null],\n"
- + " \"repeatedString\": [null, null],\n"
- + " \"repeatedBytes\": [null, null],\n"
- + " \"repeatedNestedMessage\": [null, null],\n"
- + " \"repeatedNestedEnum\": [null, null]\n"
- + "}", builder);
- message = builder.build();
- // "null" elements will be parsed to default values.
- assertEquals(2, message.getRepeatedInt32Count());
- assertEquals(0, message.getRepeatedInt32(0));
- assertEquals(0, message.getRepeatedInt32(1));
- assertEquals(2, message.getRepeatedInt64Count());
- assertEquals(0, message.getRepeatedInt64(0));
- assertEquals(0, message.getRepeatedInt64(1));
- assertEquals(2, message.getRepeatedUint32Count());
- assertEquals(0, message.getRepeatedUint32(0));
- assertEquals(0, message.getRepeatedUint32(1));
- assertEquals(2, message.getRepeatedUint64Count());
- assertEquals(0, message.getRepeatedUint64(0));
- assertEquals(0, message.getRepeatedUint64(1));
- assertEquals(2, message.getRepeatedSint32Count());
- assertEquals(0, message.getRepeatedSint32(0));
- assertEquals(0, message.getRepeatedSint32(1));
- assertEquals(2, message.getRepeatedSint64Count());
- assertEquals(0, message.getRepeatedSint64(0));
- assertEquals(0, message.getRepeatedSint64(1));
- assertEquals(2, message.getRepeatedFixed32Count());
- assertEquals(0, message.getRepeatedFixed32(0));
- assertEquals(0, message.getRepeatedFixed32(1));
- assertEquals(2, message.getRepeatedFixed64Count());
- assertEquals(0, message.getRepeatedFixed64(0));
- assertEquals(0, message.getRepeatedFixed64(1));
- assertEquals(2, message.getRepeatedSfixed32Count());
- assertEquals(0, message.getRepeatedSfixed32(0));
- assertEquals(0, message.getRepeatedSfixed32(1));
- assertEquals(2, message.getRepeatedSfixed64Count());
- assertEquals(0, message.getRepeatedSfixed64(0));
- assertEquals(0, message.getRepeatedSfixed64(1));
- assertEquals(2, message.getRepeatedFloatCount());
- assertEquals(0f, message.getRepeatedFloat(0));
- assertEquals(0f, message.getRepeatedFloat(1));
- assertEquals(2, message.getRepeatedDoubleCount());
- assertEquals(0.0, message.getRepeatedDouble(0));
- assertEquals(0.0, message.getRepeatedDouble(1));
- assertEquals(2, message.getRepeatedBoolCount());
- assertFalse(message.getRepeatedBool(0));
- assertFalse(message.getRepeatedBool(1));
- assertEquals(2, message.getRepeatedStringCount());
- assertTrue(message.getRepeatedString(0).isEmpty());
- assertTrue(message.getRepeatedString(1).isEmpty());
- assertEquals(2, message.getRepeatedBytesCount());
- assertTrue(message.getRepeatedBytes(0).isEmpty());
- assertTrue(message.getRepeatedBytes(1).isEmpty());
- assertEquals(2, message.getRepeatedNestedMessageCount());
- assertEquals(NestedMessage.getDefaultInstance(), message.getRepeatedNestedMessage(0));
- assertEquals(NestedMessage.getDefaultInstance(), message.getRepeatedNestedMessage(1));
- assertEquals(2, message.getRepeatedNestedEnumCount());
- assertEquals(0, message.getRepeatedNestedEnumValue(0));
- assertEquals(0, message.getRepeatedNestedEnumValue(1));
+ builder = TestAllTypes.newBuilder();
+ + " \"repeatedInt32\": [null, null],\n"
+ fail();
+ // Exception expected.
+ + " \"repeatedNestedMessage\": [null, null],\n"
+ public void testParserRejectDuplicatedFields() throws Exception {
+ // TODO(xiaofeng): The parser we are currently using (GSON) will accept and keep the last
+ // one if multiple entries have the same name. This is not the desired behavior but it can
+ // only be fixed by using our own parser. Here we only test the cases where the names are
+ // different but still referring to the same field.
+ // Duplicated optional fields.
+ + " \"optionalNestedMessage\": {},\n"
+ + " \"optional_nested_message\": {}\n"
+ // Duplicated repeated fields.
+ + " \"repeated_nested_message\": [null, null]\n"
+ // Duplicated oneof fields.
+ TestOneof.Builder builder = TestOneof.newBuilder();
+ + " \"oneofInt32\": 1,\n"
+ + " \"oneof_int32\": 2\n"
public void testMapFields() throws Exception {
@@ -592,18 +615,30 @@ public class JsonFormatTest extends TestCase {
assertRoundTripEquals(message);
- public void testMapNullValueIsDefault() throws Exception {
- TestMap.Builder builder = TestMap.newBuilder();
- + " \"int32ToInt32Map\": {\"1\": null},\n"
- + " \"int32ToMessageMap\": {\"2\": null}\n"
- TestMap message = builder.build();
- assertTrue(message.getInt32ToInt32Map().containsKey(1));
- assertEquals(0, message.getInt32ToInt32Map().get(1).intValue());
- assertTrue(message.getInt32ToMessageMap().containsKey(2));
- assertEquals(0, message.getInt32ToMessageMap().get(2).getValue());
+ public void testMapNullValueIsRejected() throws Exception {
+ TestMap.Builder builder = TestMap.newBuilder();
+ + " \"int32ToInt32Map\": {null: 1},\n"
+ + " \"int32ToMessageMap\": {null: 2}\n"
+ + " \"int32ToInt32Map\": {\"1\": null},\n"
+ + " \"int32ToMessageMap\": {\"2\": null}\n"
public void testParserAcceptNonQuotedObjectKey() throws Exception {
@@ -743,6 +778,15 @@ public class JsonFormatTest extends TestCase {
+ builder = TestStruct.newBuilder();
+ builder.setValue(Value.newBuilder().setNullValueValue(0).build());
+ message = builder.build();
+ + " \"value\": null\n"
+ + "}", toJsonString(message));
public void testAnyFields() throws Exception {
@@ -891,6 +935,15 @@ public class JsonFormatTest extends TestCase {
+ "}", printer.print(anyMessage));
assertRoundTripEquals(anyMessage, registry);
+ Value.Builder valueBuilder = Value.newBuilder();
+ valueBuilder.setNumberValue(1);
+ anyMessage = Any.pack(valueBuilder.build());
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n"
+ + " \"value\": 1.0\n"
+ + "}", printer.print(anyMessage));
+ assertRoundTripEquals(anyMessage, registry);
public void testParserMissingTypeUrl() throws Exception {
@@ -949,16 +1002,9 @@ public class JsonFormatTest extends TestCase {
public void testParserRejectInvalidBase64() throws Exception {
- TestAllTypes.Builder builder = TestAllTypes.newBuilder();
- + " \"optionalBytes\": \"!@#$\"\n"
- fail("Exception is expected.");
- } catch (InvalidProtocolBufferException e) {
- // Expected.
+ assertRejects("optionalBytes", "!@#$");
+ // We use standard BASE64 with paddings.
+ assertRejects("optionalBytes", "AQI");
public void testParserRejectInvalidEnumValue() throws Exception {
@@ -973,4 +1019,138 @@ public class JsonFormatTest extends TestCase {
+ public void testCustomJsonName() throws Exception {
+ TestCustomJsonName message = TestCustomJsonName.newBuilder().setValue(12345).build();
+ assertEquals("{\n" + " \"@value\": 12345\n" + "}", JsonFormat.printer().print(message));
+ public void testIncludingDefaultValueFields() throws Exception {
+ TestAllTypes message = TestAllTypes.getDefaultInstance();
+ assertEquals("{\n}", JsonFormat.printer().print(message));
+ + " \"optionalInt32\": 0,\n"
+ + " \"optionalInt64\": \"0\",\n"
+ + " \"optionalUint32\": 0,\n"
+ + " \"optionalUint64\": \"0\",\n"
+ + " \"optionalSint32\": 0,\n"
+ + " \"optionalSint64\": \"0\",\n"
+ + " \"optionalFixed32\": 0,\n"
+ + " \"optionalFixed64\": \"0\",\n"
+ + " \"optionalSfixed32\": 0,\n"
+ + " \"optionalSfixed64\": \"0\",\n"
+ + " \"optionalFloat\": 0.0,\n"
+ + " \"optionalDouble\": 0.0,\n"
+ + " \"optionalBool\": false,\n"
+ + " \"optionalString\": \"\",\n"
+ + " \"optionalBytes\": \"\",\n"
+ + " \"optionalNestedEnum\": \"FOO\",\n"
+ + " \"repeatedInt32\": [],\n"
+ + " \"repeatedInt64\": [],\n"
+ + " \"repeatedUint32\": [],\n"
+ + " \"repeatedUint64\": [],\n"
+ + " \"repeatedSint32\": [],\n"
+ + " \"repeatedSint64\": [],\n"
+ + " \"repeatedFixed32\": [],\n"
+ + " \"repeatedFixed64\": [],\n"
+ + " \"repeatedSfixed32\": [],\n"
+ + " \"repeatedSfixed64\": [],\n"
+ + " \"repeatedFloat\": [],\n"
+ + " \"repeatedDouble\": [],\n"
+ + " \"repeatedBool\": [],\n"
+ + " \"repeatedString\": [],\n"
+ + " \"repeatedBytes\": [],\n"
+ + " \"repeatedNestedMessage\": [],\n"
+ + " \"repeatedNestedEnum\": []\n"
+ + "}",
+ JsonFormat.printer().includingDefaultValueFields().print(message));
+ TestMap mapMessage = TestMap.getDefaultInstance();
+ assertEquals("{\n}", JsonFormat.printer().print(mapMessage));
+ + " \"int32ToInt32Map\": {\n"
+ + " },\n"
+ + " \"int64ToInt32Map\": {\n"
+ + " \"uint32ToInt32Map\": {\n"
+ + " \"uint64ToInt32Map\": {\n"
+ + " \"sint32ToInt32Map\": {\n"
+ + " \"sint64ToInt32Map\": {\n"
+ + " \"fixed32ToInt32Map\": {\n"
+ + " \"fixed64ToInt32Map\": {\n"
+ + " \"sfixed32ToInt32Map\": {\n"
+ + " \"sfixed64ToInt32Map\": {\n"
+ + " \"boolToInt32Map\": {\n"
+ + " \"stringToInt32Map\": {\n"
+ + " \"int32ToInt64Map\": {\n"
+ + " \"int32ToUint32Map\": {\n"
+ + " \"int32ToUint64Map\": {\n"
+ + " \"int32ToSint32Map\": {\n"
+ + " \"int32ToSint64Map\": {\n"
+ + " \"int32ToFixed32Map\": {\n"
+ + " \"int32ToFixed64Map\": {\n"
+ + " \"int32ToSfixed32Map\": {\n"
+ + " \"int32ToSfixed64Map\": {\n"
+ + " \"int32ToFloatMap\": {\n"
+ + " \"int32ToDoubleMap\": {\n"
+ + " \"int32ToBoolMap\": {\n"
+ + " \"int32ToStringMap\": {\n"
+ + " \"int32ToBytesMap\": {\n"
+ + " \"int32ToMessageMap\": {\n"
+ + " \"int32ToEnumMap\": {\n"
+ + " }\n"
+ JsonFormat.printer().includingDefaultValueFields().print(mapMessage));
+ public void testPreservingProtoFieldNames() throws Exception {
+ TestAllTypes message = TestAllTypes.newBuilder().setOptionalInt32(12345).build();
+ assertEquals("{\n" + " \"optionalInt32\": 12345\n" + "}", JsonFormat.printer().print(message));
+ "{\n" + " \"optional_int32\": 12345\n" + "}",
+ JsonFormat.printer().preservingProtoFieldNames().print(message));
+ // The json_name field option is ignored when configured to use original proto field names.
+ TestCustomJsonName messageWithCustomJsonName =
+ TestCustomJsonName.newBuilder().setValue(12345).build();
+ "{\n" + " \"value\": 12345\n" + "}",
+ JsonFormat.printer().preservingProtoFieldNames().print(messageWithCustomJsonName));
+ // Parsers accept both original proto field names and lowerCamelCase names.
+ JsonFormat.parser().merge("{\"optionalInt32\": 12345}", builder);
+ assertEquals(12345, builder.getOptionalInt32());
+ JsonFormat.parser().merge("{\"optional_int32\": 54321}", builder);
+ assertEquals(54321, builder.getOptionalInt32());
@@ -38,6 +38,8 @@ import junit.framework.TestCase;
import org.junit.Assert;
import java.text.ParseException;
+import java.util.ArrayList;
/** Unit tests for {@link TimeUtil}. */
public class TimeUtilTest extends TestCase {
@@ -76,6 +78,71 @@ public class TimeUtilTest extends TestCase {
assertEquals("1970-01-01T08:00:00.010Z", TimeUtil.toString(value));
+ private volatile boolean stopParsingThreads = false;
+ private volatile String errorMessage = "";
+ private class ParseTimestampThread extends Thread {
+ private final String[] strings;
+ private final Timestamp[] values;
+ public ParseTimestampThread(String[] strings, Timestamp[] values) {
+ this.strings = strings;
+ this.values = values;
+ public void run() {
+ int index = 0;
+ while (!stopParsingThreads) {
+ Timestamp result;
+ result = TimeUtil.parseTimestamp(strings[index]);
+ } catch (ParseException e) {
+ errorMessage = "Failed to parse timestamp: " + strings[index];
+ if (result.getSeconds() != values[index].getSeconds()
+ || result.getNanos() != values[index].getNanos()) {
+ errorMessage = "Actual result: " + result.toString() + ", expected: "
+ + values[index].toString();
+ index = (index + 1) % strings.length;
+ public void testTimestampConcurrentParsing() throws Exception {
+ String[] timestampStrings = new String[]{
+ "9999-12-31T23:59:59.999999999Z",
+ "1970-01-01T00:00:00Z",
+ "1969-12-31T23:59:59.999Z",
+ Timestamp[] timestampValues = new Timestamp[timestampStrings.length];
+ for (int i = 0; i < timestampStrings.length; i++) {
+ timestampValues[i] = TimeUtil.parseTimestamp(timestampStrings[i]);
+ final int THREAD_COUNT = 16;
+ final int RUNNING_TIME = 5000; // in milliseconds.
+ final List<Thread> threads = new ArrayList<Thread>();
+ stopParsingThreads = false;
+ errorMessage = "";
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ Thread thread = new ParseTimestampThread(
+ timestampStrings, timestampValues);
+ thread.start();
+ threads.add(thread);
+ Thread.sleep(RUNNING_TIME);
+ stopParsingThreads = true;
+ for (Thread thread : threads) {
+ thread.join();
+ Assert.assertEquals("", errorMessage);
public void testTimetampInvalidFormat() throws Exception {
// Value too small.
@@ -65,7 +65,7 @@ message TestAllTypes {
float optional_float = 11;
double optional_double = 12;
bool optional_bool = 13;
- string optional_string = 14;
+ string optional_string = 14 [enforce_utf8 = false];
bytes optional_bytes = 15;
NestedMessage optional_nested_message = 18;
NestedEnum optional_nested_enum = 21;
@@ -84,12 +84,19 @@ message TestAllTypes {
repeated float repeated_float = 41;
repeated double repeated_double = 42;
repeated bool repeated_bool = 43;
- repeated string repeated_string = 44;
+ repeated string repeated_string = 44 [enforce_utf8 = false];
repeated bytes repeated_bytes = 45;
repeated NestedMessage repeated_nested_message = 48;
repeated NestedEnum repeated_nested_enum = 51;
+message TestOneof {
+ oneof oneof_field {
+ int32 oneof_int32 = 1;
+ TestAllTypes.NestedMessage oneof_nested_message = 2;
// Instead of testing all combinations (too many), we only make sure all
// valid types have been used at least in one field as key and in one
@@ -105,7 +112,7 @@ message TestMap {
map<sfixed32, int32> sfixed32_to_int32_map = 9;
map<sfixed64, int32> sfixed64_to_int32_map = 10;
map<bool, int32> bool_to_int32_map = 11;
- map<string, int32> string_to_int32_map = 12;
+ map<string, int32> string_to_int32_map = 12 [enforce_utf8 = false];
map<int32, int64> int32_to_int64_map = 101;
map<int32, uint32> int32_to_uint32_map = 102;
@@ -119,7 +126,7 @@ message TestMap {
map<int32, float> int32_to_float_map = 110;
map<int32, double> int32_to_double_map = 111;
map<int32, bool> int32_to_bool_map = 112;
- map<int32, string> int32_to_string_map = 113;
+ map<int32, string> int32_to_string_map = 113 [enforce_utf8 = false];
map<int32, bytes> int32_to_bytes_map = 114;
map<int32, TestAllTypes.NestedMessage> int32_to_message_map = 115;
map<int32, TestAllTypes.NestedEnum> int32_to_enum_map = 116;
@@ -151,8 +158,13 @@ message TestFieldMask {
message TestStruct {
google.protobuf.Struct struct_value = 1;
+ google.protobuf.Value value = 2;
message TestAny {
google.protobuf.Any any_value = 1;
+message TestCustomJsonName {
+ int32 value = 1 [json_name = "@value"];
@@ -0,0 +1,413 @@
+ * @fileoverview This file contains helper code used by jspb.utils to
+ * handle 64-bit integer conversion to/from strings.
+ * @author cfallin@google.com (Chris Fallin)
+ * TODO(haberman): move this to javascript/closure/math?
+goog.provide('jspb.arith.Int64');
+goog.provide('jspb.arith.UInt64');
+ * UInt64 implements some 64-bit arithmetic routines necessary for properly
+ * handling 64-bit integer fields. It implements lossless integer arithmetic on
+ * top of JavaScript's number type, which has only 53 bits of precision, by
+ * representing 64-bit integers as two 32-bit halves.
+ * @param {number} lo The low 32 bits.
+ * @param {number} hi The high 32 bits.
+ * @constructor
+jspb.arith.UInt64 = function(lo, hi) {
+ * The low 32 bits.
+ * @public {number}
+ this.lo = lo;
+ * The high 32 bits.
+ this.hi = hi;
+};
+ * Compare two 64-bit numbers. Returns -1 if the first is
+ * less, +1 if the first is greater, or 0 if both are equal.
+ * @param {!jspb.arith.UInt64} other
+ * @return {number}
+jspb.arith.UInt64.prototype.cmp = function(other) {
+ if (this.hi < other.hi || (this.hi == other.hi && this.lo < other.lo)) {
+ } else if (this.hi == other.hi && this.lo == other.lo) {
+ return 1;
+ * Right-shift this number by one bit.
+ * @return {!jspb.arith.UInt64}
+jspb.arith.UInt64.prototype.rightShift = function() {
+ var hi = this.hi >>> 1;
+ var lo = (this.lo >>> 1) | ((this.hi & 1) << 31);
+ return new jspb.arith.UInt64(lo >>> 0, hi >>> 0);
+ * Left-shift this number by one bit.
+jspb.arith.UInt64.prototype.leftShift = function() {
+ var lo = this.lo << 1;
+ var hi = (this.hi << 1) | (this.lo >>> 31);
+ * Test the MSB.
+ * @return {boolean}
+jspb.arith.UInt64.prototype.msb = function() {
+ return !!(this.hi & 0x80000000);
+ * Test the LSB.
+jspb.arith.UInt64.prototype.lsb = function() {
+ return !!(this.lo & 1);
+ * Test whether this number is zero.
+jspb.arith.UInt64.prototype.zero = function() {
+ return this.lo == 0 && this.hi == 0;
+ * Add two 64-bit numbers to produce a 64-bit number.
+jspb.arith.UInt64.prototype.add = function(other) {
+ var lo = ((this.lo + other.lo) & 0xffffffff) >>> 0;
+ var hi =
+ (((this.hi + other.hi) & 0xffffffff) >>> 0) +
+ (((this.lo + other.lo) >= 0x100000000) ? 1 : 0);
+ * Subtract two 64-bit numbers to produce a 64-bit number.
+jspb.arith.UInt64.prototype.sub = function(other) {
+ var lo = ((this.lo - other.lo) & 0xffffffff) >>> 0;
+ (((this.hi - other.hi) & 0xffffffff) >>> 0) -
+ (((this.lo - other.lo) < 0) ? 1 : 0);
+ * Multiply two 32-bit numbers to produce a 64-bit number.
+ * @param {number} a The first integer: must be in [0, 2^32-1).
+ * @param {number} b The second integer: must be in [0, 2^32-1).
+jspb.arith.UInt64.mul32x32 = function(a, b) {
+ // Directly multiplying two 32-bit numbers may produce up to 64 bits of
+ // precision, thus losing precision because of the 53-bit mantissa of
+ // JavaScript numbers. So we multiply with 16-bit digits (radix 65536)
+ // instead.
+ var aLow = (a & 0xffff);
+ var aHigh = (a >>> 16);
+ var bLow = (b & 0xffff);
+ var bHigh = (b >>> 16);
+ var productLow =
+ // 32-bit result, result bits 0-31, take all 32 bits
+ (aLow * bLow) +
+ // 32-bit result, result bits 16-47, take bottom 16 as our top 16
+ ((aLow * bHigh) & 0xffff) * 0x10000 +
+ ((aHigh * bLow) & 0xffff) * 0x10000;
+ var productHigh =
+ // 32-bit result, result bits 32-63, take all 32 bits
+ (aHigh * bHigh) +
+ // 32-bit result, result bits 16-47, take top 16 as our bottom 16
+ ((aLow * bHigh) >>> 16) +
+ ((aHigh * bLow) >>> 16);
+ // Carry. Note that we actually have up to *two* carries due to addition of
+ // three terms.
+ while (productLow >= 0x100000000) {
+ productLow -= 0x100000000;
+ productHigh += 1;
+ return new jspb.arith.UInt64(productLow >>> 0, productHigh >>> 0);
+ * Multiply this number by a 32-bit number, producing a 96-bit number, then
+ * truncate the top 32 bits.
+ * @param {number} a The multiplier.
+jspb.arith.UInt64.prototype.mul = function(a) {
+ // Produce two parts: at bits 0-63, and 32-95.
+ var lo = jspb.arith.UInt64.mul32x32(this.lo, a);
+ var hi = jspb.arith.UInt64.mul32x32(this.hi, a);
+ // Left-shift hi by 32 bits, truncating its top bits. The parts will then be
+ // aligned for addition.
+ hi.hi = hi.lo;
+ hi.lo = 0;
+ return lo.add(hi);
+ * Divide a 64-bit number by a 32-bit number to produce a
+ * 64-bit quotient and a 32-bit remainder.
+ * @param {number} _divisor
+ * @return {Array.<jspb.arith.UInt64>} array of [quotient, remainder],
+ * unless divisor is 0, in which case an empty array is returned.
+jspb.arith.UInt64.prototype.div = function(_divisor) {
+ if (_divisor == 0) {
+ return [];
+ // We perform long division using a radix-2 algorithm, for simplicity (i.e.,
+ // one bit at a time). TODO: optimize to a radix-2^32 algorithm, taking care
+ // to get the variable shifts right.
+ var quotient = new jspb.arith.UInt64(0, 0);
+ var remainder = new jspb.arith.UInt64(this.lo, this.hi);
+ var divisor = new jspb.arith.UInt64(_divisor, 0);
+ var unit = new jspb.arith.UInt64(1, 0);
+ // Left-shift the divisor and unit until the high bit of divisor is set.
+ while (!divisor.msb()) {
+ divisor = divisor.leftShift();
+ unit = unit.leftShift();
+ // Perform long division one bit at a time.
+ while (!unit.zero()) {
+ // If divisor < remainder, add unit to quotient and subtract divisor from
+ // remainder.
+ if (divisor.cmp(remainder) <= 0) {
+ quotient = quotient.add(unit);
+ remainder = remainder.sub(divisor);
+ // Right-shift the divisor and unit.
+ divisor = divisor.rightShift();
+ unit = unit.rightShift();
+ return [quotient, remainder];
+ * Convert a 64-bit number to a string.
+ * @return {string}
+ * @override
+jspb.arith.UInt64.prototype.toString = function() {
+ var result = '';
+ var num = this;
+ while (!num.zero()) {
+ var divResult = num.div(10);
+ var quotient = divResult[0], remainder = divResult[1];
+ result = remainder.lo + result;
+ num = quotient;
+ if (result == '') {
+ result = '0';
+ return result;
+ * Parse a string into a 64-bit number. Returns `null` on a parse error.
+ * @param {string} s
+ * @return {?jspb.arith.UInt64}
+jspb.arith.UInt64.fromString = function(s) {
+ var result = new jspb.arith.UInt64(0, 0);
+ // optimization: reuse this instance for each digit.
+ var digit64 = new jspb.arith.UInt64(0, 0);
+ for (var i = 0; i < s.length; i++) {
+ if (s[i] < '0' || s[i] > '9') {
+ return null;
+ var digit = parseInt(s[i], 10);
+ digit64.lo = digit;
+ result = result.mul(10).add(digit64);
+ * Make a copy of the uint64.
+jspb.arith.UInt64.prototype.clone = function() {
+ return new jspb.arith.UInt64(this.lo, this.hi);
+ * Int64 is like UInt64, but modifies string conversions to interpret the stored
+ * 64-bit value as a twos-complement-signed integer. It does *not* support the
+ * full range of operations that UInt64 does: only add, subtract, and string
+ * conversions.
+ * N.B. that multiply and divide routines are *NOT* supported. They will throw
+ * exceptions. (They are not necessary to implement string conversions, which
+ * are the only operations we really need in jspb.)
+jspb.arith.Int64 = function(lo, hi) {
+ * @param {!jspb.arith.Int64} other
+ * @return {!jspb.arith.Int64}
+jspb.arith.Int64.prototype.add = function(other) {
+ return new jspb.arith.Int64(lo >>> 0, hi >>> 0);
+jspb.arith.Int64.prototype.sub = function(other) {
+ * Make a copy of the int64.
+jspb.arith.Int64.prototype.clone = function() {
+ return new jspb.arith.Int64(this.lo, this.hi);
+jspb.arith.Int64.prototype.toString = function() {
+ // If the number is negative, find its twos-complement inverse.
+ var sign = (this.hi & 0x80000000) != 0;
+ var num = new jspb.arith.UInt64(this.lo, this.hi);
+ if (sign) {
+ num = new jspb.arith.UInt64(0, 0).sub(num);
+ return (sign ? '-' : '') + num.toString();
+ * @return {?jspb.arith.Int64}
+jspb.arith.Int64.fromString = function(s) {
+ var hasNegative = (s.length > 0 && s[0] == '-');
+ if (hasNegative) {
+ s = s.substring(1);
+ var num = jspb.arith.UInt64.fromString(s);
+ if (num === null) {
+ return new jspb.arith.Int64(num.lo, num.hi);
@@ -0,0 +1,355 @@
+ * @fileoverview Test cases for Int64-manipulation functions.
+ * Test suite is written using Jasmine -- see http://jasmine.github.io/
+goog.require('goog.testing.asserts');
+goog.require('jspb.arith.Int64');
+goog.require('jspb.arith.UInt64');
+describe('binaryArithTest', function() {
+ * Tests comparison operations.
+ it('testCompare', function() {
+ var a = new jspb.arith.UInt64(1234, 5678);
+ var b = new jspb.arith.UInt64(1234, 5678);
+ assertEquals(a.cmp(b), 0);
+ assertEquals(b.cmp(a), 0);
+ b.lo -= 1;
+ assertEquals(a.cmp(b), 1);
+ assertEquals(b.cmp(a), -1);
+ b.lo += 2;
+ assertEquals(a.cmp(b), -1);
+ assertEquals(b.cmp(a), 1);
+ b.lo = a.lo;
+ b.hi = a.hi - 1;
+ assertEquals(a.zero(), false);
+ assertEquals(a.msb(), false);
+ assertEquals(a.lsb(), false);
+ a.hi = 0;
+ a.lo = 0;
+ assertEquals(a.zero(), true);
+ a.hi = 0x80000000;
+ assertEquals(a.msb(), true);
+ a.lo = 0x00000001;
+ assertEquals(a.lsb(), true);
+ * Tests shifts.
+ it('testShifts', function() {
+ var a = new jspb.arith.UInt64(1, 0);
+ assertEquals(a.lo, 1);
+ assertEquals(a.hi, 0);
+ var orig = a;
+ a = a.leftShift();
+ assertEquals(orig.lo, 1); // original unmodified.
+ assertEquals(orig.hi, 0);
+ assertEquals(a.lo, 2);
+ assertEquals(a.lo, 4);
+ for (var i = 0; i < 29; i++) {
+ assertEquals(a.lo, 0x80000000);
+ assertEquals(a.lo, 0);
+ assertEquals(a.hi, 1);
+ assertEquals(a.hi, 2);
+ a = a.rightShift();
+ assertEquals(a.lo, 0x40000000);
+ * Tests additions.
+ it('testAdd', function() {
+ var a = new jspb.arith.UInt64(/* lo = */ 0x89abcdef,
+ /* hi = */ 0x01234567);
+ var b = new jspb.arith.UInt64(/* lo = */ 0xff52ab91,
+ /* hi = */ 0x92fa2123);
+ // Addition with carry.
+ var c = a.add(b);
+ assertEquals(a.lo, 0x89abcdef); // originals unmodified.
+ assertEquals(a.hi, 0x01234567);
+ assertEquals(b.lo, 0xff52ab91);
+ assertEquals(b.hi, 0x92fa2123);
+ assertEquals(c.lo, 0x88fe7980);
+ assertEquals(c.hi, 0x941d668b);
+ // Simple addition without carry.
+ a.lo = 2;
+ b.lo = 3;
+ b.hi = 0;
+ c = a.add(b);
+ assertEquals(c.lo, 5);
+ assertEquals(c.hi, 0);
+ * Test subtractions.
+ it('testSub', function() {
+ var kLength = 10;
+ var hiValues = [0x1682ef32,
+ 0x583902f7,
+ 0xb62f5955,
+ 0x6ea99bbf,
+ 0x25a39c20,
+ 0x0700a08b,
+ 0x00f7304d,
+ 0x91a5b5af,
+ 0x89077fd2,
+ 0xe09e347c];
+ var loValues = [0xe1538b18,
+ 0xbeacd556,
+ 0x74100758,
+ 0x96e3cb26,
+ 0x56c37c3f,
+ 0xe00b3f7d,
+ 0x859f25d7,
+ 0xc2ee614a,
+ 0xe1d21cd7,
+ 0x30aae6a4];
+ for (var i = 0; i < kLength; i++) {
+ for (var j = 0; j < kLength; j++) {
+ var a = new jspb.arith.UInt64(loValues[i], hiValues[j]);
+ var b = new jspb.arith.UInt64(loValues[j], hiValues[i]);
+ var c = a.add(b).sub(b);
+ assertEquals(c.hi, a.hi);
+ assertEquals(c.lo, a.lo);
+ * Tests 32-by-32 multiplication.
+ it('testMul32x32', function() {
+ var testData = [
+ // a b low(a*b) high(a*b)
+ [0xc0abe2f8, 0x1607898a, 0x5de711b0, 0x109471b8],
+ [0x915eb3cb, 0x4fb66d0e, 0xbd0d441a, 0x2d43d0bc],
+ [0xfe4efe70, 0x80b48c37, 0xbcddea10, 0x7fdada0c],
+ [0xe222fd4a, 0xe43d524a, 0xd5e0eb64, 0xc99d549c],
+ [0xd171f469, 0xb94ebd01, 0x4be17969, 0x979bc4fa],
+ [0x829cc1df, 0xe2598b38, 0xf4157dc8, 0x737c12ad],
+ [0xf10c3767, 0x8382881e, 0x942b3612, 0x7bd428b8],
+ [0xb0f6dd24, 0x232597e1, 0x079c98a4, 0x184bbce7],
+ [0xfcdb05a7, 0x902f55bc, 0x636199a4, 0x8e69f412],
+ [0x0dd0bfa9, 0x916e27b1, 0x6e2542d9, 0x07d92e65]
+ ];
+ for (var i = 0; i < testData.length; i++) {
+ var a = testData[i][0] >>> 0;
+ var b = testData[i][1] >>> 0;
+ var cLow = testData[i][2] >>> 0;
+ var cHigh = testData[i][3] >>> 0;
+ var c = jspb.arith.UInt64.mul32x32(a, b);
+ assertEquals(c.lo, cLow);
+ assertEquals(c.hi, cHigh);
+ * Tests 64-by-32 multiplication.
+ it('testMul', function() {
+ // 64x32 bits produces 96 bits of product. The multiplication function under
+ // test truncates the top 32 bits, so we compare against a 64-bit expected
+ // product.
+ // low(a) high(a) low(a*b) high(a*b)
+ [0xec10955b, 0x360eb168, 0x4b7f3f5b, 0xbfcb7c59, 0x9517da5f],
+ [0x42b000fc, 0x9d101642, 0x6fa1ab72, 0x2584c438, 0x6a9e6d2b],
+ [0xf42d4fb4, 0xae366403, 0xa65a1000, 0x92434000, 0x1ff978df],
+ [0x17e2f56b, 0x25487693, 0xf13f98c7, 0x73794e2d, 0xa96b0c6a],
+ [0x492f241f, 0x76c0eb67, 0x7377ac44, 0xd4336c3c, 0xfc4b1ebe],
+ [0xd6b92321, 0xe184fa48, 0xd6e76904, 0x93141584, 0xcbf44da1],
+ [0x4bf007ea, 0x968c0a9e, 0xf5e4026a, 0x4fdb1ae4, 0x61b9fb7d],
+ [0x10a83be7, 0x2d685ba6, 0xc9e5fb7f, 0x2ad43499, 0x3742473d],
+ [0x2f261829, 0x1aca681a, 0x3d3494e3, 0x8213205b, 0x283719f8],
+ [0xe4f2ce21, 0x2e74b7bd, 0xd801b38b, 0xbc17feeb, 0xc6c44e0f]
+ var a = new jspb.arith.UInt64(testData[i][0], testData[i][1]);
+ var prod = a.mul(testData[i][2]);
+ assertEquals(prod.lo, testData[i][3]);
+ assertEquals(prod.hi, testData[i][4]);
+ * Tests 64-div-by-32 division.
+ it('testDiv', function() {
+ // Compute a/b, yielding quot = a/b and rem = a%b.
+ // --- divisors in (0, 2^32-1) to test full divisor range
+ // low(a) high(a) b low(quot) high(quot) rem
+ [0x712443f1, 0xe85cefcc, 0xc1a7050b, 0x332c79ad, 0x00000001, 0x92ffa882],
+ [0x11912915, 0xb2699eb5, 0x30467cbe, 0xb21b4be4, 0x00000003, 0x283465dd],
+ [0x0d917982, 0x201f2a6e, 0x3f35bf03, 0x8217c8e4, 0x00000000, 0x153402d6],
+ [0xa072c108, 0x74020c96, 0xc60568fd, 0x95f9613e, 0x00000000, 0x3f4676c2],
+ [0xd845d5d8, 0xcdd235c4, 0x20426475, 0x6154e78b, 0x00000006, 0x202fb751],
+ [0xa4dbf71f, 0x9e90465e, 0xf08e022f, 0xa8be947f, 0x00000000, 0xbe43b5ce],
+ [0x3dbe627f, 0xa791f4b9, 0x28a5bd89, 0x1f5dfe93, 0x00000004, 0x02bf9ed4],
+ [0x5c1c53ee, 0xccf5102e, 0x198576e7, 0x07e3ae31, 0x00000008, 0x02ea8fb7],
+ [0xfef1e581, 0x04714067, 0xca6540c1, 0x059e73ec, 0x00000000, 0x31658095],
+ [0x1e2dd90c, 0x13dd6667, 0x8b2184c3, 0x248d1a42, 0x00000000, 0x4ca6d0c6],
+ // --- divisors in (0, 2^16-1) to test larger quotient high-words
+ [0x86722b47, 0x2cd57c9a, 0x00003123, 0x2ae41b7a, 0x0000e995, 0x00000f99],
+ [0x1dd7884c, 0xf5e839bc, 0x00009eeb, 0x5c886242, 0x00018c21, 0x000099b6],
+ [0x5c53d625, 0x899fc7e5, 0x000087d7, 0xd625007a, 0x0001035c, 0x000019af],
+ [0x6932d932, 0x9d0a5488, 0x000051fb, 0x9d976143, 0x0001ea63, 0x00004981],
+ [0x4d18bb85, 0x0c92fb31, 0x00001d9f, 0x03265ab4, 0x00006cac, 0x000001b9],
+ [0xbe756768, 0xdea67ccb, 0x00008a03, 0x58add442, 0x00019cff, 0x000056a2],
+ [0xe2466f9a, 0x2521f114, 0x0000c350, 0xa0c0860d, 0x000030ab, 0x0000a48a],
+ [0xf00ddad1, 0xe2f5446a, 0x00002cfc, 0x762697a6, 0x00050b96, 0x00000b69],
+ [0xa879152a, 0x0a70e0a5, 0x00007cdf, 0xb44151b3, 0x00001567, 0x0000363d],
+ [0x7179a74c, 0x46083fff, 0x0000253c, 0x4d39ba6e, 0x0001e17f, 0x00000f84]
+ var result = a.div(testData[i][2]);
+ var quotient = result[0];
+ var remainder = result[1];
+ assertEquals(quotient.lo, testData[i][3]);
+ assertEquals(quotient.hi, testData[i][4]);
+ assertEquals(remainder.lo, testData[i][5]);
+ * Tests .toString() and .fromString().
+ it('testStrings', function() {
+ [0x5e84c935, 0xcae33d0e, '14619595947299359029'],
+ [0x62b3b8b8, 0x93480544, '10612738313170434232'],
+ [0x319bfb13, 0xc01c4172, '13843011313344445203'],
+ [0x5b8a65fb, 0xa5885b31, '11927883880638080507'],
+ [0x6bdb80f1, 0xb0d1b16b, '12741159895737008369'],
+ [0x4b82b442, 0x2e0d8c97, '3318463081876730946'],
+ [0x780d5208, 0x7d76752c, '9040542135845999112'],
+ [0x2e46800f, 0x0993778d, '690026616168284175'],
+ [0xf00a7e32, 0xcd8e3931, '14811839111111540274'],
+ [0x1baeccd6, 0x923048c4, '10533999535534820566'],
+ [0x03669d29, 0xbff3ab72, '13831587386756603177'],
+ [0x2526073e, 0x01affc81, '121593346566522686'],
+ [0xc24244e0, 0xd7f40d0e, '15561076969511732448'],
+ [0xc56a341e, 0xa68b66a7, '12000798502816461854'],
+ [0x8738d64d, 0xbfe78604, '13828168534871037517'],
+ [0x5baff03b, 0xd7572aea, '15516918227177304123'],
+ [0x4a843d8a, 0x864e132b, '9677693725920476554'],
+ [0x25b4e94d, 0x22b54dc6, '2500990681505655117'],
+ [0x6bbe664b, 0x55a5cc0e, '6171563226690381387'],
+ [0xee916c81, 0xb00aabb3, '12685140089732426881']
+ var roundtrip = jspb.arith.UInt64.fromString(a.toString());
+ assertEquals(roundtrip.lo, a.lo);
+ assertEquals(roundtrip.hi, a.hi);
+ assertEquals(a.toString(), testData[i][2]);
+ * Tests signed Int64s. These are built on UInt64s, so we only need to test
+ * the explicit overrides: .toString() and .fromString().
+ it('testSignedInt64', function() {
+ var testStrings = [
+ '-7847499644178593666',
+ '3771946501229139523',
+ '2872856549054995060',
+ '-5780049594274350904',
+ '3383785956695105201',
+ '2973055184857072610',
+ '-3879428459215627206',
+ '4589812431064156631',
+ '8484075557333689940',
+ '1075325817098092407',
+ '-4346697501012292314',
+ '2488620459718316637',
+ '6112655187423520672',
+ '-3655278273928612104',
+ '3439154019435803196',
+ '1004112478843763757',
+ '-6587790776614368413',
+ '664320065099714586',
+ '4760412909973292912',
+ '-7911903989602274672'
+ for (var i = 0; i < testStrings.length; i++) {
+ var roundtrip =
+ jspb.arith.Int64.fromString(testStrings[i]).toString();
+ assertEquals(roundtrip, testStrings[i]);
+});
@@ -0,0 +1,320 @@
+ * @fileoverview This file contains constants and typedefs used by
+ * jspb.BinaryReader and BinaryWriter.
+ * @author aappleby@google.com (Austin Appleby)
+goog.provide('jspb.AnyFieldType');
+goog.provide('jspb.BinaryConstants');
+goog.provide('jspb.BinaryMessage');
+goog.provide('jspb.BuilderFunction');
+goog.provide('jspb.ByteSource');
+goog.provide('jspb.ClonerFunction');
+goog.provide('jspb.ConstBinaryMessage');
+goog.provide('jspb.ReaderFunction');
+goog.provide('jspb.RecyclerFunction');
+goog.provide('jspb.WriterFunction');
+goog.forwardDeclare('jspb.Message');
+goog.forwardDeclare('jsproto.BinaryExtension');
+ * Base interface class for all const messages. Does __not__ define any
+ * methods, as doing so on a widely-used interface defeats dead-code
+ * elimination.
+ * @interface
+jspb.ConstBinaryMessage = function() {};
+ * Base interface class for all messages. Does __not__ define any methods, as
+ * doing so on a widely-used interface defeats dead-code elimination.
+ * @extends {jspb.ConstBinaryMessage}
+jspb.BinaryMessage = function() {};
+ * The types convertible to Uint8Arrays. Strings are assumed to be
+ * base64-encoded.
+ * @typedef {ArrayBuffer|Uint8Array|Array<number>|string}
+jspb.ByteSource;
+ * A field in jspb can be a scalar, a block of bytes, another proto, or an
+ * array of any of the above.
+ * @typedef {boolean|number|string|Uint8Array|
+ jspb.BinaryMessage|jsproto.BinaryExtension|
+ Array<jspb.AnyFieldType>}
+jspb.AnyFieldType;
+ * A builder function creates an instance of a message object.
+ * @typedef {function():!jspb.BinaryMessage}
+jspb.BuilderFunction;
+ * A cloner function creates a deep copy of a message object.
+ * @typedef {function(jspb.ConstBinaryMessage):jspb.BinaryMessage}
+jspb.ClonerFunction;
+ * A recycler function destroys an instance of a message object.
+ * @typedef {function(!jspb.BinaryMessage):void}
+jspb.RecyclerFunction;
+ * A reader function initializes a message using data from a BinaryReader.
+ * @typedef {function(!jspb.BinaryMessage, !jspb.BinaryReader):void}
+jspb.ReaderFunction;
+ * A writer function serializes a message to a BinaryWriter.
+ * @typedef {!function(!jspb.Message, !jspb.BinaryWriter):void |
+ * !function(!jspb.ConstBinaryMessage, !jspb.BinaryWriter):void}
+jspb.WriterFunction;
+ * Field type codes, taken from proto2/public/wire_format_lite.h.
+ * @enum {number}
+jspb.BinaryConstants.FieldType = {
+ INVALID: -1,
+ DOUBLE: 1,
+ FLOAT: 2,
+ INT64: 3,
+ UINT64: 4,
+ INT32: 5,
+ FIXED64: 6,
+ FIXED32: 7,
+ BOOL: 8,
+ STRING: 9,
+ GROUP: 10,
+ MESSAGE: 11,
+ BYTES: 12,
+ UINT32: 13,
+ ENUM: 14,
+ SFIXED32: 15,
+ SFIXED64: 16,
+ SINT32: 17,
+ SINT64: 18,
+ // Extended types for Javascript
+ FHASH64: 30, // 64-bit hash string, fixed-length encoding.
+ VHASH64: 31 // 64-bit hash string, varint encoding.
+ * Wire-format type codes, taken from proto2/public/wire_format_lite.h.
+jspb.BinaryConstants.WireType = {
+ VARINT: 0,
+ FIXED64: 1,
+ DELIMITED: 2,
+ START_GROUP: 3,
+ END_GROUP: 4,
+ FIXED32: 5
+ * Translates field type to wire type.
+ * @param {jspb.BinaryConstants.FieldType} fieldType
+ * @return {jspb.BinaryConstants.WireType}
+jspb.BinaryConstants.FieldTypeToWireType = function(fieldType) {
+ var fieldTypes = jspb.BinaryConstants.FieldType;
+ var wireTypes = jspb.BinaryConstants.WireType;
+ switch (fieldType) {
+ case fieldTypes.INT32:
+ case fieldTypes.INT64:
+ case fieldTypes.UINT32:
+ case fieldTypes.UINT64:
+ case fieldTypes.SINT32:
+ case fieldTypes.SINT64:
+ case fieldTypes.BOOL:
+ case fieldTypes.ENUM:
+ case fieldTypes.VHASH64:
+ return wireTypes.VARINT;
+ case fieldTypes.DOUBLE:
+ case fieldTypes.FIXED64:
+ case fieldTypes.SFIXED64:
+ case fieldTypes.FHASH64:
+ return wireTypes.FIXED64;
+ case fieldTypes.STRING:
+ case fieldTypes.MESSAGE:
+ case fieldTypes.BYTES:
+ return wireTypes.DELIMITED;
+ case fieldTypes.FLOAT:
+ case fieldTypes.FIXED32:
+ case fieldTypes.SFIXED32:
+ return wireTypes.FIXED32;
+ case fieldTypes.INVALID:
+ case fieldTypes.GROUP:
+ return wireTypes.INVALID;
+ * Flag to indicate a missing field.
+ * @const {number}
+jspb.BinaryConstants.INVALID_FIELD_NUMBER = -1;
+ * The smallest denormal float32 value.
+jspb.BinaryConstants.FLOAT32_EPS = 1.401298464324817e-45;
+ * The smallest normal float64 value.
+jspb.BinaryConstants.FLOAT32_MIN = 1.1754943508222875e-38;
+ * The largest finite float32 value.
+jspb.BinaryConstants.FLOAT32_MAX = 3.4028234663852886e+38;
+ * The smallest denormal float64 value.
+jspb.BinaryConstants.FLOAT64_EPS = 5e-324;
+jspb.BinaryConstants.FLOAT64_MIN = 2.2250738585072014e-308;
+ * The largest finite float64 value.
+jspb.BinaryConstants.FLOAT64_MAX = 1.7976931348623157e+308;
+ * Convenience constant equal to 2^20.
+jspb.BinaryConstants.TWO_TO_20 = 1048576;
+ * Convenience constant equal to 2^23.
+jspb.BinaryConstants.TWO_TO_23 = 8388608;
+ * Convenience constant equal to 2^31.
+jspb.BinaryConstants.TWO_TO_31 = 2147483648;
+ * Convenience constant equal to 2^32.
+jspb.BinaryConstants.TWO_TO_32 = 4294967296;
+ * Convenience constant equal to 2^52.
+jspb.BinaryConstants.TWO_TO_52 = 4503599627370496;
+ * Convenience constant equal to 2^63.
+jspb.BinaryConstants.TWO_TO_63 = 9223372036854775808;
+ * Convenience constant equal to 2^64.
+jspb.BinaryConstants.TWO_TO_64 = 18446744073709551616;
+ * Eight-character string of zeros, used as the default 64-bit hash value.
+ * @const {string}
+jspb.BinaryConstants.ZERO_HASH = '\0\0\0\0\0\0\0\0';
@@ -0,0 +1,1005 @@
+ * @fileoverview This file contains utilities for decoding primitive values
+ * (signed and unsigned integers, varints, booleans, enums, hashes, strings,
+ * and raw bytes) embedded in Uint8Arrays into their corresponding Javascript
+ * types.
+ * Major caveat - Javascript is unable to accurately represent integers larger
+ * than 2^53 due to its use of a double-precision floating point format or all
+ * numbers. If you need to guarantee that 64-bit values survive with all bits
+ * intact, you _must_ read them using one of the Hash64 methods, which return
+ * an 8-character string.
+goog.provide('jspb.BinaryDecoder');
+goog.provide('jspb.BinaryIterator');
+goog.require('goog.asserts');
+goog.require('jspb.utils');
+ * Simple helper class for traversing the contents of repeated scalar fields.
+ * that may or may not have been packed into a wire-format blob.
+ * @param {?jspb.BinaryDecoder=} opt_decoder
+ * @param {?function(this:jspb.BinaryDecoder):(number|boolean|string)=}
+ * opt_next The decoder method to use for next().
+ * @param {?Array.<number|boolean|string>=} opt_elements
+ * @struct
+jspb.BinaryIterator = function(opt_decoder, opt_next, opt_elements) {
+ /** @private {jspb.BinaryDecoder} */
+ this.decoder_ = null;
+ * The BinaryDecoder member function used when iterating over packed data.
+ * @private {?function(this:jspb.BinaryDecoder):(number|boolean|string)}
+ this.nextMethod_ = null;
+ /** @private {Array.<number>} */
+ this.elements_ = null;
+ /** @private {number} */
+ this.cursor_ = 0;
+ /** @private {number|boolean|string|null} */
+ this.nextValue_ = null;
+ /** @private {boolean} */
+ this.atEnd_ = true;
+ this.init_(opt_decoder, opt_next, opt_elements);
+ * @private
+jspb.BinaryIterator.prototype.init_ =
+ function(opt_decoder, opt_next, opt_elements) {
+ if (opt_decoder && opt_next) {
+ this.decoder_ = opt_decoder;
+ this.nextMethod_ = opt_next;
+ this.elements_ = opt_elements ? opt_elements : null;
+ this.atEnd_ = !this.decoder_ && !this.elements_;
+ this.next();
+ * Global pool of BinaryIterator instances.
+ * @private {!Array.<!jspb.BinaryIterator>}
+jspb.BinaryIterator.instanceCache_ = [];
+ * Allocates a BinaryIterator from the cache, creating a new one if the cache
+ * is empty.
+ * @return {!jspb.BinaryIterator}
+jspb.BinaryIterator.alloc = function(opt_decoder, opt_next, opt_elements) {
+ if (jspb.BinaryIterator.instanceCache_.length) {
+ var iterator = jspb.BinaryIterator.instanceCache_.pop();
+ iterator.init_(opt_decoder, opt_next, opt_elements);
+ return iterator;
+ return new jspb.BinaryIterator(opt_decoder, opt_next, opt_elements);
+ * Puts this instance back in the instance cache.
+jspb.BinaryIterator.prototype.free = function() {
+ this.clear();
+ if (jspb.BinaryIterator.instanceCache_.length < 100) {
+ jspb.BinaryIterator.instanceCache_.push(this);
+ * Clears the iterator.
+jspb.BinaryIterator.prototype.clear = function() {
+ if (this.decoder_) {
+ this.decoder_.free();
+ * Returns the element at the iterator, or null if the iterator is invalid or
+ * past the end of the decoder/array.
+ * @return {number|boolean|string|null}
+jspb.BinaryIterator.prototype.get = function() {
+ return this.nextValue_;
+ * Returns true if the iterator is at the end of the decoder/array.
+jspb.BinaryIterator.prototype.atEnd = function() {
+ return this.atEnd_;
+ * Returns the element at the iterator and steps to the next element,
+ * equivalent to '*pointer++' in C.
+jspb.BinaryIterator.prototype.next = function() {
+ var lastValue = this.nextValue_;
+ if (this.decoder_.atEnd()) {
+ this.nextValue_ = this.nextMethod_.call(this.decoder_);
+ } else if (this.elements_) {
+ if (this.cursor_ == this.elements_.length) {
+ this.nextValue_ = this.elements_[this.cursor_++];
+ return lastValue;
+ * BinaryDecoder implements the decoders for all the wire types specified in
+ * https://developers.google.com/protocol-buffers/docs/encoding.
+ * @param {jspb.ByteSource=} opt_bytes The bytes we're reading from.
+ * @param {number=} opt_start The optional offset to start reading at.
+ * @param {number=} opt_length The optional length of the block to read -
+ * we'll throw an assertion if we go off the end of the block.
+jspb.BinaryDecoder = function(opt_bytes, opt_start, opt_length) {
+ * Typed byte-wise view of the source buffer.
+ * @private {Uint8Array}
+ this.bytes_ = null;
+ * Start point of the block to read.
+ * @private {number}
+ this.start_ = 0;
+ * End point of the block to read.
+ this.end_ = 0;
+ * Current read location in bytes_.
+ * Temporary storage for the low 32 bits of 64-bit data types that we're
+ * decoding.
+ this.tempLow_ = 0;
+ * Temporary storage for the high 32 bits of 64-bit data types that we're
+ this.tempHigh_ = 0;
+ * Set to true if this decoder encountered an error due to corrupt data.
+ * @private {boolean}
+ this.error_ = false;
+ if (opt_bytes) {
+ this.setBlock(opt_bytes, opt_start, opt_length);
+ * Global pool of BinaryDecoder instances.
+ * @private {!Array.<!jspb.BinaryDecoder>}
+jspb.BinaryDecoder.instanceCache_ = [];
+ * Pops an instance off the instance cache, or creates one if the cache is
+ * empty.
+ * @return {!jspb.BinaryDecoder}
+jspb.BinaryDecoder.alloc = function(opt_bytes, opt_start, opt_length) {
+ if (jspb.BinaryDecoder.instanceCache_.length) {
+ var newDecoder = jspb.BinaryDecoder.instanceCache_.pop();
+ newDecoder.setBlock(opt_bytes, opt_start, opt_length);
+ return newDecoder;
+ return new jspb.BinaryDecoder(opt_bytes, opt_start, opt_length);
+jspb.BinaryDecoder.prototype.free = function() {
+ if (jspb.BinaryDecoder.instanceCache_.length < 100) {
+ jspb.BinaryDecoder.instanceCache_.push(this);
+ * Makes a copy of this decoder.
+jspb.BinaryDecoder.prototype.clone = function() {
+ return jspb.BinaryDecoder.alloc(this.bytes_,
+ this.start_, this.end_ - this.start_);
+ * Clears the decoder.
+jspb.BinaryDecoder.prototype.clear = function() {
+ * Returns the raw buffer.
+ * @return {Uint8Array} The raw buffer.
+jspb.BinaryDecoder.prototype.getBuffer = function() {
+ return this.bytes_;
+ * Changes the block of bytes we're decoding.
+ * @param {!jspb.ByteSource} data The bytes we're reading from.
+jspb.BinaryDecoder.prototype.setBlock =
+ function(data, opt_start, opt_length) {
+ this.bytes_ = jspb.utils.byteSourceToUint8Array(data);
+ this.start_ = goog.isDef(opt_start) ? opt_start : 0;
+ this.end_ =
+ goog.isDef(opt_length) ? this.start_ + opt_length : this.bytes_.length;
+ this.cursor_ = this.start_;
+jspb.BinaryDecoder.prototype.getEnd = function() {
+ return this.end_;
+ * @param {number} end
+jspb.BinaryDecoder.prototype.setEnd = function(end) {
+ this.end_ = end;
+ * Moves the read cursor back to the start of the block.
+jspb.BinaryDecoder.prototype.reset = function() {
+ * Returns the internal read cursor.
+ * @return {number} The internal read cursor.
+jspb.BinaryDecoder.prototype.getCursor = function() {
+ return this.cursor_;
+ * @param {number} cursor The new cursor.
+jspb.BinaryDecoder.prototype.setCursor = function(cursor) {
+ this.cursor_ = cursor;
+ * Advances the stream cursor by the given number of bytes.
+ * @param {number} count The number of bytes to advance by.
+jspb.BinaryDecoder.prototype.advance = function(count) {
+ this.cursor_ += count;
+ goog.asserts.assert(this.cursor_ <= this.end_);
+ * Returns true if this decoder is at the end of the block.
+jspb.BinaryDecoder.prototype.atEnd = function() {
+ return this.cursor_ == this.end_;
+jspb.BinaryDecoder.prototype.pastEnd = function() {
+ return this.cursor_ > this.end_;
+ * Returns true if this decoder encountered an error due to corrupt data.
+jspb.BinaryDecoder.prototype.getError = function() {
+ return this.error_ ||
+ (this.cursor_ < 0) ||
+ (this.cursor_ > this.end_);
+ * Reads an unsigned varint from the binary stream and stores it as a split
+ * 64-bit integer. Since this does not convert the value to a number, no
+ * precision is lost.
+ * It's possible for an unsigned varint to be incorrectly encoded - more than
+ * 64 bits' worth of data could be present. If this happens, this method will
+ * throw an error.
+ * Decoding varints requires doing some funny base-128 math - for more
+ * details on the format, see
+ * https://developers.google.com/protocol-buffers/docs/encoding
+jspb.BinaryDecoder.prototype.readSplitVarint64_ = function() {
+ var temp;
+ var lowBits = 0;
+ var highBits = 0;
+ // Read the first four bytes of the varint, stopping at the terminator if we
+ // see it.
+ for (var i = 0; i < 4; i++) {
+ temp = this.bytes_[this.cursor_++];
+ lowBits |= (temp & 0x7F) << (i * 7);
+ if (temp < 128) {
+ this.tempLow_ = lowBits >>> 0;
+ // Read the fifth byte, which straddles the low and high dwords.
+ lowBits |= (temp & 0x7F) << 28;
+ highBits |= (temp & 0x7F) >> 4;
+ this.tempHigh_ = highBits >>> 0;
+ // Read the sixth through tenth byte.
+ for (var i = 0; i < 5; i++) {
+ highBits |= (temp & 0x7F) << (i * 7 + 3);
+ // If we did not see the terminator, the encoding was invalid.
+ goog.asserts.fail('Failed to read varint, encoding is invalid.');
+ this.error_ = true;
+ * Skips over a varint in the block without decoding it.
+jspb.BinaryDecoder.prototype.skipVarint = function() {
+ while (this.bytes_[this.cursor_] & 0x80) {
+ this.cursor_++;
+ * Skips backwards over a varint in the block - to do this correctly, we have
+ * to know the value we're skipping backwards over or things are ambiguous.
+ * @param {number} value The varint value to unskip.
+jspb.BinaryDecoder.prototype.unskipVarint = function(value) {
+ while (value > 128) {
+ this.cursor_--;
+ value = value >>> 7;
+ * Reads a 32-bit varint from the binary stream. Due to a quirk of the encoding
+ * format and Javascript's handling of bitwise math, this actually works
+ * correctly for both signed and unsigned 32-bit varints.
+ * This function is called vastly more frequently than any other in
+ * BinaryDecoder, so it has been unrolled and tweaked for performance.
+ * If there are more than 32 bits of data in the varint, it _must_ be due to
+ * sign-extension. If we're in debug mode and the high 32 bits don't match the
+ * expected sign extension, this method will throw an error.
+ * @return {number} The decoded unsigned 32-bit varint.
+jspb.BinaryDecoder.prototype.readUnsignedVarint32 = function() {
+ var bytes = this.bytes_;
+ temp = bytes[this.cursor_ + 0];
+ var x = (temp & 0x7F);
+ this.cursor_ += 1;
+ return x;
+ temp = bytes[this.cursor_ + 1];
+ x |= (temp & 0x7F) << 7;
+ this.cursor_ += 2;
+ temp = bytes[this.cursor_ + 2];
+ x |= (temp & 0x7F) << 14;
+ this.cursor_ += 3;
+ temp = bytes[this.cursor_ + 3];
+ x |= (temp & 0x7F) << 21;
+ this.cursor_ += 4;
+ temp = bytes[this.cursor_ + 4];
+ x |= (temp & 0x0F) << 28;
+ // We're reading the high bits of an unsigned varint. The byte we just read
+ // also contains bits 33 through 35, which we're going to discard. Those
+ // bits _must_ be zero, or the encoding is invalid.
+ goog.asserts.assert((temp & 0xF0) == 0);
+ this.cursor_ += 5;
+ return x >>> 0;
+ // If we get here, we're reading the sign extension of a negative 32-bit int.
+ // We can skip these bytes, as we know in advance that they have to be all
+ // 1's if the varint is correctly encoded. Since we also know the value is
+ // negative, we don't have to coerce it to unsigned before we return it.
+ goog.asserts.assert((temp & 0xF0) == 0xF0);
+ goog.asserts.assert(bytes[this.cursor_ + 5] == 0xFF);
+ goog.asserts.assert(bytes[this.cursor_ + 6] == 0xFF);
+ goog.asserts.assert(bytes[this.cursor_ + 7] == 0xFF);
+ goog.asserts.assert(bytes[this.cursor_ + 8] == 0xFF);
+ goog.asserts.assert(bytes[this.cursor_ + 9] == 0x01);
+ this.cursor_ += 10;
+ * The readUnsignedVarint32 above deals with signed 32-bit varints correctly,
+ * so this is just an alias.
+ * @return {number} The decoded signed 32-bit varint.
+jspb.BinaryDecoder.prototype.readSignedVarint32 =
+ jspb.BinaryDecoder.prototype.readUnsignedVarint32;
+ * Reads a 32-bit unsigned variant and returns its value as a string.
+ * @return {string} The decoded unsigned 32-bit varint as a string.
+jspb.BinaryDecoder.prototype.readUnsignedVarint32String = function() {
+ // 32-bit integers fit in JavaScript numbers without loss of precision, so
+ // string variants of 32-bit varint readers can simply delegate then convert
+ // to string.
+ var value = this.readUnsignedVarint32();
+ return value.toString();
+ * Reads a 32-bit signed variant and returns its value as a string.
+ * @return {string} The decoded signed 32-bit varint as a string.
+jspb.BinaryDecoder.prototype.readSignedVarint32String = function() {
+ var value = this.readSignedVarint32();
+ * Reads a signed, zigzag-encoded 32-bit varint from the binary stream.
+ * Zigzag encoding is a modification of varint encoding that reduces the
+ * storage overhead for small negative integers - for more details on the
+ * format, see https://developers.google.com/protocol-buffers/docs/encoding
+ * @return {number} The decoded signed, zigzag-encoded 32-bit varint.
+jspb.BinaryDecoder.prototype.readZigzagVarint32 = function() {
+ var result = this.readUnsignedVarint32();
+ return (result >>> 1) ^ - (result & 1);
+ * Reads an unsigned 64-bit varint from the binary stream. Note that since
+ * Javascript represents all numbers as double-precision floats, there will be
+ * precision lost if the absolute value of the varint is larger than 2^53.
+ * @return {number} The decoded unsigned varint. Precision will be lost if the
+ * integer exceeds 2^53.
+jspb.BinaryDecoder.prototype.readUnsignedVarint64 = function() {
+ this.readSplitVarint64_();
+ return jspb.utils.joinUint64(this.tempLow_, this.tempHigh_);
+ * Reads an unsigned 64-bit varint from the binary stream and returns the value
+ * as a decimal string.
+ * @return {string} The decoded unsigned varint as a decimal string.
+jspb.BinaryDecoder.prototype.readUnsignedVarint64String = function() {
+ return jspb.utils.joinUnsignedDecimalString(this.tempLow_, this.tempHigh_);
+ * Reads a signed 64-bit varint from the binary stream. Note that since
+ * @return {number} The decoded signed varint. Precision will be lost if the
+jspb.BinaryDecoder.prototype.readSignedVarint64 = function() {
+ return jspb.utils.joinInt64(this.tempLow_, this.tempHigh_);
+ * Reads an signed 64-bit varint from the binary stream and returns the value
+ * @return {string} The decoded signed varint as a decimal string.
+jspb.BinaryDecoder.prototype.readSignedVarint64String = function() {
+ return jspb.utils.joinSignedDecimalString(this.tempLow_, this.tempHigh_);
+ * Reads a signed, zigzag-encoded 64-bit varint from the binary stream. Note
+ * that since Javascript represents all numbers as double-precision floats,
+ * there will be precision lost if the absolute value of the varint is larger
+ * than 2^53.
+ * @return {number} The decoded zigzag varint. Precision will be lost if the
+jspb.BinaryDecoder.prototype.readZigzagVarint64 = function() {
+ return jspb.utils.joinZigzag64(this.tempLow_, this.tempHigh_);
+ * Reads a raw unsigned 8-bit integer from the binary stream.
+ * @return {number} The unsigned 8-bit integer read from the binary stream.
+jspb.BinaryDecoder.prototype.readUint8 = function() {
+ var a = this.bytes_[this.cursor_ + 0];
+ return a;
+ * Reads a raw unsigned 16-bit integer from the binary stream.
+ * @return {number} The unsigned 16-bit integer read from the binary stream.
+jspb.BinaryDecoder.prototype.readUint16 = function() {
+ var b = this.bytes_[this.cursor_ + 1];
+ return (a << 0) | (b << 8);
+ * Reads a raw unsigned 32-bit integer from the binary stream.
+ * @return {number} The unsigned 32-bit integer read from the binary stream.
+jspb.BinaryDecoder.prototype.readUint32 = function() {
+ var c = this.bytes_[this.cursor_ + 2];
+ var d = this.bytes_[this.cursor_ + 3];
+ return ((a << 0) | (b << 8) | (c << 16) | (d << 24)) >>> 0;
+ * Reads a raw unsigned 64-bit integer from the binary stream. Note that since
+ * precision lost if the absolute value of the integer is larger than 2^53.
+ * @return {number} The unsigned 64-bit integer read from the binary stream.
+ * Precision will be lost if the integer exceeds 2^53.
+jspb.BinaryDecoder.prototype.readUint64 = function() {
+ var bitsLow = this.readUint32();
+ var bitsHigh = this.readUint32();
+ return jspb.utils.joinUint64(bitsLow, bitsHigh);
+ * Reads a raw signed 8-bit integer from the binary stream.
+ * @return {number} The signed 8-bit integer read from the binary stream.
+jspb.BinaryDecoder.prototype.readInt8 = function() {
+ return (a << 24) >> 24;
+ * Reads a raw signed 16-bit integer from the binary stream.
+ * @return {number} The signed 16-bit integer read from the binary stream.
+jspb.BinaryDecoder.prototype.readInt16 = function() {
+ return (((a << 0) | (b << 8)) << 16) >> 16;
+ * Reads a raw signed 32-bit integer from the binary stream.
+ * @return {number} The signed 32-bit integer read from the binary stream.
+jspb.BinaryDecoder.prototype.readInt32 = function() {
+ return (a << 0) | (b << 8) | (c << 16) | (d << 24);
+ * Reads a raw signed 64-bit integer from the binary stream. Note that since
+ * precision lost if the absolute vlaue of the integer is larger than 2^53.
+ * @return {number} The signed 64-bit integer read from the binary stream.
+jspb.BinaryDecoder.prototype.readInt64 = function() {
+ return jspb.utils.joinInt64(bitsLow, bitsHigh);
+ * Reads a 32-bit floating-point number from the binary stream, using the
+ * temporary buffer to realign the data.
+ * @return {number} The float read from the binary stream.
+jspb.BinaryDecoder.prototype.readFloat = function() {
+ var bitsHigh = 0;
+ return jspb.utils.joinFloat32(bitsLow, bitsHigh);
+ * Reads a 64-bit floating-point number from the binary stream, using the
+ * @return {number} The double read from the binary stream.
+jspb.BinaryDecoder.prototype.readDouble = function() {
+ return jspb.utils.joinFloat64(bitsLow, bitsHigh);
+ * Reads a boolean value from the binary stream.
+ * @return {boolean} The boolean read from the binary stream.
+jspb.BinaryDecoder.prototype.readBool = function() {
+ return !!this.bytes_[this.cursor_++];
+ * Reads an enum value from the binary stream, which are always encoded as
+ * signed varints.
+ * @return {number} The enum value read from the binary stream.
+jspb.BinaryDecoder.prototype.readEnum = function() {
+ return this.readSignedVarint32();
+ * Reads and parses a UTF-8 encoded unicode string from the stream.
+ * The code is inspired by maps.vectortown.parse.StreamedDataViewReader, with
+ * the exception that the implementation here does not get confused if it
+ * encounters characters longer than three bytes. These characters are ignored
+ * though, as they are extremely rare: three UTF-8 bytes cover virtually all
+ * characters in common use (http://en.wikipedia.org/wiki/UTF-8).
+ * @param {number} length The length of the string to read.
+ * @return {string} The decoded string.
+jspb.BinaryDecoder.prototype.readString = function(length) {
+ var cursor = this.cursor_;
+ var end = cursor + length;
+ var chars = [];
+ while (cursor < end) {
+ var c = bytes[cursor++];
+ if (c < 128) { // Regular 7-bit ASCII.
+ chars.push(c);
+ } else if (c < 192) {
+ // UTF-8 continuation mark. We are out of sync. This
+ // might happen if we attempted to read a character
+ // with more than three bytes.
+ } else if (c < 224) { // UTF-8 with two bytes.
+ var c2 = bytes[cursor++];
+ chars.push(((c & 31) << 6) | (c2 & 63));
+ } else if (c < 240) { // UTF-8 with three bytes.
+ var c3 = bytes[cursor++];
+ chars.push(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+ // String.fromCharCode.apply is faster than manually appending characters on
+ // Chrome 25+, and generates no additional cons string garbage.
+ var result = String.fromCharCode.apply(null, chars);
+ * Reads and parses a UTF-8 encoded unicode string (with length prefix) from
+ * the stream.
+jspb.BinaryDecoder.prototype.readStringWithLength = function() {
+ var length = this.readUnsignedVarint32();
+ return this.readString(length);
+ * Reads a block of raw bytes from the binary stream.
+ * @param {number} length The number of bytes to read.
+ * @return {Uint8Array} The decoded block of bytes, or null if the length was
+ * invalid.
+jspb.BinaryDecoder.prototype.readBytes = function(length) {
+ if (length < 0 ||
+ this.cursor_ + length > this.bytes_.length) {
+ var result = this.bytes_.subarray(this.cursor_, this.cursor_ + length);
+ this.cursor_ += length;
+ * Reads a 64-bit varint from the stream and returns it as an 8-character
+ * Unicode string for use as a hash table key.
+ * @return {string} The hash value.
+jspb.BinaryDecoder.prototype.readVarintHash64 = function() {
+ return jspb.utils.joinHash64(this.tempLow_, this.tempHigh_);
+ * Reads a 64-bit fixed-width value from the stream and returns it as an
+ * 8-character Unicode string for use as a hash table key.
+jspb.BinaryDecoder.prototype.readFixedHash64 = function() {
+ var a = bytes[cursor + 0];
+ var b = bytes[cursor + 1];
+ var c = bytes[cursor + 2];
+ var d = bytes[cursor + 3];
+ var e = bytes[cursor + 4];
+ var f = bytes[cursor + 5];
+ var g = bytes[cursor + 6];
+ var h = bytes[cursor + 7];
+ this.cursor_ += 8;
+ return String.fromCharCode(a, b, c, d, e, f, g, h);
@@ -0,0 +1,327 @@
+ * @fileoverview Test cases for jspb's binary protocol buffer decoder.
+ * There are two particular magic numbers that need to be pointed out -
+ * 2^64-1025 is the largest number representable as both a double and an
+ * unsigned 64-bit integer, and 2^63-513 is the largest number representable as
+ * both a double and a signed 64-bit integer.
+goog.require('jspb.BinaryConstants');
+goog.require('jspb.BinaryDecoder');
+goog.require('jspb.BinaryWriter');
+ * Tests raw encoding and decoding of unsigned types.
+ * @param {Function} readValue
+ * @param {Function} writeValue
+ * @param {number} epsilon
+ * @param {number} upperLimit
+ * @param {Function} filter
+ * @suppress {missingProperties|visibility}
+function doTestUnsignedValue(readValue,
+ writeValue, epsilon, upperLimit, filter) {
+ var writer = new jspb.BinaryWriter();
+ // Encode zero and limits.
+ writeValue.call(writer, filter(0));
+ writeValue.call(writer, filter(epsilon));
+ writeValue.call(writer, filter(upperLimit));
+ // Encode positive values.
+ for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) {
+ writeValue.call(writer, filter(cursor));
+ var reader = jspb.BinaryDecoder.alloc(writer.getResultBuffer());
+ // Check zero and limits.
+ assertEquals(filter(0), readValue.call(reader));
+ assertEquals(filter(epsilon), readValue.call(reader));
+ assertEquals(filter(upperLimit), readValue.call(reader));
+ // Check positive values.
+ if (filter(cursor) != readValue.call(reader)) throw 'fail!';
+ * Tests raw encoding and decoding of signed types.
+ * @param {number} lowerLimit
+ * @suppress {missingProperties}
+function doTestSignedValue(readValue,
+ writeValue, epsilon, lowerLimit, upperLimit, filter) {
+ writeValue.call(writer, filter(lowerLimit));
+ writeValue.call(writer, filter(-epsilon));
+ var inputValues = [];
+ // Encode negative values.
+ for (var cursor = lowerLimit; cursor < -epsilon; cursor /= 1.1) {
+ var val = filter(cursor);
+ writeValue.call(writer, val);
+ inputValues.push(val);
+ assertEquals(filter(lowerLimit), readValue.call(reader));
+ assertEquals(filter(-epsilon), readValue.call(reader));
+ // Verify decoded values.
+ for (var i = 0; i < inputValues.length; i++) {
+ assertEquals(inputValues[i], readValue.call(reader));
+describe('binaryDecoderTest', function() {
+ * Tests the decoder instance cache.
+ * @suppress {visibility}
+ it('testInstanceCache', function() {
+ // Empty the instance caches.
+ jspb.BinaryDecoder.instanceCache_ = [];
+ // Allocating and then freeing a decoder should put it in the instance
+ // cache.
+ jspb.BinaryDecoder.alloc().free();
+ assertEquals(1, jspb.BinaryDecoder.instanceCache_.length);
+ // Allocating and then freeing three decoders should leave us with three in
+ // the cache.
+ var decoder1 = jspb.BinaryDecoder.alloc();
+ var decoder2 = jspb.BinaryDecoder.alloc();
+ var decoder3 = jspb.BinaryDecoder.alloc();
+ decoder1.free();
+ decoder2.free();
+ decoder3.free();
+ assertEquals(3, jspb.BinaryDecoder.instanceCache_.length);
+ * Tests reading 64-bit integers as hash strings.
+ it('testHashStrings', function() {
+ var hashA = String.fromCharCode(0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00);
+ var hashB = String.fromCharCode(0x12, 0x34, 0x00, 0x00,
+ var hashC = String.fromCharCode(0x12, 0x34, 0x56, 0x78,
+ 0x87, 0x65, 0x43, 0x21);
+ var hashD = String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF);
+ writer.rawWriteVarintHash64(hashA);
+ writer.rawWriteVarintHash64(hashB);
+ writer.rawWriteVarintHash64(hashC);
+ writer.rawWriteVarintHash64(hashD);
+ writer.rawWriteFixedHash64(hashA);
+ writer.rawWriteFixedHash64(hashB);
+ writer.rawWriteFixedHash64(hashC);
+ writer.rawWriteFixedHash64(hashD);
+ var decoder = jspb.BinaryDecoder.alloc(writer.getResultBuffer());
+ assertEquals(hashA, decoder.readVarintHash64());
+ assertEquals(hashB, decoder.readVarintHash64());
+ assertEquals(hashC, decoder.readVarintHash64());
+ assertEquals(hashD, decoder.readVarintHash64());
+ assertEquals(hashA, decoder.readFixedHash64());
+ assertEquals(hashB, decoder.readFixedHash64());
+ assertEquals(hashC, decoder.readFixedHash64());
+ assertEquals(hashD, decoder.readFixedHash64());
+ * Verifies that misuse of the decoder class triggers assertions.
+ * @suppress {checkTypes|visibility}
+ it('testDecodeErrors', function() {
+ // Reading a value past the end of the stream should trigger an assertion.
+ var decoder = jspb.BinaryDecoder.alloc([0, 1, 2]);
+ assertThrows(function() {decoder.readUint64()});
+ // Overlong varints should trigger assertions.
+ decoder.setBlock(
+ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0]);
+ assertThrows(function() {decoder.readUnsignedVarint64()});
+ decoder.reset();
+ assertThrows(function() {decoder.readSignedVarint64()});
+ assertThrows(function() {decoder.readZigzagVarint64()});
+ // Positive 32-bit varints encoded with 1 bits in positions 33 through 35
+ // should trigger assertions.
+ decoder.setBlock([255, 255, 255, 255, 0x1F]);
+ assertThrows(function() {decoder.readUnsignedVarint32()});
+ decoder.setBlock([255, 255, 255, 255, 0x2F]);
+ decoder.setBlock([255, 255, 255, 255, 0x4F]);
+ // Negative 32-bit varints encoded with non-1 bits in the high dword should
+ // trigger assertions.
+ decoder.setBlock([255, 255, 255, 255, 255, 255, 0, 255, 255, 1]);
+ decoder.setBlock([255, 255, 255, 255, 255, 255, 255, 255, 255, 0]);
+ * Tests raw encoding and decoding of unsigned integers.
+ it('testRawUnsigned', function() {
+ doTestUnsignedValue(
+ jspb.BinaryDecoder.prototype.readUint8,
+ jspb.BinaryWriter.prototype.rawWriteUint8,
+ 1, 0xFF, Math.round);
+ jspb.BinaryDecoder.prototype.readUint16,
+ jspb.BinaryWriter.prototype.rawWriteUint16,
+ 1, 0xFFFF, Math.round);
+ jspb.BinaryDecoder.prototype.readUint32,
+ jspb.BinaryWriter.prototype.rawWriteUint32,
+ 1, 0xFFFFFFFF, Math.round);
+ jspb.BinaryDecoder.prototype.readUint64,
+ jspb.BinaryWriter.prototype.rawWriteUint64,
+ 1, Math.pow(2, 64) - 1025, Math.round);
+ * Tests raw encoding and decoding of signed integers.
+ it('testRawSigned', function() {
+ doTestSignedValue(
+ jspb.BinaryDecoder.prototype.readInt8,
+ jspb.BinaryWriter.prototype.rawWriteInt8,
+ 1, -0x80, 0x7F, Math.round);
+ jspb.BinaryDecoder.prototype.readInt16,
+ jspb.BinaryWriter.prototype.rawWriteInt16,
+ 1, -0x8000, 0x7FFF, Math.round);
+ jspb.BinaryDecoder.prototype.readInt32,
+ jspb.BinaryWriter.prototype.rawWriteInt32,
+ 1, -0x80000000, 0x7FFFFFFF, Math.round);
+ jspb.BinaryDecoder.prototype.readInt64,
+ jspb.BinaryWriter.prototype.rawWriteInt64,
+ 1, -Math.pow(2, 63), Math.pow(2, 63) - 513, Math.round);
+ * Tests raw encoding and decoding of floats.
+ it('testRawFloats', function() {
+ * @param {number} x
+ function truncate(x) {
+ var temp = new Float32Array(1);
+ temp[0] = x;
+ return temp[0];
+ jspb.BinaryDecoder.prototype.readFloat,
+ jspb.BinaryWriter.prototype.rawWriteFloat,
+ jspb.BinaryConstants.FLOAT32_EPS,
+ -jspb.BinaryConstants.FLOAT32_MAX,
+ jspb.BinaryConstants.FLOAT32_MAX,
+ truncate);
+ jspb.BinaryDecoder.prototype.readDouble,
+ jspb.BinaryWriter.prototype.rawWriteDouble,
+ jspb.BinaryConstants.FLOAT64_EPS * 10,
+ -jspb.BinaryConstants.FLOAT64_MAX,
+ jspb.BinaryConstants.FLOAT64_MAX,
+ function(x) { return x; });
@@ -0,0 +1,588 @@
+// Test suite is written using Jasmine -- see http://jasmine.github.io/
+goog.require('proto.jspb.test.ExtendsWithMessage');
+goog.require('proto.jspb.test.ForeignEnum');
+goog.require('proto.jspb.test.ForeignMessage');
+goog.require('proto.jspb.test.TestAllTypes');
+goog.require('proto.jspb.test.TestExtendable');
+var suite = {};
+ * Helper: fill all fields on a TestAllTypes message.
+ * @param {proto.jspb.test.TestAllTypes} msg
+function fillAllFields(msg) {
+ msg.setOptionalInt32(-42);
+ // can be exactly represented by JS number (64-bit double, i.e., 52-bit
+ // mantissa).
+ msg.setOptionalInt64(-0x7fffffff00000000);
+ msg.setOptionalUint32(0x80000000);
+ msg.setOptionalUint64(0xf000000000000000);
+ msg.setOptionalSint32(-100);
+ msg.setOptionalSint64(-0x8000000000000000);
+ msg.setOptionalFixed32(1234);
+ msg.setOptionalFixed64(0x1234567800000000);
+ msg.setOptionalSfixed32(-1234);
+ msg.setOptionalSfixed64(-0x1234567800000000);
+ msg.setOptionalFloat(1.5);
+ msg.setOptionalDouble(-1.5);
+ msg.setOptionalBool(true);
+ msg.setOptionalString('hello world');
+ msg.setOptionalBytes('bytes');
+ msg.setOptionalGroup(new proto.jspb.test.TestAllTypes.OptionalGroup());
+ msg.getOptionalGroup().setA(100);
+ var submsg = new proto.jspb.test.ForeignMessage();
+ submsg.setC(16);
+ msg.setOptionalForeignMessage(submsg);
+ msg.setOptionalForeignEnum(proto.jspb.test.ForeignEnum.FOREIGN_FOO);
+ msg.setOptionalInt32String('-12345');
+ msg.setOptionalUint32String('12345');
+ msg.setOptionalInt64String('-123456789012345');
+ msg.setOptionalUint64String('987654321098765');
+ msg.setOneofString('oneof');
+ msg.setRepeatedInt32List([-42]);
+ msg.setRepeatedInt64List([-0x7fffffff00000000]);
+ msg.setRepeatedUint32List([0x80000000]);
+ msg.setRepeatedUint64List([0xf000000000000000]);
+ msg.setRepeatedSint32List([-100]);
+ msg.setRepeatedSint64List([-0x8000000000000000]);
+ msg.setRepeatedFixed32List([1234]);
+ msg.setRepeatedFixed64List([0x1234567800000000]);
+ msg.setRepeatedSfixed32List([-1234]);
+ msg.setRepeatedSfixed64List([-0x1234567800000000]);
+ msg.setRepeatedFloatList([1.5]);
+ msg.setRepeatedDoubleList([-1.5]);
+ msg.setRepeatedBoolList([true]);
+ msg.setRepeatedStringList(['hello world']);
+ msg.setRepeatedBytesList(['bytes']);
+ msg.setRepeatedGroupList([new proto.jspb.test.TestAllTypes.RepeatedGroup()]);
+ msg.getRepeatedGroupList()[0].setA(100);
+ submsg = new proto.jspb.test.ForeignMessage();
+ submsg.setC(1000);
+ msg.setRepeatedForeignMessageList([submsg]);
+ msg.setRepeatedForeignEnumList([proto.jspb.test.ForeignEnum.FOREIGN_FOO]);
+ msg.setPackedRepeatedInt32List([-42]);
+ msg.setPackedRepeatedInt64List([-0x7fffffff00000000]);
+ msg.setPackedRepeatedUint32List([0x80000000]);
+ msg.setPackedRepeatedUint64List([0xf000000000000000]);
+ msg.setPackedRepeatedSint32List([-100]);
+ msg.setPackedRepeatedSint64List([-0x8000000000000000]);
+ msg.setPackedRepeatedFixed32List([1234]);
+ msg.setPackedRepeatedFixed64List([0x1234567800000000]);
+ msg.setPackedRepeatedSfixed32List([-1234]);
+ msg.setPackedRepeatedSfixed64List([-0x1234567800000000]);
+ msg.setPackedRepeatedFloatList([1.5]);
+ msg.setPackedRepeatedDoubleList([-1.5]);
+ msg.setPackedRepeatedBoolList([true]);
+ msg.setRepeatedInt32StringList(['-12345']);
+ msg.setRepeatedUint32StringList(['12345']);
+ msg.setRepeatedInt64StringList(['-123456789012345']);
+ msg.setRepeatedUint64StringList(['987654321098765']);
+ msg.setPackedRepeatedInt32StringList(['-12345']);
+ msg.setPackedRepeatedUint32StringList(['12345']);
+ msg.setPackedRepeatedInt64StringList(['-123456789012345']);
+ msg.setPackedRepeatedUint64StringList(['987654321098765']);
+ * Helper: compare a bytes field to a string with codepoints 0--255.
+ * @param {Uint8Array|string} arr
+ * @param {string} str
+function bytesCompare(arr, str) {
+ if (arr.length != str.length) {
+ if (typeof arr == 'string') {
+ for (var i = 0; i < arr.length; i++) {
+ if (arr.charCodeAt(i) != str.charCodeAt(i)) {
+ if (arr[i] != str.charCodeAt(i)) {
+ * Helper: verify contents of given TestAllTypes message as set by
+ * fillAllFields().
+function checkAllFields(msg) {
+ assertEquals(msg.getOptionalInt32(), -42);
+ assertEquals(msg.getOptionalInt64(), -0x7fffffff00000000);
+ assertEquals(msg.getOptionalUint32(), 0x80000000);
+ assertEquals(msg.getOptionalUint64(), 0xf000000000000000);
+ assertEquals(msg.getOptionalSint32(), -100);
+ assertEquals(msg.getOptionalSint64(), -0x8000000000000000);
+ assertEquals(msg.getOptionalFixed32(), 1234);
+ assertEquals(msg.getOptionalFixed64(), 0x1234567800000000);
+ assertEquals(msg.getOptionalSfixed32(), -1234);
+ assertEquals(msg.getOptionalSfixed64(), -0x1234567800000000);
+ assertEquals(msg.getOptionalFloat(), 1.5);
+ assertEquals(msg.getOptionalDouble(), -1.5);
+ assertEquals(msg.getOptionalBool(), true);
+ assertEquals(msg.getOptionalString(), 'hello world');
+ assertEquals(true, bytesCompare(msg.getOptionalBytes(), 'bytes'));
+ assertEquals(msg.getOptionalGroup().getA(), 100);
+ assertEquals(msg.getOptionalForeignMessage().getC(), 16);
+ assertEquals(msg.getOptionalForeignEnum(),
+ proto.jspb.test.ForeignEnum.FOREIGN_FOO);
+ assertEquals(msg.getOptionalInt32String(), '-12345');
+ assertEquals(msg.getOptionalUint32String(), '12345');
+ assertEquals(msg.getOptionalInt64String(), '-123456789012345');
+ assertEquals(msg.getOptionalUint64String(), '987654321098765');
+ assertEquals(msg.getOneofString(), 'oneof');
+ assertEquals(msg.getOneofFieldCase(),
+ proto.jspb.test.TestAllTypes.OneofFieldCase.ONEOF_STRING);
+ assertElementsEquals(msg.getRepeatedInt32List(), [-42]);
+ assertElementsEquals(msg.getRepeatedInt64List(), [-0x7fffffff00000000]);
+ assertElementsEquals(msg.getRepeatedUint32List(), [0x80000000]);
+ assertElementsEquals(msg.getRepeatedUint64List(), [0xf000000000000000]);
+ assertElementsEquals(msg.getRepeatedSint32List(), [-100]);
+ assertElementsEquals(msg.getRepeatedSint64List(), [-0x8000000000000000]);
+ assertElementsEquals(msg.getRepeatedFixed32List(), [1234]);
+ assertElementsEquals(msg.getRepeatedFixed64List(), [0x1234567800000000]);
+ assertElementsEquals(msg.getRepeatedSfixed32List(), [-1234]);
+ assertElementsEquals(msg.getRepeatedSfixed64List(), [-0x1234567800000000]);
+ assertElementsEquals(msg.getRepeatedFloatList(), [1.5]);
+ assertElementsEquals(msg.getRepeatedDoubleList(), [-1.5]);
+ assertElementsEquals(msg.getRepeatedBoolList(), [true]);
+ assertElementsEquals(msg.getRepeatedStringList(), ['hello world']);
+ assertEquals(msg.getRepeatedBytesList().length, 1);
+ assertEquals(true, bytesCompare(msg.getRepeatedBytesList()[0], 'bytes'));
+ assertEquals(msg.getRepeatedGroupList().length, 1);
+ assertEquals(msg.getRepeatedGroupList()[0].getA(), 100);
+ assertEquals(msg.getRepeatedForeignMessageList().length, 1);
+ assertEquals(msg.getRepeatedForeignMessageList()[0].getC(), 1000);
+ assertElementsEquals(msg.getRepeatedForeignEnumList(),
+ [proto.jspb.test.ForeignEnum.FOREIGN_FOO]);
+ assertElementsEquals(msg.getPackedRepeatedInt32List(), [-42]);
+ assertElementsEquals(msg.getPackedRepeatedInt64List(),
+ [-0x7fffffff00000000]);
+ assertElementsEquals(msg.getPackedRepeatedUint32List(), [0x80000000]);
+ assertElementsEquals(msg.getPackedRepeatedUint64List(), [0xf000000000000000]);
+ assertElementsEquals(msg.getPackedRepeatedSint32List(), [-100]);
+ assertElementsEquals(msg.getPackedRepeatedSint64List(),
+ [-0x8000000000000000]);
+ assertElementsEquals(msg.getPackedRepeatedFixed32List(), [1234]);
+ assertElementsEquals(msg.getPackedRepeatedFixed64List(),
+ [0x1234567800000000]);
+ assertElementsEquals(msg.getPackedRepeatedSfixed32List(), [-1234]);
+ assertElementsEquals(msg.getPackedRepeatedSfixed64List(),
+ [-0x1234567800000000]);
+ assertElementsEquals(msg.getPackedRepeatedFloatList(), [1.5]);
+ assertElementsEquals(msg.getPackedRepeatedDoubleList(), [-1.5]);
+ assertElementsEquals(msg.getPackedRepeatedBoolList(), [true]);
+ assertEquals(msg.getRepeatedInt32StringList().length, 1);
+ assertElementsEquals(msg.getRepeatedInt32StringList(), ['-12345']);
+ assertEquals(msg.getRepeatedUint32StringList().length, 1);
+ assertElementsEquals(msg.getRepeatedUint32StringList(), ['12345']);
+ assertEquals(msg.getRepeatedInt64StringList().length, 1);
+ assertElementsEquals(msg.getRepeatedInt64StringList(), ['-123456789012345']);
+ assertEquals(msg.getRepeatedUint64StringList().length, 1);
+ assertElementsEquals(msg.getRepeatedUint64StringList(), ['987654321098765']);
+ assertEquals(msg.getPackedRepeatedInt32StringList().length, 1);
+ assertElementsEquals(msg.getPackedRepeatedInt32StringList(), ['-12345']);
+ assertEquals(msg.getPackedRepeatedUint32StringList().length, 1);
+ assertElementsEquals(msg.getPackedRepeatedUint32StringList(), ['12345']);
+ assertEquals(msg.getPackedRepeatedInt64StringList().length, 1);
+ assertElementsEquals(msg.getPackedRepeatedInt64StringList(),
+ ['-123456789012345']);
+ assertEquals(msg.getPackedRepeatedUint64StringList().length, 1);
+ assertElementsEquals(msg.getPackedRepeatedUint64StringList(),
+ ['987654321098765']);
+ * Helper: verify that all expected extensions are present.
+ * @param {!proto.jspb.test.TestExtendable} msg
+function checkExtensions(msg) {
+ assertEquals(-42,
+ msg.getExtension(proto.jspb.test.extendOptionalInt32));
+ assertEquals(-0x7fffffff00000000,
+ msg.getExtension(proto.jspb.test.extendOptionalInt64));
+ assertEquals(0x80000000,
+ msg.getExtension(proto.jspb.test.extendOptionalUint32));
+ assertEquals(0xf000000000000000,
+ msg.getExtension(proto.jspb.test.extendOptionalUint64));
+ assertEquals(-100,
+ msg.getExtension(proto.jspb.test.extendOptionalSint32));
+ assertEquals(-0x8000000000000000,
+ msg.getExtension(proto.jspb.test.extendOptionalSint64));
+ assertEquals(1234,
+ msg.getExtension(proto.jspb.test.extendOptionalFixed32));
+ assertEquals(0x1234567800000000,
+ msg.getExtension(proto.jspb.test.extendOptionalFixed64));
+ assertEquals(-1234,
+ msg.getExtension(proto.jspb.test.extendOptionalSfixed32));
+ assertEquals(-0x1234567800000000,
+ msg.getExtension(proto.jspb.test.extendOptionalSfixed64));
+ assertEquals(1.5,
+ msg.getExtension(proto.jspb.test.extendOptionalFloat));
+ assertEquals(-1.5,
+ msg.getExtension(proto.jspb.test.extendOptionalDouble));
+ assertEquals(true,
+ msg.getExtension(proto.jspb.test.extendOptionalBool));
+ assertEquals('hello world',
+ msg.getExtension(proto.jspb.test.extendOptionalString));
+ bytesCompare(msg.getExtension(proto.jspb.test.extendOptionalBytes),
+ 'bytes'));
+ assertEquals(16,
+ msg.getExtension(
+ proto.jspb.test.ExtendsWithMessage.optionalExtension).getFoo());
+ assertEquals(proto.jspb.test.ForeignEnum.FOREIGN_FOO,
+ msg.getExtension(proto.jspb.test.extendOptionalForeignEnum));
+ assertEquals('-12345',
+ msg.getExtension(proto.jspb.test.extendOptionalInt32String));
+ assertEquals('12345',
+ msg.getExtension(proto.jspb.test.extendOptionalUint32String));
+ assertEquals('-123456789012345',
+ msg.getExtension(proto.jspb.test.extendOptionalInt64String));
+ assertEquals('987654321098765',
+ msg.getExtension(proto.jspb.test.extendOptionalUint64String));
+ assertElementsEquals(
+ msg.getExtension(proto.jspb.test.extendRepeatedInt32List),
+ [-42]);
+ msg.getExtension(proto.jspb.test.extendRepeatedInt64List),
+ msg.getExtension(proto.jspb.test.extendRepeatedUint32List),
+ [0x80000000]);
+ msg.getExtension(proto.jspb.test.extendRepeatedUint64List),
+ [0xf000000000000000]);
+ msg.getExtension(proto.jspb.test.extendRepeatedSint32List),
+ [-100]);
+ msg.getExtension(proto.jspb.test.extendRepeatedSint64List),
+ msg.getExtension(proto.jspb.test.extendRepeatedFixed32List),
+ [1234]);
+ msg.getExtension(proto.jspb.test.extendRepeatedFixed64List),
+ msg.getExtension(proto.jspb.test.extendRepeatedSfixed32List),
+ [-1234]);
+ msg.getExtension(proto.jspb.test.extendRepeatedSfixed64List),
+ msg.getExtension(proto.jspb.test.extendRepeatedFloatList),
+ [1.5]);
+ msg.getExtension(proto.jspb.test.extendRepeatedDoubleList),
+ [-1.5]);
+ msg.getExtension(proto.jspb.test.extendRepeatedBoolList),
+ [true]);
+ msg.getExtension(proto.jspb.test.extendRepeatedStringList),
+ ['hello world']);
+ bytesCompare(
+ msg.getExtension(proto.jspb.test.extendRepeatedBytesList)[0],
+ assertEquals(1000,
+ proto.jspb.test.ExtendsWithMessage.repeatedExtensionList)[0]
+ .getFoo());
+ msg.getExtension(proto.jspb.test.extendRepeatedForeignEnumList),
+ msg.getExtension(proto.jspb.test.extendRepeatedInt32StringList),
+ ['-12345']);
+ msg.getExtension(proto.jspb.test.extendRepeatedUint32StringList),
+ ['12345']);
+ msg.getExtension(proto.jspb.test.extendRepeatedInt64StringList),
+ msg.getExtension(proto.jspb.test.extendRepeatedUint64StringList),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedInt32List),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedInt64List),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedUint32List),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedUint64List),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedSint32List),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedSint64List),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedFixed32List),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedFixed64List),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedSfixed32List),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedSfixed64List),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedFloatList),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedDoubleList),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedBoolList),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedForeignEnumList),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedInt32StringList),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedUint32StringList),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedInt64StringList),
+ msg.getExtension(proto.jspb.test.extendPackedRepeatedUint64StringList),
+describe('protoBinaryTest', function() {
+ * Tests a basic serialization-deserializaton round-trip with all supported
+ * field types (on the TestAllTypes message type).
+ it('testRoundTrip', function() {
+ var msg = new proto.jspb.test.TestAllTypes();
+ fillAllFields(msg);
+ var encoded = msg.serializeBinary();
+ var decoded = proto.jspb.test.TestAllTypes.deserializeBinary(encoded);
+ checkAllFields(decoded);
+ * Helper: fill all extension values.
+ * @param {proto.jspb.test.TestExtendable} msg
+ function fillExtensions(msg) {
+ msg.setExtension(
+ proto.jspb.test.extendOptionalInt32, -42);
+ proto.jspb.test.extendOptionalInt64, -0x7fffffff00000000);
+ proto.jspb.test.extendOptionalUint32, 0x80000000);
+ proto.jspb.test.extendOptionalUint64, 0xf000000000000000);
+ proto.jspb.test.extendOptionalSint32, -100);
+ proto.jspb.test.extendOptionalSint64, -0x8000000000000000);
+ proto.jspb.test.extendOptionalFixed32, 1234);
+ proto.jspb.test.extendOptionalFixed64, 0x1234567800000000);
+ proto.jspb.test.extendOptionalSfixed32, -1234);
+ proto.jspb.test.extendOptionalSfixed64, -0x1234567800000000);
+ proto.jspb.test.extendOptionalFloat, 1.5);
+ proto.jspb.test.extendOptionalDouble, -1.5);
+ proto.jspb.test.extendOptionalBool, true);
+ proto.jspb.test.extendOptionalString, 'hello world');
+ proto.jspb.test.extendOptionalBytes, 'bytes');
+ var submsg = new proto.jspb.test.ExtendsWithMessage();
+ submsg.setFoo(16);
+ proto.jspb.test.ExtendsWithMessage.optionalExtension, submsg);
+ proto.jspb.test.extendOptionalForeignEnum,
+ proto.jspb.test.extendOptionalInt32String, '-12345');
+ proto.jspb.test.extendOptionalUint32String, '12345');
+ proto.jspb.test.extendOptionalInt64String, '-123456789012345');
+ proto.jspb.test.extendOptionalUint64String, '987654321098765');
+ proto.jspb.test.extendRepeatedInt32List, [-42]);
+ proto.jspb.test.extendRepeatedInt64List, [-0x7fffffff00000000]);
+ proto.jspb.test.extendRepeatedUint32List, [0x80000000]);
+ proto.jspb.test.extendRepeatedUint64List, [0xf000000000000000]);
+ proto.jspb.test.extendRepeatedSint32List, [-100]);
+ proto.jspb.test.extendRepeatedSint64List, [-0x8000000000000000]);
+ proto.jspb.test.extendRepeatedFixed32List, [1234]);
+ proto.jspb.test.extendRepeatedFixed64List, [0x1234567800000000]);
+ proto.jspb.test.extendRepeatedSfixed32List, [-1234]);
+ proto.jspb.test.extendRepeatedSfixed64List, [-0x1234567800000000]);
+ proto.jspb.test.extendRepeatedFloatList, [1.5]);
+ proto.jspb.test.extendRepeatedDoubleList, [-1.5]);
+ proto.jspb.test.extendRepeatedBoolList, [true]);
+ proto.jspb.test.extendRepeatedStringList, ['hello world']);
+ proto.jspb.test.extendRepeatedBytesList, ['bytes']);
+ submsg = new proto.jspb.test.ExtendsWithMessage();
+ submsg.setFoo(1000);
+ proto.jspb.test.ExtendsWithMessage.repeatedExtensionList, [submsg]);
+ msg.setExtension(proto.jspb.test.extendRepeatedForeignEnumList,
+ proto.jspb.test.extendRepeatedInt32StringList, ['-12345']);
+ proto.jspb.test.extendRepeatedUint32StringList, ['12345']);
+ proto.jspb.test.extendRepeatedInt64StringList, ['-123456789012345']);
+ proto.jspb.test.extendRepeatedUint64StringList, ['987654321098765']);
+ proto.jspb.test.extendPackedRepeatedInt32List, [-42]);
+ proto.jspb.test.extendPackedRepeatedInt64List, [-0x7fffffff00000000]);
+ proto.jspb.test.extendPackedRepeatedUint32List, [0x80000000]);
+ proto.jspb.test.extendPackedRepeatedUint64List, [0xf000000000000000]);
+ proto.jspb.test.extendPackedRepeatedSint32List, [-100]);
+ proto.jspb.test.extendPackedRepeatedSint64List, [-0x8000000000000000]);
+ proto.jspb.test.extendPackedRepeatedFixed32List, [1234]);
+ proto.jspb.test.extendPackedRepeatedFixed64List, [0x1234567800000000]);
+ proto.jspb.test.extendPackedRepeatedSfixed32List, [-1234]);
+ proto.jspb.test.extendPackedRepeatedSfixed64List,
+ proto.jspb.test.extendPackedRepeatedFloatList, [1.5]);
+ proto.jspb.test.extendPackedRepeatedDoubleList, [-1.5]);
+ proto.jspb.test.extendPackedRepeatedBoolList, [true]);
+ msg.setExtension(proto.jspb.test.extendPackedRepeatedForeignEnumList,
+ proto.jspb.test.extendPackedRepeatedInt32StringList,
+ proto.jspb.test.extendPackedRepeatedUint32StringList,
+ proto.jspb.test.extendPackedRepeatedInt64StringList,
+ proto.jspb.test.extendPackedRepeatedUint64StringList,
+ * Tests extension serialization and deserialization.
+ it('testExtensions', function() {
+ var msg = new proto.jspb.test.TestExtendable();
+ fillExtensions(msg);
+ var decoded = proto.jspb.test.TestExtendable.deserializeBinary(encoded);
+ checkExtensions(decoded);
@@ -0,0 +1,1127 @@
+ * @fileoverview This file contains utilities for converting binary,
+ * wire-format protocol buffers into Javascript data structures.
+ * jspb's BinaryReader class wraps the BinaryDecoder class to add methods
+ * that understand the protocol buffer syntax and can do the type checking and
+ * bookkeeping necessary to parse trees of nested messages.
+ * Major caveat - Users of this library _must_ keep their Javascript proto
+ * parsing code in sync with the original .proto file - presumably you'll be
+ * using the typed jspb code generator, but if you bypass that you'll need
+ * to keep things in sync by hand.
+goog.provide('jspb.BinaryReader');
+ * BinaryReader implements the decoders for all the wire types specified in
+jspb.BinaryReader = function(opt_bytes, opt_start, opt_length) {
+ * Wire-format decoder.
+ * @private {!jspb.BinaryDecoder}
+ this.decoder_ = jspb.BinaryDecoder.alloc(opt_bytes, opt_start, opt_length);
+ * Cursor immediately before the field tag.
+ this.fieldCursor_ = this.decoder_.getCursor();
+ * Field number of the next field in the buffer, filled in by nextField().
+ this.nextField_ = jspb.BinaryConstants.INVALID_FIELD_NUMBER;
+ * Wire type of the next proto field in the buffer, filled in by
+ * nextField().
+ * @private {jspb.BinaryConstants.WireType}
+ this.nextWireType_ = jspb.BinaryConstants.WireType.INVALID;
+ * Set to true if this reader encountered an error due to corrupt data.
+ * User-defined reader callbacks.
+ * @private {Object.<string, function(!jspb.BinaryReader):*>}
+ this.readCallbacks_ = null;
+ * Global pool of BinaryReader instances.
+ * @private {!Array.<!jspb.BinaryReader>}
+jspb.BinaryReader.instanceCache_ = [];
+ * @return {!jspb.BinaryReader}
+jspb.BinaryReader.alloc =
+ function(opt_bytes, opt_start, opt_length) {
+ if (jspb.BinaryReader.instanceCache_.length) {
+ var newReader = jspb.BinaryReader.instanceCache_.pop();
+ newReader.decoder_.setBlock(opt_bytes, opt_start, opt_length);
+ return newReader;
+ return new jspb.BinaryReader(opt_bytes, opt_start, opt_length);
+ * Alias for the above method.
+jspb.BinaryReader.prototype.alloc = jspb.BinaryReader.alloc;
+jspb.BinaryReader.prototype.free = function() {
+ this.decoder_.clear();
+ if (jspb.BinaryReader.instanceCache_.length < 100) {
+ jspb.BinaryReader.instanceCache_.push(this);
+ * Returns the cursor immediately before the current field's tag.
+jspb.BinaryReader.prototype.getFieldCursor = function() {
+ return this.fieldCursor_;
+jspb.BinaryReader.prototype.getCursor = function() {
+ return this.decoder_.getCursor();
+jspb.BinaryReader.prototype.getBuffer = function() {
+ return this.decoder_.getBuffer();
+ * @return {number} The field number of the next field in the buffer, or
+ * INVALID_FIELD_NUMBER if there is no next field.
+jspb.BinaryReader.prototype.getFieldNumber = function() {
+ return this.nextField_;
+ * @return {jspb.BinaryConstants.WireType} The wire type of the next field
+ * in the stream, or WireType.INVALID if there is no next field.
+jspb.BinaryReader.prototype.getWireType = function() {
+ return this.nextWireType_;
+ * @return {boolean} Whether the current wire type is an end-group tag. Used as
+ * an exit condition in decoder loops in generated code.
+jspb.BinaryReader.prototype.isEndGroup = function() {
+ return this.nextWireType_ == jspb.BinaryConstants.WireType.END_GROUP;
+ * Returns true if this reader hit an error due to corrupt data.
+jspb.BinaryReader.prototype.getError = function() {
+ return this.error_ || this.decoder_.getError();
+ * Points this reader at a new block of bytes.
+ * @param {!Uint8Array} bytes The block of bytes we're reading from.
+ * @param {number} start The offset to start reading at.
+ * @param {number} length The length of the block to read.
+jspb.BinaryReader.prototype.setBlock = function(bytes, start, length) {
+ this.decoder_.setBlock(bytes, start, length);
+ * Rewinds the stream cursor to the beginning of the buffer and resets all
+ * internal state.
+jspb.BinaryReader.prototype.reset = function() {
+ this.decoder_.reset();
+jspb.BinaryReader.prototype.advance = function(count) {
+ this.decoder_.advance(count);
+ * Reads the next field header in the stream if there is one, returns true if
+ * we saw a valid field header or false if we've read the whole stream.
+ * Throws an error if we encountered a deprecated START_GROUP/END_GROUP field.
+ * @return {boolean} True if the stream contains more fields.
+jspb.BinaryReader.prototype.nextField = function() {
+ // If we're at the end of the block, there are no more fields.
+ // If we hit an error decoding the previous field, stop now before we
+ // try to decode anything else
+ if (this.getError()) {
+ goog.asserts.fail('Decoder hit an error');
+ // Otherwise just read the header of the next field.
+ var header = this.decoder_.readUnsignedVarint32();
+ var nextField = header >>> 3;
+ var nextWireType = /** @type {jspb.BinaryConstants.WireType} */
+ (header & 0x7);
+ // If the wire type isn't one of the valid ones, something's broken.
+ if (nextWireType != jspb.BinaryConstants.WireType.VARINT &&
+ nextWireType != jspb.BinaryConstants.WireType.FIXED32 &&
+ nextWireType != jspb.BinaryConstants.WireType.FIXED64 &&
+ nextWireType != jspb.BinaryConstants.WireType.DELIMITED &&
+ nextWireType != jspb.BinaryConstants.WireType.START_GROUP &&
+ nextWireType != jspb.BinaryConstants.WireType.END_GROUP) {
+ goog.asserts.fail('Invalid wire type');
+ this.nextField_ = nextField;
+ this.nextWireType_ = nextWireType;
+ * Winds the reader back to just before this field's header.
+jspb.BinaryReader.prototype.unskipHeader = function() {
+ this.decoder_.unskipVarint((this.nextField_ << 3) | this.nextWireType_);
+ * Skips all contiguous fields whose header matches the one we just read.
+jspb.BinaryReader.prototype.skipMatchingFields = function() {
+ var field = this.nextField_;
+ this.unskipHeader();
+ while (this.nextField() && (this.getFieldNumber() == field)) {
+ this.skipField();
+ if (!this.decoder_.atEnd()) {
+ * Skips over the next varint field in the binary stream.
+jspb.BinaryReader.prototype.skipVarintField = function() {
+ if (this.nextWireType_ != jspb.BinaryConstants.WireType.VARINT) {
+ goog.asserts.fail('Invalid wire type for skipVarintField');
+ this.decoder_.skipVarint();
+ * Skips over the next delimited field in the binary stream.
+jspb.BinaryReader.prototype.skipDelimitedField = function() {
+ if (this.nextWireType_ != jspb.BinaryConstants.WireType.DELIMITED) {
+ goog.asserts.fail('Invalid wire type for skipDelimitedField');
+ var length = this.decoder_.readUnsignedVarint32();
+ this.decoder_.advance(length);
+ * Skips over the next fixed32 field in the binary stream.
+jspb.BinaryReader.prototype.skipFixed32Field = function() {
+ if (this.nextWireType_ != jspb.BinaryConstants.WireType.FIXED32) {
+ goog.asserts.fail('Invalid wire type for skipFixed32Field');
+ this.decoder_.advance(4);
+ * Skips over the next fixed64 field in the binary stream.
+jspb.BinaryReader.prototype.skipFixed64Field = function() {
+ if (this.nextWireType_ != jspb.BinaryConstants.WireType.FIXED64) {
+ goog.asserts.fail('Invalid wire type for skipFixed64Field');
+ this.decoder_.advance(8);
+ * Skips over the next group field in the binary stream.
+jspb.BinaryReader.prototype.skipGroup = function() {
+ // Keep a stack of start-group tags that must be matched by end-group tags.
+ var nestedGroups = [this.nextField_];
+ do {
+ if (!this.nextField()) {
+ goog.asserts.fail('Unmatched start-group tag: stream EOF');
+ if (this.nextWireType_ ==
+ jspb.BinaryConstants.WireType.START_GROUP) {
+ // Nested group start.
+ nestedGroups.push(this.nextField_);
+ } else if (this.nextWireType_ ==
+ jspb.BinaryConstants.WireType.END_GROUP) {
+ // Group end: check that it matches top-of-stack.
+ if (this.nextField_ != nestedGroups.pop()) {
+ goog.asserts.fail('Unmatched end-group tag');
+ } while (nestedGroups.length > 0);
+ * Skips over the next field in the binary stream - this is useful if we're
+ * decoding a message that contain unknown fields.
+jspb.BinaryReader.prototype.skipField = function() {
+ switch (this.nextWireType_) {
+ case jspb.BinaryConstants.WireType.VARINT:
+ this.skipVarintField();
+ case jspb.BinaryConstants.WireType.FIXED64:
+ this.skipFixed64Field();
+ case jspb.BinaryConstants.WireType.DELIMITED:
+ this.skipDelimitedField();
+ case jspb.BinaryConstants.WireType.FIXED32:
+ this.skipFixed32Field();
+ case jspb.BinaryConstants.WireType.START_GROUP:
+ this.skipGroup();
+ goog.asserts.fail('Invalid wire encoding for field.');
+ * Registers a user-defined read callback.
+ * @param {string} callbackName
+ * @param {function(!jspb.BinaryReader):*} callback
+jspb.BinaryReader.prototype.registerReadCallback =
+ function(callbackName, callback) {
+ if (goog.isNull(this.readCallbacks_)) {
+ this.readCallbacks_ = {};
+ goog.asserts.assert(!this.readCallbacks_[callbackName]);
+ this.readCallbacks_[callbackName] = callback;
+ * Runs a registered read callback.
+ * @param {string} callbackName The name the callback is registered under.
+ * @return {*} The value returned by the callback.
+jspb.BinaryReader.prototype.runReadCallback = function(callbackName) {
+ goog.asserts.assert(!goog.isNull(this.readCallbacks_));
+ var callback = this.readCallbacks_[callbackName];
+ goog.asserts.assert(callback);
+ return callback(this);
+ * Reads a field of any valid non-message type from the binary stream.
+ * @return {jspb.AnyFieldType}
+jspb.BinaryReader.prototype.readAny = function(fieldType) {
+ this.nextWireType_ = jspb.BinaryConstants.FieldTypeToWireType(fieldType);
+ return this.readDouble();
+ return this.readFloat();
+ return this.readInt64();
+ return this.readUint64();
+ return this.readInt32();
+ return this.readFixed64();
+ return this.readFixed32();
+ return this.readBool();
+ return this.readString();
+ goog.asserts.fail('Group field type not supported in readAny()');
+ goog.asserts.fail('Message field type not supported in readAny()');
+ return this.readBytes();
+ return this.readUint32();
+ return this.readEnum();
+ return this.readSfixed32();
+ return this.readSfixed64();
+ return this.readSint32();
+ return this.readSint64();
+ return this.readFixedHash64();
+ return this.readVarintHash64();
+ goog.asserts.fail('Invalid field type in readAny()');
+ * Deserialize a proto into the provided message object using the provided
+ * reader function. This function is templated as we currently have one client
+ * who is using manual deserialization instead of the code-generated versions.
+ * @template T
+ * @param {T} message
+ * @param {function(T, !jspb.BinaryReader)} reader
+jspb.BinaryReader.prototype.readMessage = function(message, reader) {
+ goog.asserts.assert(
+ this.nextWireType_ == jspb.BinaryConstants.WireType.DELIMITED);
+ // Save the current endpoint of the decoder and move it to the end of the
+ // embedded message.
+ var oldEnd = this.decoder_.getEnd();
+ var newEnd = this.decoder_.getCursor() + length;
+ this.decoder_.setEnd(newEnd);
+ // Deserialize the embedded message.
+ reader(message, this);
+ // Advance the decoder past the embedded message and restore the endpoint.
+ this.decoder_.setCursor(newEnd);
+ this.decoder_.setEnd(oldEnd);
+ * reader function, assuming that the message is serialized as a group
+ * with the given tag.
+ * @param {number} field
+jspb.BinaryReader.prototype.readGroup =
+ function(field, message, reader) {
+ // Ensure that the wire type is correct.
+ this.nextWireType_ == jspb.BinaryConstants.WireType.START_GROUP);
+ // Ensure that the field number is correct.
+ goog.asserts.assert(this.nextField_ == field);
+ // Deserialize the message. The deserialization will stop at an END_GROUP tag.
+ if (!this.error_ &&
+ this.nextWireType_ != jspb.BinaryConstants.WireType.END_GROUP) {
+ goog.asserts.fail('Group submessage did not end with an END_GROUP tag');
+ * Return a decoder that wraps the current delimited field.
+jspb.BinaryReader.prototype.getFieldDecoder = function() {
+ var start = this.decoder_.getCursor();
+ var end = start + length;
+ var innerDecoder = jspb.BinaryDecoder.alloc(this.decoder_.getBuffer(),
+ start, length);
+ this.decoder_.setCursor(end);
+ return innerDecoder;
+ * Reads a signed 32-bit integer field from the binary stream, or throws an
+ * error if the next field in the stream is not of the correct wire type.
+ * @return {number} The value of the signed 32-bit integer field.
+jspb.BinaryReader.prototype.readInt32 = function() {
+ this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT);
+ return this.decoder_.readSignedVarint32();
+ * Returns the value as a string.
+ * @return {string} The value of the signed 32-bit integer field as a decimal
+ * string.
+jspb.BinaryReader.prototype.readInt32String = function() {
+ return this.decoder_.readSignedVarint32String();
+ * Reads a signed 64-bit integer field from the binary stream, or throws an
+ * @return {number} The value of the signed 64-bit integer field.
+jspb.BinaryReader.prototype.readInt64 = function() {
+ return this.decoder_.readSignedVarint64();
+ * @return {string} The value of the signed 64-bit integer field as a decimal
+jspb.BinaryReader.prototype.readInt64String = function() {
+ return this.decoder_.readSignedVarint64String();
+ * Reads an unsigned 32-bit integer field from the binary stream, or throws an
+ * @return {number} The value of the unsigned 32-bit integer field.
+jspb.BinaryReader.prototype.readUint32 = function() {
+ return this.decoder_.readUnsignedVarint32();
+ * @return {string} The value of the unsigned 32-bit integer field as a decimal
+jspb.BinaryReader.prototype.readUint32String = function() {
+ return this.decoder_.readUnsignedVarint32String();
+ * Reads an unsigned 64-bit integer field from the binary stream, or throws an
+ * @return {number} The value of the unsigned 64-bit integer field.
+jspb.BinaryReader.prototype.readUint64 = function() {
+ return this.decoder_.readUnsignedVarint64();
+ * @return {string} The value of the unsigned 64-bit integer field as a decimal
+jspb.BinaryReader.prototype.readUint64String = function() {
+ return this.decoder_.readUnsignedVarint64String();
+ * Reads a signed zigzag-encoded 32-bit integer field from the binary stream,
+ * or throws an error if the next field in the stream is not of the correct
+ * wire type.
+jspb.BinaryReader.prototype.readSint32 = function() {
+ return this.decoder_.readZigzagVarint32();
+ * Reads a signed zigzag-encoded 64-bit integer field from the binary stream,
+jspb.BinaryReader.prototype.readSint64 = function() {
+ return this.decoder_.readZigzagVarint64();
+ * Reads an unsigned 32-bit fixed-length integer fiield from the binary stream,
+ * @return {number} The value of the double field.
+jspb.BinaryReader.prototype.readFixed32 = function() {
+ this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED32);
+ return this.decoder_.readUint32();
+ * Reads an unsigned 64-bit fixed-length integer fiield from the binary stream,
+ * @return {number} The value of the float field.
+jspb.BinaryReader.prototype.readFixed64 = function() {
+ this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED64);
+ return this.decoder_.readUint64();
+ * Reads a signed 32-bit fixed-length integer fiield from the binary stream, or
+ * throws an error if the next field in the stream is not of the correct wire
+ * type.
+jspb.BinaryReader.prototype.readSfixed32 = function() {
+ return this.decoder_.readInt32();
+ * Reads a signed 64-bit fixed-length integer fiield from the binary stream, or
+jspb.BinaryReader.prototype.readSfixed64 = function() {
+ return this.decoder_.readInt64();
+ * Reads a 32-bit floating-point field from the binary stream, or throws an
+jspb.BinaryReader.prototype.readFloat = function() {
+ return this.decoder_.readFloat();
+ * Reads a 64-bit floating-point field from the binary stream, or throws an
+jspb.BinaryReader.prototype.readDouble = function() {
+ return this.decoder_.readDouble();
+ * Reads a boolean field from the binary stream, or throws an error if the next
+ * field in the stream is not of the correct wire type.
+ * @return {boolean} The value of the boolean field.
+jspb.BinaryReader.prototype.readBool = function() {
+ return !!this.decoder_.readUnsignedVarint32();
+ * Reads an enum field from the binary stream, or throws an error if the next
+ * @return {number} The value of the enum field.
+jspb.BinaryReader.prototype.readEnum = function() {
+ * Reads a string field from the binary stream, or throws an error if the next
+ * @return {string} The value of the string field.
+jspb.BinaryReader.prototype.readString = function() {
+ return this.decoder_.readString(length);
+ * Reads a length-prefixed block of bytes from the binary stream, or returns
+ * null if the next field in the stream has an invalid length value.
+ * @return {Uint8Array} The block of bytes.
+jspb.BinaryReader.prototype.readBytes = function() {
+ return this.decoder_.readBytes(length);
+ * Reads a 64-bit varint or fixed64 field from the stream and returns it as a
+ * 8-character Unicode string for use as a hash table key, or throws an error
+ * if the next field in the stream is not of the correct wire type.
+jspb.BinaryReader.prototype.readVarintHash64 = function() {
+ return this.decoder_.readVarintHash64();
+jspb.BinaryReader.prototype.readFixedHash64 = function() {
+ return this.decoder_.readFixedHash64();
+ * Reads a packed scalar field using the supplied raw reader function.
+ * @param {function()} decodeMethod
+ * @return {!Array}
+jspb.BinaryReader.prototype.readPackedField_ = function(decodeMethod) {
+ var end = this.decoder_.getCursor() + length;
+ var result = [];
+ while (this.decoder_.getCursor() < end) {
+ // TODO(aappleby): .call is slow
+ result.push(decodeMethod.call(this.decoder_));
+ * Reads a packed int32 field, which consists of a length header and a list of
+ * @return {!Array.<number>}
+jspb.BinaryReader.prototype.readPackedInt32 = function() {
+ return this.readPackedField_(this.decoder_.readSignedVarint32);
+ * signed varints. Returns a list of strings.
+ * @return {!Array.<string>}
+jspb.BinaryReader.prototype.readPackedInt32String = function() {
+ return this.readPackedField_(this.decoder_.readSignedVarint32String);
+ * Reads a packed int64 field, which consists of a length header and a list of
+jspb.BinaryReader.prototype.readPackedInt64 = function() {
+ return this.readPackedField_(this.decoder_.readSignedVarint64);
+jspb.BinaryReader.prototype.readPackedInt64String = function() {
+ return this.readPackedField_(this.decoder_.readSignedVarint64String);
+ * Reads a packed uint32 field, which consists of a length header and a list of
+ * unsigned varints.
+jspb.BinaryReader.prototype.readPackedUint32 = function() {
+ return this.readPackedField_(this.decoder_.readUnsignedVarint32);
+ * unsigned varints. Returns a list of strings.
+jspb.BinaryReader.prototype.readPackedUint32String = function() {
+ return this.readPackedField_(this.decoder_.readUnsignedVarint32String);
+ * Reads a packed uint64 field, which consists of a length header and a list of
+jspb.BinaryReader.prototype.readPackedUint64 = function() {
+ return this.readPackedField_(this.decoder_.readUnsignedVarint64);
+jspb.BinaryReader.prototype.readPackedUint64String = function() {
+ return this.readPackedField_(this.decoder_.readUnsignedVarint64String);
+ * Reads a packed sint32 field, which consists of a length header and a list of
+ * zigzag varints.
+jspb.BinaryReader.prototype.readPackedSint32 = function() {
+ return this.readPackedField_(this.decoder_.readZigzagVarint32);
+ * Reads a packed sint64 field, which consists of a length header and a list of
+jspb.BinaryReader.prototype.readPackedSint64 = function() {
+ return this.readPackedField_(this.decoder_.readZigzagVarint64);
+ * Reads a packed fixed32 field, which consists of a length header and a list
+ * of unsigned 32-bit ints.
+jspb.BinaryReader.prototype.readPackedFixed32 = function() {
+ return this.readPackedField_(this.decoder_.readUint32);
+ * Reads a packed fixed64 field, which consists of a length header and a list
+ * of unsigned 64-bit ints.
+jspb.BinaryReader.prototype.readPackedFixed64 = function() {
+ return this.readPackedField_(this.decoder_.readUint64);
+ * Reads a packed sfixed32 field, which consists of a length header and a list
+ * of 32-bit ints.
+jspb.BinaryReader.prototype.readPackedSfixed32 = function() {
+ return this.readPackedField_(this.decoder_.readInt32);
+ * Reads a packed sfixed64 field, which consists of a length header and a list
+ * of 64-bit ints.
+jspb.BinaryReader.prototype.readPackedSfixed64 = function() {
+ return this.readPackedField_(this.decoder_.readInt64);
+ * Reads a packed float field, which consists of a length header and a list of
+ * floats.
+jspb.BinaryReader.prototype.readPackedFloat = function() {
+ return this.readPackedField_(this.decoder_.readFloat);
+ * Reads a packed double field, which consists of a length header and a list of
+ * doubles.
+jspb.BinaryReader.prototype.readPackedDouble = function() {
+ return this.readPackedField_(this.decoder_.readDouble);
+ * Reads a packed bool field, which consists of a length header and a list of
+ * @return {!Array.<boolean>}
+jspb.BinaryReader.prototype.readPackedBool = function() {
+ return this.readPackedField_(this.decoder_.readBool);
+ * Reads a packed enum field, which consists of a length header and a list of
+jspb.BinaryReader.prototype.readPackedEnum = function() {
+ return this.readPackedField_(this.decoder_.readEnum);
+ * Reads a packed varint hash64 field, which consists of a length header and a
+ * list of varint hash64s.
+jspb.BinaryReader.prototype.readPackedVarintHash64 = function() {
+ return this.readPackedField_(this.decoder_.readVarintHash64);
+ * Reads a packed fixed hash64 field, which consists of a length header and a
+ * list of fixed hash64s.
+jspb.BinaryReader.prototype.readPackedFixedHash64 = function() {
+ return this.readPackedField_(this.decoder_.readFixedHash64);
@@ -0,0 +1,889 @@
+ * @fileoverview Test cases for jspb's binary protocol buffer reader.
+goog.require('jspb.BinaryReader');
+describe('binaryReaderTest', function() {
+ * Tests the reader instance cache.
+ it('testInstanceCaches', function() {
+ var dummyMessage = /** @type {!jspb.BinaryMessage} */({});
+ writer.writeMessage(1, dummyMessage, goog.nullFunction);
+ writer.writeMessage(2, dummyMessage, goog.nullFunction);
+ var buffer = writer.getResultBuffer();
+ jspb.BinaryReader.instanceCache_ = [];
+ assertEquals(0, jspb.BinaryReader.instanceCache_.length);
+ // Allocating and then freeing a reader should remove one decoder from its
+ // cache, but it should stay stuck to the reader afterwards since we can't
+ // have a reader without a decoder.
+ jspb.BinaryReader.alloc().free();
+ assertEquals(2, jspb.BinaryDecoder.instanceCache_.length);
+ assertEquals(1, jspb.BinaryReader.instanceCache_.length);
+ // Allocating a reader should remove a reader from the cache.
+ var reader = jspb.BinaryReader.alloc(buffer);
+ // Processing the message reuses the current reader.
+ reader.nextField();
+ assertEquals(1, reader.getFieldNumber());
+ reader.readMessage(dummyMessage, function() {
+ assertEquals(2, reader.getFieldNumber());
+ assertEquals(false, reader.nextField());
+ // Freeing the reader should put it back into the cache.
+ reader.free();
+ * Verifies that misuse of the reader class triggers assertions.
+ it('testReadErrors', function() {
+ // Calling readMessage on a non-delimited field should trigger an
+ // assertion.
+ var reader = jspb.BinaryReader.alloc([8, 1]);
+ assertThrows(function() {
+ reader.readMessage(dummyMessage, goog.nullFunction);
+ // Reading past the end of the stream should trigger an assertion.
+ reader = jspb.BinaryReader.alloc([9, 1]);
+ assertThrows(function() {reader.readFixed64()});
+ // Reading past the end of a submessage should trigger an assertion.
+ reader = jspb.BinaryReader.alloc([10, 4, 13, 1, 1, 1]);
+ assertThrows(function() {reader.readFixed32()});
+ // Skipping an invalid field should trigger an assertion.
+ reader = jspb.BinaryReader.alloc([12, 1]);
+ reader.nextWireType_ = 1000;
+ assertThrows(function() {reader.skipField()});
+ // Reading fields with the wrong wire type should assert.
+ reader = jspb.BinaryReader.alloc([9, 0, 0, 0, 0, 0, 0, 0, 0]);
+ assertThrows(function() {reader.readInt32()});
+ assertThrows(function() {reader.readInt32String()});
+ assertThrows(function() {reader.readInt64()});
+ assertThrows(function() {reader.readInt64String()});
+ assertThrows(function() {reader.readUint32()});
+ assertThrows(function() {reader.readUint32String()});
+ assertThrows(function() {reader.readUint64()});
+ assertThrows(function() {reader.readUint64String()});
+ assertThrows(function() {reader.readSint32()});
+ assertThrows(function() {reader.readBool()});
+ assertThrows(function() {reader.readEnum()});
+ reader = jspb.BinaryReader.alloc([8, 1]);
+ assertThrows(function() {reader.readSfixed32()});
+ assertThrows(function() {reader.readSfixed64()});
+ assertThrows(function() {reader.readFloat()});
+ assertThrows(function() {reader.readDouble()});
+ assertThrows(function() {reader.readString()});
+ assertThrows(function() {reader.readBytes()});
+ * Tests encoding and decoding of unsigned field types.
+ * @param {Function} readField
+ * @param {Function} writeField
+ function doTestUnsignedField_(readField,
+ writeField, epsilon, upperLimit, filter) {
+ assertNotNull(readField);
+ assertNotNull(writeField);
+ writeField.call(writer, 1, filter(0));
+ writeField.call(writer, 2, filter(epsilon));
+ writeField.call(writer, 3, filter(upperLimit));
+ writeField.call(writer, 4, filter(cursor));
+ var reader = jspb.BinaryReader.alloc(writer.getResultBuffer());
+ assertEquals(filter(0), readField.call(reader));
+ assertEquals(filter(epsilon), readField.call(reader));
+ assertEquals(3, reader.getFieldNumber());
+ assertEquals(filter(upperLimit), readField.call(reader));
+ if (4 != reader.getFieldNumber()) throw 'fail!';
+ if (filter(cursor) != readField.call(reader)) throw 'fail!';
+ * Tests encoding and decoding of signed field types.
+ function doTestSignedField_(readField,
+ writeField, epsilon, lowerLimit, upperLimit, filter) {
+ writeField.call(writer, 1, filter(lowerLimit));
+ writeField.call(writer, 2, filter(-epsilon));
+ writeField.call(writer, 3, filter(0));
+ writeField.call(writer, 4, filter(epsilon));
+ writeField.call(writer, 5, filter(upperLimit));
+ writeField.call(writer, 6, val);
+ inputValues.push({
+ fieldNumber: 6,
+ value: val
+ writeField.call(writer, 7, val);
+ fieldNumber: 7,
+ assertEquals(filter(lowerLimit), readField.call(reader));
+ assertEquals(filter(-epsilon), readField.call(reader));
+ assertEquals(4, reader.getFieldNumber());
+ assertEquals(5, reader.getFieldNumber());
+ var expected = inputValues[i];
+ assertEquals(expected.fieldNumber, reader.getFieldNumber());
+ assertEquals(expected.value, readField.call(reader));
+ * Tests fields that use varint encoding.
+ it('testVarintFields', function() {
+ assertNotNull(jspb.BinaryReader.prototype.readUint32);
+ assertNotNull(jspb.BinaryReader.prototype.writeUint32);
+ assertNotNull(jspb.BinaryReader.prototype.readUint64);
+ assertNotNull(jspb.BinaryReader.prototype.writeUint64);
+ assertNotNull(jspb.BinaryReader.prototype.readBool);
+ assertNotNull(jspb.BinaryReader.prototype.writeBool);
+ doTestUnsignedField_(
+ jspb.BinaryReader.prototype.readUint32,
+ jspb.BinaryWriter.prototype.writeUint32,
+ 1, Math.pow(2, 32) - 1, Math.round);
+ jspb.BinaryReader.prototype.readUint64,
+ jspb.BinaryWriter.prototype.writeUint64,
+ doTestSignedField_(
+ jspb.BinaryReader.prototype.readInt32,
+ jspb.BinaryWriter.prototype.writeInt32,
+ 1, -Math.pow(2, 31), Math.pow(2, 31) - 1, Math.round);
+ jspb.BinaryReader.prototype.readInt64,
+ jspb.BinaryWriter.prototype.writeInt64,
+ jspb.BinaryReader.prototype.readEnum,
+ jspb.BinaryWriter.prototype.writeEnum,
+ jspb.BinaryReader.prototype.readBool,
+ jspb.BinaryWriter.prototype.writeBool,
+ 1, 1, function(x) { return !!x; });
+ * Tests 64-bit fields that are handled as strings.
+ it('testStringInt64Fields', function() {
+ var testSignedData = [
+ '2730538252207801776',
+ '-2688470994844604560',
+ '3398529779486536359',
+ '3568577411627971000',
+ '272477188847484900',
+ '-6649058714086158188',
+ '-7695254765712060806',
+ '-4525541438037104029',
+ '-4993706538836508568',
+ '4990160321893729138'
+ var testUnsignedData = [
+ '7822732630241694882',
+ '6753602971916687352',
+ '2399935075244442116',
+ '8724292567325338867',
+ '16948784802625696584',
+ '4136275908516066934',
+ '3575388346793700364',
+ '5167142028379259461',
+ '1557573948689737699',
+ '17100725280812548567'
+ for (var i = 0; i < testSignedData.length; i++) {
+ writer.writeInt64String(2 * i + 1, testSignedData[i]);
+ writer.writeUint64String(2 * i + 2, testUnsignedData[i]);
+ assertEquals(2 * i + 1, reader.getFieldNumber());
+ assertEquals(testSignedData[i], reader.readInt64String());
+ assertEquals(2 * i + 2, reader.getFieldNumber());
+ assertEquals(testUnsignedData[i], reader.readUint64String());
+ * Tests fields that use zigzag encoding.
+ it('testZigzagFields', function() {
+ jspb.BinaryReader.prototype.readSint32,
+ jspb.BinaryWriter.prototype.writeSint32,
+ jspb.BinaryReader.prototype.readSint64,
+ jspb.BinaryWriter.prototype.writeSint64,
+ * Tests fields that use fixed-length encoding.
+ it('testFixedFields', function() {
+ jspb.BinaryReader.prototype.readFixed32,
+ jspb.BinaryWriter.prototype.writeFixed32,
+ jspb.BinaryReader.prototype.readFixed64,
+ jspb.BinaryWriter.prototype.writeFixed64,
+ jspb.BinaryReader.prototype.readSfixed32,
+ jspb.BinaryWriter.prototype.writeSfixed32,
+ jspb.BinaryReader.prototype.readSfixed64,
+ jspb.BinaryWriter.prototype.writeSfixed64,
+ * Tests floating point fields.
+ it('testFloatFields', function() {
+ jspb.BinaryReader.prototype.readFloat,
+ jspb.BinaryWriter.prototype.writeFloat,
+ jspb.BinaryConstants.FLOAT32_MIN,
+ jspb.BinaryReader.prototype.readDouble,
+ jspb.BinaryWriter.prototype.writeDouble,
+ -jspb.BinaryConstants.FLOAT64_MIN,
+ jspb.BinaryConstants.FLOAT64_MIN,
+ * Tests length-delimited string fields.
+ it('testStringFields', function() {
+ var s1 = 'The quick brown fox jumps over the lazy dog.';
+ var s2 = '人人生而自由,在尊嚴和權利上一律平等。';
+ writer.writeString(1, s1);
+ writer.writeString(2, s2);
+ assertEquals(s1, reader.readString());
+ assertEquals(s2, reader.readString());
+ * Tests length-delimited byte fields.
+ it('testByteFields', function() {
+ var message = [];
+ var lowerLimit = 1;
+ var upperLimit = 256;
+ var scale = 1.1;
+ for (var cursor = lowerLimit; cursor < upperLimit; cursor *= 1.1) {
+ var len = Math.round(cursor);
+ var bytes = [];
+ for (var i = 0; i < len; i++) bytes.push(i % 256);
+ writer.writeBytes(len, bytes);
+ for (var cursor = lowerLimit; reader.nextField(); cursor *= 1.1) {
+ if (len != reader.getFieldNumber()) throw 'fail!';
+ var bytes = reader.readBytes();
+ if (len != bytes.length) throw 'fail!';
+ for (var i = 0; i < bytes.length; i++) {
+ if (i % 256 != bytes[i]) throw 'fail!';
+ * Tests nested messages.
+ it('testNesting', function() {
+ writer.writeInt32(1, 100);
+ // Add one message with 3 int fields.
+ writer.writeMessage(2, dummyMessage, function() {
+ writer.writeInt32(3, 300);
+ writer.writeInt32(4, 400);
+ writer.writeInt32(5, 500);
+ // Add one empty message.
+ writer.writeMessage(6, dummyMessage, goog.nullFunction);
+ writer.writeInt32(7, 700);
+ // Validate outermost message.
+ assertEquals(100, reader.readInt32());
+ // Validate embedded message 1.
+ assertEquals(300, reader.readInt32());
+ assertEquals(400, reader.readInt32());
+ assertEquals(500, reader.readInt32());
+ assertEquals(6, reader.getFieldNumber());
+ // Validate embedded message 2.
+ assertEquals(7, reader.getFieldNumber());
+ assertEquals(700, reader.readInt32());
+ * Tests skipping fields of each type by interleaving them with sentinel
+ * values and skipping everything that's not a sentinel.
+ it('testSkipField', function() {
+ var sentinel = 123456789;
+ // Write varint fields of different sizes.
+ writer.writeInt32(1, sentinel);
+ writer.writeInt32(1, 1);
+ writer.writeInt32(1, 1000);
+ writer.writeInt32(1, 1000000);
+ writer.writeInt32(1, 1000000000);
+ // Write fixed 64-bit encoded fields.
+ writer.writeInt32(2, sentinel);
+ writer.writeDouble(2, 1);
+ writer.writeFixed64(2, 1);
+ writer.writeSfixed64(2, 1);
+ // Write fixed 32-bit encoded fields.
+ writer.writeInt32(3, sentinel);
+ writer.writeFloat(3, 1);
+ writer.writeFixed32(3, 1);
+ writer.writeSfixed32(3, 1);
+ // Write delimited fields.
+ writer.writeInt32(4, sentinel);
+ writer.writeBytes(4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+ writer.writeString(4, 'The quick brown fox jumps over the lazy dog');
+ // Write a group with a nested group inside. We use the internal
+ // .rawWriteVarint() to ensure the tested wire data is what we want,
+ // independently of any serialization logic.
+ writer.writeInt32(5, sentinel);
+ // Start group, field 5.
+ writer.rawWriteVarint(
+ (5 << 3) + jspb.BinaryConstants.WireType.START_GROUP);
+ // Varint, field 42.
+ (42 << 3) + jspb.BinaryConstants.WireType.VARINT);
+ // Varint data.
+ writer.rawWriteVarint(42);
+ // Start group, field 6.
+ (6 << 3) + jspb.BinaryConstants.WireType.START_GROUP);
+ // Varint, field 84.
+ (84 << 3) + jspb.BinaryConstants.WireType.VARINT);
+ // End group, field 6.
+ (6 << 3) + jspb.BinaryConstants.WireType.END_GROUP);
+ // End group, field 5.
+ (5 << 3) + jspb.BinaryConstants.WireType.END_GROUP);
+ // Write final sentinel.
+ writer.writeInt32(6, sentinel);
+ function skip(field, count) {
+ for (var i = 0; i < count; i++) {
+ if (field != reader.getFieldNumber()) throw 'fail!';
+ reader.skipField();
+ assertEquals(sentinel, reader.readInt32());
+ skip(1, 4);
+ skip(2, 3);
+ skip(3, 3);
+ skip(4, 2);
+ skip(5, 1);
+ * Tests packed fields.
+ it('testPackedFields', function() {
+ var unsignedData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+ var signedData = [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10];
+ var floatData = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10];
+ var doubleData = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10];
+ var boolData = [true, false, true, true, false, false, true, false];
+ for (var i = 0; i < floatData.length; i++) {
+ floatData[i] = truncate(floatData[i]);
+ writer.writePackedInt32(2, signedData);
+ writer.writePackedInt64(2, signedData);
+ writer.writePackedUint32(2, unsignedData);
+ writer.writePackedUint64(2, unsignedData);
+ writer.writePackedSint32(2, signedData);
+ writer.writePackedSint64(2, signedData);
+ writer.writePackedFixed32(2, unsignedData);
+ writer.writePackedFixed64(2, unsignedData);
+ writer.writePackedSfixed32(2, signedData);
+ writer.writePackedSfixed64(2, signedData);
+ writer.writePackedFloat(2, floatData);
+ writer.writePackedDouble(2, doubleData);
+ writer.writePackedBool(2, boolData);
+ writer.writePackedEnum(2, unsignedData);
+ assertElementsEquals(reader.readPackedInt32(), signedData);
+ assertElementsEquals(reader.readPackedInt64(), signedData);
+ assertElementsEquals(reader.readPackedUint32(), unsignedData);
+ assertElementsEquals(reader.readPackedUint64(), unsignedData);
+ assertElementsEquals(reader.readPackedSint32(), signedData);
+ assertElementsEquals(reader.readPackedSint64(), signedData);
+ assertElementsEquals(reader.readPackedFixed32(), unsignedData);
+ assertElementsEquals(reader.readPackedFixed64(), unsignedData);
+ assertElementsEquals(reader.readPackedSfixed32(), signedData);
+ assertElementsEquals(reader.readPackedSfixed64(), signedData);
+ assertElementsEquals(reader.readPackedFloat(), floatData);
+ assertElementsEquals(reader.readPackedDouble(), doubleData);
+ assertElementsEquals(reader.readPackedBool(), boolData);
+ assertElementsEquals(reader.readPackedEnum(), unsignedData);
+ * Byte blobs inside nested messages should always have their byte offset set
+ * relative to the start of the outermost blob, not the start of their parent
+ * blob.
+ it('testNestedBlobs', function() {
+ // Create a proto consisting of two nested messages, with the inner one
+ // containing a blob of bytes.
+ var fieldTag = (1 << 3) | jspb.BinaryConstants.WireType.DELIMITED;
+ var blob = [1, 2, 3, 4, 5];
+ writer.writeMessage(1, dummyMessage, function() {
+ writer.writeBytes(1, blob);
+ // Peel off the outer two message layers. Each layer should have two bytes
+ // of overhead, one for the field tag and one for the length of the inner
+ // blob.
+ var decoder1 = new jspb.BinaryDecoder(writer.getResultBuffer());
+ assertEquals(fieldTag, decoder1.readUnsignedVarint32());
+ assertEquals(blob.length + 4, decoder1.readUnsignedVarint32());
+ var decoder2 = new jspb.BinaryDecoder(decoder1.readBytes(blob.length + 4));
+ assertEquals(fieldTag, decoder2.readUnsignedVarint32());
+ assertEquals(blob.length + 2, decoder2.readUnsignedVarint32());
+ assertEquals(blob.length, decoder2.readUnsignedVarint32());
+ var bytes = decoder2.readBytes(blob.length);
+ assertElementsEquals(bytes, blob);
+ * Tests read callbacks.
+ it('testReadCallbacks', function() {
+ // Add an int, a submessage, and another int.
+ // Create the reader and register a custom read callback.
+ * @param {!jspb.BinaryReader} reader
+ * @return {*}
+ function readCallback(reader) {
+ reader.registerReadCallback('readCallback', readCallback);
+ // Read the container message.
+ // Decode the embedded message using the registered callback.
+ reader.runReadCallback('readCallback');
@@ -0,0 +1,979 @@
+ * @fileoverview This file contains helper code used by jspb.BinaryReader
+ * and BinaryWriter.
+goog.provide('jspb.utils');
+goog.require('goog.crypt.base64');
+goog.require('goog.string');
+ * Javascript can't natively handle 64-bit data types, so to manipulate them we
+ * have to split them into two 32-bit halves and do the math manually.
+ * Instead of instantiating and passing small structures around to do this, we
+ * instead just use two global temporary values. This one stores the low 32
+ * bits of a split value - for example, if the original value was a 64-bit
+ * integer, this temporary value will contain the low 32 bits of that integer.
+ * If the original value was a double, this temporary value will contain the
+ * low 32 bits of the binary representation of that double, etcetera.
+ * @type {number}
+jspb.utils.split64Low = 0;
+ * And correspondingly, this temporary variable will contain the high 32 bits
+ * of whatever value was split.
+jspb.utils.split64High = 0;
+ * Splits an unsigned Javascript integer into two 32-bit halves and stores it
+ * in the temp values above.
+ * @param {number} value The number to split.
+jspb.utils.splitUint64 = function(value) {
+ // Extract low 32 bits and high 32 bits as unsigned integers.
+ var lowBits = value >>> 0;
+ var highBits = Math.floor((value - lowBits) /
+ jspb.BinaryConstants.TWO_TO_32) >>> 0;
+ jspb.utils.split64Low = lowBits;
+ jspb.utils.split64High = highBits;
+ * Splits a signed Javascript integer into two 32-bit halves and stores it in
+ * the temp values above.
+jspb.utils.splitInt64 = function(value) {
+ // Convert to sign-magnitude representation.
+ var sign = (value < 0);
+ value = Math.abs(value);
+ jspb.BinaryConstants.TWO_TO_32);
+ highBits = highBits >>> 0;
+ // Perform two's complement conversion if the sign bit was set.
+ highBits = ~highBits >>> 0;
+ lowBits = ~lowBits >>> 0;
+ lowBits += 1;
+ if (lowBits > 0xFFFFFFFF) {
+ lowBits = 0;
+ highBits++;
+ if (highBits > 0xFFFFFFFF) highBits = 0;
+ * Convers a signed Javascript integer into zigzag format, splits it into two
+ * 32-bit halves, and stores it in the temp values above.
+jspb.utils.splitZigzag64 = function(value) {
+ // Convert to sign-magnitude and scale by 2 before we split the value.
+ value = Math.abs(value) * 2;
+ jspb.utils.splitUint64(value);
+ var lowBits = jspb.utils.split64Low;
+ var highBits = jspb.utils.split64High;
+ // If the value is negative, subtract 1 from the split representation so we
+ // don't lose the sign bit due to precision issues.
+ if (lowBits == 0) {
+ if (highBits == 0) {
+ lowBits = 0xFFFFFFFF;
+ highBits = 0xFFFFFFFF;
+ highBits--;
+ lowBits--;
+ * Converts a floating-point number into 32-bit IEEE representation and stores
+ * it in the temp values above.
+ * @param {number} value
+jspb.utils.splitFloat32 = function(value) {
+ var sign = (value < 0) ? 1 : 0;
+ value = sign ? -value : value;
+ var exp;
+ var mant;
+ // Handle zeros.
+ if (value === 0) {
+ if ((1 / value) > 0) {
+ // Positive zero.
+ jspb.utils.split64High = 0;
+ jspb.utils.split64Low = 0x00000000;
+ // Negative zero.
+ jspb.utils.split64Low = 0x80000000;
+ // Handle nans.
+ if (isNaN(value)) {
+ jspb.utils.split64Low = 0x7FFFFFFF;
+ // Handle infinities.
+ if (value > jspb.BinaryConstants.FLOAT32_MAX) {
+ jspb.utils.split64Low = ((sign << 31) | (0x7F800000)) >>> 0;
+ // Handle denormals.
+ if (value < jspb.BinaryConstants.FLOAT32_MIN) {
+ // Number is a denormal.
+ mant = Math.round(value / Math.pow(2, -149));
+ jspb.utils.split64Low = ((sign << 31) | mant) >>> 0;
+ exp = Math.floor(Math.log(value) / Math.LN2);
+ mant = value * Math.pow(2, -exp);
+ mant = Math.round(mant * jspb.BinaryConstants.TWO_TO_23) & 0x7FFFFF;
+ jspb.utils.split64Low = ((sign << 31) | ((exp + 127) << 23) | mant) >>> 0;
+ * Converts a floating-point number into 64-bit IEEE representation and stores
+jspb.utils.splitFloat64 = function(value) {
+ jspb.utils.split64High = 0x00000000;
+ jspb.utils.split64High = 0x80000000;
+ jspb.utils.split64High = 0x7FFFFFFF;
+ jspb.utils.split64Low = 0xFFFFFFFF;
+ if (value > jspb.BinaryConstants.FLOAT64_MAX) {
+ jspb.utils.split64High = ((sign << 31) | (0x7FF00000)) >>> 0;
+ jspb.utils.split64Low = 0;
+ if (value < jspb.BinaryConstants.FLOAT64_MIN) {
+ var mant = value / Math.pow(2, -1074);
+ var mantHigh = (mant / jspb.BinaryConstants.TWO_TO_32);
+ jspb.utils.split64High = ((sign << 31) | mantHigh) >>> 0;
+ jspb.utils.split64Low = (mant >>> 0);
+ var exp = Math.floor(Math.log(value) / Math.LN2);
+ if (exp == 1024) exp = 1023;
+ var mant = value * Math.pow(2, -exp);
+ var mantHigh = (mant * jspb.BinaryConstants.TWO_TO_20) & 0xFFFFF;
+ var mantLow = (mant * jspb.BinaryConstants.TWO_TO_52) >>> 0;
+ jspb.utils.split64High =
+ ((sign << 31) | ((exp + 1023) << 20) | mantHigh) >>> 0;
+ jspb.utils.split64Low = mantLow;
+ * Converts an 8-character hash string into two 32-bit numbers and stores them
+ * @param {string} hash
+jspb.utils.splitHash64 = function(hash) {
+ var a = hash.charCodeAt(0);
+ var b = hash.charCodeAt(1);
+ var c = hash.charCodeAt(2);
+ var d = hash.charCodeAt(3);
+ var e = hash.charCodeAt(4);
+ var f = hash.charCodeAt(5);
+ var g = hash.charCodeAt(6);
+ var h = hash.charCodeAt(7);
+ jspb.utils.split64Low = (a + (b << 8) + (c << 16) + (d << 24)) >>> 0;
+ jspb.utils.split64High = (e + (f << 8) + (g << 16) + (h << 24)) >>> 0;
+ * Joins two 32-bit values into a 64-bit unsigned integer. Precision will be
+ * lost if the result is greater than 2^52.
+ * @param {number} bitsLow
+ * @param {number} bitsHigh
+jspb.utils.joinUint64 = function(bitsLow, bitsHigh) {
+ return bitsHigh * jspb.BinaryConstants.TWO_TO_32 + bitsLow;
+ * Joins two 32-bit values into a 64-bit signed integer. Precision will be lost
+ * if the result is greater than 2^52.
+jspb.utils.joinInt64 = function(bitsLow, bitsHigh) {
+ // If the high bit is set, do a manual two's complement conversion.
+ var sign = (bitsHigh & 0x80000000);
+ bitsLow = (~bitsLow + 1) >>> 0;
+ bitsHigh = ~bitsHigh >>> 0;
+ if (bitsLow == 0) {
+ bitsHigh = (bitsHigh + 1) >>> 0;
+ var result = jspb.utils.joinUint64(bitsLow, bitsHigh);
+ return sign ? -result : result;
+ * Joins two 32-bit values into a 64-bit unsigned integer and applies zigzag
+ * decoding. Precision will be lost if the result is greater than 2^52.
+jspb.utils.joinZigzag64 = function(bitsLow, bitsHigh) {
+ // Extract the sign bit and shift right by one.
+ var sign = bitsLow & 1;
+ bitsLow = ((bitsLow >>> 1) | (bitsHigh << 31)) >>> 0;
+ bitsHigh = bitsHigh >>> 1;
+ // Increment the split value if the sign bit was set.
+ bitsLow = (bitsLow + 1) >>> 0;
+ * Joins two 32-bit values into a 32-bit IEEE floating point number and
+ * converts it back into a Javascript number.
+ * @param {number} bitsLow The low 32 bits of the binary number;
+ * @param {number} bitsHigh The high 32 bits of the binary number.
+jspb.utils.joinFloat32 = function(bitsLow, bitsHigh) {
+ var sign = ((bitsLow >> 31) * 2 + 1);
+ var exp = (bitsLow >>> 23) & 0xFF;
+ var mant = bitsLow & 0x7FFFFF;
+ if (exp == 0xFF) {
+ if (mant) {
+ return NaN;
+ return sign * Infinity;
+ if (exp == 0) {
+ // Denormal.
+ return sign * Math.pow(2, -149) * mant;
+ return sign * Math.pow(2, exp - 150) *
+ (mant + Math.pow(2, 23));
+ * Joins two 32-bit values into a 64-bit IEEE floating point number and
+jspb.utils.joinFloat64 = function(bitsLow, bitsHigh) {
+ var sign = ((bitsHigh >> 31) * 2 + 1);
+ var exp = (bitsHigh >>> 20) & 0x7FF;
+ var mant = jspb.BinaryConstants.TWO_TO_32 * (bitsHigh & 0xFFFFF) + bitsLow;
+ if (exp == 0x7FF) {
+ return sign * Math.pow(2, -1074) * mant;
+ return sign * Math.pow(2, exp - 1075) *
+ (mant + jspb.BinaryConstants.TWO_TO_52);
+ * Joins two 32-bit values into an 8-character hash string.
+jspb.utils.joinHash64 = function(bitsLow, bitsHigh) {
+ var a = (bitsLow >>> 0) & 0xFF;
+ var b = (bitsLow >>> 8) & 0xFF;
+ var c = (bitsLow >>> 16) & 0xFF;
+ var d = (bitsLow >>> 24) & 0xFF;
+ var e = (bitsHigh >>> 0) & 0xFF;
+ var f = (bitsHigh >>> 8) & 0xFF;
+ var g = (bitsHigh >>> 16) & 0xFF;
+ var h = (bitsHigh >>> 24) & 0xFF;
+ * Individual digits for number->string conversion.
+ * @const {!Array.<number>}
+jspb.utils.DIGITS = [
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+];
+ * Losslessly converts a 64-bit unsigned integer in 32:32 split representation
+ * into a decimal string.
+ * @return {string} The binary number represented as a string.
+jspb.utils.joinUnsignedDecimalString = function(bitsLow, bitsHigh) {
+ // Skip the expensive conversion if the number is small enough to use the
+ // built-in conversions.
+ if (bitsHigh <= 0x1FFFFF) {
+ return '' + (jspb.BinaryConstants.TWO_TO_32 * bitsHigh + bitsLow);
+ // What this code is doing is essentially converting the input number from
+ // base-2 to base-1e7, which allows us to represent the 64-bit range with
+ // only 3 (very large) digits. Those digits are then trivial to convert to
+ // a base-10 string.
+ // The magic numbers used here are -
+ // 2^24 = 16777216 = (1,6777216) in base-1e7.
+ // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7.
+ // Split 32:32 representation into 16:24:24 representation so our
+ // intermediate digits don't overflow.
+ var low = bitsLow & 0xFFFFFF;
+ var mid = (((bitsLow >>> 24) | (bitsHigh << 8)) >>> 0) & 0xFFFFFF;
+ var high = (bitsHigh >> 16) & 0xFFFF;
+ // Assemble our three base-1e7 digits, ignoring carries. The maximum
+ // value in a digit at this step is representable as a 48-bit integer, which
+ // can be stored in a 64-bit floating point number.
+ var digitA = low + (mid * 6777216) + (high * 6710656);
+ var digitB = mid + (high * 8147497);
+ var digitC = (high * 2);
+ // Apply carries from A to B and from B to C.
+ var base = 10000000;
+ if (digitA >= base) {
+ digitB += Math.floor(digitA / base);
+ digitA %= base;
+ if (digitB >= base) {
+ digitC += Math.floor(digitB / base);
+ digitB %= base;
+ // Convert base-1e7 digits to base-10, omitting leading zeroes.
+ var table = jspb.utils.DIGITS;
+ var start = false;
+ function emit(digit) {
+ var temp = base;
+ for (var i = 0; i < 7; i++) {
+ temp /= 10;
+ var decimalDigit = ((digit / temp) % 10) >>> 0;
+ if ((decimalDigit == 0) && !start) continue;
+ start = true;
+ result += table[decimalDigit];
+ if (digitC || start) emit(digitC);
+ if (digitB || start) emit(digitB);
+ if (digitA || start) emit(digitA);
+ * Losslessly converts a 64-bit signed integer in 32:32 split representation
+jspb.utils.joinSignedDecimalString = function(bitsLow, bitsHigh) {
+ // If we're treating the input as a signed value and the high bit is set, do
+ // a manual two's complement conversion before the decimal conversion.
+ var negative = (bitsHigh & 0x80000000);
+ if (negative) {
+ var carry = (bitsLow == 0) ? 1 : 0;
+ bitsHigh = (~bitsHigh + carry) >>> 0;
+ var result = jspb.utils.joinUnsignedDecimalString(bitsLow, bitsHigh);
+ return negative ? '-' + result : result;
+ * Convert an 8-character hash string representing either a signed or unsigned
+ * 64-bit integer into its decimal representation without losing accuracy.
+ * @param {string} hash The hash string to convert.
+ * @param {boolean} signed True if we should treat the hash string as encoding
+ * a signed integer.
+jspb.utils.hash64ToDecimalString = function(hash, signed) {
+ jspb.utils.splitHash64(hash);
+ var bitsLow = jspb.utils.split64Low;
+ var bitsHigh = jspb.utils.split64High;
+ return signed ?
+ jspb.utils.joinSignedDecimalString(bitsLow, bitsHigh) :
+ jspb.utils.joinUnsignedDecimalString(bitsLow, bitsHigh);
+ * Converts an array of 8-character hash strings into their decimal
+ * representations.
+ * @param {!Array.<string>} hashes The array of hash strings to convert.
+jspb.utils.hash64ArrayToDecimalStrings = function(hashes, signed) {
+ var result = new Array(hashes.length);
+ for (var i = 0; i < hashes.length; i++) {
+ result[i] = jspb.utils.hash64ToDecimalString(hashes[i], signed);
+ * Converts an 8-character hash string into its hexadecimal representation.
+jspb.utils.hash64ToHexString = function(hash) {
+ var temp = new Array(18);
+ temp[0] = '0';
+ temp[1] = 'x';
+ for (var i = 0; i < 8; i++) {
+ var c = hash.charCodeAt(7 - i);
+ temp[i * 2 + 2] = jspb.utils.DIGITS[c >> 4];
+ temp[i * 2 + 3] = jspb.utils.DIGITS[c & 0xF];
+ var result = temp.join('');
+ * Converts a '0x<16 digits>' hex string into its hash string representation.
+ * @param {string} hex
+jspb.utils.hexStringToHash64 = function(hex) {
+ hex = hex.toLowerCase();
+ goog.asserts.assert(hex.length == 18);
+ goog.asserts.assert(hex[0] == '0');
+ goog.asserts.assert(hex[1] == 'x');
+ var hi = jspb.utils.DIGITS.indexOf(hex[i * 2 + 2]);
+ var lo = jspb.utils.DIGITS.indexOf(hex[i * 2 + 3]);
+ result = String.fromCharCode(hi * 16 + lo) + result;
+ * 64-bit integer into a Javascript number. Will lose accuracy if the result is
+ * larger than 2^52.
+ * @param {boolean} signed True if the has should be interpreted as a signed
+ * number.
+jspb.utils.hash64ToNumber = function(hash, signed) {
+ return signed ? jspb.utils.joinInt64(bitsLow, bitsHigh) :
+ jspb.utils.joinUint64(bitsLow, bitsHigh);
+ * Convert a Javascript number into an 8-character hash string. Will lose
+ * precision if the value is non-integral or greater than 2^64.
+ * @param {number} value The integer to convert.
+jspb.utils.numberToHash64 = function(value) {
+ jspb.utils.splitInt64(value);
+ return jspb.utils.joinHash64(jspb.utils.split64Low,
+ jspb.utils.split64High);
+ * Counts the number of contiguous varints in a buffer.
+ * @param {!Uint8Array} buffer The buffer to scan.
+ * @param {number} start The starting point in the buffer to scan.
+ * @param {number} end The end point in the buffer to scan.
+ * @return {number} The number of varints in the buffer.
+jspb.utils.countVarints = function(buffer, start, end) {
+ // Count how many high bits of each byte were set in the buffer.
+ var count = 0;
+ for (var i = start; i < end; i++) {
+ count += buffer[i] >> 7;
+ // The number of varints in the buffer equals the size of the buffer minus
+ // the number of non-terminal bytes in the buffer (those with the high bit
+ // set).
+ return (end - start) - count;
+ * Counts the number of contiguous varint fields with the given field number in
+ * the buffer.
+ * @param {number} field The field number to count.
+ * @return {number} The number of matching fields in the buffer.
+jspb.utils.countVarintFields = function(buffer, start, end, field) {
+ var cursor = start;
+ var tag = field * 8 + jspb.BinaryConstants.WireType.VARINT;
+ if (tag < 128) {
+ // Single-byte field tag, we can use a slightly quicker count.
+ // Skip the field tag, or exit if we find a non-matching tag.
+ if (buffer[cursor++] != tag) return count;
+ // Field tag matches, we've found a valid field.
+ count++;
+ // Skip the varint.
+ while (1) {
+ var x = buffer[cursor++];
+ if ((x & 0x80) == 0) break;
+ var temp = tag;
+ while (temp > 128) {
+ if (buffer[cursor] != ((temp & 0x7F) | 0x80)) return count;
+ cursor++;
+ temp >>= 7;
+ if (buffer[cursor++] != temp) return count;
+ return count;
+ * Counts the number of contiguous fixed32 fields with the given tag in the
+ * buffer.
+ * @param {number} tag The tag value to count.
+ * @param {number} stride The number of bytes to skip per field.
+ * @return {number} The number of fields with a matching tag in the buffer.
+jspb.utils.countFixedFields_ =
+ function(buffer, start, end, tag, stride) {
+ // Skip the value.
+ cursor += stride;
+ if (buffer[cursor++] != ((temp & 0x7F) | 0x80)) return count;
+ * Counts the number of contiguous fixed32 fields with the given field number
+ * in the buffer.
+jspb.utils.countFixed32Fields = function(buffer, start, end, field) {
+ var tag = field * 8 + jspb.BinaryConstants.WireType.FIXED32;
+ return jspb.utils.countFixedFields_(buffer, start, end, tag, 4);
+ * Counts the number of contiguous fixed64 fields with the given field number
+ * @param {number} field The field number to count
+jspb.utils.countFixed64Fields = function(buffer, start, end, field) {
+ var tag = field * 8 + jspb.BinaryConstants.WireType.FIXED64;
+ return jspb.utils.countFixedFields_(buffer, start, end, tag, 8);
+ * Counts the number of contiguous delimited fields with the given field number
+jspb.utils.countDelimitedFields = function(buffer, start, end, field) {
+ var tag = field * 8 + jspb.BinaryConstants.WireType.DELIMITED;
+ // Decode the length prefix.
+ var length = 0;
+ var shift = 1;
+ temp = buffer[cursor++];
+ length += (temp & 0x7f) * shift;
+ shift *= 128;
+ if ((temp & 0x80) == 0) break;
+ // Advance the cursor past the blob.
+ cursor += length;
+ * Clones a scalar field. Pulling this out to a helper method saves us a few
+ * bytes of generated code.
+ * @param {Array} array
+ * @return {Array}
+jspb.utils.cloneRepeatedScalarField = function(array) {
+ return array ? array.slice() : null;
+ * Clones an array of messages using the provided cloner function.
+ * @param {Array.<jspb.BinaryMessage>} messages
+ * @param {jspb.ClonerFunction} cloner
+ * @return {Array.<jspb.BinaryMessage>}
+jspb.utils.cloneRepeatedMessageField = function(messages, cloner) {
+ if (messages === null) return null;
+ for (var i = 0; i < messages.length; i++) {
+ result.push(cloner(messages[i]));
+ * Clones an array of byte blobs.
+ * @param {Array.<Uint8Array>} blobs
+ * @return {Array.<Uint8Array>}
+jspb.utils.cloneRepeatedBlobField = function(blobs) {
+ if (blobs === null) return null;
+ for (var i = 0; i < blobs.length; i++) {
+ result.push(new Uint8Array(blobs[i]));
+ * String-ify bytes for text format. Should be optimized away in non-debug.
+ * The returned string uses \xXX escapes for all values and is itself quoted.
+ * [1, 31] serializes to '"\x01\x1f"'.
+ * @param {jspb.ByteSource} byteSource The bytes to serialize.
+ * @param {boolean=} opt_stringIsRawBytes The string is interpreted as a series
+ * of raw bytes rather than base64 data.
+ * @return {string} Stringified bytes for text format.
+jspb.utils.debugBytesToTextFormat = function(byteSource,
+ opt_stringIsRawBytes) {
+ var s = '"';
+ if (byteSource) {
+ var bytes =
+ jspb.utils.byteSourceToUint8Array(byteSource, opt_stringIsRawBytes);
+ s += '\\x';
+ if (bytes[i] < 16) s += '0';
+ s += bytes[i].toString(16);
+ return s + '"';
+ * String-ify a scalar for text format. Should be optimized away in non-debug.
+ * @param {string|number|boolean} scalar The scalar to stringify.
+ * @return {string} Stringified scalar for text format.
+jspb.utils.debugScalarToTextFormat = function(scalar) {
+ if (goog.isString(scalar)) {
+ return goog.string.quote(scalar);
+ return scalar.toString();
+ * Utility function: convert a string with codepoints 0--255 inclusive to a
+ * Uint8Array. If any codepoints greater than 255 exist in the string, throws an
+ * exception.
+ * @return {!Uint8Array}
+jspb.utils.stringToByteArray_ = function(str) {
+ var arr = new Uint8Array(str.length);
+ for (var i = 0; i < str.length; i++) {
+ var codepoint = str.charCodeAt(i);
+ if (codepoint > 255) {
+ throw new Error('Conversion error: string contains codepoint ' +
+ 'outside of byte range');
+ arr[i] = codepoint;
+ return arr;
+ * Converts any type defined in jspb.ByteSource into a Uint8Array.
+ * @param {!jspb.ByteSource} data
+ * @param {boolean=} opt_stringIsRawBytes Interpret a string as a series of raw
+ * bytes (encoded as codepoints 0--255 inclusive) rather than base64 data
+ * (default behavior).
+ * @suppress {invalidCasts}
+jspb.utils.byteSourceToUint8Array = function(data, opt_stringIsRawBytes) {
+ if (data.constructor === Uint8Array) {
+ return /** @type {!Uint8Array} */(data);
+ if (data.constructor === ArrayBuffer) {
+ data = /** @type {!ArrayBuffer} */(data);
+ return /** @type {!Uint8Array} */(new Uint8Array(data));
+ if (data.constructor === Array) {
+ data = /** @type {!Array.<number>} */(data);
+ if (data.constructor === String) {
+ data = /** @type {string} */(data);
+ if (opt_stringIsRawBytes) {
+ return jspb.utils.stringToByteArray_(data);
+ return goog.crypt.base64.decodeStringToUint8Array(data);
+ goog.asserts.fail('Type not convertible to Uint8Array.');
+ return /** @type {!Uint8Array} */(new Uint8Array(0));
@@ -0,0 +1,632 @@
+ * @fileoverview Test cases for jspb's helper functions.
+function truncate(x) {
+ * Converts an 64-bit integer in split representation to a 64-bit hash string
+ * (8 bits encoded per character).
+ * @param {number} bitsLow The low 32 bits of the split 64-bit integer.
+ * @param {number} bitsHigh The high 32 bits of the split 64-bit integer.
+ * @return {string} The encoded hash string, 8 bits per character.
+function toHashString(bitsLow, bitsHigh) {
+ return String.fromCharCode((bitsLow >>> 0) & 0xFF,
+ (bitsLow >>> 8) & 0xFF,
+ (bitsLow >>> 16) & 0xFF,
+ (bitsLow >>> 24) & 0xFF,
+ (bitsHigh >>> 0) & 0xFF,
+ (bitsHigh >>> 8) & 0xFF,
+ (bitsHigh >>> 16) & 0xFF,
+ (bitsHigh >>> 24) & 0xFF);
+describe('binaryUtilsTest', function() {
+ * Tests lossless binary-to-decimal conversion.
+ it('testDecimalConversion', function() {
+ // Check some magic numbers.
+ var result =
+ jspb.utils.joinUnsignedDecimalString(0x89e80001, 0x8ac72304);
+ assertEquals('10000000000000000001', result);
+ result = jspb.utils.joinUnsignedDecimalString(0xacd05f15, 0x1b69b4b);
+ assertEquals('123456789123456789', result);
+ result = jspb.utils.joinUnsignedDecimalString(0xeb1f0ad2, 0xab54a98c);
+ assertEquals('12345678901234567890', result);
+ result = jspb.utils.joinUnsignedDecimalString(0xe3b70cb1, 0x891087b8);
+ assertEquals('9876543210987654321', result);
+ // Check limits.
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x00000000);
+ assertEquals('0', result);
+ result = jspb.utils.joinUnsignedDecimalString(0xFFFFFFFF, 0xFFFFFFFF);
+ assertEquals('18446744073709551615', result);
+ // Check each bit of the low dword.
+ for (var i = 0; i < 32; i++) {
+ var low = (1 << i) >>> 0;
+ result = jspb.utils.joinUnsignedDecimalString(low, 0);
+ assertEquals('' + Math.pow(2, i), result);
+ // Check the first 20 bits of the high dword.
+ for (var i = 0; i < 20; i++) {
+ var high = (1 << i) >>> 0;
+ result = jspb.utils.joinUnsignedDecimalString(0, high);
+ assertEquals('' + Math.pow(2, 32 + i), result);
+ // V8's internal double-to-string conversion is inaccurate for values above
+ // 2^52, even if they're representable integers - check the rest of the bits
+ // manually against the correct string representations of 2^N.
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x00100000);
+ assertEquals('4503599627370496', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x00200000);
+ assertEquals('9007199254740992', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x00400000);
+ assertEquals('18014398509481984', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x00800000);
+ assertEquals('36028797018963968', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x01000000);
+ assertEquals('72057594037927936', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x02000000);
+ assertEquals('144115188075855872', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x04000000);
+ assertEquals('288230376151711744', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x08000000);
+ assertEquals('576460752303423488', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x10000000);
+ assertEquals('1152921504606846976', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x20000000);
+ assertEquals('2305843009213693952', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x40000000);
+ assertEquals('4611686018427387904', result);
+ result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x80000000);
+ assertEquals('9223372036854775808', result);
+ * Going from hash strings to decimal strings should also be lossless.
+ it('testHashToDecimalConversion', function() {
+ var result;
+ var convert = jspb.utils.hash64ToDecimalString;
+ result = convert(toHashString(0x00000000, 0x00000000), false);
+ result = convert(toHashString(0x00000000, 0x00000000), true);
+ result = convert(toHashString(0xFFFFFFFF, 0xFFFFFFFF), false);
+ result = convert(toHashString(0xFFFFFFFF, 0xFFFFFFFF), true);
+ assertEquals('-1', result);
+ result = convert(toHashString(0x00000000, 0x80000000), false);
+ result = convert(toHashString(0x00000000, 0x80000000), true);
+ assertEquals('-9223372036854775808', result);
+ result = convert(toHashString(0xacd05f15, 0x01b69b4b), false);
+ result = convert(toHashString(~0xacd05f15 + 1, ~0x01b69b4b), true);
+ assertEquals('-123456789123456789', result);
+ // And converting arrays of hashes should work the same way.
+ result = jspb.utils.hash64ArrayToDecimalStrings([
+ toHashString(0xFFFFFFFF, 0xFFFFFFFF),
+ toHashString(0x00000000, 0x80000000),
+ toHashString(0xacd05f15, 0x01b69b4b)], false);
+ assertEquals(3, result.length);
+ assertEquals('18446744073709551615', result[0]);
+ assertEquals('9223372036854775808', result[1]);
+ assertEquals('123456789123456789', result[2]);
+ * Going from hash strings to hex strings should be lossless.
+ it('testHashToHexConversion', function() {
+ var convert = jspb.utils.hash64ToHexString;
+ result = convert(toHashString(0x00000000, 0x00000000));
+ assertEquals('0x0000000000000000', result);
+ result = convert(toHashString(0xFFFFFFFF, 0xFFFFFFFF));
+ assertEquals('0xffffffffffffffff', result);
+ result = convert(toHashString(0x12345678, 0x9ABCDEF0));
+ assertEquals('0x9abcdef012345678', result);
+ * Going from hex strings to hash strings should be lossless.
+ it('testHexToHashConversion', function() {
+ var convert = jspb.utils.hexStringToHash64;
+ result = convert('0x0000000000000000');
+ assertEquals(String.fromCharCode.apply(null,
+ [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), result);
+ result = convert('0xffffffffffffffff');
+ [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]), result);
+ // Hex string is big-endian, hash string is little-endian.
+ result = convert('0x123456789ABCDEF0');
+ [0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12]), result);
+ // Capitalization should not matter.
+ result = convert('0x0000abcdefABCDEF');
+ [0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB, 0x00, 0x00]), result);
+ * Going from numbers to hash strings should be lossless for up to 53 bits of
+ * precision.
+ it('testNumberToHashConversion', function() {
+ var convert = jspb.utils.numberToHash64;
+ result = convert(0x0000000000000);
+ assertEquals('0x0000000000000000', jspb.utils.hash64ToHexString(result));
+ result = convert(0xFFFFFFFFFFFFF);
+ assertEquals('0x000fffffffffffff', jspb.utils.hash64ToHexString(result));
+ result = convert(0x123456789ABCD);
+ assertEquals('0x000123456789abcd', jspb.utils.hash64ToHexString(result));
+ result = convert(0xDCBA987654321);
+ assertEquals('0x000dcba987654321', jspb.utils.hash64ToHexString(result));
+ // 53 bits of precision should not be truncated.
+ result = convert(0x10000000000001);
+ assertEquals('0x0010000000000001', jspb.utils.hash64ToHexString(result));
+ // 54 bits of precision should be truncated.
+ result = convert(0x20000000000001);
+ assertNotEquals(
+ '0x0020000000000001', jspb.utils.hash64ToHexString(result));
+ * Sanity check the behavior of Javascript's strings when doing funny things
+ * with unicode characters.
+ it('sanityCheckUnicodeStrings', function() {
+ var strings = new Array(65536);
+ // All possible unsigned 16-bit values should be storable in a string, they
+ // shouldn't do weird things with the length of the string, and they should
+ // come back out of the string unchanged.
+ for (var i = 0; i < 65536; i++) {
+ strings[i] = 'a' + String.fromCharCode(i) + 'a';
+ if (3 != strings[i].length) throw 'fail!';
+ if (i != strings[i].charCodeAt(1)) throw 'fail!';
+ // Each unicode character should compare equal to itself and not equal to a
+ // different unicode character.
+ if (strings[i] != strings[i]) throw 'fail!';
+ if (strings[i] == strings[(i + 1) % 65536]) throw 'fail!';
+ * Tests conversion from 32-bit floating point numbers to split64 numbers.
+ it('testFloat32ToSplit64', function() {
+ var f32_eps = jspb.BinaryConstants.FLOAT32_EPS;
+ var f32_min = jspb.BinaryConstants.FLOAT32_MIN;
+ var f32_max = jspb.BinaryConstants.FLOAT32_MAX;
+ // NaN.
+ jspb.utils.splitFloat32(NaN);
+ if (!isNaN(jspb.utils.joinFloat32(jspb.utils.split64Low,
+ jspb.utils.split64High))) {
+ throw 'fail!';
+ * @param {number=} opt_bits
+ function test(x, opt_bits) {
+ jspb.utils.splitFloat32(x);
+ if (goog.isDef(opt_bits)) {
+ if (opt_bits != jspb.utils.split64Low) throw 'fail!';
+ if (truncate(x) != jspb.utils.joinFloat32(jspb.utils.split64Low,
+ jspb.utils.split64High)) {
+ // Positive and negative infinity.
+ test(Infinity, 0x7f800000);
+ test(-Infinity, 0xff800000);
+ // Positive and negative zero.
+ test(0, 0x00000000);
+ test(-0, 0x80000000);
+ // Positive and negative epsilon.
+ test(f32_eps, 0x00000001);
+ test(-f32_eps, 0x80000001);
+ // Positive and negative min.
+ test(f32_min, 0x00800000);
+ test(-f32_min, 0x80800000);
+ // Positive and negative max.
+ test(f32_max, 0x7F7FFFFF);
+ test(-f32_max, 0xFF7FFFFF);
+ // Various positive values.
+ var cursor = f32_eps * 10;
+ while (cursor != Infinity) {
+ test(cursor);
+ cursor *= 1.1;
+ // Various negative values.
+ cursor = -f32_eps * 10;
+ while (cursor != -Infinity) {
+ * Tests conversion from 64-bit floating point numbers to split64 numbers.
+ it('testFloat64ToSplit64', function() {
+ var f64_eps = jspb.BinaryConstants.FLOAT64_EPS;
+ var f64_min = jspb.BinaryConstants.FLOAT64_MIN;
+ var f64_max = jspb.BinaryConstants.FLOAT64_MAX;
+ jspb.utils.splitFloat64(NaN);
+ if (!isNaN(jspb.utils.joinFloat64(jspb.utils.split64Low,
+ * @param {number=} opt_highBits
+ * @param {number=} opt_lowBits
+ function test(x, opt_highBits, opt_lowBits) {
+ jspb.utils.splitFloat64(x);
+ if (goog.isDef(opt_highBits)) {
+ if (opt_highBits != jspb.utils.split64High) throw 'fail!';
+ if (goog.isDef(opt_lowBits)) {
+ if (opt_lowBits != jspb.utils.split64Low) throw 'fail!';
+ if (x != jspb.utils.joinFloat64(jspb.utils.split64Low,
+ test(Infinity, 0x7ff00000, 0x00000000);
+ test(-Infinity, 0xfff00000, 0x00000000);
+ test(0, 0x00000000, 0x00000000);
+ test(-0, 0x80000000, 0x00000000);
+ test(f64_eps, 0x00000000, 0x00000001);
+ test(-f64_eps, 0x80000000, 0x00000001);
+ test(f64_min, 0x00100000, 0x00000000);
+ test(-f64_min, 0x80100000, 0x00000000);
+ test(f64_max, 0x7FEFFFFF, 0xFFFFFFFF);
+ test(-f64_max, 0xFFEFFFFF, 0xFFFFFFFF);
+ var cursor = f64_eps * 10;
+ cursor = -f64_eps * 10;
+ * Tests counting packed varints.
+ it('testCountVarints', function() {
+ for (var i = 1; i < 1000000000; i *= 1.1) {
+ writer.rawWriteVarint(Math.floor(i));
+ var buffer = new Uint8Array(writer.getResultBuffer());
+ assertEquals(count, jspb.utils.countVarints(buffer, 0, buffer.length));
+ * Tests counting matching varint fields.
+ it('testCountVarintFields', function() {
+ writer.writeUint64(1, Math.floor(i));
+ writer.writeString(2, 'terminator');
+ assertEquals(count,
+ jspb.utils.countVarintFields(buffer, 0, buffer.length, 1));
+ writer = new jspb.BinaryWriter();
+ count = 0;
+ writer.writeUint64(123456789, Math.floor(i));
+ buffer = new Uint8Array(writer.getResultBuffer());
+ jspb.utils.countVarintFields(buffer, 0, buffer.length, 123456789));
+ * Tests counting matching fixed32 fields.
+ it('testCountFixed32Fields', function() {
+ writer.writeFixed32(1, Math.floor(i));
+ jspb.utils.countFixed32Fields(buffer, 0, buffer.length, 1));
+ writer.writeFixed32(123456789, Math.floor(i));
+ jspb.utils.countFixed32Fields(buffer, 0, buffer.length, 123456789));
+ * Tests counting matching fixed64 fields.
+ it('testCountFixed64Fields', function() {
+ writer.writeDouble(1, i);
+ jspb.utils.countFixed64Fields(buffer, 0, buffer.length, 1));
+ writer.writeDouble(123456789, i);
+ jspb.utils.countFixed64Fields(buffer, 0, buffer.length, 123456789));
+ * Tests counting matching delimited fields.
+ it('testCountDelimitedFields', function() {
+ for (var i = 1; i < 1000; i *= 1.1) {
+ writer.writeBytes(1, [Math.floor(i)]);
+ jspb.utils.countDelimitedFields(buffer, 0, buffer.length, 1));
+ writer.writeBytes(123456789, [Math.floor(i)]);
+ jspb.utils.countDelimitedFields(buffer, 0, buffer.length, 123456789));
+ * Tests byte format for debug strings.
+ it('testDebugBytesToTextFormat', function() {
+ assertEquals('""', jspb.utils.debugBytesToTextFormat(null));
+ assertEquals('"\\x00\\x10\\xff"',
+ jspb.utils.debugBytesToTextFormat([0, 16, 255]));
+ * Tests converting byte blob sources into byte blobs.
+ it('testByteSourceToUint8Array', function() {
+ var convert = jspb.utils.byteSourceToUint8Array;
+ var sourceData = [];
+ for (var i = 0; i < 256; i++) {
+ sourceData.push(i);
+ var sourceBytes = new Uint8Array(sourceData);
+ var sourceBuffer = sourceBytes.buffer;
+ var sourceBase64 = goog.crypt.base64.encodeByteArray(sourceData);
+ var sourceString = String.fromCharCode.apply(null, sourceData);
+ function check(result) {
+ assertEquals(Uint8Array, result.constructor);
+ assertEquals(sourceData.length, result.length);
+ for (var i = 0; i < result.length; i++) {
+ assertEquals(sourceData[i], result[i]);
+ // Converting Uint8Arrays into Uint8Arrays should be a no-op.
+ assertEquals(sourceBytes, convert(sourceBytes));
+ // Converting Array.<numbers> into Uint8Arrays should work.
+ check(convert(sourceData));
+ // Converting ArrayBuffers into Uint8Arrays should work.
+ check(convert(sourceBuffer));
+ // Converting base64-encoded strings into Uint8Arrays should work.
+ check(convert(sourceBase64));
+ // Converting binary-data strings into Uint8Arrays should work.
+ check(convert(sourceString, /* opt_stringIsRawBytes = */ true));
@@ -0,0 +1,2124 @@
+ * @fileoverview This file contains utilities for encoding Javascript objects
+ * into binary, wire-format protocol buffers (in the form of Uint8Arrays) that
+ * a server can consume directly.
+ * jspb's BinaryWriter class defines methods for efficiently encoding
+ * Javascript objects into binary, wire-format protocol buffers and supports
+ * all the fundamental field types used in protocol buffers.
+ * Major caveat 1 - Users of this library _must_ keep their Javascript proto
+ * Major caveat 2 - Javascript is unable to accurately represent integers
+ * larger than 2^53 due to its use of a double-precision floating point format
+ * for all numbers. BinaryWriter does not make any special effort to preserve
+ * precision for values above this limit - if you need to pass 64-bit integers
+ * (hash codes, for example) between the client and server without precision
+ * loss, do _not_ use this library.
+ * Major caveat 3 - This class uses typed arrays and must not be used on older
+ * browsers that do not support them.
+goog.provide('jspb.BinaryWriter');
+ * BinaryWriter implements encoders for all the wire types specified in
+jspb.BinaryWriter = function() {
+ * Blocks of serialized data that will be concatenated once all messages have
+ * been written.
+ * @private {!Array<!Uint8Array|!Array<number>>}
+ this.blocks_ = [];
+ * Total number of bytes in the blocks_ array. Does _not_ include the temp
+ this.totalLength_ = 0;
+ * Temporary buffer holding a message that we're still serializing. When we
+ * get to a stopping point (either the start of a new submessage, or when we
+ * need to append a raw Uint8Array), the temp buffer will be added to the
+ * block array above and a new temp buffer will be created.
+ * @private {!Array.<number>}
+ this.temp_ = [];
+ * A stack of bookmarks containing the parent blocks for each message started
+ * via beginSubMessage(), needed as bookkeeping for endSubMessage().
+ * TODO(aappleby): Deprecated, users should be calling writeMessage().
+ * @private {!Array.<!jspb.BinaryWriter.Bookmark_>}
+ this.bookmarks_ = [];
+ * @typedef {{block: !Array.<number>, length: number}}
+jspb.BinaryWriter.Bookmark_;
+ * Saves the current temp buffer in the blocks_ array and starts a new one.
+ * @return {!Array.<number>} Returns a reference to the old temp buffer.
+jspb.BinaryWriter.prototype.saveTempBuffer_ = function() {
+ var oldTemp = this.temp_;
+ this.blocks_.push(this.temp_);
+ this.totalLength_ += this.temp_.length;
+ return oldTemp;
+ * Append a typed array of bytes onto the buffer.
+ * @param {!Uint8Array} arr The byte array to append.
+jspb.BinaryWriter.prototype.appendUint8Array_ = function(arr) {
+ if (this.temp_.length) {
+ this.saveTempBuffer_();
+ this.blocks_.push(arr);
+ this.totalLength_ += arr.length;
+ * Append an untyped array of bytes onto the buffer.
+ * @param {!Array.<number>} arr The byte array to append.
+jspb.BinaryWriter.prototype.appendArray_ = function(arr) {
+ this.temp_ = arr;
+ * Begins a length-delimited section by writing the field header to the current
+ * temp buffer and then saving it in the block array. Returns the saved block,
+ * which we will append the length to in endDelimited_ below.
+ * @return {!jspb.BinaryWriter.Bookmark_}
+jspb.BinaryWriter.prototype.beginDelimited_ = function(field) {
+ this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED);
+ return {block: this.saveTempBuffer_(), length: this.totalLength_};
+ * Ends a length-delimited block by encoding the _change_ in length of the
+ * buffer to the parent block and adds the number of bytes needed to encode
+ * that length to the total byte length. Note that 'parentLength' _must_ be the
+ * total length _after_ the field header was written in beginDelimited_ above.
+ * @param {!jspb.BinaryWriter.Bookmark_} bookmark
+jspb.BinaryWriter.prototype.endDelimited_ = function(bookmark) {
+ var messageLength = this.totalLength_ + this.temp_.length - bookmark.length;
+ goog.asserts.assert(messageLength >= 0);
+ var bytes = 1;
+ while (messageLength > 127) {
+ bookmark.block.push((messageLength & 0x7f) | 0x80);
+ messageLength = messageLength >>> 7;
+ bytes++;
+ bookmark.block.push(messageLength);
+ this.totalLength_ += bytes;
+ * Resets the writer, throwing away any accumulated buffers.
+jspb.BinaryWriter.prototype.reset = function() {
+ * Converts the encoded data into a Uint8Array.
+jspb.BinaryWriter.prototype.getResultBuffer = function() {
+ goog.asserts.assert(this.bookmarks_.length == 0);
+ var flat = new Uint8Array(this.totalLength_ + this.temp_.length);
+ var blocks = this.blocks_;
+ var blockCount = blocks.length;
+ var offset = 0;
+ for (var i = 0; i < blockCount; i++) {
+ var block = blocks[i];
+ flat.set(block, offset);
+ offset += block.length;
+ flat.set(this.temp_, offset);
+ offset += this.temp_.length;
+ // Post condition: `flattened` must have had every byte written.
+ goog.asserts.assert(offset == flat.length);
+ // Replace our block list with the flattened block, which lets GC reclaim
+ // the temp blocks sooner.
+ this.blocks_ = [flat];
+ return flat;
+ * Converts the encoded data into a bas64-encoded string.
+jspb.BinaryWriter.prototype.getResultBase64String = function() {
+ return goog.crypt.base64.encodeByteArray(this.getResultBuffer());
+ * Begins a new sub-message. The client must call endSubMessage() when they're
+ * done.
+ * TODO(aappleby): Deprecated. Move callers to writeMessage().
+ * @param {number} field The field number of the sub-message.
+jspb.BinaryWriter.prototype.beginSubMessage = function(field) {
+ this.bookmarks_.push(this.beginDelimited_(field));
+ * Finishes a sub-message and packs it into the parent messages' buffer.
+jspb.BinaryWriter.prototype.endSubMessage = function() {
+ goog.asserts.assert(this.bookmarks_.length >= 0);
+ this.endDelimited_(this.bookmarks_.pop());
+ * Encodes a 32-bit unsigned integer into its wire-format varint representation
+ * and stores it in the buffer.
+jspb.BinaryWriter.prototype.rawWriteUnsignedVarint32 = function(value) {
+ goog.asserts.assert(value == Math.floor(value));
+ while (value > 127) {
+ this.temp_.push((value & 0x7f) | 0x80);
+ this.temp_.push(value);
+ * Encodes a 32-bit signed integer into its wire-format varint representation
+jspb.BinaryWriter.prototype.rawWriteSignedVarint32 = function(value) {
+ if (value >= 0) {
+ this.rawWriteUnsignedVarint32(value);
+ // Write nine bytes with a _signed_ right shift so we preserve the sign bit.
+ for (var i = 0; i < 9; i++) {
+ value = value >> 7;
+ // The above loop writes out 63 bits, so the last byte is always the sign bit
+ // which is always set for negative numbers.
+ this.temp_.push(1);
+ * Encodes an unsigned 64-bit integer in 32:32 split representation into its
+ * wire-format varint representation and stores it in the buffer.
+ * @param {number} lowBits The low 32 bits of the int.
+ * @param {number} highBits The high 32 bits of the int.
+jspb.BinaryWriter.prototype.rawWriteSplitVarint =
+ function(lowBits, highBits) {
+ // Break the binary representation into chunks of 7 bits, set the 8th bit
+ // in each chunk if it's not the final chunk, and append to the result.
+ while (highBits > 0 || lowBits > 127) {
+ this.temp_.push((lowBits & 0x7f) | 0x80);
+ lowBits = ((lowBits >>> 7) | (highBits << 25)) >>> 0;
+ highBits = highBits >>> 7;
+ this.temp_.push(lowBits);
+ * Encodes a JavaScript integer into its wire-format varint representation and
+ * stores it in the buffer. Due to the way the varint encoding works this
+ * behaves correctly for both signed and unsigned integers, though integers
+ * that are not representable in 64 bits will still be truncated.
+jspb.BinaryWriter.prototype.rawWriteVarint = function(value) {
+ this.rawWriteSplitVarint(jspb.utils.split64Low,
+ * Encodes a jspb.arith.{Int64,UInt64} instance into its wire-format
+ * varint representation and stores it in the buffer. Due to the way the varint
+ * encoding works this behaves correctly for both signed and unsigned integers,
+ * though integers that are not representable in 64 bits will still be
+ * truncated.
+ * @param {jspb.arith.Int64|jspb.arith.UInt64} value
+jspb.BinaryWriter.prototype.rawWriteVarintFromNum = function(value) {
+ this.rawWriteSplitVarint(value.lo, value.hi);
+ * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint
+ * representation and stores it in the buffer.
+jspb.BinaryWriter.prototype.rawWriteZigzagVarint32 = function(value) {
+ this.rawWriteUnsignedVarint32(((value << 1) ^ (value >> 31)) >>> 0);
+ * representation and stores it in the buffer. Integers not representable in 64
+ * bits will be truncated.
+jspb.BinaryWriter.prototype.rawWriteZigzagVarint = function(value) {
+ jspb.utils.splitZigzag64(value);
+ * Writes a raw 8-bit unsigned integer to the buffer. Numbers outside the range
+ * [0,2^8) will be truncated.
+ * @param {number} value The value to write.
+jspb.BinaryWriter.prototype.rawWriteUint8 = function(value) {
+ goog.asserts.assert((value >= 0) && (value < 256));
+ this.temp_.push((value >>> 0) & 0xFF);
+ * Writes a raw 16-bit unsigned integer to the buffer. Numbers outside the
+ * range [0,2^16) will be truncated.
+jspb.BinaryWriter.prototype.rawWriteUint16 = function(value) {
+ goog.asserts.assert((value >= 0) && (value < 65536));
+ this.temp_.push((value >>> 8) & 0xFF);
+ * Writes a raw 32-bit unsigned integer to the buffer. Numbers outside the
+ * range [0,2^32) will be truncated.
+jspb.BinaryWriter.prototype.rawWriteUint32 = function(value) {
+ goog.asserts.assert((value >= 0) &&
+ (value < jspb.BinaryConstants.TWO_TO_32));
+ this.temp_.push((value >>> 16) & 0xFF);
+ this.temp_.push((value >>> 24) & 0xFF);
+ * Writes a raw 64-bit unsigned integer to the buffer. Numbers outside the
+ * range [0,2^64) will be truncated.
+jspb.BinaryWriter.prototype.rawWriteUint64 = function(value) {
+ (value < jspb.BinaryConstants.TWO_TO_64));
+ this.rawWriteUint32(jspb.utils.split64Low);
+ this.rawWriteUint32(jspb.utils.split64High);
+ * Writes a raw 8-bit integer to the buffer. Numbers outside the range
+ * [-2^7,2^7) will be truncated.
+jspb.BinaryWriter.prototype.rawWriteInt8 = function(value) {
+ goog.asserts.assert((value >= -128) && (value < 128));
+ * Writes a raw 16-bit integer to the buffer. Numbers outside the range
+ * [-2^15,2^15) will be truncated.
+jspb.BinaryWriter.prototype.rawWriteInt16 = function(value) {
+ goog.asserts.assert((value >= -32768) && (value < 32768));
+ * Writes a raw 32-bit integer to the buffer. Numbers outside the range
+ * [-2^31,2^31) will be truncated.
+jspb.BinaryWriter.prototype.rawWriteInt32 = function(value) {
+ goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) &&
+ (value < jspb.BinaryConstants.TWO_TO_31));
+ * Writes a raw 64-bit integer to the buffer. Numbers outside the range
+ * [-2^63,2^63) will be truncated.
+jspb.BinaryWriter.prototype.rawWriteInt64 = function(value) {
+ goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) &&
+ (value < jspb.BinaryConstants.TWO_TO_63));
+ * Writes a raw single-precision floating point value to the buffer. Numbers
+ * requiring more than 32 bits of precision will be truncated.
+jspb.BinaryWriter.prototype.rawWriteFloat = function(value) {
+ jspb.utils.splitFloat32(value);
+ * Writes a raw double-precision floating point value to the buffer. As this is
+ * the native format used by JavaScript, no precision will be lost.
+jspb.BinaryWriter.prototype.rawWriteDouble = function(value) {
+ jspb.utils.splitFloat64(value);
+ * Writes a raw boolean value to the buffer as a varint.
+ * @param {boolean} value The value to write.
+jspb.BinaryWriter.prototype.rawWriteBool = function(value) {
+ goog.asserts.assert(goog.isBoolean(value));
+ this.temp_.push(~~value);
+ * Writes an raw enum value to the buffer as a varint.
+jspb.BinaryWriter.prototype.rawWriteEnum = function(value) {
+ this.rawWriteSignedVarint32(value);
+ * Writes a raw string value to the buffer.
+ * @param {string} string The string to write.
+jspb.BinaryWriter.prototype.rawWriteUtf8String = function(string) {
+ for (var i = 0; i < string.length; i++) {
+ this.temp_.push(string.charCodeAt(i));
+ * Writes an arbitrary raw byte array to the buffer.
+ * @param {!Uint8Array} bytes The array of bytes to write.
+jspb.BinaryWriter.prototype.rawWriteBytes = function(bytes) {
+ this.appendUint8Array_(bytes);
+ * @param {number} start The start of the range to write.
+ * @param {number} end The end of the range to write.
+jspb.BinaryWriter.prototype.rawWriteByteRange = function(bytes, start, end) {
+ this.appendUint8Array_(bytes.subarray(start, end));
+ * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the
+ * buffer as a varint.
+ * @param {string} hash The hash to write.
+jspb.BinaryWriter.prototype.rawWriteVarintHash64 = function(hash) {
+ * buffer as a fixed64.
+jspb.BinaryWriter.prototype.rawWriteFixedHash64 = function(hash) {
+ * Encodes a (field number, wire type) tuple into a wire-format field header
+ * and stores it in the buffer as a varint.
+ * @param {number} field The field number.
+ * @param {number} wireType The wire-type of the field, as specified in the
+ * protocol buffer documentation.
+jspb.BinaryWriter.prototype.rawWriteFieldHeader_ =
+ function(field, wireType) {
+ goog.asserts.assert(field >= 1 && field == Math.floor(field));
+ var x = field * 8 + wireType;
+ this.rawWriteUnsignedVarint32(x);
+ * Writes a field of any valid scalar type to the binary stream.
+ * @param {jspb.AnyFieldType} value
+jspb.BinaryWriter.prototype.writeAny = function(fieldType, field, value) {
+ this.writeDouble(field, /** @type {number} */(value));
+ this.writeFloat(field, /** @type {number} */(value));
+ this.writeInt64(field, /** @type {number} */(value));
+ this.writeUint64(field, /** @type {number} */(value));
+ this.writeInt32(field, /** @type {number} */(value));
+ this.writeFixed64(field, /** @type {number} */(value));
+ this.writeFixed32(field, /** @type {number} */(value));
+ this.writeBool(field, /** @type {boolean} */(value));
+ this.writeString(field, /** @type {string} */(value));
+ goog.asserts.fail('Group field type not supported in writeAny()');
+ goog.asserts.fail('Message field type not supported in writeAny()');
+ this.writeBytes(field, /** @type {?Uint8Array} */(value));
+ this.writeUint32(field, /** @type {number} */(value));
+ this.writeEnum(field, /** @type {number} */(value));
+ this.writeSfixed32(field, /** @type {number} */(value));
+ this.writeSfixed64(field, /** @type {number} */(value));
+ this.writeSint32(field, /** @type {number} */(value));
+ this.writeSint64(field, /** @type {number} */(value));
+ this.writeFixedHash64(field, /** @type {string} */(value));
+ this.writeVarintHash64(field, /** @type {string} */(value));
+ goog.asserts.fail('Invalid field type in writeAny()');
+ * Writes a varint field to the buffer without range checking.
+ * @param {number?} value The value to write.
+jspb.BinaryWriter.prototype.writeUnsignedVarint32_ = function(field, value) {
+ if (value == null) return;
+ this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT);
+jspb.BinaryWriter.prototype.writeSignedVarint32_ = function(field, value) {
+jspb.BinaryWriter.prototype.writeVarint_ = function(field, value) {
+ this.rawWriteVarint(value);
+ * Writes a zigzag varint field to the buffer without range checking.
+jspb.BinaryWriter.prototype.writeZigzagVarint32_ = function(field, value) {
+ this.rawWriteZigzagVarint32(value);
+jspb.BinaryWriter.prototype.writeZigzagVarint_ = function(field, value) {
+ this.rawWriteZigzagVarint(value);
+ * Writes an int32 field to the buffer. Numbers outside the range [-2^31,2^31)
+ * will be truncated.
+jspb.BinaryWriter.prototype.writeInt32 = function(field, value) {
+ this.writeSignedVarint32_(field, value);
+ * Writes an int32 field represented as a string to the buffer. Numbers outside
+ * the range [-2^31,2^31) will be truncated.
+ * @param {string?} value The value to write.
+jspb.BinaryWriter.prototype.writeInt32String = function(field, value) {
+ var intValue = /** {number} */ parseInt(value, 10);
+ goog.asserts.assert((intValue >= -jspb.BinaryConstants.TWO_TO_31) &&
+ (intValue < jspb.BinaryConstants.TWO_TO_31));
+ this.writeSignedVarint32_(field, intValue);
+ * Writes an int64 field to the buffer. Numbers outside the range [-2^63,2^63)
+jspb.BinaryWriter.prototype.writeInt64 = function(field, value) {
+ this.writeVarint_(field, value);
+ * Writes a int64 field (with value as a string) to the buffer.
+jspb.BinaryWriter.prototype.writeInt64String = function(field, value) {
+ var num = jspb.arith.Int64.fromString(value);
+ this.rawWriteVarintFromNum(num);
+ * Writes a uint32 field to the buffer. Numbers outside the range [0,2^32)
+jspb.BinaryWriter.prototype.writeUint32 = function(field, value) {
+ this.writeUnsignedVarint32_(field, value);
+ * Writes a uint32 field represented as a string to the buffer. Numbers outside
+ * the range [0,2^32) will be truncated.
+jspb.BinaryWriter.prototype.writeUint32String = function(field, value) {
+ goog.asserts.assert((intValue >= 0) &&
+ (intValue < jspb.BinaryConstants.TWO_TO_32));
+ this.writeUnsignedVarint32_(field, intValue);
+ * Writes a uint64 field to the buffer. Numbers outside the range [0,2^64)
+jspb.BinaryWriter.prototype.writeUint64 = function(field, value) {
+ * Writes a uint64 field (with value as a string) to the buffer.
+jspb.BinaryWriter.prototype.writeUint64String = function(field, value) {
+ var num = jspb.arith.UInt64.fromString(value);
+ * Writes a sint32 field to the buffer. Numbers outside the range [-2^31,2^31)
+jspb.BinaryWriter.prototype.writeSint32 = function(field, value) {
+ this.writeZigzagVarint32_(field, value);
+ * Writes a sint64 field to the buffer. Numbers outside the range [-2^63,2^63)
+jspb.BinaryWriter.prototype.writeSint64 = function(field, value) {
+ this.writeZigzagVarint_(field, value);
+ * Writes a fixed32 field to the buffer. Numbers outside the range [0,2^32)
+jspb.BinaryWriter.prototype.writeFixed32 = function(field, value) {
+ this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED32);
+ this.rawWriteUint32(value);
+ * Writes a fixed64 field to the buffer. Numbers outside the range [0,2^64)
+jspb.BinaryWriter.prototype.writeFixed64 = function(field, value) {
+ this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64);
+ this.rawWriteUint64(value);
+ * Writes a sfixed32 field to the buffer. Numbers outside the range
+jspb.BinaryWriter.prototype.writeSfixed32 = function(field, value) {
+ this.rawWriteInt32(value);
+ * Writes a sfixed64 field to the buffer. Numbers outside the range
+jspb.BinaryWriter.prototype.writeSfixed64 = function(field, value) {
+ this.rawWriteInt64(value);
+ * Writes a single-precision floating point field to the buffer. Numbers
+jspb.BinaryWriter.prototype.writeFloat = function(field, value) {
+ this.rawWriteFloat(value);
+ * Writes a double-precision floating point field to the buffer. As this is the
+ * native format used by JavaScript, no precision will be lost.
+jspb.BinaryWriter.prototype.writeDouble = function(field, value) {
+ this.rawWriteDouble(value);
+ * Writes a boolean field to the buffer.
+ * @param {boolean?} value The value to write.
+jspb.BinaryWriter.prototype.writeBool = function(field, value) {
+ * Writes an enum field to the buffer.
+jspb.BinaryWriter.prototype.writeEnum = function(field, value) {
+ * Writes a string field to the buffer.
+ * @param {string?} value The string to write.
+jspb.BinaryWriter.prototype.writeString = function(field, value) {
+ // Conversion loop swiped from goog.crypt.stringToUtf8ByteArray. Note that
+ // 'bytes' will be at least as long as 'value', but could be longer if we
+ // need to unpack unicode characters.
+ for (var i = 0; i < value.length; i++) {
+ var c = value.charCodeAt(i);
+ if (c < 128) {
+ bytes.push(c);
+ } else if (c < 2048) {
+ bytes.push((c >> 6) | 192);
+ bytes.push((c & 63) | 128);
+ bytes.push((c >> 12) | 224);
+ bytes.push(((c >> 6) & 63) | 128);
+ this.rawWriteUnsignedVarint32(bytes.length);
+ this.appendArray_(bytes);
+ * Writes an arbitrary byte field to the buffer. Note - to match the behavior
+ * of the C++ implementation, empty byte arrays _are_ serialized.
+ * If 'value' is null, this method will try and copy the pre-serialized value
+ * in 'opt_buffer' if present.
+ * @param {jspb.ByteSource} value The array of bytes to write.
+ * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values.
+ * @param {?number=} opt_start The starting point in the above buffer.
+ * @param {?number=} opt_end The ending point in the above buffer.
+ * @param {boolean=} opt_stringIsRawBytes If `value` is a string, interpret it
+ * as a series of raw bytes (codepoints 0--255 inclusive) rather than base64
+ * data.
+jspb.BinaryWriter.prototype.writeBytes =
+ function(field, value, opt_buffer, opt_start, opt_end,
+ if (value != null) {
+ this.rawWriteFieldHeader_(field,
+ jspb.BinaryConstants.WireType.DELIMITED);
+ this.rawWriteUnsignedVarint32(value.length);
+ this.rawWriteBytes(
+ jspb.utils.byteSourceToUint8Array(value, opt_stringIsRawBytes));
+ } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) {
+ this.rawWriteByteRange(opt_buffer, opt_start, opt_end);
+ * Writes an arbitrary byte field to the buffer, with `opt_stringIsRawBytes`
+ * flag implicitly true.
+jspb.BinaryWriter.prototype.writeBytesRawString = function(field, value) {
+ this.writeBytes(field, value, null, null, null, true);
+ * Writes a message to the buffer.
+ * @template MessageType
+ * @param {?MessageType} value The message to write.
+ * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value
+ * to write and the writer to write it with.
+jspb.BinaryWriter.prototype.writeMessage =
+ function(field, value, writerCallback, opt_buffer, opt_start, opt_end) {
+ if (value !== null) {
+ var bookmark = this.beginDelimited_(field);
+ writerCallback(value, this);
+ this.endDelimited_(bookmark);
+ } else if (opt_buffer && (opt_start != null) && (opt_end != null)) {
+ * Writes a group message to the buffer.
+ * @param {?MessageType} value The message to write, wrapped with START_GROUP /
+ * END_GROUP tags. Will be a no-op if 'value' is null.
+jspb.BinaryWriter.prototype.writeGroup =
+ function(field, value, writerCallback) {
+ if (value) {
+ this.rawWriteFieldHeader_(
+ field, jspb.BinaryConstants.WireType.START_GROUP);
+ field, jspb.BinaryConstants.WireType.END_GROUP);
+ * Writes a 64-bit hash string field (8 characters @ 8 bits of data each) to
+ * @param {string?} value The hash string.
+jspb.BinaryWriter.prototype.writeFixedHash64 = function(field, value) {
+ goog.asserts.assert(value.length == 8);
+ this.rawWriteFixedHash64(value);
+jspb.BinaryWriter.prototype.writeVarintHash64 = function(field, value) {
+ this.rawWriteVarintHash64(value);
+ * Writes an array of numbers to the buffer as a repeated varint field.
+ * @param {?Array.<number>} value The array of ints to write.
+jspb.BinaryWriter.prototype.writeRepeatedUnsignedVarint32_ =
+ function(field, value) {
+ this.writeUnsignedVarint32_(field, value[i]);
+jspb.BinaryWriter.prototype.writeRepeatedSignedVarint32_ =
+ this.writeSignedVarint32_(field, value[i]);
+jspb.BinaryWriter.prototype.writeRepeatedVarint_ = function(field, value) {
+ this.writeVarint_(field, value[i]);
+ * Writes an array of numbers to the buffer as a repeated zigzag field.
+jspb.BinaryWriter.prototype.writeRepeatedZigzag32_ = function(field, value) {
+ this.writeZigzagVarint32_(field, value[i]);
+jspb.BinaryWriter.prototype.writeRepeatedZigzag_ = function(field, value) {
+ this.writeZigzagVarint_(field, value[i]);
+ * Writes an array of numbers to the buffer as a repeated 32-bit int field.
+jspb.BinaryWriter.prototype.writeRepeatedInt32 =
+ jspb.BinaryWriter.prototype.writeRepeatedSignedVarint32_;
+ * Writes an array of numbers formatted as strings to the buffer as a repeated
+ * 32-bit int field.
+ * @param {?Array.<string>} value The array of ints to write.
+jspb.BinaryWriter.prototype.writeRepeatedInt32String =
+ this.writeInt32String(field, value[i]);
+ * Writes an array of numbers to the buffer as a repeated 64-bit int field.
+jspb.BinaryWriter.prototype.writeRepeatedInt64 =
+ jspb.BinaryWriter.prototype.writeRepeatedVarint_;
+ * 64-bit int field.
+jspb.BinaryWriter.prototype.writeRepeatedInt64String =
+ this.writeInt64String(field, value[i]);
+ * Writes an array numbers to the buffer as a repeated unsigned 32-bit int
+ * field.
+jspb.BinaryWriter.prototype.writeRepeatedUint32 =
+ jspb.BinaryWriter.prototype.writeRepeatedUnsignedVarint32_;
+ * unsigned 32-bit int field.
+jspb.BinaryWriter.prototype.writeRepeatedUint32String =
+ this.writeUint32String(field, value[i]);
+ * Writes an array numbers to the buffer as a repeated unsigned 64-bit int
+jspb.BinaryWriter.prototype.writeRepeatedUint64 =
+ * unsigned 64-bit int field.
+jspb.BinaryWriter.prototype.writeRepeatedUint64String =
+ this.writeUint64String(field, value[i]);
+ * Writes an array numbers to the buffer as a repeated signed 32-bit int field.
+jspb.BinaryWriter.prototype.writeRepeatedSint32 =
+ jspb.BinaryWriter.prototype.writeRepeatedZigzag32_;
+ * Writes an array numbers to the buffer as a repeated signed 64-bit int field.
+jspb.BinaryWriter.prototype.writeRepeatedSint64 =
+ jspb.BinaryWriter.prototype.writeRepeatedZigzag_;
+ * Writes an array of numbers to the buffer as a repeated fixed32 field. This
+ * works for both signed and unsigned fixed32s.
+jspb.BinaryWriter.prototype.writeRepeatedFixed32 = function(field, value) {
+ this.writeFixed32(field, value[i]);
+ * Writes an array of numbers to the buffer as a repeated fixed64 field. This
+ * works for both signed and unsigned fixed64s.
+jspb.BinaryWriter.prototype.writeRepeatedFixed64 = function(field, value) {
+ this.writeFixed64(field, value[i]);
+ * Writes an array of numbers to the buffer as a repeated sfixed32 field.
+jspb.BinaryWriter.prototype.writeRepeatedSfixed32 = function(field, value) {
+ this.writeSfixed32(field, value[i]);
+ * Writes an array of numbers to the buffer as a repeated sfixed64 field.
+jspb.BinaryWriter.prototype.writeRepeatedSfixed64 = function(field, value) {
+ this.writeSfixed64(field, value[i]);
+ * Writes an array of numbers to the buffer as a repeated float field.
+jspb.BinaryWriter.prototype.writeRepeatedFloat = function(field, value) {
+ this.writeFloat(field, value[i]);
+ * Writes an array of numbers to the buffer as a repeated double field.
+jspb.BinaryWriter.prototype.writeRepeatedDouble = function(field, value) {
+ this.writeDouble(field, value[i]);
+ * Writes an array of booleans to the buffer as a repeated bool field.
+ * @param {?Array.<boolean>} value The array of ints to write.
+jspb.BinaryWriter.prototype.writeRepeatedBool = function(field, value) {
+ this.writeBool(field, value[i]);
+ * Writes an array of enums to the buffer as a repeated enum field.
+jspb.BinaryWriter.prototype.writeRepeatedEnum = function(field, value) {
+ this.writeEnum(field, value[i]);
+ * Writes an array of strings to the buffer as a repeated string field.
+ * @param {?Array.<string>} value The array of strings to write.
+jspb.BinaryWriter.prototype.writeRepeatedString = function(field, value) {
+ this.writeString(field, value[i]);
+ * Writes an array of arbitrary byte fields to the buffer.
+ * @param {?Array.<!Uint8Array|string>} value
+ * The arrays of arrays of bytes to write.
+ * @param {boolean=} opt_stringIsRawBytes Any values that are strings are
+ * interpreted as raw bytes rather than base64 data.
+jspb.BinaryWriter.prototype.writeRepeatedBytes =
+ this.writeBytes(field, value[i], null, null, null, opt_stringIsRawBytes);
+ * Writes an array of arbitrary byte fields to the buffer, with
+ * `opt_stringIsRawBytes` implicitly true.
+ * @param {?Array.<string>} value
+jspb.BinaryWriter.prototype.writeRepeatedBytesRawString =
+ this.writeRepeatedBytes(field, value, null, null, null, true);
+ * Writes an array of messages to the buffer.
+ * @param {?Array.<!MessageType>} value The array of messages to
+ * write.
+jspb.BinaryWriter.prototype.writeRepeatedMessage =
+ writerCallback(value[i], this);
+ * Writes an array of group messages to the buffer.
+jspb.BinaryWriter.prototype.writeRepeatedGroup =
+ * @param {?Array.<string>} value The array of hashes to write.
+jspb.BinaryWriter.prototype.writeRepeatedFixedHash64 =
+ this.writeFixedHash64(field, value[i]);
+ * Writes a repeated 64-bit hash string field (8 characters @ 8 bits of data
+ * each) to the buffer.
+jspb.BinaryWriter.prototype.writeRepeatedVarintHash64 =
+ this.writeVarintHash64(field, value[i]);
+ * Writes an array of numbers to the buffer as a packed varint field.
+jspb.BinaryWriter.prototype.writePackedUnsignedVarint32_ =
+ function(field, value, opt_buffer, opt_start, opt_end) {
+ if (value != null && value.length) {
+ this.rawWriteUnsignedVarint32(value[i]);
+jspb.BinaryWriter.prototype.writePackedSignedVarint32_ =
+ this.rawWriteSignedVarint32(value[i]);
+jspb.BinaryWriter.prototype.writePackedVarint_ =
+ this.rawWriteVarint(value[i]);
+ * Writes an array of numbers to the buffer as a packed zigzag field.
+jspb.BinaryWriter.prototype.writePackedZigzag_ =
+ this.rawWriteZigzagVarint(value[i]);
+ * Writes an array of numbers to the buffer as a packed 32-bit int field.
+jspb.BinaryWriter.prototype.writePackedInt32 =
+ jspb.BinaryWriter.prototype.writePackedSignedVarint32_;
+ * Writes an array of numbers represented as strings to the buffer as a packed
+jspb.BinaryWriter.prototype.writePackedInt32String = function(field, value) {
+ if (value == null || !value.length) return;
+ this.rawWriteSignedVarint32(parseInt(value[i], 10));
+ * Writes an array of numbers to the buffer as a packed 64-bit int field.
+jspb.BinaryWriter.prototype.writePackedInt64 =
+ jspb.BinaryWriter.prototype.writePackedVarint_;
+jspb.BinaryWriter.prototype.writePackedInt64String =
+ var num = jspb.arith.Int64.fromString(value[i]);
+ * Writes an array numbers to the buffer as a packed unsigned 32-bit int field.
+jspb.BinaryWriter.prototype.writePackedUint32 =
+ jspb.BinaryWriter.prototype.writePackedUnsignedVarint32_;
+jspb.BinaryWriter.prototype.writePackedUint32String =
+ this.rawWriteUnsignedVarint32(parseInt(value[i], 10));
+ * Writes an array numbers to the buffer as a packed unsigned 64-bit int field.
+jspb.BinaryWriter.prototype.writePackedUint64 =
+jspb.BinaryWriter.prototype.writePackedUint64String =
+ var num = jspb.arith.UInt64.fromString(value[i]);
+ * Writes an array numbers to the buffer as a packed signed 32-bit int field.
+jspb.BinaryWriter.prototype.writePackedSint32 =
+ jspb.BinaryWriter.prototype.writePackedZigzag_;
+ * Writes an array numbers to the buffer as a packed signed 64-bit int field.
+jspb.BinaryWriter.prototype.writePackedSint64 =
+ * Writes an array of numbers to the buffer as a packed fixed32 field.
+jspb.BinaryWriter.prototype.writePackedFixed32 =
+ this.rawWriteUnsignedVarint32(value.length * 4);
+ this.rawWriteUint32(value[i]);
+ * Writes an array of numbers to the buffer as a packed fixed64 field.
+jspb.BinaryWriter.prototype.writePackedFixed64 =
+ this.rawWriteUnsignedVarint32(value.length * 8);
+ this.rawWriteUint64(value[i]);
+ * Writes an array of numbers to the buffer as a packed sfixed32 field.
+jspb.BinaryWriter.prototype.writePackedSfixed32 =
+ this.rawWriteInt32(value[i]);
+ * Writes an array of numbers to the buffer as a packed sfixed64 field.
+jspb.BinaryWriter.prototype.writePackedSfixed64 =
+ this.rawWriteInt64(value[i]);
+ * Writes an array of numbers to the buffer as a packed float field.
+jspb.BinaryWriter.prototype.writePackedFloat =
+ this.rawWriteFloat(value[i]);
+ * Writes an array of numbers to the buffer as a packed double field.
+jspb.BinaryWriter.prototype.writePackedDouble =
+ this.rawWriteDouble(value[i]);
+ * Writes an array of booleans to the buffer as a packed bool field.
+jspb.BinaryWriter.prototype.writePackedBool =
+ this.rawWriteBool(value[i]);
+ * Writes an array of enums to the buffer as a packed enum field.
+jspb.BinaryWriter.prototype.writePackedEnum =
+ this.rawWriteEnum(value[i]);
+jspb.BinaryWriter.prototype.writePackedFixedHash64 =
+ this.rawWriteFixedHash64(value[i]);
+jspb.BinaryWriter.prototype.writePackedVarintHash64 =
+ this.rawWriteVarintHash64(value[i]);
@@ -0,0 +1,123 @@
+ * @fileoverview Test cases for jspb's binary protocol buffer writer. In
+ * practice BinaryWriter is used to drive the Decoder and Reader test cases,
+ * so only writer-specific tests are here.
+goog.require('goog.crypt');
+ * @param {function()} func This function should throw an error when run.
+function assertFails(func) {
+ var e = assertThrows(func);
+ console.log(e);
+ //assertNotNull(e.toString().match(/Error/));
+describe('binaryWriterTest', function() {
+ * Verifies that misuse of the writer class triggers assertions.
+ it('testWriteErrors', function() {
+ // Submessages with invalid field indices should assert.
+ assertFails(function() {
+ writer.writeMessage(-1, dummyMessage, goog.nullFunction);
+ // Writing invalid field indices should assert.
+ assertFails(function() {writer.writeUint64(-1, 1);});
+ // Writing out-of-range field values should assert.
+ assertFails(function() {writer.writeInt32(1, -Infinity);});
+ assertFails(function() {writer.writeInt32(1, Infinity);});
+ assertFails(function() {writer.writeInt64(1, -Infinity);});
+ assertFails(function() {writer.writeInt64(1, Infinity);});
+ assertFails(function() {writer.writeUint32(1, -1);});
+ assertFails(function() {writer.writeUint32(1, Infinity);});
+ assertFails(function() {writer.writeUint64(1, -1);});
+ assertFails(function() {writer.writeUint64(1, Infinity);});
+ assertFails(function() {writer.writeSint32(1, -Infinity);});
+ assertFails(function() {writer.writeSint32(1, Infinity);});
+ assertFails(function() {writer.writeSint64(1, -Infinity);});
+ assertFails(function() {writer.writeSint64(1, Infinity);});
+ assertFails(function() {writer.writeFixed32(1, -1);});
+ assertFails(function() {writer.writeFixed32(1, Infinity);});
+ assertFails(function() {writer.writeFixed64(1, -1);});
+ assertFails(function() {writer.writeFixed64(1, Infinity);});
+ assertFails(function() {writer.writeSfixed32(1, -Infinity);});
+ assertFails(function() {writer.writeSfixed32(1, Infinity);});
+ assertFails(function() {writer.writeSfixed64(1, -Infinity);});
+ assertFails(function() {writer.writeSfixed64(1, Infinity);});
+ * Basic test of retrieving the result as a Uint8Array buffer
+ it('testGetResultBuffer', function() {
+ var expected = '0864120b48656c6c6f20776f726c641a0301020320c801';
+ writer.writeUint32(1, 100);
+ writer.writeString(2, 'Hello world');
+ writer.writeBytes(3, new Uint8Array([1, 2, 3]));
+ writer.writeUint32(4, 200);
+ assertEquals(expected, goog.crypt.byteArrayToHex(buffer));
@@ -0,0 +1,51 @@
+// Author: mwr@google.com (Mark Rawling)
+syntax = "proto2";
+option java_package = "com.google.apps.jspb.proto";
+option java_multiple_files = true;
+package jspb.test;
+// legacy data, must be nested
+message data {
+ message NestedData {
+ required string str = 1;
+// new data, does not require nesting
+message UnnestedData {
@@ -0,0 +1,140 @@
+ * @fileoverview Utilities to debug JSPB based proto objects.
+goog.provide('jspb.debug');
+goog.require('goog.array');
+goog.require('goog.object');
+goog.require('jspb.Message');
+ * Turns a proto into a human readable object that can i.e. be written to the
+ * console: {@code console.log(jspb.debug.dump(myProto))}.
+ * This function makes a best effort and may not work in all cases. It will not
+ * work in obfuscated and or optimized code.
+ * Use this in environments where {@see jspb.Message.prototype.toObject} is
+ * not available for code size reasons.
+ * @param {jspb.Message} message A jspb.Message.
+ * @return {Object}
+jspb.debug.dump = function(message) {
+ if (!goog.DEBUG) {
+ goog.asserts.assert(message instanceof jspb.Message,
+ 'jspb.Message instance expected');
+ /** @type {Object} */
+ var object = message;
+ goog.asserts.assert(object['getExtension'],
+ 'Only unobfuscated and unoptimized compilation modes supported.');
+ return /** @type {Object} */ (jspb.debug.dump_(message));
+ * Recursively introspects a message and the values its getters return to
+ * make a best effort in creating a human readable representation of the
+ * message.
+ * @param {*} thing A jspb.Message, Array or primitive type to dump.
+jspb.debug.dump_ = function(thing) {
+ var type = goog.typeOf(thing);
+ if (type == 'number' || type == 'string' || type == 'boolean' ||
+ type == 'null' || type == 'undefined') {
+ return thing;
+ if (type == 'array') {
+ goog.asserts.assertArray(thing);
+ return goog.array.map(thing, jspb.debug.dump_);
+ var message = thing; // Copy because we don't want type inference on thing.
+ 'Only messages expected: ' + thing);
+ var ctor = message.constructor;
+ var messageName = ctor.name || ctor.displayName;
+ var object = {
+ '$name': messageName
+ for (var name in ctor.prototype) {
+ var match = /^get([A-Z]\w*)/.exec(name);
+ if (match && name != 'getExtension' &&
+ name != 'getJsPbMessageId') {
+ var val = thing[name]();
+ if (val != null) {
+ object[jspb.debug.formatFieldName_(match[1])] = jspb.debug.dump_(val);
+ if (COMPILED && thing['extensionObject_']) {
+ object['$extensions'] = 'Recursive dumping of extensions not supported ' +
+ 'in compiled code. Switch to uncompiled or dump extension object ' +
+ 'directly';
+ return object;
+ var extensionsObject;
+ for (var id in ctor['extensions']) {
+ if (/^\d+$/.test(id)) {
+ var ext = ctor['extensions'][id];
+ var extVal = thing.getExtension(ext);
+ var fieldName = goog.object.getKeys(ext.fieldName)[0];
+ if (extVal != null) {
+ if (!extensionsObject) {
+ extensionsObject = object['$extensions'] = {};
+ extensionsObject[jspb.debug.formatFieldName_(fieldName)] =
+ jspb.debug.dump_(extVal);
+ * Formats a field name for output as camelCase.
+ * @param {string} name Name of the field.
+jspb.debug.formatFieldName_ = function(name) {
+ // Name may be in TitleCase.
+ return name.replace(/^[A-Z]/, function(c) {
+ return c.toLowerCase();
@@ -0,0 +1,101 @@
+goog.setTestOnly();
+goog.require('jspb.debug');
+goog.require('proto.jspb.test.HasExtensions');
+goog.require('proto.jspb.test.IsExtension');
+goog.require('proto.jspb.test.Simple1');
+describe('debugTest', function() {
+ it('testSimple1', function() {
+ if (COMPILED) {
+ var message = new proto.jspb.test.Simple1();
+ message.setAString('foo');
+ assertObjectEquals({
+ $name: 'proto.jspb.test.Simple1',
+ 'aString': 'foo',
+ 'aRepeatedStringList': []
+ }, jspb.debug.dump(message));
+ message.setABoolean(true);
+ message.setARepeatedStringList(['1', '2']);
+ 'aRepeatedStringList': ['1', '2'],
+ 'aBoolean': true
+ message.setAString(undefined);
+ var extension = new proto.jspb.test.IsExtension();
+ extension.setExt1('ext1field');
+ var extendable = new proto.jspb.test.HasExtensions();
+ extendable.setStr1('v1');
+ extendable.setStr2('v2');
+ extendable.setStr3('v3');
+ extendable.setExtension(proto.jspb.test.IsExtension.extField, extension);
+ '$name': 'proto.jspb.test.HasExtensions',
+ 'str1': 'v1',
+ 'str2': 'v2',
+ 'str3': 'v3',
+ '$extensions': {
+ 'extField': {
+ '$name': 'proto.jspb.test.IsExtension',
+ 'ext1': 'ext1field'
+ },
+ 'repeatedSimpleList': []
+ }, jspb.debug.dump(extendable));
@@ -0,0 +1,1125 @@
+ * @fileoverview Definition of jspb.Message.
+ * @author mwr@google.com (Mark Rawling)
+goog.provide('jspb.ExtensionFieldInfo');
+goog.provide('jspb.Message');
+goog.require('goog.json');
+// Not needed in compilation units that have no protos with xids.
+goog.forwardDeclare('xid.String');
+ * Stores information for a single extension field.
+ * For example, an extension field defined like so:
+ * extend BaseMessage {
+ * optional MyMessage my_field = 123;
+ * }
+ * will result in an ExtensionFieldInfo object with these properties:
+ * {
+ * fieldIndex: 123,
+ * fieldName: {my_field_renamed: 0},
+ * ctor: proto.example.MyMessage,
+ * toObjectFn: proto.example.MyMessage.toObject,
+ * isRepeated: 0
+ * We include `toObjectFn` to allow the JSCompiler to perform dead-code removal
+ * on unused toObject() methods.
+ * If an extension field is primitive, ctor and toObjectFn will be null.
+ * isRepeated should be 0 or 1.
+ * binary{Reader,Writer}Fn and (if message type) binaryMessageSerializeFn are
+ * always provided. binaryReaderFn and binaryWriterFn are references to the
+ * appropriate methods on BinaryReader/BinaryWriter to read/write the value of
+ * this extension, and binaryMessageSerializeFn is a reference to the message
+ * class's .serializeBinary method, if available.
+ * @param {number} fieldNumber
+ * @param {Object} fieldName This has the extension field name as a property.
+ * @param {?function(new: jspb.Message, Array=)} ctor
+ * @param {?function((boolean|undefined),!jspb.Message):!Object} toObjectFn
+ * @param {number} isRepeated
+ * @param {?function(number,?)=} opt_binaryReaderFn
+ * @param {?function(number,?)|function(number,?,?,?,?,?)=} opt_binaryWriterFn
+ * @param {?function(?,?)=} opt_binaryMessageSerializeFn
+ * @param {?function(?,?)=} opt_binaryMessageDeserializeFn
+ * @param {?boolean=} opt_isPacked
+jspb.ExtensionFieldInfo = function(fieldNumber, fieldName, ctor, toObjectFn,
+ isRepeated, opt_binaryReaderFn, opt_binaryWriterFn,
+ opt_binaryMessageSerializeFn, opt_binaryMessageDeserializeFn,
+ opt_isPacked) {
+ /** @const */
+ this.fieldIndex = fieldNumber;
+ this.fieldName = fieldName;
+ this.ctor = ctor;
+ this.toObjectFn = toObjectFn;
+ this.binaryReaderFn = opt_binaryReaderFn;
+ this.binaryWriterFn = opt_binaryWriterFn;
+ this.binaryMessageSerializeFn = opt_binaryMessageSerializeFn;
+ this.binaryMessageDeserializeFn = opt_binaryMessageDeserializeFn;
+ this.isRepeated = isRepeated;
+ this.isPacked = opt_isPacked;
+ * Base class for all JsPb messages.
+jspb.Message = function() {
+ * @define {boolean} Whether to generate toObject methods for objects. Turn
+ * this off, if you do not want toObject to be ever used in your project.
+ * When turning off this flag, consider adding a conformance test that bans
+ * calling toObject. Enabling this will disable the JSCompiler's ability to
+ * dead code eliminate fields used in protocol buffers that are never used
+ * in an application.
+goog.define('jspb.Message.GENERATE_TO_OBJECT', true);
+ * @define {boolean} Whether to generate fromObject methods for objects. Turn
+ * this off, if you do not want fromObject to be ever used in your project.
+ * calling fromObject. Enabling this might disable the JSCompiler's ability
+ * to dead code eliminate fields used in protocol buffers that are never
+ * used in an application.
+ * NOTE: By default no protos actually have a fromObject method. You need to
+ * add the jspb.generate_from_object options to the proto definition to
+ * activate the feature.
+ * By default this is enabled for test code only.
+goog.define('jspb.Message.GENERATE_FROM_OBJECT', !goog.DISALLOW_TEST_ONLY_CODE);
+ * @define {boolean} Turning on this flag does NOT change the behavior of JSPB
+ * and only affects private internal state. It may, however, break some
+ * tests that use naive deeply-equals algorithms, because using a proto
+ * mutates its internal state.
+ * Projects are advised to turn this flag always on.
+goog.define('jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS', COMPILED);
+// TODO(b/19419436) Turn this on by default.
+ * The internal data array.
+ * @type {!Array}
+ * @protected
+jspb.Message.prototype.array;
+ * Wrappers are the constructed instances of message-type fields. They are built
+ * on demand from the raw array data. Includes message fields, repeated message
+ * fields and extension message fields. Indexed by field number.
+ * @type {Object}
+jspb.Message.prototype.wrappers_;
+ * The object that contains extension fields, if any. This is an object that
+ * maps from a proto field number to the field's value.
+jspb.Message.prototype.extensionObject_;
+ * Non-extension fields with a field number at or above the pivot are
+ * stored in the extension object (in addition to all extension fields).
+jspb.Message.prototype.pivot_;
+ * The JsPb message_id of this proto.
+ * @type {string|undefined} the message id or undefined if this message
+ * has no id.
+jspb.Message.prototype.messageId_;
+ * The xid of this proto type (The same for all instances of a proto). Provides
+ * a way to identify a proto by stable obfuscated name.
+ * @see {xid}.
+ * Available if {@link jspb.generate_xid} is added as a Message option to
+ * a protocol buffer.
+ * @const {!xid.String|undefined} The xid or undefined if message is
+ * annotated to generate the xid.
+jspb.Message.prototype.messageXid;
+ * Returns the JsPb message_id of this proto.
+ * @return {string|undefined} the message id or undefined if this message
+jspb.Message.prototype.getJsPbMessageId = function() {
+ return this.messageId_;
+ * An offset applied to lookups into this.array to account for the presence or
+ * absence of a messageId at position 0. For response messages, this will be 0.
+ * Otherwise, it will be -1 so that the first array position is not wasted.
+jspb.Message.prototype.arrayIndexOffset_;
+ * Returns the index into msg.array at which the proto field with tag number
+ * fieldNumber will be located.
+ * @param {!jspb.Message} msg Message for which we're calculating an index.
+ * @param {number} fieldNumber The field number.
+ * @return {number} The index.
+jspb.Message.getIndex_ = function(msg, fieldNumber) {
+ return fieldNumber + msg.arrayIndexOffset_;
+ * Initializes a JsPb Message.
+ * @param {!jspb.Message} msg The JsPb proto to modify.
+ * @param {Array|undefined} data An initial data array.
+ * @param {string|number} messageId For response messages, the message id or ''
+ * if no message id is specified. For non-response messages, 0.
+ * @param {number} suggestedPivot The field number at which to start putting
+ * fields into the extension object. This is only used if data does not
+ * contain an extension object already. -1 if no extension object is
+ * required for this message type.
+ * @param {Array<number>} repeatedFields The message's repeated fields.
+ * @param {Array<!Array<number>>=} opt_oneofFields The fields belonging to
+ * each of the message's oneof unions.
+jspb.Message.initialize = function(
+ msg, data, messageId, suggestedPivot, repeatedFields, opt_oneofFields) {
+ msg.wrappers_ = jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS ? null : {};
+ if (!data) {
+ data = messageId ? [messageId] : [];
+ msg.messageId_ = messageId ? String(messageId) : undefined;
+ // If the messageId is 0, this message is not a response message, so we shift
+ // array indices down by 1 so as not to waste the first position in the array,
+ // which would otherwise go unused.
+ msg.arrayIndexOffset_ = messageId === 0 ? -1 : 0;
+ msg.array = data;
+ jspb.Message.materializeExtensionObject_(msg, suggestedPivot);
+ if (repeatedFields) {
+ for (var i = 0; i < repeatedFields.length; i++) {
+ var fieldNumber = repeatedFields[i];
+ if (fieldNumber < msg.pivot_) {
+ var index = jspb.Message.getIndex_(msg, fieldNumber);
+ msg.array[index] = msg.array[index] ||
+ (jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS ?
+ jspb.Message.EMPTY_LIST_SENTINEL_ :
+ []);
+ msg.extensionObject_[fieldNumber] =
+ msg.extensionObject_[fieldNumber] ||
+ if (opt_oneofFields && opt_oneofFields.length) {
+ // Compute the oneof case for each union. This ensures only one value is
+ // set in the union.
+ goog.array.forEach(
+ opt_oneofFields, goog.partial(jspb.Message.computeOneofCase, msg));
+ * Used to mark empty repeated fields. Serializes to null when serialized
+ * to JSON.
+ * When reading a repeated field readers must check the return value against
+ * this value and return and replace it with a new empty array if it is
+ * present.
+ * @private @const {!Object}
+jspb.Message.EMPTY_LIST_SENTINEL_ = goog.DEBUG && Object.freeze ?
+ Object.freeze([]) :
+ [];
+ * Ensures that the array contains an extension object if necessary.
+ * If the array contains an extension object in its last position, then the
+ * object is kept in place and its position is used as the pivot. If not, then
+ * create an extension object using suggestedPivot. If suggestedPivot is -1,
+ * we don't have an extension object at all, in which case all fields are stored
+ * in the array.
+ * @param {number} suggestedPivot See description for initialize().
+jspb.Message.materializeExtensionObject_ = function(msg, suggestedPivot) {
+ if (msg.array.length) {
+ var foundIndex = msg.array.length - 1;
+ var obj = msg.array[foundIndex];
+ // Normal fields are never objects, so we can be sure that if we find an
+ // object here, then it's the extension object. However, we must ensure that
+ // the object is not an array, since arrays are valid field values.
+ // NOTE(lukestebbing): We avoid looking at .length to avoid a JIT bug
+ // in Safari on iOS 8. See the description of CL/86511464 for details.
+ if (obj && typeof obj == 'object' && !goog.isArray(obj)) {
+ msg.pivot_ = foundIndex - msg.arrayIndexOffset_;
+ msg.extensionObject_ = obj;
+ // This complexity exists because we keep all extension fields in the
+ // extensionObject_ regardless of proto field number. Changing this would
+ // simplify the code here, but it would require changing the serialization
+ // format from the server, which is not backwards compatible.
+ // TODO(jshneier): Should we just treat extension fields the same as
+ // non-extension fields, and select whether they appear in the object or in
+ // the array purely based on tag number? This would allow simplifying all the
+ // get/setExtension logic, but it would require the breaking change described
+ // above.
+ if (suggestedPivot > -1) {
+ msg.pivot_ = suggestedPivot;
+ var pivotIndex = jspb.Message.getIndex_(msg, suggestedPivot);
+ if (!jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS) {
+ msg.extensionObject_ = msg.array[pivotIndex] = {};
+ // Initialize to null to avoid changing the shape of the proto when it
+ // gets eventually set.
+ msg.extensionObject_ = null;
+ msg.pivot_ = Number.MAX_VALUE;
+ * Creates an empty extensionObject_ if non exists.
+jspb.Message.maybeInitEmptyExtensionObject_ = function(msg) {
+ var pivotIndex = jspb.Message.getIndex_(msg, msg.pivot_);
+ if (!msg.array[pivotIndex]) {
+ * Converts a JsPb repeated message field into an object list.
+ * @param {!Array<T>} field The repeated message field to be
+ * converted.
+ * @param {?function(boolean=): Object|
+ * function((boolean|undefined),T): Object} toObjectFn The toObject
+ * function for this field. We need to pass this for effective dead code
+ * removal.
+ * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
+ * for transitional soy proto support: http://goto/soy-param-migration
+ * @return {!Array<Object>} An array of converted message objects.
+jspb.Message.toObjectList = function(field, toObjectFn, opt_includeInstance) {
+ // Not using goog.array.map in the generated code to keep it small.
+ // And not using it here to avoid a function call.
+ for (var i = 0; i < field.length; i++) {
+ result[i] = toObjectFn.call(field[i], opt_includeInstance,
+ /** @type {!jspb.Message} */ (field[i]));
+ * Adds a proto's extension data to a Soy rendering object.
+ * @param {!jspb.Message} proto The proto whose extensions to convert.
+ * @param {!Object} obj The Soy object to add converted extension data to.
+ * @param {!Object} extensions The proto class' registered extensions.
+ * @param {function(jspb.ExtensionFieldInfo) : *} getExtensionFn The proto
+ * class' getExtension function. Passed for effective dead code removal.
+jspb.Message.toObjectExtension = function(proto, obj, extensions,
+ getExtensionFn, opt_includeInstance) {
+ for (var fieldNumber in extensions) {
+ var fieldInfo = extensions[fieldNumber];
+ var value = getExtensionFn.call(proto, fieldInfo);
+ for (var name in fieldInfo.fieldName) {
+ if (fieldInfo.fieldName.hasOwnProperty(name)) {
+ break; // the compiled field name
+ if (!fieldInfo.toObjectFn) {
+ obj[name] = value;
+ if (fieldInfo.isRepeated) {
+ obj[name] = jspb.Message.toObjectList(
+ /** @type {!Array<jspb.Message>} */ (value),
+ fieldInfo.toObjectFn, opt_includeInstance);
+ obj[name] = fieldInfo.toObjectFn(opt_includeInstance, value);
+ * Writes a proto's extension data to a binary-format output stream.
+ * @param {*} writer The binary-format writer to write to.
+jspb.Message.serializeBinaryExtensions = function(proto, writer, extensions,
+ getExtensionFn) {
+ // The old codegen doesn't add the extra fields to ExtensionFieldInfo, so we
+ // need to gracefully error-out here rather than produce a null dereference
+ // below.
+ if (!fieldInfo.binaryWriterFn) {
+ throw new Error('Message extension present that was generated ' +
+ 'without binary serialization support');
+ if (fieldInfo.ctor) { // is this a message type?
+ // If the message type of the extension was generated without binary
+ // support, there may not be a binary message serializer function, and
+ // we can't know when we codegen the extending message that the extended
+ // message may require binary support, so we can *only* catch this error
+ // here, at runtime (and this decoupled codegen is the whole point of
+ // extensions!).
+ if (fieldInfo.binaryMessageSerializeFn) {
+ fieldInfo.binaryWriterFn.call(writer, fieldInfo.fieldIndex,
+ value, fieldInfo.binaryMessageSerializeFn);
+ throw new Error('Message extension present holding submessage ' +
+ 'without binary support enabled, and message is ' +
+ 'being serialized to binary format');
+ fieldInfo.binaryWriterFn.call(writer, fieldInfo.fieldIndex, value);
+ * Reads an extension field from the given reader and, if a valid extension,
+ * sets the extension value.
+ * @param {!jspb.Message} msg A jspb proto.
+ * @param {{skipField:function(),getFieldNumber:function():number}} reader
+ * @param {!Object} extensions The extensions object.
+ * @param {function(jspb.ExtensionFieldInfo)} getExtensionFn
+ * @param {function(jspb.ExtensionFieldInfo, ?)} setExtensionFn
+jspb.Message.readBinaryExtension = function(msg, reader, extensions,
+ getExtensionFn, setExtensionFn) {
+ var fieldInfo = extensions[reader.getFieldNumber()];
+ if (!fieldInfo) {
+ if (!fieldInfo.binaryReaderFn) {
+ throw new Error('Deserializing extension whose generated code does not ' +
+ 'support binary format');
+ var value;
+ if (fieldInfo.ctor) {
+ // Message type.
+ value = new fieldInfo.ctor();
+ fieldInfo.binaryReaderFn.call(
+ reader, value, fieldInfo.binaryMessageDeserializeFn);
+ // All other types.
+ value = fieldInfo.binaryReaderFn.call(reader);
+ if (fieldInfo.isRepeated && !fieldInfo.isPacked) {
+ var currentList = getExtensionFn.call(msg, fieldInfo);
+ if (!currentList) {
+ setExtensionFn.call(msg, fieldInfo, [value]);
+ currentList.push(value);
+ setExtensionFn.call(msg, fieldInfo, value);
+ * Gets the value of a non-extension field.
+ * @return {string|number|boolean|Uint8Array|Array|null|undefined}
+ * The field's value.
+jspb.Message.getField = function(msg, fieldNumber) {
+ var val = msg.array[index];
+ if (val === jspb.Message.EMPTY_LIST_SENTINEL_) {
+ return msg.array[index] = [];
+ return val;
+ var val = msg.extensionObject_[fieldNumber];
+ return msg.extensionObject_[fieldNumber] = [];
+ * Gets the value of a non-extension primitive field, with proto3 (non-nullable
+ * primitives) semantics. Returns `defaultValue` if the field is not otherwise
+ * set.
+ * @param {T} defaultValue The default value.
+ * @return {T} The field's value.
+jspb.Message.getFieldProto3 = function(msg, fieldNumber, defaultValue) {
+ var value = jspb.Message.getField(msg, fieldNumber);
+ if (value == null) {
+ return defaultValue;
+ return value;
+ * Sets the value of a non-extension field.
+ * @param {string|number|boolean|Uint8Array|Array|undefined} value New value
+jspb.Message.setField = function(msg, fieldNumber, value) {
+ msg.array[jspb.Message.getIndex_(msg, fieldNumber)] = value;
+ msg.extensionObject_[fieldNumber] = value;
+ * Sets the value of a field in a oneof union and clears all other fields in
+ * the union.
+ * @param {!Array<number>} oneof The fields belonging to the union.
+jspb.Message.setOneofField = function(msg, fieldNumber, oneof, value) {
+ var currentCase = jspb.Message.computeOneofCase(msg, oneof);
+ if (currentCase && currentCase !== fieldNumber && value !== undefined) {
+ if (msg.wrappers_ && currentCase in msg.wrappers_) {
+ msg.wrappers_[currentCase] = undefined;
+ jspb.Message.setField(msg, currentCase, undefined);
+ jspb.Message.setField(msg, fieldNumber, value);
+ * Computes the selection in a oneof group for the given message, ensuring
+ * only one field is set in the process.
+ * According to the protobuf language guide (
+ * https://developers.google.com/protocol-buffers/docs/proto#oneof), "if the
+ * parser encounters multiple members of the same oneof on the wire, only the
+ * last member seen is used in the parsed message." Since JSPB serializes
+ * messages to a JSON array, the "last member seen" will always be the field
+ * with the greatest field number (directly corresponding to the greatest
+ * array index).
+ * @param {!Array<number>} oneof The field numbers belonging to the union.
+ * @return {number} The field number currently set in the union, or 0 if none.
+jspb.Message.computeOneofCase = function(msg, oneof) {
+ var oneofField;
+ var oneofValue;
+ goog.array.forEach(oneof, function(fieldNumber) {
+ if (goog.isDefAndNotNull(value)) {
+ oneofField = fieldNumber;
+ oneofValue = value;
+ jspb.Message.setField(msg, fieldNumber, undefined);
+ if (oneofField) {
+ // NB: We know the value is unique, so we can call jspb.Message.setField
+ // directly instead of jpsb.Message.setOneofField. Also, setOneofField
+ // calls this function.
+ jspb.Message.setField(msg, oneofField, oneofValue);
+ return oneofField;
+ * Gets and wraps a proto field on access.
+ * @param {function(new:jspb.Message, Array)} ctor Constructor for the field.
+ * @param {number=} opt_required True (1) if this is a required field.
+ * @return {jspb.Message} The field as a jspb proto.
+jspb.Message.getWrapperField = function(msg, ctor, fieldNumber, opt_required) {
+ // TODO(mwr): Consider copying data and/or arrays.
+ if (!msg.wrappers_) {
+ msg.wrappers_ = {};
+ if (!msg.wrappers_[fieldNumber]) {
+ var data = /** @type {Array} */ (jspb.Message.getField(msg, fieldNumber));
+ if (opt_required || data) {
+ // TODO(mwr): Remove existence test for always valid default protos.
+ msg.wrappers_[fieldNumber] = new ctor(data);
+ return /** @type {jspb.Message} */ (msg.wrappers_[fieldNumber]);
+ * Gets and wraps a repeated proto field on access.
+ * @return {Array<!jspb.Message>} The repeated field as an array of protos.
+jspb.Message.getRepeatedWrapperField = function(msg, ctor, fieldNumber) {
+ var data = jspb.Message.getField(msg, fieldNumber);
+ for (var wrappers = [], i = 0; i < data.length; i++) {
+ wrappers[i] = new ctor(data[i]);
+ msg.wrappers_[fieldNumber] = wrappers;
+ var val = msg.wrappers_[fieldNumber];
+ if (val == jspb.Message.EMPTY_LIST_SENTINEL_) {
+ val = msg.wrappers_[fieldNumber] = [];
+ return /** @type {Array<!jspb.Message>} */ (val);
+ * Sets a proto field and syncs it to the backing array.
+ * @param {jspb.Message|undefined} value A new value for this proto field.
+jspb.Message.setWrapperField = function(msg, fieldNumber, value) {
+ var data = value ? value.toArray() : value;
+ msg.wrappers_[fieldNumber] = value;
+ jspb.Message.setField(msg, fieldNumber, data);
+ * Sets a proto field in a oneof union and syncs it to the backing array.
+jspb.Message.setOneofWrapperField = function(msg, fieldNumber, oneof, value) {
+ jspb.Message.setOneofField(msg, fieldNumber, oneof, data);
+ * Sets a repeated proto field and syncs it to the backing array.
+ * @param {Array<!jspb.Message>|undefined} value An array of protos.
+jspb.Message.setRepeatedWrapperField = function(msg, fieldNumber, value) {
+ value = value || [];
+ for (var data = [], i = 0; i < value.length; i++) {
+ data[i] = value[i].toArray();
+ * Converts a JsPb repeated message field into a map. The map will contain
+ * protos unless an optional toObject function is given, in which case it will
+ * contain objects suitable for Soy rendering.
+ * @param {function() : string?} mapKeyGetterFn The function to get the key of
+ * the map.
+ * function((boolean|undefined),T): Object} opt_toObjectFn The
+ * toObject function for this field. We need to pass this for effective
+ * dead code removal.
+ * @return {!Object.<string, Object>} A map of proto or Soy objects.
+jspb.Message.toMap = function(
+ field, mapKeyGetterFn, opt_toObjectFn, opt_includeInstance) {
+ var result = {};
+ result[mapKeyGetterFn.call(field[i])] = opt_toObjectFn ?
+ opt_toObjectFn.call(field[i], opt_includeInstance,
+ /** @type {!jspb.Message} */ (field[i])) : field[i];
+ * Returns the internal array of this proto.
+ * <p>Note: If you use this array to construct a second proto, the content
+ * would then be partially shared between the two protos.
+ * @return {!Array} The proto represented as an array.
+jspb.Message.prototype.toArray = function() {
+ return this.array;
+ * Creates a string representation of the internal data array of this proto.
+ * <p>NOTE: This string is *not* suitable for use in server requests.
+ * @return {string} A string representation of this proto.
+jspb.Message.prototype.toString = function() {
+ return this.array.toString();
+ * Gets the value of the extension field from the extended object.
+ * @param {jspb.ExtensionFieldInfo.<T>} fieldInfo Specifies the field to get.
+ * @return {T} The value of the field.
+jspb.Message.prototype.getExtension = function(fieldInfo) {
+ if (!this.extensionObject_) {
+ return undefined;
+ if (!this.wrappers_) {
+ this.wrappers_ = {};
+ var fieldNumber = fieldInfo.fieldIndex;
+ if (!this.wrappers_[fieldNumber]) {
+ this.wrappers_[fieldNumber] =
+ goog.array.map(this.extensionObject_[fieldNumber] || [],
+ function(arr) {
+ return new fieldInfo.ctor(arr);
+ return this.wrappers_[fieldNumber];
+ return this.extensionObject_[fieldNumber];
+ if (!this.wrappers_[fieldNumber] && this.extensionObject_[fieldNumber]) {
+ this.wrappers_[fieldNumber] = new fieldInfo.ctor(
+ /** @type {Array|undefined} */ (
+ this.extensionObject_[fieldNumber]));
+ * Sets the value of the extension field in the extended object.
+ * @param {jspb.ExtensionFieldInfo} fieldInfo Specifies the field to set.
+ * @param {jspb.Message|string|number|boolean|Array} value The value to set.
+jspb.Message.prototype.setExtension = function(fieldInfo, value) {
+ jspb.Message.maybeInitEmptyExtensionObject_(this);
+ this.wrappers_[fieldNumber] = value;
+ this.extensionObject_[fieldNumber] = goog.array.map(
+ /** @type {Array<jspb.Message>} */ (value), function(msg) {
+ return msg.toArray();
+ this.extensionObject_[fieldNumber] = value;
+ this.extensionObject_[fieldNumber] = value ? value.toArray() : value;
+ * Creates a difference object between two messages.
+ * The result will contain the top-level fields of m2 that differ from those of
+ * m1 at any level of nesting. No data is cloned, the result object will
+ * share its top-level elements with m2 (but not with m1).
+ * Note that repeated fields should not have null/undefined elements, but if
+ * they do, this operation will treat repeated fields of different length as
+ * the same if the only difference between them is due to trailing
+ * null/undefined values.
+ * @param {!jspb.Message} m1 The first message object.
+ * @param {!jspb.Message} m2 The second message object.
+ * @return {!jspb.Message} The difference returned as a proto message.
+ * Note that the returned message may be missing required fields. This is
+ * currently tolerated in Js, but would cause an error if you tried to
+ * send such a proto to the server. You can access the raw difference
+ * array with result.toArray().
+ * @throws {Error} If the messages are responses with different types.
+jspb.Message.difference = function(m1, m2) {
+ if (!(m1 instanceof m2.constructor)) {
+ throw new Error('Messages have different types.');
+ var arr1 = m1.toArray();
+ var arr2 = m2.toArray();
+ var res = [];
+ var start = 0;
+ var length = arr1.length > arr2.length ? arr1.length : arr2.length;
+ if (m1.getJsPbMessageId()) {
+ res[0] = m1.getJsPbMessageId();
+ start = 1;
+ for (var i = start; i < length; i++) {
+ if (!jspb.Message.compareFields(arr1[i], arr2[i])) {
+ res[i] = arr2[i];
+ return new m1.constructor(res);
+ * Tests whether two messages are equal.
+ * @param {jspb.Message|undefined} m1 The first message object.
+ * @param {jspb.Message|undefined} m2 The second message object.
+ * @return {boolean} true if both messages are null/undefined, or if both are
+ * of the same type and have the same field values.
+jspb.Message.equals = function(m1, m2) {
+ return m1 == m2 || (!!(m1 && m2) && (m1 instanceof m2.constructor) &&
+ jspb.Message.compareFields(m1.toArray(), m2.toArray()));
+ * Compares two message fields recursively.
+ * @param {*} field1 The first field.
+ * @param {*} field2 The second field.
+ * @return {boolean} true if the fields are null/undefined, or otherwise equal.
+jspb.Message.compareFields = function(field1, field2) {
+ if (goog.isObject(field1) && goog.isObject(field2)) {
+ var keys = {}, name, extensionObject1, extensionObject2;
+ for (name in field1) {
+ field1.hasOwnProperty(name) && (keys[name] = 0);
+ for (name in field2) {
+ field2.hasOwnProperty(name) && (keys[name] = 0);
+ for (name in keys) {
+ var val1 = field1[name], val2 = field2[name];
+ if (goog.isObject(val1) && !goog.isArray(val1)) {
+ if (extensionObject1 !== undefined) {
+ throw new Error('invalid jspb state');
+ extensionObject1 = goog.object.isEmpty(val1) ? undefined : val1;
+ val1 = undefined;
+ if (goog.isObject(val2) && !goog.isArray(val2)) {
+ if (extensionObject2 !== undefined) {
+ extensionObject2 = goog.object.isEmpty(val2) ? undefined : val2;
+ val2 = undefined;
+ if (!jspb.Message.compareFields(val1, val2)) {
+ if (extensionObject1 || extensionObject2) {
+ return jspb.Message.compareFields(extensionObject1, extensionObject2);
+ // Primitive fields, null and undefined compare as equal.
+ // This also forces booleans and 0/1 to compare as equal to ensure
+ // compatibility with the jspb serializer.
+ return field1 == field2;
+ * Static clone function. NOTE: A type-safe method called "cloneMessage" exists
+ * on each generated JsPb class. Do not call this function directly.
+ * @param {!jspb.Message} msg A message to clone.
+ * @return {!jspb.Message} A deep clone of the given message.
+jspb.Message.clone = function(msg) {
+ // Although we could include the wrappers, we leave them out here.
+ return jspb.Message.cloneMessage(msg);
+jspb.Message.cloneMessage = function(msg) {
+ return new msg.constructor(jspb.Message.clone_(msg.toArray()));
+ * Takes 2 messages of the same type and copies the contents of the first
+ * message into the second. After this the 2 messages will equals in terms of
+ * value semantics but share no state. All data in the destination message will
+ * be overridden.
+ * @param {MESSAGE} fromMessage Message that will be copied into toMessage.
+ * @param {MESSAGE} toMessage Message which will receive a copy of fromMessage
+ * as its contents.
+ * @template MESSAGE
+jspb.Message.copyInto = function(fromMessage, toMessage) {
+ goog.asserts.assertInstanceof(fromMessage, jspb.Message);
+ goog.asserts.assertInstanceof(toMessage, jspb.Message);
+ goog.asserts.assert(fromMessage.constructor == toMessage.constructor,
+ 'Copy source and target message should have the same type.');
+ var copyOfFrom = jspb.Message.clone(fromMessage);
+ var to = toMessage.toArray();
+ var from = copyOfFrom.toArray();
+ // Empty destination in case it has more values at the end of the array.
+ to.length = 0;
+ // and then copy everything from the new to the existing message.
+ for (var i = 0; i < from.length; i++) {
+ to[i] = from[i];
+ // This is either null or empty for a fresh copy.
+ toMessage.wrappers_ = copyOfFrom.wrappers_;
+ // Just a reference into the shared array.
+ toMessage.extensionObject_ = copyOfFrom.extensionObject_;
+ * Helper for cloning an internal JsPb object.
+ * @param {!Object} obj A JsPb object, eg, a field, to be cloned.
+ * @return {!Object} A clone of the input object.
+jspb.Message.clone_ = function(obj) {
+ var o;
+ if (goog.isArray(obj)) {
+ // Allocate array of correct size.
+ var clonedArray = new Array(obj.length);
+ // Use array iteration where possible because it is faster than for-in.
+ for (var i = 0; i < obj.length; i++) {
+ if ((o = obj[i]) != null) {
+ clonedArray[i] = typeof o == 'object' ? jspb.Message.clone_(o) : o;
+ return clonedArray;
+ var clone = {};
+ for (var key in obj) {
+ if ((o = obj[key]) != null) {
+ clone[key] = typeof o == 'object' ? jspb.Message.clone_(o) : o;
+ return clone;
+ * Registers a JsPb message type id with its constructor.
+ * @param {string} id The id for this type of message.
+ * @param {Function} constructor The message constructor.
+jspb.Message.registerMessageType = function(id, constructor) {
+ jspb.Message.registry_[id] = constructor;
+ // This is needed so we can later access messageId directly on the contructor,
+ // otherwise it is not available due to 'property collapsing' by the compiler.
+ constructor.messageId = id;
+ * The registry of message ids to message constructors.
+jspb.Message.registry_ = {};
+ * The extensions registered on MessageSet. This is a map of extension
+ * field number to field info object. This should be considered as a
+ * private API.
+ * This is similar to [jspb class name].extensions object for
+ * non-MessageSet. We special case MessageSet so that we do not need
+ * to goog.require MessageSet from classes that extends MessageSet.
+ * @type {!Object.<number, jspb.ExtensionFieldInfo>}
+jspb.Message.messageSetExtensions = {};
@@ -0,0 +1,970 @@
+goog.require('proto.jspb.exttest.beta.floatingStrField');
+goog.require('proto.jspb.exttest.floatingMsgField');
+goog.require('proto.jspb.exttest.floatingMsgFieldTwo');
+goog.require('proto.jspb.test.CloneExtension');
+goog.require('proto.jspb.test.Complex');
+goog.require('proto.jspb.test.DefaultValues');
+goog.require('proto.jspb.test.Empty');
+goog.require('proto.jspb.test.EnumContainer');
+goog.require('proto.jspb.test.ExtensionMessage');
+goog.require('proto.jspb.test.floatingMsgField');
+goog.require('proto.jspb.test.floatingStrField');
+goog.require('proto.jspb.test.IndirectExtension');
+goog.require('proto.jspb.test.OptionalFields');
+goog.require('proto.jspb.test.OuterEnum');
+goog.require('proto.jspb.test.simple1');
+goog.require('proto.jspb.test.Simple2');
+goog.require('proto.jspb.test.SpecialCases');
+goog.require('proto.jspb.test.TestClone');
+goog.require('proto.jspb.test.TestExtensionsMessage');
+goog.require('proto.jspb.test.TestGroup');
+goog.require('proto.jspb.test.TestGroup1');
+goog.require('proto.jspb.test.TestMessageWithOneof');
+goog.require('proto.jspb.test.TestReservedNames');
+goog.require('proto.jspb.test.TestReservedNamesExtension');
+describe('Message test suite', function() {
+ it('testEmptyProto', function() {
+ var empty1 = new proto.jspb.test.Empty([]);
+ var empty2 = new proto.jspb.test.Empty([]);
+ assertObjectEquals({}, empty1.toObject());
+ assertObjectEquals('Message should not be corrupted:', empty2, empty1);
+ it('testTopLevelEnum', function() {
+ var response = new proto.jspb.test.EnumContainer([]);
+ response.setOuterEnum(proto.jspb.test.OuterEnum.FOO);
+ assertEquals(proto.jspb.test.OuterEnum.FOO, response.getOuterEnum());
+ it('testByteStrings', function() {
+ var data = new proto.jspb.test.DefaultValues([]);
+ data.setBytesField('some_bytes');
+ assertEquals('some_bytes', data.getBytesField());
+ it('testComplexConversion', function() {
+ var data1 = ['a',,, [, 11], [[, 22], [, 33]],, ['s1', 's2'],, 1];
+ var data2 = ['a',,, [, 11], [[, 22], [, 33]],, ['s1', 's2'],, 1];
+ var foo = new proto.jspb.test.Complex(data1);
+ var bar = new proto.jspb.test.Complex(data2);
+ var result = foo.toObject();
+ aString: 'a',
+ anOutOfOrderBool: 1,
+ aNestedMessage: {
+ anInt: 11
+ aRepeatedMessageList: [{anInt: 22}, {anInt: 33}],
+ aRepeatedStringList: ['s1', 's2']
+ }, result);
+ // Now test with the jspb instances included.
+ result = foo.toObject(true /* opt_includeInstance */);
+ anInt: 11,
+ $jspbMessageInstance: foo.getANestedMessage()
+ aRepeatedMessageList: [
+ {anInt: 22, $jspbMessageInstance: foo.getARepeatedMessageList()[0]},
+ {anInt: 33, $jspbMessageInstance: foo.getARepeatedMessageList()[1]}
+ ],
+ aRepeatedStringList: ['s1', 's2'],
+ $jspbMessageInstance: foo
+ it('testMissingFields', function() {
+ var foo = new proto.jspb.test.Complex([
+ undefined, undefined, undefined, [],
+ undefined, undefined, undefined, undefined]);
+ var bar = new proto.jspb.test.Complex([
+ aString: undefined,
+ anOutOfOrderBool: undefined,
+ anInt: undefined
+ // Note: JsPb converts undefined repeated fields to empty arrays.
+ aRepeatedMessageList: [],
+ aRepeatedStringList: []
+ it('testSpecialCases', function() {
+ // Note: Some property names are reserved in JavaScript.
+ // These names are converted to the Js property named pb_<reserved_name>.
+ var special =
+ new proto.jspb.test.SpecialCases(['normal', 'default', 'function',
+ 'var']);
+ var result = special.toObject();
+ normal: 'normal',
+ pb_default: 'default',
+ pb_function: 'function',
+ pb_var: 'var'
+ it('testDefaultValues', function() {
+ var defaultString = "default<>\'\"abc";
+ var response = new proto.jspb.test.DefaultValues();
+ // Test toObject
+ var expectedObject = {
+ stringField: defaultString,
+ boolField: true,
+ intField: 11,
+ enumField: 13,
+ emptyField: '',
+ bytesField: 'bW9v'
+ assertObjectEquals(expectedObject, response.toObject());
+ // Test getters
+ response = new proto.jspb.test.DefaultValues();
+ assertEquals(defaultString, response.getStringField());
+ assertEquals(true, response.getBoolField());
+ assertEquals(11, response.getIntField());
+ assertEquals(13, response.getEnumField());
+ assertEquals('', response.getEmptyField());
+ assertEquals('bW9v', response.getBytesField());
+ function makeDefault(values) {
+ return new proto.jspb.test.DefaultValues(values);
+ // Test with undefined values,
+ // Use push to workaround IE treating undefined array elements as holes.
+ response = makeDefault([undefined, undefined, undefined, undefined]);
+ // Test with null values, as would be returned by a JSON serializer.
+ response = makeDefault([null, null, null, null]);
+ // Test with false-like values.
+ response = makeDefault(['', false, 0, 0]);
+ assertEquals('', response.getStringField());
+ assertEquals(false, response.getBoolField());
+ assertEquals(true, response.getIntField() == 0);
+ assertEquals(true, response.getEnumField() == 0);
+ // Test that clearing the values reverts them to the default state.
+ response = makeDefault(['blah', false, 111, 77]);
+ response.clearStringField(); response.clearBoolField();
+ response.clearIntField(); response.clearEnumField();
+ // Test that setFoo(null) clears the values.
+ response.setStringField(null); response.setBoolField(null);
+ response.setIntField(undefined); response.setEnumField(undefined);
+ it('testMessageRegistration', function() {
+ // goog.require(SomeResponse) will include its library, which will in
+ // turn add SomeResponse to the message registry.
+ assertEquals(jspb.Message.registry_['res'], proto.jspb.test.SomeResponse);
+ it('testClearFields', function() {
+ // We don't set 'proper' defaults, rather, bools, strings,
+ // etc, are cleared to undefined or null and take on the Javascript
+ // meaning for that value. Repeated fields are set to [] when cleared.
+ var data = ['str', true, [11], [[22], [33]], ['s1', 's2']];
+ var foo = new proto.jspb.test.OptionalFields(data);
+ foo.clearAString();
+ foo.clearABool();
+ foo.clearANestedMessage();
+ foo.clearARepeatedMessageList();
+ foo.clearARepeatedStringList();
+ assertUndefined(foo.getAString());
+ assertUndefined(foo.getABool());
+ assertUndefined(foo.getANestedMessage());
+ assertObjectEquals([], foo.getARepeatedMessageList());
+ assertObjectEquals([], foo.getARepeatedStringList());
+ // NOTE: We want the missing fields in 'expected' to be undefined,
+ // but we actually get a sparse array instead. We could use something
+ // like [1,undefined,2] to avoid this, except that this is still
+ // sparse on IE. No comment...
+ var expected = [,,, [], []];
+ expected[0] = expected[1] = expected[2] = undefined;
+ assertObjectEquals(expected, foo.toArray());
+ // Test set(null). We could deprecated this in favor of clear(), but
+ // it's also convenient to have.
+ data = ['str', true, [11], [[22], [33]], ['s1', 's2']];
+ foo = new proto.jspb.test.OptionalFields(data);
+ foo.setAString(null);
+ foo.setABool(null);
+ foo.setANestedMessage(null);
+ foo.setARepeatedMessageList(null);
+ foo.setARepeatedStringList(null);
+ assertNull(foo.getAString());
+ assertNull(foo.getABool());
+ assertNull(foo.getANestedMessage());
+ assertObjectEquals([null, null, null, [], []], foo.toArray());
+ // Test set(undefined). Again, not something we really need, and not
+ // supported directly by our typing, but it should 'do the right thing'.
+ foo.setAString(undefined);
+ foo.setABool(undefined);
+ foo.setANestedMessage(undefined);
+ foo.setARepeatedMessageList(undefined);
+ foo.setARepeatedStringList(undefined);
+ expected = [,,, [], []];
+ it('testDifferenceRawObject', function() {
+ var p1 = new proto.jspb.test.HasExtensions(['hi', 'diff', {}]);
+ var p2 = new proto.jspb.test.HasExtensions(['hi', 'what',
+ {1000: 'unique'}]);
+ var diff = /** @type {proto.jspb.test.HasExtensions} */
+ (jspb.Message.difference(p1, p2));
+ assertUndefined(diff.getStr1());
+ assertEquals('what', diff.getStr2());
+ assertUndefined(diff.getStr3());
+ assertEquals('unique', diff.extensionObject_[1000]);
+ it('testEqualsSimple', function() {
+ var s1 = new proto.jspb.test.Simple1(['hi']);
+ assertTrue(jspb.Message.equals(s1, new proto.jspb.test.Simple1(['hi'])));
+ assertFalse(jspb.Message.equals(s1, new proto.jspb.test.Simple1(['bye'])));
+ var s1b = new proto.jspb.test.Simple1(['hi', ['hello']]);
+ assertTrue(jspb.Message.equals(s1b,
+ new proto.jspb.test.Simple1(['hi', ['hello']])));
+ new proto.jspb.test.Simple1(['hi', ['hello', undefined,
+ undefined, undefined]])));
+ assertFalse(jspb.Message.equals(s1b,
+ new proto.jspb.test.Simple1(['no', ['hello']])));
+ // Test with messages of different types
+ var s2 = new proto.jspb.test.Simple2(['hi']);
+ assertFalse(jspb.Message.equals(s1, s2));
+ it('testEquals_softComparison', function() {
+ var s1 = new proto.jspb.test.Simple1(['hi', [], null]);
+ assertTrue(jspb.Message.equals(s1,
+ new proto.jspb.test.Simple1(['hi', []])));
+ var s1b = new proto.jspb.test.Simple1(['hi', [], true]);
+ new proto.jspb.test.Simple1(['hi', [], 1])));
+ it('testEqualsComplex', function() {
+ var data2 = ['a',,, [, 11], [[, 22], [, 34]],, ['s1', 's2'],, 1];
+ var data3 = ['a',,, [, 11], [[, 22]],, ['s1', 's2'],, 1];
+ var data4 = ['hi'];
+ var c1a = new proto.jspb.test.Complex(data1);
+ var c1b = new proto.jspb.test.Complex(data1);
+ var c2 = new proto.jspb.test.Complex(data2);
+ var c3 = new proto.jspb.test.Complex(data3);
+ var s1 = new proto.jspb.test.Simple1(data4);
+ assertTrue(jspb.Message.equals(c1a, c1b));
+ assertFalse(jspb.Message.equals(c1a, c2));
+ assertFalse(jspb.Message.equals(c2, c3));
+ assertFalse(jspb.Message.equals(c1a, s1));
+ it('testEqualsExtensionsConstructed', function() {
+ assertTrue(jspb.Message.equals(
+ new proto.jspb.test.HasExtensions([]),
+ new proto.jspb.test.HasExtensions([{}])
+ ));
+ new proto.jspb.test.HasExtensions(['hi', {100: [{200: 'a'}]}]),
+ new proto.jspb.test.HasExtensions(['hi', {100: [{200: 'a'}]}])
+ assertFalse(jspb.Message.equals(
+ new proto.jspb.test.HasExtensions(['hi', {100: [{200: 'b'}]}])
+ new proto.jspb.test.HasExtensions([{100: [{200: 'a'}]}]),
+ new proto.jspb.test.HasExtensions([{100: [{200: 'a'}]}])
+ new proto.jspb.test.HasExtensions([,,, {100: [{200: 'a'}]}])
+ new proto.jspb.test.HasExtensions([,,, {100: [{200: 'a'}]}]),
+ new proto.jspb.test.HasExtensions(['hi',,, {100: [{200: 'a'}]}])
+ new proto.jspb.test.HasExtensions(['hi',,, {100: [{200: 'a'}]}]),
+ it('testEqualsExtensionsUnconstructed', function() {
+ assertTrue(jspb.Message.compareFields([], [{}]));
+ assertTrue(jspb.Message.compareFields([,,, {}], []));
+ assertTrue(jspb.Message.compareFields([,,, {}], [,, {}]));
+ assertTrue(jspb.Message.compareFields(
+ ['hi', {100: [{200: 'a'}]}], ['hi', {100: [{200: 'a'}]}]));
+ assertFalse(jspb.Message.compareFields(
+ ['hi', {100: [{200: 'a'}]}], ['hi', {100: [{200: 'b'}]}]));
+ [{100: [{200: 'a'}]}], [{100: [{200: 'a'}]}]));
+ [{100: [{200: 'a'}]}], [,,, {100: [{200: 'a'}]}]));
+ [,,, {100: [{200: 'a'}]}], [{100: [{200: 'a'}]}]));
+ ['hi', {100: [{200: 'a'}]}], ['hi',,, {100: [{200: 'a'}]}]));
+ ['hi',,, {100: [{200: 'a'}]}], ['hi', {100: [{200: 'a'}]}]));
+ it('testToMap', function() {
+ var p1 = new proto.jspb.test.Simple1(['k', ['v']]);
+ var p2 = new proto.jspb.test.Simple1(['k1', ['v1', 'v2']]);
+ var soymap = jspb.Message.toMap([p1, p2],
+ proto.jspb.test.Simple1.prototype.getAString,
+ proto.jspb.test.Simple1.prototype.toObject);
+ assertEquals('k', soymap['k'].aString);
+ assertArrayEquals(['v'], soymap['k'].aRepeatedStringList);
+ var protomap = jspb.Message.toMap([p1, p2],
+ proto.jspb.test.Simple1.prototype.getAString);
+ assertEquals('k', protomap['k'].getAString());
+ assertArrayEquals(['v'], protomap['k'].getARepeatedStringList());
+ it('testClone', function() {
+ var original = new proto.jspb.test.TestClone();
+ original.setStr('v1');
+ var simple1 = new proto.jspb.test.Simple1(['x1', ['y1', 'z1']]);
+ var simple2 = new proto.jspb.test.Simple1(['x2', ['y2', 'z2']]);
+ var simple3 = new proto.jspb.test.Simple1(['x3', ['y3', 'z3']]);
+ original.setSimple1(simple1);
+ original.setSimple2List([simple2, simple3]);
+ var extension = new proto.jspb.test.CloneExtension();
+ extension.setExt('e1');
+ original.setExtension(proto.jspb.test.IsExtension.extField, extension);
+ var clone = original.cloneMessage();
+ assertArrayEquals(['v1',, ['x1', ['y1', 'z1']],,
+ [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]],,, { 100: [, 'e1'] }],
+ clone.toArray());
+ clone.setStr('v2');
+ var simple4 = new proto.jspb.test.Simple1(['a1', ['b1', 'c1']]);
+ var simple5 = new proto.jspb.test.Simple1(['a2', ['b2', 'c2']]);
+ var simple6 = new proto.jspb.test.Simple1(['a3', ['b3', 'c3']]);
+ clone.setSimple1(simple4);
+ clone.setSimple2List([simple5, simple6]);
+ var newExtension = new proto.jspb.test.CloneExtension();
+ newExtension.setExt('e2');
+ clone.setExtension(proto.jspb.test.CloneExtension.extField, newExtension);
+ assertArrayEquals(['v2',, ['a1', ['b1', 'c1']],,
+ [['a2', ['b2', 'c2']], ['a3', ['b3', 'c3']]],,, { 100: [, 'e2'] }],
+ original.toArray());
+ it('testCopyInto', function() {
+ var dest = new proto.jspb.test.TestClone();
+ dest.setStr('override');
+ var destSimple1 = new proto.jspb.test.Simple1(['ox1', ['oy1', 'oz1']]);
+ var destSimple2 = new proto.jspb.test.Simple1(['ox2', ['oy2', 'oz2']]);
+ var destSimple3 = new proto.jspb.test.Simple1(['ox3', ['oy3', 'oz3']]);
+ dest.setSimple1(destSimple1);
+ dest.setSimple2List([destSimple2, destSimple3]);
+ original.setExtension(proto.jspb.test.CloneExtension.extField, extension);
+ jspb.Message.copyInto(original, dest);
+ assertArrayEquals(original.toArray(), dest.toArray());
+ assertEquals('x1', dest.getSimple1().getAString());
+ assertEquals('e1',
+ dest.getExtension(proto.jspb.test.CloneExtension.extField).getExt());
+ dest.getSimple1().setAString('new value');
+ assertNotEquals(dest.getSimple1().getAString(),
+ original.getSimple1().getAString());
+ dest.getExtension(proto.jspb.test.CloneExtension.extField).
+ setExt('new value');
+ dest.getExtension(proto.jspb.test.CloneExtension.extField).getExt(),
+ original.getExtension(
+ proto.jspb.test.CloneExtension.extField).getExt());
+ it('testCopyInto_notSameType', function() {
+ var a = new proto.jspb.test.TestClone();
+ var b = new proto.jspb.test.Simple1(['str', ['s1', 's2']]);
+ var e = assertThrows(function() {
+ jspb.Message.copyInto(a, b);
+ assertContains('should have the same type', e.message);
+ var extension1 = new proto.jspb.test.IsExtension(['ext1field']);
+ var extension2 = new proto.jspb.test.Simple1(['str', ['s1', 's2']]);
+ var extendable = new proto.jspb.test.HasExtensions(['v1', 'v2', 'v3']);
+ extendable.setExtension(proto.jspb.test.IsExtension.extField, extension1);
+ extendable.setExtension(proto.jspb.test.IndirectExtension.simple,
+ extension2);
+ extendable.setExtension(proto.jspb.test.IndirectExtension.str, 'xyzzy');
+ extendable.setExtension(proto.jspb.test.IndirectExtension.repeatedStrList,
+ ['a', 'b']);
+ var s1 = new proto.jspb.test.Simple1(['foo', ['s1', 's2']]);
+ var s2 = new proto.jspb.test.Simple1(['bar', ['t1', 't2']]);
+ extendable.setExtension(
+ proto.jspb.test.IndirectExtension.repeatedSimpleList,
+ [s1, s2]);
+ assertObjectEquals(extension1,
+ extendable.getExtension(proto.jspb.test.IsExtension.extField));
+ assertObjectEquals(extension2,
+ extendable.getExtension(proto.jspb.test.IndirectExtension.simple));
+ assertObjectEquals('xyzzy',
+ extendable.getExtension(proto.jspb.test.IndirectExtension.str));
+ assertObjectEquals(['a', 'b'], extendable.getExtension(
+ proto.jspb.test.IndirectExtension.repeatedStrList));
+ assertObjectEquals([s1, s2], extendable.getExtension(
+ proto.jspb.test.IndirectExtension.repeatedSimpleList));
+ // Not supported yet, but it should work...
+ extendable.setExtension(proto.jspb.test.IndirectExtension.simple, null);
+ assertNull(
+ extendable.setExtension(proto.jspb.test.IndirectExtension.str, null);
+ assertNull(extendable.getExtension(proto.jspb.test.IndirectExtension.str));
+ // These assertions will only work properly in uncompiled mode.
+ // Extension fields defined on proto2 Descriptor messages are filtered out.
+ assertUndefined(proto.jspb.test.IsExtension['simpleOption']);
+ // Extension fields with jspb.ignore = true are ignored.
+ assertUndefined(proto.jspb.test.IndirectExtension['ignored']);
+ assertUndefined(proto.jspb.test.HasExtensions['ignoredFloating']);
+ it('testFloatingExtensions', function() {
+ // From an autogenerated container.
+ var extension = new proto.jspb.test.Simple1(['foo', ['s1', 's2']]);
+ extendable.setExtension(proto.jspb.test.simple1, extension);
+ assertObjectEquals(extension,
+ extendable.getExtension(proto.jspb.test.simple1));
+ // From _lib mode.
+ extension = new proto.jspb.test.ExtensionMessage(['s1']);
+ extendable = new proto.jspb.test.TestExtensionsMessage([16]);
+ extendable.setExtension(proto.jspb.test.floatingMsgField, extension);
+ extendable.setExtension(proto.jspb.test.floatingStrField, 's2');
+ extendable.getExtension(proto.jspb.test.floatingMsgField));
+ assertObjectEquals('s2',
+ extendable.getExtension(proto.jspb.test.floatingStrField));
+ assertNotUndefined(proto.jspb.exttest.floatingMsgField);
+ assertNotUndefined(proto.jspb.exttest.floatingMsgFieldTwo);
+ assertNotUndefined(proto.jspb.exttest.beta.floatingStrField);
+ it('testToObject_extendedObject', function() {
+ var extension2 = new proto.jspb.test.Simple1(['str', ['s1', 's2'], true]);
+ var s1 = new proto.jspb.test.Simple1(['foo', ['s1', 's2'], true]);
+ var s2 = new proto.jspb.test.Simple1(['bar', ['t1', 't2'], false]);
+ str1: 'v1', str2: 'v2', str3: 'v3',
+ extField: { ext1: 'ext1field' },
+ simple: {
+ aString: 'str', aRepeatedStringList: ['s1', 's2'], aBoolean: true
+ str: 'xyzzy',
+ repeatedStrList: ['a', 'b'],
+ repeatedSimpleList: [
+ { aString: 'foo', aRepeatedStringList: ['s1', 's2'], aBoolean: true},
+ { aString: 'bar', aRepeatedStringList: ['t1', 't2'], aBoolean: false}
+ }, extendable.toObject());
+ // Now, with instances included.
+ extField: {
+ ext1: 'ext1field',
+ $jspbMessageInstance:
+ extendable.getExtension(proto.jspb.test.IsExtension.extField)
+ aString: 'str',
+ aBoolean: true,
+ extendable.getExtension(proto.jspb.test.IndirectExtension.simple)
+ repeatedSimpleList: [{
+ aString: 'foo',
+ $jspbMessageInstance: s1
+ }, {
+ aString: 'bar',
+ aRepeatedStringList: ['t1', 't2'],
+ aBoolean: false,
+ $jspbMessageInstance: s2
+ }],
+ $jspbMessageInstance: extendable
+ }, extendable.toObject(true /* opt_includeInstance */));
+ it('testInitialization_emptyArray', function() {
+ var msg = new proto.jspb.test.HasExtensions([]);
+ if (jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS) {
+ assertArrayEquals([], msg.toArray());
+ // Extension object is created past all regular fields.
+ assertArrayEquals([,,, {}], msg.toArray());
+ it('testInitialization_justExtensionObject', function() {
+ var msg = new proto.jspb.test.Empty([{1: 'hi'}]);
+ // The extensionObject is not moved from its original location.
+ assertArrayEquals([{1: 'hi'}], msg.toArray());
+ it('testInitialization_incompleteList', function() {
+ var msg = new proto.jspb.test.Empty([1, {4: 'hi'}]);
+ assertArrayEquals([1, {4: 'hi'}], msg.toArray());
+ it('testInitialization_forwardCompatible', function() {
+ var msg = new proto.jspb.test.Empty([1, 2, 3, {1: 'hi'}]);
+ assertArrayEquals([1, 2, 3, {1: 'hi'}], msg.toArray());
+ it('testExtendedMessageEnsureObject', function() {
+ var data = new proto.jspb.test.HasExtensions(['str1',
+ {'a_key': 'an_object'}]);
+ assertEquals('an_object', data.extensionObject_['a_key']);
+ it('testToObject_hasExtensionField', function() {
+ var data = new proto.jspb.test.HasExtensions(['str1', {100: ['ext1']}]);
+ var obj = data.toObject();
+ assertEquals('str1', obj.str1);
+ assertEquals('ext1', obj.extField.ext1);
+ it('testGetExtension', function() {
+ assertEquals('str1', data.getStr1());
+ var extension = data.getExtension(proto.jspb.test.IsExtension.extField);
+ assertNotNull(extension);
+ assertEquals('ext1', extension.getExt1());
+ it('testSetExtension', function() {
+ var data = new proto.jspb.test.HasExtensions();
+ var extensionMessage = new proto.jspb.test.IsExtension(['is_extension']);
+ data.setExtension(proto.jspb.test.IsExtension.extField, extensionMessage);
+ assertNotNull(
+ data.getExtension(proto.jspb.test.IsExtension.extField));
+ assertEquals('is_extension', obj.extField.ext1);
+ * Note that group is long deprecated, we only support it because JsPb has
+ * a goal of being able to generate JS classes for all proto descriptors.
+ it('testGroups', function() {
+ var group = new proto.jspb.test.TestGroup();
+ var someGroup = new proto.jspb.test.TestGroup.RepeatedGroup();
+ someGroup.setId('g1');
+ someGroup.setSomeBoolList([true, false]);
+ group.setRepeatedGroupList([someGroup]);
+ var groups = group.getRepeatedGroupList();
+ assertEquals('g1', groups[0].getId());
+ assertObjectEquals([true, false], groups[0].getSomeBoolList());
+ assertObjectEquals({id: 'g1', someBoolList: [true, false]},
+ groups[0].toObject());
+ repeatedGroupList: [{id: 'g1', someBoolList: [true, false]}],
+ requiredGroup: {id: undefined},
+ optionalGroup: undefined,
+ requiredSimple: {aRepeatedStringList: [], aString: undefined},
+ optionalSimple: undefined,
+ id: undefined
+ }, group.toObject());
+ var group1 = new proto.jspb.test.TestGroup1();
+ group1.setGroup(someGroup);
+ assertEquals(someGroup, group1.getGroup());
+ it('testNonExtensionFieldsAfterExtensionRange', function() {
+ var data = [{'1': 'a_string'}];
+ var message = new proto.jspb.test.Complex(data);
+ assertArrayEquals([], message.getARepeatedStringList());
+ it('testReservedGetterNames', function() {
+ var message = new proto.jspb.test.TestReservedNames();
+ message.setExtension$(11);
+ message.setExtension(proto.jspb.test.TestReservedNamesExtension.foo, 12);
+ assertEquals(11, message.getExtension$());
+ assertEquals(12, message.getExtension(
+ proto.jspb.test.TestReservedNamesExtension.foo));
+ assertObjectEquals({extension: 11, foo: 12}, message.toObject());
+ it('testInitializeMessageWithUnsetOneof', function() {
+ var message = new proto.jspb.test.TestMessageWithOneof([]);
+ proto.jspb.test.TestMessageWithOneof.PartialOneofCase.
+ PARTIAL_ONEOF_NOT_SET,
+ message.getPartialOneofCase());
+ proto.jspb.test.TestMessageWithOneof.RecursiveOneofCase.
+ RECURSIVE_ONEOF_NOT_SET,
+ message.getRecursiveOneofCase());
+ it('testInitializeMessageWithSingleValueSetInOneof', function() {
+ var message = new proto.jspb.test.TestMessageWithOneof([,, 'x']);
+ assertEquals('x', message.getPone());
+ assertUndefined(message.getPthree());
+ proto.jspb.test.TestMessageWithOneof.PartialOneofCase.PONE,
+ it('testKeepsLastWireValueSetInUnion_multipleValues', function() {
+ var message = new proto.jspb.test.TestMessageWithOneof([,, 'x',, 'y']);
+ assertUndefined('x', message.getPone());
+ assertEquals('y', message.getPthree());
+ proto.jspb.test.TestMessageWithOneof.PartialOneofCase.PTHREE,
+ it('testSettingOneofFieldClearsOthers', function() {
+ var message = new proto.jspb.test.TestMessageWithOneof;
+ assertUndefined(message.getPone());
+ message.setPone('hi');
+ assertEquals('hi', message.getPone());
+ message.setPthree('bye');
+ assertEquals('bye', message.getPthree());
+ it('testSettingOneofFieldDoesNotClearFieldsFromOtherUnions', function() {
+ var other = new proto.jspb.test.TestMessageWithOneof;
+ assertUndefined(message.getRone());
+ message.setRone(other);
+ assertEquals(other, message.getRone());
+ it('testUnsetsOneofCaseWhenFieldIsCleared', function() {
+ message.clearPone();
+ it('testMessageWithDefaultOneofValues', function() {
+ assertEquals(1234, message.getAone());
+ assertUndefined(message.getAtwo());
+ proto.jspb.test.TestMessageWithOneof.DefaultOneofACase
+ .DEFAULT_ONEOF_A_NOT_SET,
+ message.getDefaultOneofACase());
+ message.setAone(567);
+ assertEquals(567, message.getAone());
+ proto.jspb.test.TestMessageWithOneof.DefaultOneofACase.AONE,
+ message.setAtwo(890);
+ assertEquals(890, message.getAtwo());
+ proto.jspb.test.TestMessageWithOneof.DefaultOneofACase.ATWO,
+ message.clearAtwo();
+ it('testMessageWithDefaultOneofValues_defaultNotOnFirstField', function() {
+ assertUndefined(message.getBone());
+ assertEquals(1234, message.getBtwo());
+ proto.jspb.test.TestMessageWithOneof.DefaultOneofBCase
+ .DEFAULT_ONEOF_B_NOT_SET,
+ message.getDefaultOneofBCase());
+ message.setBone(2);
+ assertEquals(2, message.getBone());
+ proto.jspb.test.TestMessageWithOneof.DefaultOneofBCase.BONE,
+ message.setBtwo(3);
+ assertEquals(3, message.getBtwo());
+ proto.jspb.test.TestMessageWithOneof.DefaultOneofBCase.BTWO,
+ message.clearBtwo();
+ it('testInitializeMessageWithOneofDefaults', function() {
+ var message =
+ new proto.jspb.test.TestMessageWithOneof(new Array(9).concat(567));
+ message =
+ new proto.jspb.test.TestMessageWithOneof(new Array(10).concat(890));
+ new proto.jspb.test.TestMessageWithOneof(new Array(9).concat(567,890));
+ it('testInitializeMessageWithOneofDefaults_defaultNotSetOnFirstField',
+ function() {
+ var message;
+ new proto.jspb.test.TestMessageWithOneof(new Array(11).concat(567));
+ assertEquals(567, message.getBone());
+ new proto.jspb.test.TestMessageWithOneof(new Array(12).concat(890));
+ assertEquals(890, message.getBtwo());
+ message = new proto.jspb.test.TestMessageWithOneof(
+ new Array(11).concat(567,890));
+ it('testOneofContainingAnotherMessage', function() {
+ assertUndefined(message.getRtwo());
+ proto.jspb.test.TestMessageWithOneof.RecursiveOneofCase.RONE,
+ message.setRtwo('hi');
+ assertEquals('hi', message.getRtwo());
+ proto.jspb.test.TestMessageWithOneof.RecursiveOneofCase.RTWO,
+ it('testQueryingOneofCaseEnsuresOnlyOneFieldIsSetInUnderlyingArray',
+ message.setPone('x');
+ var array = message.toArray();
+ assertEquals('x', array[2]);
+ assertUndefined(array[4]);
+ array[4] = 'y';
+ assertUndefined(array[2]);
+ assertEquals('y', array[4]);
@@ -0,0 +1,279 @@
+goog.require('proto.jspb.test.Proto3Enum');
+goog.require('proto.jspb.test.TestProto3');
+describe('proto3Test', function() {
+ * Test defaults for proto3 message fields.
+ it('testProto3FieldDefaults', function() {
+ var msg = new proto.jspb.test.TestProto3();
+ assertEquals(msg.getOptionalInt32(), 0);
+ assertEquals(msg.getOptionalInt64(), 0);
+ assertEquals(msg.getOptionalUint32(), 0);
+ assertEquals(msg.getOptionalUint64(), 0);
+ assertEquals(msg.getOptionalSint32(), 0);
+ assertEquals(msg.getOptionalSint64(), 0);
+ assertEquals(msg.getOptionalFixed32(), 0);
+ assertEquals(msg.getOptionalFixed64(), 0);
+ assertEquals(msg.getOptionalSfixed32(), 0);
+ assertEquals(msg.getOptionalSfixed64(), 0);
+ assertEquals(msg.getOptionalFloat(), 0);
+ assertEquals(msg.getOptionalDouble(), 0);
+ assertEquals(msg.getOptionalString(), '');
+ // If/when we change bytes fields to return Uint8Array, we'll want to switch
+ // to this assertion instead:
+ //assertEquals(msg.getOptionalBytes() instanceof Uint8Array, true);
+ assertEquals(typeof msg.getOptionalBytes(), 'string');
+ assertEquals(msg.getOptionalBytes().length, 0);
+ assertEquals(msg.getOptionalForeignEnum(), proto.jspb.test.Proto3Enum.PROTO3_FOO);
+ assertEquals(msg.getOptionalForeignMessage(), undefined);
+ assertEquals(msg.getRepeatedInt32List().length, 0);
+ assertEquals(msg.getRepeatedInt64List().length, 0);
+ assertEquals(msg.getRepeatedUint32List().length, 0);
+ assertEquals(msg.getRepeatedUint64List().length, 0);
+ assertEquals(msg.getRepeatedSint32List().length, 0);
+ assertEquals(msg.getRepeatedSint64List().length, 0);
+ assertEquals(msg.getRepeatedFixed32List().length, 0);
+ assertEquals(msg.getRepeatedFixed64List().length, 0);
+ assertEquals(msg.getRepeatedSfixed32List().length, 0);
+ assertEquals(msg.getRepeatedSfixed64List().length, 0);
+ assertEquals(msg.getRepeatedFloatList().length, 0);
+ assertEquals(msg.getRepeatedDoubleList().length, 0);
+ assertEquals(msg.getRepeatedStringList().length, 0);
+ assertEquals(msg.getRepeatedBytesList().length, 0);
+ assertEquals(msg.getRepeatedForeignEnumList().length, 0);
+ assertEquals(msg.getRepeatedForeignMessageList().length, 0);
+ * Test that all fields can be set and read via a serialization roundtrip.
+ it('testProto3FieldSetGet', function() {
+ msg.setOptionalForeignEnum(proto.jspb.test.Proto3Enum.PROTO3_BAR);
+ msg.setRepeatedForeignEnumList([proto.jspb.test.Proto3Enum.PROTO3_BAR]);
+ msg.setOneofString('asdf');
+ var serialized = msg.serializeBinary();
+ msg = proto.jspb.test.TestProto3.deserializeBinary(serialized);
+ proto.jspb.test.Proto3Enum.PROTO3_BAR);
+ [proto.jspb.test.Proto3Enum.PROTO3_BAR]);
+ assertEquals(msg.getOneofString(), 'asdf');
+ * Test that oneofs continue to have a notion of field presence.
+ it('testOneofs', function() {
+ assertEquals(msg.getOneofUint32(), undefined);
+ assertEquals(msg.getOneofForeignMessage(), undefined);
+ assertEquals(msg.getOneofString(), undefined);
+ assertEquals(msg.getOneofBytes(), undefined);
+ msg.setOneofUint32(42);
+ assertEquals(msg.getOneofUint32(), 42);
+ msg.setOneofForeignMessage(submsg);
+ assertEquals(msg.getOneofForeignMessage(), submsg);
+ msg.setOneofString('hello');
+ assertEquals(msg.getOneofString(), 'hello');
+ msg.setOneofBytes('\u00FF\u00FF');
+ assertEquals(msg.getOneofBytes(), '\u00FF\u00FF');
+ * Test that "default"-valued primitive fields are not emitted on the wire.
+ it('testNoSerializeDefaults', function() {
+ // Set each primitive to a non-default value, then back to its default, to
+ // ensure that the serialization is actually checking the value and not just
+ // whether it has ever been set.
+ msg.setOptionalInt32(42);
+ msg.setOptionalInt32(0);
+ msg.setOptionalDouble(3.14);
+ msg.setOptionalDouble(0.0);
+ msg.setOptionalBool(false);
+ msg.setOptionalString('');
+ msg.setOptionalBytes('\u00FF\u00FF');
+ msg.setOptionalBytes('');
+ msg.setOptionalForeignMessage(new proto.jspb.test.ForeignMessage());
+ msg.setOptionalForeignMessage(null);
+ msg.setOptionalForeignEnum(proto.jspb.test.Proto3Enum.PROTO3_FOO);
+ msg.setOneofUint32(32);
+ msg.setOneofUint32(null);
+ assertEquals(0, serialized.length);
@@ -0,0 +1,89 @@
+syntax = "proto3";
+import "testbinary.proto";
+message TestProto3 {
+ int32 optional_int32 = 1;
+ int64 optional_int64 = 2;
+ uint32 optional_uint32 = 3;
+ uint64 optional_uint64 = 4;
+ sint32 optional_sint32 = 5;
+ sint64 optional_sint64 = 6;
+ fixed32 optional_fixed32 = 7;
+ fixed64 optional_fixed64 = 8;
+ sfixed32 optional_sfixed32 = 9;
+ sfixed64 optional_sfixed64 = 10;
+ float optional_float = 11;
+ double optional_double = 12;
+ bool optional_bool = 13;
+ string optional_string = 14;
+ bytes optional_bytes = 15;
+ ForeignMessage optional_foreign_message = 19;
+ Proto3Enum optional_foreign_enum = 22;
+ repeated int32 repeated_int32 = 31;
+ repeated int64 repeated_int64 = 32;
+ repeated uint32 repeated_uint32 = 33;
+ repeated uint64 repeated_uint64 = 34;
+ repeated sint32 repeated_sint32 = 35;
+ repeated sint64 repeated_sint64 = 36;
+ repeated fixed32 repeated_fixed32 = 37;
+ repeated fixed64 repeated_fixed64 = 38;
+ repeated sfixed32 repeated_sfixed32 = 39;
+ repeated sfixed64 repeated_sfixed64 = 40;
+ repeated float repeated_float = 41;
+ repeated double repeated_double = 42;
+ repeated bool repeated_bool = 43;
+ repeated string repeated_string = 44;
+ repeated bytes repeated_bytes = 45;
+ repeated ForeignMessage repeated_foreign_message = 49;
+ repeated Proto3Enum repeated_foreign_enum = 52;
+ uint32 oneof_uint32 = 111;
+ ForeignMessage oneof_foreign_message = 112;
+ string oneof_string = 113;
+ bytes oneof_bytes = 114;
+enum Proto3Enum {
+ PROTO3_FOO = 0;
+ PROTO3_BAR = 1;
+ PROTO3_BAZ = 2;
@@ -0,0 +1,212 @@
+import "google/protobuf/descriptor.proto";
+message Empty {
+enum OuterEnum {
+ FOO = 1;
+ BAR = 2;
+message EnumContainer {
+ optional OuterEnum outer_enum = 1;
+message Simple1 {
+ required string a_string = 1;
+ repeated string a_repeated_string = 2;
+ optional bool a_boolean = 3;
+// A message that differs from Simple1 only by name
+message Simple2 {
+message SpecialCases {
+ required string normal = 1;
+ // Examples of Js reserved names that are converted to pb_<name>.
+ required string default = 2;
+ required string function = 3;
+ required string var = 4;
+message OptionalFields {
+ message Nested {
+ optional int32 an_int = 1;
+ optional string a_string = 1;
+ required bool a_bool = 2;
+ optional Nested a_nested_message = 3;
+ repeated Nested a_repeated_message = 4;
+ repeated string a_repeated_string = 5;
+message HasExtensions {
+ optional string str1 = 1;
+ optional string str2 = 2;
+ optional string str3 = 3;
+ extensions 10 to max;
+message Complex {
+ required int32 an_int = 2;
+ required bool an_out_of_order_bool = 9;
+ optional Nested a_nested_message = 4;
+ repeated Nested a_repeated_message = 5;
+ repeated string a_repeated_string = 7;
+message IsExtension {
+ extend HasExtensions {
+ optional IsExtension ext_field = 100;
+ optional string ext1 = 1;
+ // Extensions of proto2 Descriptor messages will be ignored.
+ extend google.protobuf.EnumOptions {
+ optional string simple_option = 42113038;
+message IndirectExtension {
+ optional Simple1 simple = 101;
+ optional string str = 102;
+ repeated string repeated_str = 103;
+ repeated Simple1 repeated_simple = 104;
+extend HasExtensions {
+ optional Simple1 simple1 = 105;
+message DefaultValues {
+ enum Enum {
+ E1 = 13;
+ E2 = 77;
+ optional string string_field = 1 [default="default<>\'\"abc"];
+ optional bool bool_field = 2 [default=true];
+ optional int64 int_field = 3 [default=11];
+ optional Enum enum_field = 4 [default=E1];
+ optional string empty_field = 6 [default=""];
+ optional bytes bytes_field = 8 [default="moo"]; // Base64 encoding is "bW9v"
+message TestClone {
+ optional string str = 1;
+ optional Simple1 simple1 = 3;
+ repeated Simple1 simple2 = 5;
+ optional string unused = 7;
+message CloneExtension {
+ extend TestClone {
+ optional CloneExtension ext_field = 100;
+ optional string ext = 2;
+message TestGroup {
+ repeated group RepeatedGroup = 1 {
+ required string id = 1;
+ repeated bool some_bool = 2;
+ required group RequiredGroup = 2 {
+ optional group OptionalGroup = 3 {
+ optional string id = 4;
+ required Simple2 required_simple = 5;
+ optional Simple2 optional_simple = 6;
+message TestGroup1 {
+ optional TestGroup.RepeatedGroup group = 1;
+message TestReservedNames {
+ optional int32 extension = 1;
+message TestReservedNamesExtension {
+ extend TestReservedNames {
+ optional int32 foo = 10;
+message TestMessageWithOneof {
+ oneof partial_oneof {
+ string pone = 3;
+ string pthree = 5;
+ oneof recursive_oneof {
+ TestMessageWithOneof rone = 6;
+ string rtwo = 7;
+ optional bool normal_field = 8;
+ repeated string repeated_field = 9;
+ oneof default_oneof_a {
+ int32 aone = 10 [default = 1234];
+ int32 atwo = 11;
+ oneof default_oneof_b {
+ int32 bone = 12;
+ int32 btwo = 13 [default = 1234];
@@ -0,0 +1,54 @@
+message TestExtensionsMessage {
+ optional int32 intfield = 1;
+ extensions 100 to max;
+message ExtensionMessage {
+ extend TestExtensionsMessage {
+ optional ExtensionMessage ext_field = 100;
+// Floating extensions are only supported when generating a _lib.js library.
+extend TestExtensionsMessage {
+ optional ExtensionMessage floating_msg_field = 101;
+ optional string floating_str_field = 102;
@@ -0,0 +1,53 @@
+package jspb.exttest;
@@ -0,0 +1,42 @@
+import "test3.proto";
+ optional ExtensionMessage floating_msg_field_two = 103;
@@ -0,0 +1,44 @@
+package jspb.exttest.beta;
+message TestBetaExtensionsMessage {
+extend TestBetaExtensionsMessage {
+ optional string floating_str_field = 101;
@@ -0,0 +1,41 @@
+ * @fileoverview Sets flags for uncompiled JSUnit tests.
+ * Set uncompiled flags.
+var CLOSURE_DEFINES = {
+ // Enable the fromObject method on the message class.
+ 'jspb.Message.GENERATE_FROM_OBJECT': true
@@ -0,0 +1,185 @@
+// LINT: ALLOW_GROUPS
+// These types are borrowed from `unittest.proto` in the protobuf tree. We want
+// to ensure that the binary-format support will handle all field types
+// properly.
+message TestAllTypes {
+ optional int32 optional_int32 = 1;
+ optional int64 optional_int64 = 2;
+ optional uint32 optional_uint32 = 3;
+ optional uint64 optional_uint64 = 4;
+ optional sint32 optional_sint32 = 5;
+ optional sint64 optional_sint64 = 6;
+ optional fixed32 optional_fixed32 = 7;
+ optional fixed64 optional_fixed64 = 8;
+ optional sfixed32 optional_sfixed32 = 9;
+ optional sfixed64 optional_sfixed64 = 10;
+ optional float optional_float = 11;
+ optional double optional_double = 12;
+ optional bool optional_bool = 13;
+ optional string optional_string = 14;
+ optional bytes optional_bytes = 15;
+ optional group OptionalGroup = 16 {
+ optional int32 a = 17;
+ optional ForeignMessage optional_foreign_message = 19;
+ optional ForeignEnum optional_foreign_enum = 22;
+ // Repeated
+ repeated group RepeatedGroup = 46 {
+ optional int32 a = 47;
+ repeated ForeignEnum repeated_foreign_enum = 52;
+ // Packed repeated
+ repeated int32 packed_repeated_int32 = 61 [packed=true];
+ repeated int64 packed_repeated_int64 = 62 [packed=true];
+ repeated uint32 packed_repeated_uint32 = 63 [packed=true];
+ repeated uint64 packed_repeated_uint64 = 64 [packed=true];
+ repeated sint32 packed_repeated_sint32 = 65 [packed=true];
+ repeated sint64 packed_repeated_sint64 = 66 [packed=true];
+ repeated fixed32 packed_repeated_fixed32 = 67 [packed=true];
+ repeated fixed64 packed_repeated_fixed64 = 68 [packed=true];
+ repeated sfixed32 packed_repeated_sfixed32 = 69 [packed=true];
+ repeated sfixed64 packed_repeated_sfixed64 = 70 [packed=true];
+ repeated float packed_repeated_float = 71 [packed=true];
+ repeated double packed_repeated_double = 72 [packed=true];
+ repeated bool packed_repeated_bool = 73 [packed=true];
+message ForeignMessage {
+ optional int32 c = 1;
+enum ForeignEnum {
+ FOREIGN_FOO = 4;
+ FOREIGN_BAR = 5;
+ FOREIGN_BAZ = 6;
+message TestExtendable {
+ extensions 1 to max;
+message ExtendsWithMessage {
+ extend TestExtendable {
+ optional ExtendsWithMessage optional_extension = 19;
+ repeated ExtendsWithMessage repeated_extension = 49;
+ optional int32 foo = 1;
+extend TestExtendable {
+ optional int32 extend_optional_int32 = 1;
+ optional int64 extend_optional_int64 = 2;
+ optional uint32 extend_optional_uint32 = 3;
+ optional uint64 extend_optional_uint64 = 4;
+ optional sint32 extend_optional_sint32 = 5;
+ optional sint64 extend_optional_sint64 = 6;
+ optional fixed32 extend_optional_fixed32 = 7;
+ optional fixed64 extend_optional_fixed64 = 8;
+ optional sfixed32 extend_optional_sfixed32 = 9;
+ optional sfixed64 extend_optional_sfixed64 = 10;
+ optional float extend_optional_float = 11;
+ optional double extend_optional_double = 12;
+ optional bool extend_optional_bool = 13;
+ optional string extend_optional_string = 14;
+ optional bytes extend_optional_bytes = 15;
+ optional ForeignEnum extend_optional_foreign_enum = 22;
+ repeated int32 extend_repeated_int32 = 31;
+ repeated int64 extend_repeated_int64 = 32;
+ repeated uint32 extend_repeated_uint32 = 33;
+ repeated uint64 extend_repeated_uint64 = 34;
+ repeated sint32 extend_repeated_sint32 = 35;
+ repeated sint64 extend_repeated_sint64 = 36;
+ repeated fixed32 extend_repeated_fixed32 = 37;
+ repeated fixed64 extend_repeated_fixed64 = 38;
+ repeated sfixed32 extend_repeated_sfixed32 = 39;
+ repeated sfixed64 extend_repeated_sfixed64 = 40;
+ repeated float extend_repeated_float = 41;
+ repeated double extend_repeated_double = 42;
+ repeated bool extend_repeated_bool = 43;
+ repeated string extend_repeated_string = 44;
+ repeated bytes extend_repeated_bytes = 45;
+ repeated ForeignEnum extend_repeated_foreign_enum = 52;
+ repeated int32 extend_packed_repeated_int32 = 61 [packed=true];
+ repeated int64 extend_packed_repeated_int64 = 62 [packed=true];
+ repeated uint32 extend_packed_repeated_uint32 = 63 [packed=true];
+ repeated uint64 extend_packed_repeated_uint64 = 64 [packed=true];
+ repeated sint32 extend_packed_repeated_sint32 = 65 [packed=true];
+ repeated sint64 extend_packed_repeated_sint64 = 66 [packed=true];
+ repeated fixed32 extend_packed_repeated_fixed32 = 67 [packed=true];
+ repeated fixed64 extend_packed_repeated_fixed64 = 68 [packed=true];
+ repeated sfixed32 extend_packed_repeated_sfixed32 = 69 [packed=true];
+ repeated sfixed64 extend_packed_repeated_sfixed64 = 70 [packed=true];
+ repeated float extend_packed_repeated_float = 71 [packed=true];
+ repeated double extend_packed_repeated_double = 72 [packed=true];
+ repeated bool extend_packed_repeated_bool = 73 [packed=true];
+ repeated ForeignEnum extend_packed_repeated_foreign_enum = 82
+ [packed=true];
@@ -0,0 +1,34 @@
+package javatests.com.google.apps.jspb;
@@ -34,6 +34,7 @@ typedef GPB_ENUM(GPBAny_FieldNumber) {
// `Any` contains an arbitrary serialized message along with a URL
// that describes the type of the serialized message.
// JSON
// ====
// The JSON representation of an `Any` value uses the regular
@@ -54,8 +55,8 @@ typedef GPB_ENUM(GPBAny_FieldNumber) {
// If the embedded message type is well-known and has a custom JSON
// representation, that representation will be embedded adding a field
-// `value` which holds the custom JSON in addition to the the `@type`
-// field. Example (for message [google.protobuf.Duration][google.protobuf.Duration]):
+// `value` which holds the custom JSON in addition to the `@type`
+// field. Example (for message [google.protobuf.Duration][]):
// {
// "@type": "type.googleapis.com/google.protobuf.Duration",
@@ -72,7 +73,7 @@ typedef GPB_ENUM(GPBAny_FieldNumber) {
// * If no schema is provided, `https` is assumed.
// * The last segment of the URL's path must represent the fully
// qualified name of the type (as in `path/google.protobuf.Duration`).
-// * An HTTP GET on the URL must yield a [google.protobuf.Type][google.protobuf.Type]
+// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
// value in binary format, or produce an error.
// * Applications are allowed to cache lookup results based on the
// URL, or have them precompiled into a binary to avoid any
@@ -174,7 +174,6 @@ typedef GPB_ENUM(GPBMixin_FieldNumber) {
// package google.storage.v2;
// service Storage {
-// // (-- see AccessControl.GetAcl --)
// rpc GetAcl(GetAclRequest) returns (Acl);
// // Get a data record.
@@ -61,7 +61,7 @@ typedef GPB_ENUM(GPBFieldMask_FieldNumber) {
// z: 8
// The result will not contain specific values for fields x,y and z
-// (there value will be set to the default, and omitted in proto text
+// (their value will be set to the default, and omitted in proto text
// output):
@@ -18,13 +18,13 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Enum GPBSyntax
-// Syntax specifies the syntax in which a service element was defined.
+// The syntax in which a protocol buffer element is defined.
typedef GPB_ENUM(GPBSyntax) {
GPBSyntax_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
- // Syntax "proto2"
+ // Syntax `proto2`.
GPBSyntax_SyntaxProto2 = 0,
- // Syntax "proto3"
+ // Syntax `proto3`.
GPBSyntax_SyntaxProto3 = 1,
@@ -34,7 +34,7 @@ BOOL GPBSyntax_IsValidValue(int32_t value);
#pragma mark - Enum GPBField_Kind
-// Kind represents a basic field type.
+// Basic field types.
typedef GPB_ENUM(GPBField_Kind) {
GPBField_Kind_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
// Field type unknown.
@@ -67,7 +67,7 @@ typedef GPB_ENUM(GPBField_Kind) {
// Field type string.
GPBField_Kind_TypeString = 9,
- // Field type group (deprecated proto2 type)
+ // Field type group. Proto2 syntax only, and deprecated.
GPBField_Kind_TypeGroup = 10,
// Field type message.
@@ -101,17 +101,16 @@ BOOL GPBField_Kind_IsValidValue(int32_t value);
#pragma mark - Enum GPBField_Cardinality
-// Cardinality represents whether a field is optional, required, or
-// repeated.
+// Whether a field is optional, required, or repeated.
typedef GPB_ENUM(GPBField_Cardinality) {
GPBField_Cardinality_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
- // The field cardinality is unknown. Typically an error condition.
+ // For fields with unknown cardinality.
GPBField_Cardinality_CardinalityUnknown = 0,
// For optional fields.
GPBField_Cardinality_CardinalityOptional = 1,
- // For required fields. Not used for proto3.
+ // For required fields. Proto2 syntax only.
GPBField_Cardinality_CardinalityRequired = 2,
// For repeated fields.
@@ -144,7 +143,7 @@ typedef GPB_ENUM(GPBType_FieldNumber) {
GPBType_FieldNumber_Syntax = 6,
-// A light-weight descriptor for a proto message type.
+// A protocol buffer message type.
@interface GPBType : GPBMessage
// The fully qualified message name.
@@ -155,12 +154,12 @@ typedef GPB_ENUM(GPBType_FieldNumber) {
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray *fieldsArray;
@property(nonatomic, readonly) NSUInteger fieldsArray_Count;
-// The list of oneof definitions.
+// The list of types appearing in `oneof` definitions in this type.
// |oneofsArray| contains |NSString|
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray *oneofsArray;
@property(nonatomic, readonly) NSUInteger oneofsArray_Count;
-// The proto options.
+// The protocol buffer options.
// |optionsArray| contains |GPBOption|
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray *optionsArray;
@property(nonatomic, readonly) NSUInteger optionsArray_Count;
@@ -189,41 +188,46 @@ typedef GPB_ENUM(GPBField_FieldNumber) {
GPBField_FieldNumber_Packed = 8,
GPBField_FieldNumber_OptionsArray = 9,
GPBField_FieldNumber_JsonName = 10,
+ GPBField_FieldNumber_DefaultValue = 11,
-// Field represents a single field of a message type.
+// A single field of a message type.
@interface GPBField : GPBMessage
-// The field kind.
+// The field type.
@property(nonatomic, readwrite) GPBField_Kind kind;
-// The field cardinality, i.e. optional/required/repeated.
+// The field cardinality.
@property(nonatomic, readwrite) GPBField_Cardinality cardinality;
-// The proto field number.
+// The field number.
@property(nonatomic, readwrite) int32_t number;
// The field name.
@property(nonatomic, readwrite, copy, null_resettable) NSString *name;
-// The type URL (without the scheme) when the type is MESSAGE or ENUM,
-// such as `type.googleapis.com/google.protobuf.Empty`.
+// The field type URL, without the scheme, for message or enumeration
+// types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`.
@property(nonatomic, readwrite, copy, null_resettable) NSString *typeURL;
-// Index in Type.oneofs. Starts at 1. Zero means no oneof mapping.
+// The index of the field type in `Type.oneofs`, for message or enumeration
+// types. The first type has index 1; zero means the type is not in the list.
@property(nonatomic, readwrite) int32_t oneofIndex;
// Whether to use alternative packed wire representation.
@property(nonatomic, readwrite) BOOL packed;
-// The JSON name for this field.
+// The field JSON name.
@property(nonatomic, readwrite, copy, null_resettable) NSString *jsonName;
+// The string value of the default value of this field. Proto2 syntax only.
+@property(nonatomic, readwrite, copy, null_resettable) NSString *defaultValue;
@end
int32_t GPBField_Kind_RawValue(GPBField *message);
@@ -253,7 +257,7 @@ typedef GPB_ENUM(GPBEnum_FieldNumber) {
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray *enumvalueArray;
@property(nonatomic, readonly) NSUInteger enumvalueArray_Count;
-// Proto options for the enum type.
+// Protocol buffer options.
@@ -287,7 +291,7 @@ typedef GPB_ENUM(GPBEnumValue_FieldNumber) {
// Enum value number.
-// Proto options for the enum value.
@@ -301,13 +305,14 @@ typedef GPB_ENUM(GPBOption_FieldNumber) {
GPBOption_FieldNumber_Value = 2,
-// Proto option attached to messages/fields/enums etc.
+// A protocol buffer option, which can be attached to a message, field,
+// enumeration, etc.
@interface GPBOption : GPBMessage
-// Proto option name.
+// The option's name. For example, `"java_package"`.
-// Proto option value.
+// The option's value. For example, `"com.google.protobuf"`.
@property(nonatomic, readwrite) BOOL hasValue;
@property(nonatomic, readwrite, strong, null_resettable) GPBAny *value;
@@ -786,25 +786,33 @@ class FileDescriptor(DescriptorBase):
message_types_by_name: Dict of message names of their descriptors.
enum_types_by_name: Dict of enum names and their descriptors.
extensions_by_name: Dict of extension names and their descriptors.
+ pool: the DescriptorPool this descriptor belongs to. When not passed to the
+ constructor, the global default pool is used.
"""
if _USE_C_DESCRIPTORS:
_C_DESCRIPTOR_CLASS = _message.FileDescriptor
def __new__(cls, name, package, options=None, serialized_pb=None,
- dependencies=None, syntax=None):
+ dependencies=None, syntax=None, pool=None):
# FileDescriptor() is called from various places, not only from generated
# files, to register dynamic proto files and messages.
if serialized_pb:
+ # TODO(amauryfa): use the pool passed as argument. This will work only
+ # for C++-implemented DescriptorPools.
return _message.default_pool.AddSerializedFile(serialized_pb)
else:
return super(FileDescriptor, cls).__new__(cls)
def __init__(self, name, package, options=None, serialized_pb=None,
"""Constructor."""
super(FileDescriptor, self).__init__(options, 'FileOptions')
+ if pool is None:
+ from google.protobuf import descriptor_pool
+ pool = descriptor_pool.Default()
+ self.pool = pool
self.message_types_by_name = {}
self.name = name
self.package = package
@@ -65,6 +65,7 @@ class DescriptorDatabase(object):
raise DescriptorDatabaseConflictingDefinitionError(
'%s already added, but with different descriptor.' % proto_name)
+ # Add the top-level Message, Enum and Extension descriptors to the index.
package = file_desc_proto.package
for message in file_desc_proto.message_type:
self._file_desc_protos_by_symbol.update(
@@ -72,6 +73,9 @@ class DescriptorDatabase(object):
for enum in file_desc_proto.enum_type:
self._file_desc_protos_by_symbol[
'.'.join((package, enum.name))] = file_desc_proto
+ for extension in file_desc_proto.extension:
+ self._file_desc_protos_by_symbol[
+ '.'.join((package, extension.name))] = file_desc_proto
def FindFileByName(self, name):
"""Finds the file descriptor proto by file name.
@@ -83,6 +83,12 @@ def _NormalizeFullyQualifiedName(name):
class DescriptorPool(object):
"""A collection of protobufs dynamically constructed by descriptor protos."""
+ if _USE_C_DESCRIPTORS:
+ def __new__(cls, descriptor_db=None):
+ # pylint: disable=protected-access
+ return descriptor._message.DescriptorPool(descriptor_db)
def __init__(self, descriptor_db=None):
"""Initializes a Pool of proto buffs.
@@ -264,6 +270,39 @@ class DescriptorPool(object):
self.FindFileContainingSymbol(full_name)
return self._enum_descriptors[full_name]
+ def FindFieldByName(self, full_name):
+ """Loads the named field descriptor from the pool.
+ Args:
+ full_name: The full name of the field descriptor to load.
+ Returns:
+ The field descriptor for the named field.
+ """
+ full_name = _NormalizeFullyQualifiedName(full_name)
+ message_name, _, field_name = full_name.rpartition('.')
+ message_descriptor = self.FindMessageTypeByName(message_name)
+ return message_descriptor.fields_by_name[field_name]
+ def FindExtensionByName(self, full_name):
+ """Loads the named extension descriptor from the pool.
+ full_name: The full name of the extension descriptor to load.
+ A FieldDescriptor, describing the named extension.
+ message_name, _, extension_name = full_name.rpartition('.')
+ try:
+ # Most extensions are nested inside a message.
+ scope = self.FindMessageTypeByName(message_name)
+ except KeyError:
+ # Some extensions are defined at file scope.
+ scope = self.FindFileContainingSymbol(full_name)
+ return scope.extensions_by_name[extension_name]
def _ConvertFileProtoToFileDescriptor(self, file_proto):
"""Creates a FileDescriptor from a proto or returns a cached copy.
@@ -282,6 +321,7 @@ class DescriptorPool(object):
direct_deps = [self.FindFileByName(n) for n in file_proto.dependency]
file_descriptor = descriptor.FileDescriptor(
+ pool=self,
name=file_proto.name,
package=file_proto.package,
syntax=file_proto.syntax,
@@ -598,10 +638,24 @@ class DescriptorPool(object):
field_desc.default_value = text_encoding.CUnescape(
field_proto.default_value)
+ # All other types are of the "int" type.
field_desc.default_value = int(field_proto.default_value)
field_desc.has_default_value = False
- field_desc.default_value = None
+ if (field_proto.type == descriptor.FieldDescriptor.TYPE_DOUBLE or
+ field_proto.type == descriptor.FieldDescriptor.TYPE_FLOAT):
+ field_desc.default_value = 0.0
+ elif field_proto.type == descriptor.FieldDescriptor.TYPE_STRING:
+ field_desc.default_value = u''
+ elif field_proto.type == descriptor.FieldDescriptor.TYPE_BOOL:
+ field_desc.default_value = False
+ elif field_proto.type == descriptor.FieldDescriptor.TYPE_ENUM:
+ field_desc.default_value = field_desc.enum_type.values[0].number
+ elif field_proto.type == descriptor.FieldDescriptor.TYPE_BYTES:
+ field_desc.default_value = b''
+ else:
+ field_desc.default_value = 0
field_desc.type = field_proto.type
@@ -680,3 +734,16 @@ class DescriptorPool(object):
def _PrefixWithDot(name):
return name if name.startswith('.') else '.%s' % name
+if _USE_C_DESCRIPTORS:
+ # TODO(amauryfa): This pool could be constructed from Python code, when we
+ # support a flag like 'use_cpp_generated_pool=True'.
+ _DEFAULT = descriptor._message.default_pool
+else:
+ _DEFAULT = DescriptorPool()
+def Default():
+ return _DEFAULT
+// Author: jieluo@google.com (Jie Luo)
+package google.protobuf.internal;
+message TestAny {
+ google.protobuf.Any value = 1;
+ int32 int_value = 2;
@@ -464,6 +464,9 @@ class ScalarMap(MutableMapping):
return val
def __contains__(self, item):
+ # We check the key's type to match the strong-typing flavor of the API.
+ # Also this makes it easier to match the behavior of the C++ implementation.
+ self._key_checker.CheckValue(item)
return item in self._values
# We need to override this explicitly, because our defaultdict-like behavior
@@ -491,10 +494,20 @@ class ScalarMap(MutableMapping):
def __iter__(self):
return iter(self._values)
+ def __repr__(self):
+ return repr(self._values)
def MergeFrom(self, other):
self._values.update(other._values)
self._message_listener.Modified()
+ def InvalidateIterators(self):
+ # It appears that the only way to reliably invalidate iterators to
+ # self._values is to ensure that its size changes.
+ original = self._values
+ self._values = original.copy()
+ original[None] = None
# This is defined in the abstract base, but we can do it much more cheaply.
def clear(self):
self._values.clear()
@@ -576,12 +589,22 @@ class MessageMap(MutableMapping):
for key in other:
self[key].MergeFrom(other[key])
# self._message_listener.Modified() not required here, because
# mutations to submessages already propagate.
@@ -40,6 +40,8 @@ try:
import unittest2 as unittest
except ImportError:
import unittest
+from google.protobuf import unittest_import_pb2
+from google.protobuf import unittest_import_public_pb2
from google.protobuf import unittest_pb2
from google.protobuf import descriptor_pb2
from google.protobuf.internal import api_implementation
@@ -51,13 +53,17 @@ from google.protobuf.internal import test_util
from google.protobuf import descriptor
from google.protobuf import descriptor_database
from google.protobuf import descriptor_pool
+from google.protobuf import message_factory
from google.protobuf import symbol_database
class DescriptorPoolTest(unittest.TestCase):
+ def CreatePool(self):
+ return descriptor_pool.DescriptorPool()
def setUp(self):
- self.pool = descriptor_pool.DescriptorPool()
+ self.pool = self.CreatePool()
self.factory_test1_fd = descriptor_pb2.FileDescriptorProto.FromString(
factory_test1_pb2.DESCRIPTOR.serialized_pb)
self.factory_test2_fd = descriptor_pb2.FileDescriptorProto.FromString(
@@ -89,7 +95,7 @@ class DescriptorPoolTest(unittest.TestCase):
'google.protobuf.python.internal.Factory1Message')
self.assertIsInstance(file_desc1, descriptor.FileDescriptor)
self.assertEqual('google/protobuf/internal/factory_test1.proto',
- file_desc1.name)
+ file_desc1.name)
self.assertEqual('google.protobuf.python.internal', file_desc1.package)
self.assertIn('Factory1Message', file_desc1.message_types_by_name)
@@ -97,7 +103,7 @@ class DescriptorPoolTest(unittest.TestCase):
'google.protobuf.python.internal.Factory2Message')
self.assertIsInstance(file_desc2, descriptor.FileDescriptor)
self.assertEqual('google/protobuf/internal/factory_test2.proto',
- file_desc2.name)
+ file_desc2.name)
self.assertEqual('google.protobuf.python.internal', file_desc2.package)
self.assertIn('Factory2Message', file_desc2.message_types_by_name)
@@ -111,7 +117,7 @@ class DescriptorPoolTest(unittest.TestCase):
self.assertIsInstance(msg1, descriptor.Descriptor)
self.assertEqual('Factory1Message', msg1.name)
self.assertEqual('google.protobuf.python.internal.Factory1Message',
- msg1.full_name)
+ msg1.full_name)
self.assertEqual(None, msg1.containing_type)
nested_msg1 = msg1.nested_types[0]
@@ -132,7 +138,7 @@ class DescriptorPoolTest(unittest.TestCase):
self.assertIsInstance(msg2, descriptor.Descriptor)
self.assertEqual('Factory2Message', msg2.name)
self.assertEqual('google.protobuf.python.internal.Factory2Message',
- msg2.full_name)
+ msg2.full_name)
self.assertIsNone(msg2.containing_type)
nested_msg2 = msg2.nested_types[0]
@@ -223,6 +229,37 @@ class DescriptorPoolTest(unittest.TestCase):
with self.assertRaises(KeyError):
self.pool.FindEnumTypeByName('Does not exist')
+ def testFindFieldByName(self):
+ field = self.pool.FindFieldByName(
+ 'google.protobuf.python.internal.Factory1Message.list_value')
+ self.assertEqual(field.name, 'list_value')
+ self.assertEqual(field.label, field.LABEL_REPEATED)
+ with self.assertRaises(KeyError):
+ self.pool.FindFieldByName('Does not exist')
+ def testFindExtensionByName(self):
+ # An extension defined in a message.
+ extension = self.pool.FindExtensionByName(
+ 'google.protobuf.python.internal.Factory2Message.one_more_field')
+ self.assertEqual(extension.name, 'one_more_field')
+ # An extension defined at file scope.
+ 'google.protobuf.python.internal.another_field')
+ self.assertEqual(extension.name, 'another_field')
+ self.assertEqual(extension.number, 1002)
+ def testExtensionsAreNotFields(self):
+ self.pool.FindFieldByName('google.protobuf.python.internal.another_field')
+ self.pool.FindFieldByName(
+ self.pool.FindExtensionByName(
def testUserDefinedDB(self):
db = descriptor_database.DescriptorDatabase()
self.pool = descriptor_pool.DescriptorPool(db)
@@ -231,8 +268,7 @@ class DescriptorPoolTest(unittest.TestCase):
self.testFindMessageTypeByName()
def testAddSerializedFile(self):
- db = descriptor_database.DescriptorDatabase()
- self.pool = descriptor_pool.DescriptorPool(db)
+ self.pool = descriptor_pool.DescriptorPool()
self.pool.AddSerializedFile(self.factory_test1_fd.SerializeToString())
self.pool.AddSerializedFile(self.factory_test2_fd.SerializeToString())
@@ -274,6 +310,56 @@ class DescriptorPoolTest(unittest.TestCase):
'google/protobuf/internal/descriptor_pool_test1.proto')
_CheckDefaultValue(file_descriptor)
+ def testDefaultValueForCustomMessages(self):
+ """Check the value returned by non-existent fields."""
+ def _CheckValueAndType(value, expected_value, expected_type):
+ self.assertEqual(value, expected_value)
+ self.assertIsInstance(value, expected_type)
+ def _CheckDefaultValues(msg):
+ int64 = long
+ except NameError: # Python3
+ int64 = int
+ unicode_type = unicode
+ unicode_type = str
+ _CheckValueAndType(msg.optional_int32, 0, int)
+ _CheckValueAndType(msg.optional_uint64, 0, (int64, int))
+ _CheckValueAndType(msg.optional_float, 0, (float, int))
+ _CheckValueAndType(msg.optional_double, 0, (float, int))
+ _CheckValueAndType(msg.optional_bool, False, bool)
+ _CheckValueAndType(msg.optional_string, u'', unicode_type)
+ _CheckValueAndType(msg.optional_bytes, b'', bytes)
+ _CheckValueAndType(msg.optional_nested_enum, msg.FOO, int)
+ # First for the generated message
+ _CheckDefaultValues(unittest_pb2.TestAllTypes())
+ # Then for a message built with from the DescriptorPool.
+ pool = descriptor_pool.DescriptorPool()
+ pool.Add(descriptor_pb2.FileDescriptorProto.FromString(
+ unittest_import_public_pb2.DESCRIPTOR.serialized_pb))
+ unittest_import_pb2.DESCRIPTOR.serialized_pb))
+ unittest_pb2.DESCRIPTOR.serialized_pb))
+ message_class = message_factory.MessageFactory(pool).GetPrototype(
+ pool.FindMessageTypeByName(
+ unittest_pb2.TestAllTypes.DESCRIPTOR.full_name))
+ _CheckDefaultValues(message_class())
+@unittest.skipIf(api_implementation.Type() != 'cpp',
+ 'explicit tests of the C++ implementation')
+class CppDescriptorPoolTest(DescriptorPoolTest):
+ # TODO(amauryfa): remove when descriptor_pool.DescriptorPool() creates true
+ # C++ descriptor pool object for C++ implementation.
+ # pylint: disable=g-import-not-at-top
+ from google.protobuf.pyext import _message
+ return _message.DescriptorPool()
class ProtoFile(object):
@@ -468,6 +554,8 @@ class AddDescriptorTest(unittest.TestCase):
pool.FindFileContainingSymbol(
prefix + 'protobuf_unittest.TestAllTypes.NestedMessage').name)
+ @unittest.skipIf(api_implementation.Type() == 'cpp',
+ 'With the cpp implementation, Add() must be called first')
def testMessage(self):
self._TestMessage('')
self._TestMessage('.')
@@ -502,10 +590,14 @@ class AddDescriptorTest(unittest.TestCase):
prefix + 'protobuf_unittest.TestAllTypes.NestedEnum').name)
def testEnum(self):
self._TestEnum('')
self._TestEnum('.')
def testFile(self):
pool = descriptor_pool.DescriptorPool()
pool.AddFileDescriptor(unittest_pb2.DESCRIPTOR)
@@ -520,6 +612,76 @@ class AddDescriptorTest(unittest.TestCase):
'protobuf_unittest.TestAllTypes')
+ def _GetDescriptorPoolClass(self):
+ # Test with both implementations of descriptor pools.
+ if api_implementation.Type() == 'cpp':
+ return _message.DescriptorPool
+ return descriptor_pool.DescriptorPool
+ def testEmptyDescriptorPool(self):
+ # Check that an empty DescriptorPool() contains no message.
+ pool = self._GetDescriptorPoolClass()()
+ proto_file_name = descriptor_pb2.DESCRIPTOR.name
+ self.assertRaises(KeyError, pool.FindFileByName, proto_file_name)
+ # Add the above file to the pool
+ file_descriptor = descriptor_pb2.FileDescriptorProto()
+ descriptor_pb2.DESCRIPTOR.CopyToProto(file_descriptor)
+ pool.Add(file_descriptor)
+ # Now it exists.
+ self.assertTrue(pool.FindFileByName(proto_file_name))
+ def testCustomDescriptorPool(self):
+ # Create a new pool, and add a file descriptor.
+ file_desc = descriptor_pb2.FileDescriptorProto(
+ name='some/file.proto', package='package')
+ file_desc.message_type.add(name='Message')
+ pool.Add(file_desc)
+ self.assertEqual(pool.FindFileByName('some/file.proto').name,
+ 'some/file.proto')
+ self.assertEqual(pool.FindMessageTypeByName('package.Message').name,
+ 'Message')
+@unittest.skipIf(
+ api_implementation.Type() != 'cpp',
+ 'default_pool is only supported by the C++ implementation')
+class DefaultPoolTest(unittest.TestCase):
+ def testFindMethods(self):
+ pool = _message.default_pool
+ self.assertIs(
+ pool.FindFileByName('google/protobuf/unittest.proto'),
+ unittest_pb2.DESCRIPTOR)
+ pool.FindMessageTypeByName('protobuf_unittest.TestAllTypes'),
+ unittest_pb2.TestAllTypes.DESCRIPTOR)
+ pool.FindFieldByName('protobuf_unittest.TestAllTypes.optional_int32'),
+ unittest_pb2.TestAllTypes.DESCRIPTOR.fields_by_name['optional_int32'])
+ pool.FindExtensionByName('protobuf_unittest.optional_int32_extension'),
+ unittest_pb2.DESCRIPTOR.extensions_by_name['optional_int32_extension'])
+ pool.FindEnumTypeByName('protobuf_unittest.ForeignEnum'),
+ unittest_pb2.ForeignEnum.DESCRIPTOR)
+ pool.FindOneofByName('protobuf_unittest.TestAllTypes.oneof_field'),
+ unittest_pb2.TestAllTypes.DESCRIPTOR.oneofs_by_name['oneof_field'])
+ def testAddFileDescriptor(self):
+ file_desc = descriptor_pb2.FileDescriptorProto(name='some/file.proto')
+ pool.AddSerializedFile(file_desc.SerializeToString())
TEST1_FILE = ProtoFile(
'google/protobuf/internal/descriptor_pool_test1.proto',
@@ -47,6 +47,7 @@ from google.protobuf import descriptor_pb2
from google.protobuf.internal import test_util
+from google.protobuf import descriptor_pool
from google.protobuf import text_format
@@ -75,9 +76,9 @@ class DescriptorTest(unittest.TestCase):
enum_proto.value.add(name='FOREIGN_BAR', number=5)
enum_proto.value.add(name='FOREIGN_BAZ', number=6)
- descriptor_pool = symbol_database.Default().pool
- descriptor_pool.Add(file_proto)
- self.my_file = descriptor_pool.FindFileByName(file_proto.name)
+ self.pool = self.GetDescriptorPool()
+ self.pool.Add(file_proto)
+ self.my_file = self.pool.FindFileByName(file_proto.name)
self.my_message = self.my_file.message_types_by_name[message_proto.name]
self.my_enum = self.my_message.enum_types_by_name[enum_proto.name]
@@ -97,6 +98,9 @@ class DescriptorTest(unittest.TestCase):
self.my_method
])
+ def GetDescriptorPool(self):
+ return symbol_database.Default().pool
def testEnumValueName(self):
self.assertEqual(self.my_message.EnumValueName('ForeignEnum', 4),
'FOREIGN_FOO')
@@ -393,6 +397,9 @@ class DescriptorTest(unittest.TestCase):
def testFileDescriptor(self):
self.assertEqual(self.my_file.name, 'some/filename/some.proto')
self.assertEqual(self.my_file.package, 'protobuf_unittest')
+ self.assertEqual(self.my_file.pool, self.pool)
+ # Generated modules also belong to the default pool.
+ self.assertEqual(unittest_pb2.DESCRIPTOR.pool, descriptor_pool.Default())
@unittest.skipIf(
api_implementation.Type() != 'cpp' or api_implementation.Version() != 2,
@@ -407,6 +414,13 @@ class DescriptorTest(unittest.TestCase):
message_descriptor.fields.append(None)
+class NewDescriptorTest(DescriptorTest):
+ """Redo the same tests as above, but with a separate DescriptorPool."""
class GeneratedDescriptorTest(unittest.TestCase):
"""Tests for the properties of descriptors in generated code."""
@@ -42,6 +42,7 @@ try:
+from google.protobuf.internal import well_known_types
from google.protobuf import json_format
from google.protobuf.util import json_format_proto3_pb2
@@ -269,15 +270,15 @@ class JsonFormatTest(JsonFormatBase):
'}'))
parsed_message = json_format_proto3_pb2.TestTimestamp()
self.CheckParseBack(message, parsed_message)
- text = (r'{"value": "1972-01-01T01:00:00.01+08:00",'
+ text = (r'{"value": "1970-01-01T00:00:00.01+08:00",'
r'"repeatedValue":['
- r' "1972-01-01T01:00:00.01+08:30",'
- r' "1972-01-01T01:00:00.01-01:23"]}')
+ r' "1970-01-01T00:00:00.01+08:30",'
+ r' "1970-01-01T00:00:00.01-01:23"]}')
json_format.Parse(text, parsed_message)
- self.assertEqual(parsed_message.value.seconds, 63104400)
+ self.assertEqual(parsed_message.value.seconds, -8 * 3600)
self.assertEqual(parsed_message.value.nanos, 10000000)
- self.assertEqual(parsed_message.repeated_value[0].seconds, 63106200)
- self.assertEqual(parsed_message.repeated_value[1].seconds, 63070620)
+ self.assertEqual(parsed_message.repeated_value[0].seconds, -8.5 * 3600)
+ self.assertEqual(parsed_message.repeated_value[1].seconds, 3600 + 23 * 60)
def testDurationMessage(self):
message = json_format_proto3_pb2.TestDuration()
@@ -389,7 +390,7 @@ class JsonFormatTest(JsonFormatBase):
def testParseEmptyText(self):
self.CheckError('',
- r'Failed to load JSON: (Expecting value)|(No JSON)')
+ r'Failed to load JSON: (Expecting value)|(No JSON).')
def testParseBadEnumValue(self):
self.CheckError(
@@ -414,7 +415,7 @@ class JsonFormatTest(JsonFormatBase):
if sys.version_info < (2, 7):
return
self.CheckError('{"int32Value": 1,\n"int32Value":2}',
- 'Failed to load JSON: duplicate key int32Value')
+ 'Failed to load JSON: duplicate key int32Value.')
def testInvalidBoolValue(self):
self.CheckError('{"boolValue": 1}',
@@ -431,39 +432,43 @@ class JsonFormatTest(JsonFormatBase):
json_format.Parse, text, message)
self.CheckError('{"int32Value": 012345}',
(r'Failed to load JSON: Expecting \'?,\'? delimiter: '
- r'line 1'))
+ r'line 1.'))
self.CheckError('{"int32Value": 1.0}',
'Failed to parse int32Value field: '
- 'Couldn\'t parse integer: 1.0')
+ 'Couldn\'t parse integer: 1.0.')
self.CheckError('{"int32Value": " 1 "}',
- 'Couldn\'t parse integer: " 1 "')
+ 'Couldn\'t parse integer: " 1 ".')
+ self.CheckError('{"int32Value": "1 "}',
+ 'Failed to parse int32Value field: '
+ 'Couldn\'t parse integer: "1 ".')
self.CheckError('{"int32Value": 12345678901234567890}',
'Failed to parse int32Value field: Value out of range: '
- '12345678901234567890')
+ '12345678901234567890.')
self.CheckError('{"int32Value": 1e5}',
- 'Couldn\'t parse integer: 100000.0')
+ 'Couldn\'t parse integer: 100000.0.')
self.CheckError('{"uint32Value": -1}',
- 'Failed to parse uint32Value field: Value out of range: -1')
+ 'Failed to parse uint32Value field: '
+ 'Value out of range: -1.')
def testInvalidFloatValue(self):
self.CheckError('{"floatValue": "nan"}',
'Failed to parse floatValue field: Couldn\'t '
- 'parse float "nan", use "NaN" instead')
+ 'parse float "nan", use "NaN" instead.')
def testInvalidBytesValue(self):
self.CheckError('{"bytesValue": "AQI"}',
- 'Failed to parse bytesValue field: Incorrect padding')
+ 'Failed to parse bytesValue field: Incorrect padding.')
self.CheckError('{"bytesValue": "AQI*"}',
def testInvalidMap(self):
message = json_format_proto3_pb2.TestMap()
text = '{"int32Map": {"null": 2, "2": 3}}'
self.assertRaisesRegexp(
json_format.ParseError,
- 'Failed to parse int32Map field: Couldn\'t parse integer: "null"',
+ 'Failed to parse int32Map field: invalid literal',
text = '{"int32Map": {1: 2, "2": 3}}'
@@ -474,7 +479,7 @@ class JsonFormatTest(JsonFormatBase):
text = '{"boolMap": {"null": 1}}'
- 'Failed to parse boolMap field: Expect "true" or "false", not null.',
+ 'Failed to parse boolMap field: Expected "true" or "false", not null.',
@@ -490,30 +495,29 @@ class JsonFormatTest(JsonFormatBase):
'time data \'10000-01-01T00:00:00\' does not match'
- ' format \'%Y-%m-%dT%H:%M:%S\'',
+ ' format \'%Y-%m-%dT%H:%M:%S\'.',
text = '{"value": "1970-01-01T00:00:00.0123456789012Z"}'
- json_format.ParseError,
- 'Failed to parse value field: Failed to parse Timestamp: '
+ well_known_types.ParseError,
'nanos 0123456789012 more than 9 fractional digits.',
text = '{"value": "1972-01-01T01:00:00.01+08"}'
- (r'Failed to parse value field: Invalid timezone offset value: \+08'),
+ (r'Invalid timezone offset value: \+08.'),
# Time smaller than minimum time.
text = '{"value": "0000-01-01T00:00:00Z"}'
- 'Failed to parse value field: year is out of range',
+ 'Failed to parse value field: year is out of range.',
# Time bigger than maxinum time.
message.value.seconds = 253402300800
- json_format.SerializeToJsonError,
- 'Failed to serialize value field: year is out of range',
+ OverflowError,
+ 'date value out of range',
json_format.MessageToJson, message)
def testInvalidOneof(self):
@@ -45,6 +45,7 @@ from google.protobuf import descriptor_database
from google.protobuf import message_factory
class MessageFactoryTest(unittest.TestCase):
@@ -104,8 +105,8 @@ class MessageFactoryTest(unittest.TestCase):
def testGetMessages(self):
# performed twice because multiple calls with the same input must be allowed
for _ in range(2):
- messages = message_factory.GetMessages([self.factory_test2_fd,
- self.factory_test1_fd])
+ messages = message_factory.GetMessages([self.factory_test1_fd,
+ self.factory_test2_fd])
self.assertTrue(
set(['google.protobuf.python.internal.Factory2Message',
'google.protobuf.python.internal.Factory1Message'],
@@ -116,7 +117,7 @@ class MessageFactoryTest(unittest.TestCase):
set(['google.protobuf.python.internal.Factory2Message.one_more_field',
'google.protobuf.python.internal.another_field'],
).issubset(
- set(messages['google.protobuf.python.internal.Factory1Message']
+ set(messages['google.protobuf.python.internal.Factory1Message']
._extensions_by_name.keys())))
factory_msg1 = messages['google.protobuf.python.internal.Factory1Message']
msg1 = messages['google.protobuf.python.internal.Factory1Message']()
@@ -54,6 +54,14 @@ message TestMessageSetExtension2 {
optional string str = 25;
+message TestMessageSetExtension3 {
+ optional string text = 35;
+extend TestMessageSet {
+ optional TestMessageSetExtension3 message_set_extension3 = 98418655;
// This message was used to generate
// //net/proto2/python/internal/testdata/message_set_message, but is commented
// out since it must not actually exist in code, to simulate an "unknown"
@@ -60,6 +60,7 @@ from google.protobuf.internal import _parameterized
from google.protobuf import map_unittest_pb2
from google.protobuf import unittest_proto3_arena_pb2
+from google.protobuf.internal import any_test_pb2
from google.protobuf.internal import packed_field_test_pb2
@@ -1279,12 +1280,13 @@ class Proto3Test(unittest.TestCase):
self.assertIsInstance(msg.map_string_string['abc'], six.text_type)
- # Accessing an unset key still throws TypeError of the type of the key
+ # Accessing an unset key still throws TypeError if the type of the key
# is incorrect.
with self.assertRaises(TypeError):
msg.map_string_string[123]
- self.assertFalse(123 in msg.map_string_string)
+ with self.assertRaises(TypeError):
+ 123 in msg.map_string_string
def testMapGet(self):
# Need to test that get() properly returns the default, even though the dict
@@ -1591,31 +1593,49 @@ class Proto3Test(unittest.TestCase):
# For the C++ implementation this tests the correctness of
# ScalarMapContainer::Release()
msg = map_unittest_pb2.TestMap()
- map = msg.map_int32_int32
+ int32_map = msg.map_int32_int32
- map[2] = 4
- map[3] = 6
- map[4] = 8
+ int32_map[2] = 4
+ int32_map[3] = 6
+ int32_map[4] = 8
msg.ClearField('map_int32_int32')
+ self.assertEqual(b'', msg.SerializeToString())
matching_dict = {2: 4, 3: 6, 4: 8}
- self.assertMapIterEquals(map.items(), matching_dict)
+ self.assertMapIterEquals(int32_map.items(), matching_dict)
- def testMapIterValidAfterFieldCleared(self):
- # Map iterator needs to work even if field is cleared.
+ def testMessageMapValidAfterFieldCleared(self):
+ # Map needs to work even if field is cleared.
+ int32_foreign_message = msg.map_int32_foreign_message
- msg.map_int32_int32[2] = 4
- msg.map_int32_int32[3] = 6
- msg.map_int32_int32[4] = 8
+ int32_foreign_message[2].c = 5
- it = msg.map_int32_int32.items()
+ msg.ClearField('map_int32_foreign_message')
+ self.assertTrue(2 in int32_foreign_message.keys())
+ def testMapIterInvalidatedByClearField(self):
+ # Map iterator is invalidated when field is cleared.
+ # But this case does need to not crash the interpreter.
+ # For the C++ implementation this tests the correctness of
+ # ScalarMapContainer::Release()
+ msg = map_unittest_pb2.TestMap()
+ it = iter(msg.map_int32_int32)
- matching_dict = {2: 4, 3: 6, 4: 8}
- self.assertMapIterEquals(it, matching_dict)
+ with self.assertRaises(RuntimeError):
+ for _ in it:
+ pass
+ it = iter(msg.map_int32_foreign_message)
def testMapDelete(self):
@@ -1646,6 +1666,37 @@ class Proto3Test(unittest.TestCase):
msg.map_string_foreign_message['foo'].c = 5
self.assertEqual(0, len(msg.FindInitializationErrors()))
+ def testAnyMessage(self):
+ # Creates and sets message.
+ msg = any_test_pb2.TestAny()
+ msg_descriptor = msg.DESCRIPTOR
+ all_types = unittest_pb2.TestAllTypes()
+ all_descriptor = all_types.DESCRIPTOR
+ all_types.repeated_string.append(u'\u00fc\ua71f')
+ # Packs to Any.
+ msg.value.Pack(all_types)
+ self.assertEqual(msg.value.type_url,
+ 'type.googleapis.com/%s' % all_descriptor.full_name)
+ self.assertEqual(msg.value.value,
+ all_types.SerializeToString())
+ # Tests Is() method.
+ self.assertTrue(msg.value.Is(all_descriptor))
+ self.assertFalse(msg.value.Is(msg_descriptor))
+ # Unpacks Any.
+ unpacked_message = unittest_pb2.TestAllTypes()
+ self.assertTrue(msg.value.Unpack(unpacked_message))
+ self.assertEqual(all_types, unpacked_message)
+ # Unpacks to different type.
+ self.assertFalse(msg.value.Unpack(msg))
+ # Only Any messages have Pack method.
+ msg.Pack(all_types)
+ except AttributeError:
+ raise AttributeError('%s should not have Pack method.' %
+ msg_descriptor.full_name)
class ValidTypeNamesTest(unittest.TestCase):
@@ -65,6 +65,7 @@ from google.protobuf.internal import encoder
from google.protobuf.internal import enum_type_wrapper
from google.protobuf.internal import message_listener as message_listener_mod
from google.protobuf.internal import type_checkers
from google.protobuf.internal import wire_format
from google.protobuf import descriptor as descriptor_mod
from google.protobuf import message as message_mod
@@ -72,6 +73,7 @@ from google.protobuf import symbol_database
_FieldDescriptor = descriptor_mod.FieldDescriptor
+_AnyFullTypeName = 'google.protobuf.Any'
class GeneratedProtocolMessageType(type):
@@ -127,6 +129,8 @@ class GeneratedProtocolMessageType(type):
Newly-allocated class.
descriptor = dictionary[GeneratedProtocolMessageType._DESCRIPTOR_KEY]
+ if descriptor.full_name in well_known_types.WKTBASES:
+ bases += (well_known_types.WKTBASES[descriptor.full_name],)
_AddClassAttributesForNestedExtensions(descriptor, dictionary)
_AddSlots(descriptor, dictionary)
@@ -261,7 +265,6 @@ def _IsMessageSetExtension(field):
field.containing_type.has_options and
field.containing_type.GetOptions().message_set_wire_format and
field.type == _FieldDescriptor.TYPE_MESSAGE and
- field.message_type == field.extension_scope and
field.label == _FieldDescriptor.LABEL_OPTIONAL)
@@ -543,7 +546,8 @@ def _GetFieldByName(message_descriptor, field_name):
try:
return message_descriptor.fields_by_name[field_name]
except KeyError:
- raise ValueError('Protocol message has no "%s" field.' % field_name)
+ raise ValueError('Protocol message %s has no "%s" field.' %
+ (message_descriptor.name, field_name))
def _AddPropertiesForFields(descriptor, cls):
@@ -848,9 +852,15 @@ def _AddClearFieldMethod(message_descriptor, cls):
+ raise ValueError('Protocol message %s() has no "%s" field.' %
if field in self._fields:
+ # To match the C++ implementation, we need to invalidate iterators
+ # for map fields when ClearField() happens.
+ if hasattr(self._fields[field], 'InvalidateIterators'):
+ self._fields[field].InvalidateIterators()
# Note: If the field is a sub-message, its listener will still point
# at us. That's fine, because the worst than can happen is that it
# will call _Modified() and invalidate our byte size. Big deal.
@@ -904,7 +914,19 @@ def _AddHasExtensionMethod(cls):
return extension_handle in self._fields
cls.HasExtension = HasExtension
-def _UnpackAny(msg):
+def _InternalUnpackAny(msg):
+ """Unpacks Any message and returns the unpacked message.
+ This internal method is differnt from public Any Unpack method which takes
+ the target message as argument. _InternalUnpackAny method does not have
+ target message type and need to find the message type in descriptor pool.
+ msg: An Any message to be unpacked.
+ The unpacked message.
type_url = msg.type_url
db = symbol_database.Default()
@@ -935,9 +957,9 @@ def _AddEqualsMethod(message_descriptor, cls):
if self is other:
return True
- if self.DESCRIPTOR.full_name == "google.protobuf.Any":
- any_a = _UnpackAny(self)
- any_b = _UnpackAny(other)
+ if self.DESCRIPTOR.full_name == _AnyFullTypeName:
+ any_a = _InternalUnpackAny(self)
+ any_b = _InternalUnpackAny(other)
if any_a and any_b:
return any_a == any_b
@@ -962,6 +984,13 @@ def _AddStrMethod(message_descriptor, cls):
cls.__str__ = __str__
+def _AddReprMethod(message_descriptor, cls):
+ """Helper for _AddMessageMethods()."""
+ return text_format.MessageToString(self)
+ cls.__repr__ = __repr__
def _AddUnicodeMethod(unused_message_descriptor, cls):
"""Helper for _AddMessageMethods()."""
@@ -1270,6 +1299,7 @@ def _AddMessageMethods(message_descriptor, cls):
_AddClearMethod(message_descriptor, cls)
_AddEqualsMethod(message_descriptor, cls)
_AddStrMethod(message_descriptor, cls)
+ _AddReprMethod(message_descriptor, cls)
_AddUnicodeMethod(message_descriptor, cls)
_AddSetListenerMethod(cls)
_AddByteSizeMethod(message_descriptor, cls)
@@ -1280,6 +1310,7 @@ def _AddMessageMethods(message_descriptor, cls):
_AddMergeFromMethod(cls)
_AddWhichOneofMethod(message_descriptor, cls)
def _AddPrivateHelperMethods(message_descriptor, cls):
"""Adds implementation of private helper methods to cls."""