From 8552e0b77828da877b64113e846850cf8a642103 Mon Sep 17 00:00:00 2001 From: dsavvinov Date: Mon, 18 Jul 2016 16:05:56 +0300 Subject: [PATCH] Protobuf: Fixed stub in length-delimited fields serialization. Now related methods write size of object correctly. Added necessary methods for estimating byte-size of objects in run-time in ProtoKot runtime-library. --- .../google/protobuf/compiler/kotlin/Makefile | 7 +- .../kotlin/src/kotlin_class_generator.cc | 57 ++--- .../kotlin/src/kotlin_class_generator.h | 2 +- .../kotlin/src/kotlin_field_generator.cc | 158 +++++++++++-- .../kotlin/src/kotlin_field_generator.h | 1 + .../kotlin/src/kotlin_name_resolver.cc | 7 +- .../protobuf/compiler/kotlin/src/main.cc | 1 - .../compiler/kotlin/test/addressbook.kt | 160 ++++++++++---- proto/compiler/src/CodedInputStream.kt | 10 +- proto/compiler/src/CodedOutputStream.kt | 14 +- proto/compiler/src/PersonMessage.kt | 208 ++++++++++++------ proto/compiler/src/TestMessages.kt | 2 +- proto/compiler/src/WireFormat.kt | 97 ++++++++ 13 files changed, 548 insertions(+), 176 deletions(-) diff --git a/proto/compiler/google/src/google/protobuf/compiler/kotlin/Makefile b/proto/compiler/google/src/google/protobuf/compiler/kotlin/Makefile index acfcc7ee630..c73425b371c 100644 --- a/proto/compiler/google/src/google/protobuf/compiler/kotlin/Makefile +++ b/proto/compiler/google/src/google/protobuf/compiler/kotlin/Makefile @@ -26,4 +26,9 @@ clean: generate: ./protoc --kotlin_out=./ ./test/addressbook.proto -.PHONY: clean all generate \ No newline at end of file +.PHONY: clean all generate + +low: dummy + +dummy: CXXFLAGS += -DKOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW +dummy: all \ No newline at end of file diff --git a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_class_generator.cc b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_class_generator.cc index 1d42b10e873..1505621c582 100644 --- a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_class_generator.cc +++ b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_class_generator.cc @@ -48,8 +48,6 @@ void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const { // write serialization methods only for fair classes, read methods only for Builders) printer->Print("\n"); generateSerializers(printer, /* isRead = */ isBuilder); - printer->Print("\n"); - generateSerializersNoTag(printer, /* isRead = */ isBuilder); // builder, mergeFrom and only for fair classes if (!isBuilder) { @@ -69,6 +67,9 @@ void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const { generateParseMethods(printer); } + // getSize() + generateGetSizeMethod(printer); + printer->Outdent(); printer->Print("}\n"); } @@ -172,32 +173,10 @@ void ClassGenerator::generateMergeMethods(io::Printer *printer) const { printer->Print("}\n"); } -void ClassGenerator::generateSerializers(io::Printer * printer, bool isRead) const { - // readFrom(input: CodedInputStream) OR - // writeTo(output: CodedOutputStream) + +void ClassGenerator::generateSerializers(io::Printer *printer, bool isRead) const { map vars; - vars["funName"]= isRead ? "readFrom" : "writeTo"; - vars["returnType"] = isRead ? builderName : "Unit"; - vars["stream"] = isRead ? "CodedInputStream" : "CodedOutputStream"; - vars["arg"] = isRead ? "input" : "output"; - vars["maybeSeparator"] = isRead ? "" : ", "; - vars["maybeReturn"] = isRead ? "return " : ""; - - // generate function header - printer->Print(vars, - "fun $funName$ ($arg$: $stream$): $returnType$ {" - "\n"); - printer->Indent(); - - printer->Print(vars, "$maybeReturn$$funName$NoTag($arg$)\n"); - - printer->Outdent(); - printer->Print("}\n"); -} - -void ClassGenerator::generateSerializersNoTag(io::Printer *printer, bool isRead) const { - map vars; - vars["funName"]= isRead ? "readFromNoTag" : "writeToNoTag"; + vars["funName"]= isRead ? "readFrom" : "writeTo"; vars["stream"] = isRead ? "CodedInputStream" : "CodedOutputStream"; vars["arg"] = isRead ? "input" : "output"; vars["returnType"] = isRead ? builderName : "Unit"; @@ -341,6 +320,30 @@ void ClassGenerator::generateParseMethods(io::Printer *printer) const { printer->Print("}\n"); } +void ClassGenerator::generateGetSizeMethod(io::Printer *printer) const { + printer->Print("fun getSize(): Int {\n"); + printer->Indent(); + + printer->Print("var size = 0\n"); + for (int i = 0; i < properties.size(); ++i) { + properties[i]->generateSizeEstimationCode(printer, "size"); + } + + printer->Print("return size\n"); + printer->Outdent(); + printer->Print("}\n"); +} + + + +//int ClassGenerator::getSizeWithoutHeader() { +// int size = 0; +// for (int i = 0; i < properties.size(); ++i) { +// size += properties[i].getSizeWithHeader(); +// } +// return 0; +//} + const string ClassModifier::getName() const { string result = ""; diff --git a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_class_generator.h b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_class_generator.h index 44a6212d5e8..62158289644 100644 --- a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_class_generator.h +++ b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_class_generator.h @@ -62,10 +62,10 @@ private: * both methods are structurally the same with some trivial substitutions * (read -> write and etc.) */ - void generateSerializersNoTag(io::Printer *printer, bool isRead = false) const; void generateSerializers(io::Printer * printer, bool isRead = false) const; void generateMergeMethods(io::Printer *printer) const; void generateParseMethods(io::Printer * printer) const; + void generateGetSizeMethod(io::Printer * printer) const; }; } // namespace kotlin diff --git a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_field_generator.cc b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_field_generator.cc index d0e044c44ac..948b130980d 100644 --- a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_field_generator.cc +++ b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_field_generator.cc @@ -16,12 +16,10 @@ namespace kotlin { string FieldGenerator::getInitValue() const { if (descriptor->is_repeated()) - #ifndef KOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW return "mutableListOf()"; - #else - return "arrayOf()"; - #endif - + else if (protoType == FieldDescriptor::TYPE_MESSAGE) { + return enclosingClass->builderName + "().build()"; + } return name_resolving::protobufTypeToInitValue(descriptor); } @@ -82,8 +80,9 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead if (!noTag) { printer->Print(vars, "val tag = input.readTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n"); } - printer->Print(vars, "val listSize = input.readInt32NoTag()\n"); - printer->Print(vars, "for (i in 1..listSize) {\n"); + printer->Print(vars, "val expectedSize = input.readInt32NoTag()\n"); + printer->Print("var readSize = 0\n"); + printer->Print(vars, "while(readSize != expectedSize) {\n"); printer->Indent(); /* hack: copy current FieldGenerator and change label to OPTIONAL. Also change name to @@ -99,17 +98,25 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead /* Another dirty hack here: create tmp variable of a given type and read it from input stream then add that tmp var into list. - This is made because simple recursive call will generate code, that tries to array[i].mergeFrom() - but this is incorrect, because array has old size, while 'i' iterates over new size, and of course - they are not necessary the same. + This is made because simple recursive call will generate code that tries to array[i].mergeFrom(). + This is incorrect because array has old size, while 'i' iterates over new size, which can lead + to ArrayOutOfIndex errors. */ // TODO: stub here, resolve name properly! vars["fieldType"] = underlyingType + ".Builder" + underlyingType; - vars["initValue"] = getUnderlyingTypeInitValue(); - printer->Print(vars, "val tmp: $fieldType$ = $fieldType$()\n"); + vars["initValue"] = underlyingType + ".Builder" + underlyingType + "()"; + printer->Print(vars, "val tmp: $fieldType$ = $initValue$\n"); singleFieldGen.simpleName = "tmp"; singleFieldGen.modifier = FieldDescriptor::LABEL_OPTIONAL; - singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true); + + // Note that primitive types are packed by default in proto3, i.e. they are should be written without tag + bool isPrimitive = descriptor->type() != FieldDescriptor::TYPE_BYTES && + descriptor->type() != FieldDescriptor::TYPE_MESSAGE && + descriptor->type() != FieldDescriptor::TYPE_STRING && + descriptor->type() != FieldDescriptor::TYPE_ENUM; + + singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ isPrimitive); + singleFieldGen.generateSizeEstimationCode(printer, "readSize"); // add size of current element to total size printer->Print(vars, "$fieldName$.add(tmp.build())\n"); @@ -124,7 +131,9 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead printer->Print(vars, "output.writeTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n"); // length - printer->Print(vars, "output.writeInt32NoTag($fieldName$.size)\n"); + printer->Print(vars, "var arrayByteSize = 0\n"); + generateSizeEstimationCode(printer, "arrayByteSize", /* noTag = */ true); + printer->Print(vars, "output.writeInt32NoTag(arrayByteSize)\n"); // all elements printer->Print(vars, "for (item in $fieldName$) {\n"); @@ -134,7 +143,15 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead FieldGenerator singleFieldGen = FieldGenerator(descriptor, enclosingClass); singleFieldGen.simpleName = "item"; singleFieldGen.modifier = FieldDescriptor::LABEL_OPTIONAL; - singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true); + + // TODO: maybe refactor this in name_resolving or separate method at least + // Note that primitive types are packed by default in proto3, i.e. they are should be written without tag + bool isPrimitive = descriptor->type() != FieldDescriptor::TYPE_BYTES && + descriptor->type() != FieldDescriptor::TYPE_MESSAGE && + descriptor->type() != FieldDescriptor::TYPE_STRING && + descriptor->type() != FieldDescriptor::TYPE_ENUM; + + singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ isPrimitive); printer->Outdent(); // for-loop printer->Print("}\n"); @@ -166,17 +183,42 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead /* Then check for nested messages. Then we re-use writeTo method, that should be defined in - that message + that message. + Note that readFrom/writeTo methods write message as it's top-level message, i.e. without + any tags. Therefore, we have to prepend tags and size manually. */ if (descriptor->type() == FieldDescriptor::TYPE_MESSAGE) { - vars["maybeNoTag"] = "NoTag"; if (isRead) { + vars["fieldNumber"] = std::to_string(fieldNumber); + vars["dollar"] = "$"; + + // read tag + printer->Print(vars, "input.readTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n"); + + // read expected size + printer->Print(vars, "val expectedSize = input.readInt32NoTag()\n"); + + // read message itself without tag printer->Print(vars, - "$fieldName$.readFrom$maybeNoTag$(input)\n"); + "$fieldName$.readFrom(input)\n"); + + // check that actual size equal to expected size + printer->Print(vars, "if (expectedSize != $fieldName$.getSize()) { " + "throw InvalidProtocolBufferException (" + "\"Expected size $dollar${expectedSize} got $dollar${$fieldName$.getSize()}" + "\") }\n"); } else { + vars["fieldNumber"] = std::to_string(fieldNumber); + // write tag + printer->Print(vars, "output.writeTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n"); + + // write message length via runtime-call + printer->Print(vars, "output.writeInt32NoTag($fieldName$.getSize())\n"); + + // write message itself without tag printer->Print(vars, - "$fieldName$.writeTo$maybeNoTag$(output)\n"); + "$fieldName$.writeTo(output)\n"); } return; } @@ -224,7 +266,6 @@ void FieldGenerator::generateRepeatedMethods(io::Printer * printer, bool isBuild printer->Print("}\n"); } - #ifndef KOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW if (isBuilder) { // generate single-add for builders printer->Print(vars, "fun add$elementType$($arg$: $elementType$): $builderName$ {\n"); @@ -249,7 +290,6 @@ void FieldGenerator::generateRepeatedMethods(io::Printer * printer, bool isBuild printer->Outdent(); // function body printer->Print("}\n"); } - #endif } string FieldGenerator::getKotlinFunctionSuffix() const { @@ -257,9 +297,85 @@ string FieldGenerator::getKotlinFunctionSuffix() const { } string FieldGenerator::getUnderlyingTypeInitValue() const { + if (protoType == FieldDescriptor::TYPE_MESSAGE) { + return "Builder" + underlyingType + "().build()"; + } return name_resolving::protobufTypeToInitValue(descriptor); } +void FieldGenerator::generateSizeEstimationCode(io::Printer *printer, string varName, bool noTag) const { + map vars; + vars["varName"] = varName; + vars["fieldName"] = simpleName; + vars["fieldNumber"] = std::to_string(fieldNumber); + + // First of all, generate code for repeated fields + if (modifier == FieldDescriptor::LABEL_REPEATED) { + // We will need total byte size of array, because that size is itself a part of the message and + // adds to total message size. + // For the sake of hygiene, temporary variables are created in anonymous scope + printer->Print("run {\n"); + printer->Indent(); + + // Create a temporary variable that will collect array byte size + printer->Print("var arraySize = 0\n"); + + // iterate over all elements of array + printer->Print(vars, "for (item in $fieldName$) {\n"); + printer->Indent(); + + // hack: reuse generateSizeEstimationCode in the same manner as in generateSerializationCode + FieldGenerator singleFieldGen = FieldGenerator(descriptor, enclosingClass); + singleFieldGen.modifier = FieldDescriptor::LABEL_OPTIONAL; + singleFieldGen.simpleName = "item"; + singleFieldGen.generateSizeEstimationCode(printer, "arraySize"); + + printer->Outdent(); // for-loop + printer->Print("}\n"); + + // now add to total message size size of array, consisting of: + printer->Print(vars, + "$varName$ += arraySize"); // actual array size + if (!noTag) { + printer->Print(vars, + " + " + "WireFormat.getTagSize($fieldNumber$, WireType.LENGTH_DELIMITED)" // tag size + " + " + "WireFormat.getVarint32Size(arraySize)"); // runtime call, that will get size of varint, denoting size of array + } + printer->Print("\n"); + printer->Outdent(); // anonymous scope + printer->Print("}\n"); + + return; + } + + // Then, call getSize recursively for nested messages + // TODO: currently suboptimal repeatative calls getSize() are being made. We can optimize it later via caching calls to getSize() + if (protoType == FieldDescriptor::TYPE_MESSAGE) { + // don't forget about tag and length annotation + printer->Print(vars, "$varName$ += $fieldName$.getSize()" + " + " + "WireFormat.getTagSize($fieldNumber$, WireType.LENGTH_DELIMITED)" + " + " + "WireFormat.getVarint32Size($fieldName$.getSize())\n" + ); + return; + } + + // Next, process enums as they should be casted to ints manually + if (protoType == FieldDescriptor::TYPE_ENUM) { + vars["enumName"] = fullType; + printer->Print(vars, "$varName$ += WireFormat.getEnumSize($fieldNumber$, $fieldName$.ord)\n"); + return; + } + + // Finally, get size of all primitive types trivially via call to WireFormat in runtime + vars["kotlinSuffix"] = name_resolving::protobufTypeToKotlinFunctionSuffix(descriptor->type()); + printer->Print(vars, "$varName$ += WireFormat.get$kotlinSuffix$Size($fieldNumber$, $fieldName$)\n"); + return; +} + } // namespace kotlin } // namespace compiler } // namespace protobuf diff --git a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_field_generator.h b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_field_generator.h index 4ce6ea425a1..05a5e293762 100644 --- a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_field_generator.h +++ b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_field_generator.h @@ -45,6 +45,7 @@ public: void generateCode(io::Printer * printer, bool isBuilder) const; void generateSerializationCode(io::Printer * printer, bool isRead = false, bool noTag = false) const; + void generateSizeEstimationCode(io::Printer * printer, string varName, bool noTag = false) const; FieldGenerator(FieldDescriptor const * descriptor, ClassGenerator const * enclosingClass); string getKotlinFunctionSuffix() const; diff --git a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_name_resolver.cc b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_name_resolver.cc index ea8549b583c..e799519121f 100644 --- a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_name_resolver.cc +++ b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/kotlin_name_resolver.cc @@ -83,19 +83,14 @@ string protobufToKotlinField(FieldDescriptor const * descriptor) { case FieldDescriptor::LABEL_OPTIONAL: break; case FieldDescriptor::LABEL_REPEATED: -#ifndef KOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW preamble = "MutableList <"; postamble = "> "; break; -#else - preamble = "Array <"; - postamble = "> "; - break; -#endif } return preamble + protobufToKotlinType(descriptor) + postamble; } +// TODO: think about nested arrays string protobufTypeToInitValue(FieldDescriptor const * descriptor) { FieldDescriptor::Type type = descriptor->type(); switch(type) { diff --git a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/main.cc b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/main.cc index 9e4c462ef47..9282634da38 100644 --- a/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/main.cc +++ b/proto/compiler/google/src/google/protobuf/compiler/kotlin/src/main.cc @@ -10,7 +10,6 @@ #include "kotlin_file_generator.h" #include -//#define KOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW int main(int argc, const char* const * argv) { google::protobuf::compiler::CommandLineInterface cli; diff --git a/proto/compiler/google/src/google/protobuf/compiler/kotlin/test/addressbook.kt b/proto/compiler/google/src/google/protobuf/compiler/kotlin/test/addressbook.kt index 357b50bb03a..c4518b2454b 100644 --- a/proto/compiler/google/src/google/protobuf/compiler/kotlin/test/addressbook.kt +++ b/proto/compiler/google/src/google/protobuf/compiler/kotlin/test/addressbook.kt @@ -52,10 +52,6 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email: } fun writeTo (output: CodedOutputStream): Unit { - writeToNoTag(output) - } - - fun writeToNoTag (output: CodedOutputStream): Unit { output.writeString (1, number) output.writeEnum (2, type.ord) } @@ -82,10 +78,6 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email: } fun readFrom (input: CodedInputStream): BuilderPhoneNumber { - return readFromNoTag(input) - } - - fun readFromNoTag (input: CodedInputStream): BuilderPhoneNumber { number = input.readString(1) type = PhoneType.fromIntToPhoneType(input.readEnum(2)) return this @@ -110,6 +102,12 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email: while(parseFieldFrom(input)) {} return this } + fun getSize(): Int { + var size = 0 + size += WireFormat.getStringSize(1, number) + size += WireFormat.getEnumSize(2, type.ord) + return size + } } @@ -121,22 +119,34 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email: fun mergeFrom (input: CodedInputStream) { val builder = BuilderPhoneNumber() mergeWith(builder.parseFrom(input).build())} + fun getSize(): Int { + var size = 0 + size += WireFormat.getStringSize(1, number) + size += WireFormat.getEnumSize(2, type.ord) + return size + } } fun writeTo (output: CodedOutputStream): Unit { - writeToNoTag(output) - } - - fun writeToNoTag (output: CodedOutputStream): Unit { output.writeString (1, name) output.writeInt32 (2, id) output.writeString (3, email) if (phones.size > 0) { output.writeTag(4, WireType.LENGTH_DELIMITED) - output.writeInt32NoTag(phones.size) + var arrayByteSize = 0 + run { + var arraySize = 0 + for (item in phones) { + arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + arrayByteSize += arraySize + } + output.writeInt32NoTag(arrayByteSize) for (item in phones) { - item.writeToNoTag(output) + output.writeTag(4, WireType.LENGTH_DELIMITED) + output.writeInt32NoTag(item.getSize()) + item.writeTo(output) } } output.writeBytes (5, someBytes) @@ -202,18 +212,19 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email: } fun readFrom (input: CodedInputStream): BuilderPerson { - return readFromNoTag(input) - } - - fun readFromNoTag (input: CodedInputStream): BuilderPerson { name = input.readString(1) id = input.readInt32(2) email = input.readString(3) val tag = input.readTag(4, WireType.LENGTH_DELIMITED) - val listSize = input.readInt32NoTag() - for (i in 1..listSize) { + val expectedSize = input.readInt32NoTag() + var readSize = 0 + while(readSize != expectedSize) { val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber() - tmp.readFromNoTag(input) + input.readTag(4, WireType.LENGTH_DELIMITED) + val expectedSize = input.readInt32NoTag() + tmp.readFrom(input) + if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") } + readSize += tmp.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize()) phones.add(tmp.build()) } someBytes = input.readBytes(5) @@ -235,10 +246,15 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email: 2 -> id = input.readInt32NoTag() 3 -> email = input.readStringNoTag() 4 -> { - val listSize = input.readInt32NoTag() - for (i in 1..listSize) { + val expectedSize = input.readInt32NoTag() + var readSize = 0 + while(readSize != expectedSize) { val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber() - tmp.readFromNoTag(input) + input.readTag(4, WireType.LENGTH_DELIMITED) + val expectedSize = input.readInt32NoTag() + tmp.readFrom(input) + if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") } + readSize += tmp.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize()) phones.add(tmp.build()) } } @@ -249,6 +265,21 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email: while(parseFieldFrom(input)) {} return this } + fun getSize(): Int { + var size = 0 + size += WireFormat.getStringSize(1, name) + size += WireFormat.getInt32Size(2, id) + size += WireFormat.getStringSize(3, email) + run { + var arraySize = 0 + for (item in phones) { + arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + size += arraySize + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize) + } + size += WireFormat.getBytesSize(5, someBytes) + return size + } } @@ -263,6 +294,21 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email: fun mergeFrom (input: CodedInputStream) { val builder = BuilderPerson() mergeWith(builder.parseFrom(input).build())} + fun getSize(): Int { + var size = 0 + size += WireFormat.getStringSize(1, name) + size += WireFormat.getInt32Size(2, id) + size += WireFormat.getStringSize(3, email) + run { + var arraySize = 0 + for (item in phones) { + arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + size += arraySize + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize) + } + size += WireFormat.getBytesSize(5, someBytes) + return size + } } @@ -276,15 +322,21 @@ class AddressBook private constructor (people: MutableList = mutableLi } fun writeTo (output: CodedOutputStream): Unit { - writeToNoTag(output) - } - - fun writeToNoTag (output: CodedOutputStream): Unit { if (people.size > 0) { output.writeTag(1, WireType.LENGTH_DELIMITED) - output.writeInt32NoTag(people.size) + var arrayByteSize = 0 + run { + var arraySize = 0 + for (item in people) { + arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + arrayByteSize += arraySize + } + output.writeInt32NoTag(arrayByteSize) for (item in people) { - item.writeToNoTag(output) + output.writeTag(1, WireType.LENGTH_DELIMITED) + output.writeInt32NoTag(item.getSize()) + item.writeTo(output) } } } @@ -317,15 +369,16 @@ class AddressBook private constructor (people: MutableList = mutableLi } fun readFrom (input: CodedInputStream): BuilderAddressBook { - return readFromNoTag(input) - } - - fun readFromNoTag (input: CodedInputStream): BuilderAddressBook { val tag = input.readTag(1, WireType.LENGTH_DELIMITED) - val listSize = input.readInt32NoTag() - for (i in 1..listSize) { + val expectedSize = input.readInt32NoTag() + var readSize = 0 + while(readSize != expectedSize) { val tmp: Person.BuilderPerson = Person.BuilderPerson() - tmp.readFromNoTag(input) + input.readTag(1, WireType.LENGTH_DELIMITED) + val expectedSize = input.readInt32NoTag() + tmp.readFrom(input) + if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") } + readSize += tmp.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize()) people.add(tmp.build()) } return this @@ -343,10 +396,15 @@ class AddressBook private constructor (people: MutableList = mutableLi val wireType = WireFormat.getTagWireType(tag) when(fieldNumber) { 1 -> { - val listSize = input.readInt32NoTag() - for (i in 1..listSize) { + val expectedSize = input.readInt32NoTag() + var readSize = 0 + while(readSize != expectedSize) { val tmp: Person.BuilderPerson = Person.BuilderPerson() - tmp.readFromNoTag(input) + input.readTag(1, WireType.LENGTH_DELIMITED) + val expectedSize = input.readInt32NoTag() + tmp.readFrom(input) + if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") } + readSize += tmp.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize()) people.add(tmp.build()) } } @@ -356,6 +414,17 @@ class AddressBook private constructor (people: MutableList = mutableLi while(parseFieldFrom(input)) {} return this } + fun getSize(): Int { + var size = 0 + run { + var arraySize = 0 + for (item in people) { + arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + size += arraySize + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize) + } + return size + } } @@ -366,6 +435,17 @@ class AddressBook private constructor (people: MutableList = mutableLi fun mergeFrom (input: CodedInputStream) { val builder = BuilderAddressBook() mergeWith(builder.parseFrom(input).build())} + fun getSize(): Int { + var size = 0 + run { + var arraySize = 0 + for (item in people) { + arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + size += arraySize + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize) + } + return size + } } diff --git a/proto/compiler/src/CodedInputStream.kt b/proto/compiler/src/CodedInputStream.kt index b565df64a9c..382cdf620ce 100644 --- a/proto/compiler/src/CodedInputStream.kt +++ b/proto/compiler/src/CodedInputStream.kt @@ -1,5 +1,8 @@ import java.nio.ByteBuffer import java.nio.ByteOrder +import WireFormat.VARINT_INFO_BITS_COUNT +import WireFormat.VARINT_INFO_BITS_MASK +import WireFormat.VARINT_UTIL_BIT_MASK /** * Created by Dmitry Savvinov on 7/6/16. @@ -13,7 +16,6 @@ import java.nio.ByteOrder // TODO: refactor correctness checks into readTag class CodedInputStream(input: java.io.InputStream) { - val bufferedInput: java.io.BufferedInputStream init { bufferedInput = java.io.BufferedInputStream(input) // TODO: Java's realization uses hand-written buffers. Why? @@ -299,11 +301,5 @@ class CodedInputStream(input: java.io.InputStream) { bufferedInput.reset() return byte == -1 } - - // couple of constants for magic numbers - val VARINT_INFO_BITS_COUNT: Int = 7 - val VARINT_INFO_BITS_MASK: Int = 0b01111111 // mask for separating lowest 7 bits, where actual information stored - val VARINT_UTIL_BIT_MASK: Int = 0b10000000 // mask for separating highest bit, that indicates next byte presence - } diff --git a/proto/compiler/src/CodedOutputStream.kt b/proto/compiler/src/CodedOutputStream.kt index ea1fc24f0d3..646189cfd8f 100644 --- a/proto/compiler/src/CodedOutputStream.kt +++ b/proto/compiler/src/CodedOutputStream.kt @@ -1,5 +1,8 @@ import java.nio.ByteBuffer import java.nio.ByteOrder +import WireFormat.VARINT_INFO_BITS_COUNT +import WireFormat.VARINT_INFO_BITS_MASK +import WireFormat.VARINT_UTIL_BIT_MASK /** * Created by user on 7/6/16. @@ -75,7 +78,7 @@ class CodedOutputStream(val output: java.io.OutputStream) { } fun writeSInt64NoTag(value: Long) { - writeInt64NoTag((value shl 1) xor (value shr 31)) + writeInt64NoTag((value shl 1) xor (value shr 63)) } fun writeFixed32(fieldNumber: Int, value: Int?) { @@ -148,7 +151,7 @@ class CodedOutputStream(val output: java.io.OutputStream) { fun writeStringNoTag(value: String) { writeInt32NoTag(value.length) - output.write(value.toByteArray()) + output.write(value.toByteArray(Charsets.UTF_8)) } fun writeBytes(fieldNumber: Int, value: ByteArray?) { @@ -209,7 +212,7 @@ class CodedOutputStream(val output: java.io.OutputStream) { var resSize = 0 do { // encode current 7 bits - var curByte = (curValue and VARINT_INFO_BITS_MASK) + var curByte = (curValue and WireFormat.VARINT_INFO_BITS_MASK) // discard encoded bits. Note that unsigned shift is needed for cases with negative numbers curValue = curValue ushr VARINT_INFO_BITS_COUNT @@ -251,8 +254,5 @@ class CodedOutputStream(val output: java.io.OutputStream) { output.write(res, 0, resSize) } - // couple of constants for magic numbers - val VARINT_INFO_BITS_COUNT: Int = 7 - val VARINT_INFO_BITS_MASK: Int = 0b01111111 // mask for separating lowest 7 bits, where actual information stored - val VARINT_UTIL_BIT_MASK: Int = 0b10000000 // mask for separating highest bit, that indicates next byte presence + } diff --git a/proto/compiler/src/PersonMessage.kt b/proto/compiler/src/PersonMessage.kt index d582f0bf9f7..70d01f986ba 100644 --- a/proto/compiler/src/PersonMessage.kt +++ b/proto/compiler/src/PersonMessage.kt @@ -1,17 +1,17 @@ -class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email: kotlin.String? = "", phones: MutableList = mutableListOf(), someBytes: ByteArray? = ByteArray(0)) { - var name : kotlin.String? +class Person private constructor (name: kotlin.String = "", id: Int = 0, email: kotlin.String = "", phones: MutableList = mutableListOf(), someBytes: ByteArray = ByteArray(0)) { + var name : kotlin.String private set - var id : Int? + var id : Int private set - var email : kotlin.String? + var email : kotlin.String private set var phones : MutableList private set - var someBytes : ByteArray? + var someBytes : ByteArray private set @@ -38,11 +38,11 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email } } } - class PhoneNumber private constructor (number: kotlin.String? = "", type: PhoneType? = PhoneType.fromIntToPhoneType(0)) { - var number : kotlin.String? + class PhoneNumber private constructor (number: kotlin.String = "", type: PhoneType = PhoneType.fromIntToPhoneType(0)) { + var number : kotlin.String private set - var type : PhoneType? + var type : PhoneType private set @@ -52,25 +52,21 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email } fun writeTo (output: CodedOutputStream): Unit { - writeToNoTag(output) - } - - fun writeToNoTag (output: CodedOutputStream): Unit { output.writeString (1, number) - output.writeEnum (2, type?.ord) + output.writeEnum (2, type.ord) } - class BuilderPhoneNumber constructor (number: kotlin.String? = "", type: PhoneType? = PhoneType.fromIntToPhoneType(0)) { - var number : kotlin.String? + class BuilderPhoneNumber constructor (number: kotlin.String = "", type: PhoneType = PhoneType.fromIntToPhoneType(0)) { + var number : kotlin.String private set - fun setNumber(value: kotlin.String?): BuilderPhoneNumber { + fun setNumber(value: kotlin.String): BuilderPhoneNumber { number = value return this } - var type : PhoneType? + var type : PhoneType private set - fun setType(value: PhoneType?): BuilderPhoneNumber { + fun setType(value: PhoneType): BuilderPhoneNumber { type = value return this } @@ -82,10 +78,6 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email } fun readFrom (input: CodedInputStream): BuilderPhoneNumber { - return readFromNoTag(input) - } - - fun readFromNoTag (input: CodedInputStream): BuilderPhoneNumber { number = input.readString(1) type = PhoneType.fromIntToPhoneType(input.readEnum(2)) return this @@ -110,6 +102,12 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email while(parseFieldFrom(input)) {} return this } + fun getSize(): Int { + var size = 0 + size += WireFormat.getStringSize(1, number) + size += WireFormat.getEnumSize(2, type.ord) + return size + } } @@ -121,45 +119,57 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email fun mergeFrom (input: CodedInputStream) { val builder = BuilderPhoneNumber() mergeWith(builder.parseFrom(input).build())} + fun getSize(): Int { + var size = 0 + size += WireFormat.getStringSize(1, number) + size += WireFormat.getEnumSize(2, type.ord) + return size + } } fun writeTo (output: CodedOutputStream): Unit { - writeToNoTag(output) - } - - fun writeToNoTag (output: CodedOutputStream): Unit { output.writeString (1, name) output.writeInt32 (2, id) output.writeString (3, email) if (phones.size > 0) { output.writeTag(4, WireType.LENGTH_DELIMITED) - output.writeInt32NoTag(phones.size) + var arrayByteSize = 0 + run { + var arraySize = 0 + for (item in phones) { + arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + arrayByteSize += arraySize + } + output.writeInt32NoTag(arrayByteSize) for (item in phones) { - item.writeToNoTag(output) + output.writeTag(4, WireType.LENGTH_DELIMITED) + output.writeInt32NoTag(item.getSize()) + item.writeTo(output) } } output.writeBytes (5, someBytes) } - class BuilderPerson constructor (name: kotlin.String? = "", id: Int? = 0, email: kotlin.String? = "", phones: MutableList = mutableListOf(), someBytes: ByteArray? = ByteArray(0)) { - var name : kotlin.String? + class BuilderPerson constructor (name: kotlin.String = "", id: Int = 0, email: kotlin.String = "", phones: MutableList = mutableListOf(), someBytes: ByteArray = ByteArray(0)) { + var name : kotlin.String private set - fun setName(value: kotlin.String?): BuilderPerson { + fun setName(value: kotlin.String): BuilderPerson { name = value return this } - var id : Int? + var id : Int private set - fun setId(value: Int?): BuilderPerson { + fun setId(value: Int): BuilderPerson { id = value return this } - var email : kotlin.String? + var email : kotlin.String private set - fun setEmail(value: kotlin.String?): BuilderPerson { + fun setEmail(value: kotlin.String): BuilderPerson { email = value return this } @@ -185,9 +195,9 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email return this } - var someBytes : ByteArray? + var someBytes : ByteArray private set - fun setSomeBytes(value: ByteArray?): BuilderPerson { + fun setSomeBytes(value: ByteArray): BuilderPerson { someBytes = value return this } @@ -202,18 +212,19 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email } fun readFrom (input: CodedInputStream): BuilderPerson { - return readFromNoTag(input) - } - - fun readFromNoTag (input: CodedInputStream): BuilderPerson { name = input.readString(1) id = input.readInt32(2) email = input.readString(3) val tag = input.readTag(4, WireType.LENGTH_DELIMITED) - val listSize = input.readInt32NoTag() - for (i in 1..listSize) { + val expectedSize = input.readInt32NoTag() + var readSize = 0 + while(readSize != expectedSize) { val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber() - tmp.readFromNoTag(input) + input.readTag(4, WireType.LENGTH_DELIMITED) + val expectedSize = input.readInt32NoTag() + tmp.readFrom(input) + if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") } + readSize += tmp.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize()) phones.add(tmp.build()) } someBytes = input.readBytes(5) @@ -235,10 +246,15 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email 2 -> id = input.readInt32NoTag() 3 -> email = input.readStringNoTag() 4 -> { - val listSize = input.readInt32NoTag() - for (i in 1..listSize) { + val expectedSize = input.readInt32NoTag() + var readSize = 0 + while(readSize != expectedSize) { val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber() - tmp.readFromNoTag(input) + input.readTag(4, WireType.LENGTH_DELIMITED) + val expectedSize = input.readInt32NoTag() + tmp.readFrom(input) + if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") } + readSize += tmp.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize()) phones.add(tmp.build()) } } @@ -249,6 +265,21 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email while(parseFieldFrom(input)) {} return this } + fun getSize(): Int { + var size = 0 + size += WireFormat.getStringSize(1, name) + size += WireFormat.getInt32Size(2, id) + size += WireFormat.getStringSize(3, email) + run { + var arraySize = 0 + for (item in phones) { + arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + size += arraySize + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize) + } + size += WireFormat.getBytesSize(5, someBytes) + return size + } } @@ -257,12 +288,27 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email id = other.id email = other.email phones.addAll(other.phones) - someBytes?.plus(other.someBytes ?: ByteArray(0)) + someBytes.plus(other.someBytes) } fun mergeFrom (input: CodedInputStream) { val builder = BuilderPerson() mergeWith(builder.parseFrom(input).build())} + fun getSize(): Int { + var size = 0 + size += WireFormat.getStringSize(1, name) + size += WireFormat.getInt32Size(2, id) + size += WireFormat.getStringSize(3, email) + run { + var arraySize = 0 + for (item in phones) { + arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + size += arraySize + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize) + } + size += WireFormat.getBytesSize(5, someBytes) + return size + } } @@ -276,15 +322,21 @@ class AddressBook private constructor (people: MutableList = mutableLi } fun writeTo (output: CodedOutputStream): Unit { - writeToNoTag(output) - } - - fun writeToNoTag (output: CodedOutputStream): Unit { if (people.size > 0) { output.writeTag(1, WireType.LENGTH_DELIMITED) - output.writeInt32NoTag(people.size) + var arrayByteSize = 0 + run { + var arraySize = 0 + for (item in people) { + arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + arrayByteSize += arraySize + } + output.writeInt32NoTag(arrayByteSize) for (item in people) { - item.writeToNoTag(output) + output.writeTag(1, WireType.LENGTH_DELIMITED) + output.writeInt32NoTag(item.getSize()) + item.writeTo(output) } } } @@ -317,15 +369,16 @@ class AddressBook private constructor (people: MutableList = mutableLi } fun readFrom (input: CodedInputStream): BuilderAddressBook { - return readFromNoTag(input) - } - - fun readFromNoTag (input: CodedInputStream): BuilderAddressBook { val tag = input.readTag(1, WireType.LENGTH_DELIMITED) - val listSize = input.readInt32NoTag() - for (i in 1..listSize) { + val expectedSize = input.readInt32NoTag() + var readSize = 0 + while(readSize != expectedSize) { val tmp: Person.BuilderPerson = Person.BuilderPerson() - tmp.readFromNoTag(input) + input.readTag(1, WireType.LENGTH_DELIMITED) + val expectedSize = input.readInt32NoTag() + tmp.readFrom(input) + if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") } + readSize += tmp.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize()) people.add(tmp.build()) } return this @@ -343,10 +396,15 @@ class AddressBook private constructor (people: MutableList = mutableLi val wireType = WireFormat.getTagWireType(tag) when(fieldNumber) { 1 -> { - val listSize = input.readInt32NoTag() - for (i in 1..listSize) { + val expectedSize = input.readInt32NoTag() + var readSize = 0 + while(readSize != expectedSize) { val tmp: Person.BuilderPerson = Person.BuilderPerson() - tmp.readFromNoTag(input) + input.readTag(1, WireType.LENGTH_DELIMITED) + val expectedSize = input.readInt32NoTag() + tmp.readFrom(input) + if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") } + readSize += tmp.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize()) people.add(tmp.build()) } } @@ -356,6 +414,17 @@ class AddressBook private constructor (people: MutableList = mutableLi while(parseFieldFrom(input)) {} return this } + fun getSize(): Int { + var size = 0 + run { + var arraySize = 0 + for (item in people) { + arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + size += arraySize + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize) + } + return size + } } @@ -366,6 +435,17 @@ class AddressBook private constructor (people: MutableList = mutableLi fun mergeFrom (input: CodedInputStream) { val builder = BuilderAddressBook() mergeWith(builder.parseFrom(input).build())} + fun getSize(): Int { + var size = 0 + run { + var arraySize = 0 + for (item in people) { + arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize()) + } + size += arraySize + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize) + } + return size + } } diff --git a/proto/compiler/src/TestMessages.kt b/proto/compiler/src/TestMessages.kt index 34c152bc394..ffe8e78295c 100644 --- a/proto/compiler/src/TestMessages.kt +++ b/proto/compiler/src/TestMessages.kt @@ -29,7 +29,7 @@ fun testMessageSerialization() { .build() ) .build() // don't forget to call build() to produce message - msg.writeToNoTag(outs) + msg.writeTo(outs) // Now let's use output stream as input to read our message from it! var ins = CodedInputStream(ByteArrayInputStream(s.toByteArray())) diff --git a/proto/compiler/src/WireFormat.kt b/proto/compiler/src/WireFormat.kt index ebe56175fee..1caabc33a9c 100644 --- a/proto/compiler/src/WireFormat.kt +++ b/proto/compiler/src/WireFormat.kt @@ -4,8 +4,14 @@ object WireFormat { + // couple of constants for magic numbers val TAG_TYPE_BITS: Int = 3 val TAG_TYPE_MASK: Int = (1 shl TAG_TYPE_BITS) - 1 + val VARINT_INFO_BITS_COUNT: Int = 7 + val VARINT_INFO_BITS_MASK: Int = 0b01111111 // mask for separating lowest 7 bits, where actual information stored + val VARINT_UTIL_BIT_MASK: Int = 0b10000000 // mask for separating highest bit, that indicates next byte presence + val FIXED_32_BYTE_SIZE: Int = 4 + val FIXED_64_BYTE_SIZE: Int = 8 fun getTagWireType(tag: Int): WireType { return WireType from (tag and TAG_TYPE_MASK).toByte() @@ -14,4 +20,95 @@ object WireFormat { fun getTagFieldNumber(tag: Int): Int { return tag ushr TAG_TYPE_BITS } + + // TODO: refactor casts into function overloading as soon as translator will support it + fun getTagSize(fieldNumber: Int, wireType: WireType): Int { + return getVarint32Size((fieldNumber shl 3) or wireType.ordinal) + } + + fun getVarint32Size(value: Int): Int { + var curValue = value + var size = 0 + while (curValue != 0) { + size += 1 + curValue = value ushr VARINT_INFO_BITS_COUNT + } + return size + } + + fun getVarint64Size(value: Long): Int { + var curValue = value + var size = 0 + while (curValue != 0L) { + size += 1 + curValue = value ushr VARINT_INFO_BITS_COUNT + } + return size + } + + fun getZigZag32Size(value: Int): Int { + return getVarint32Size((value shl 1) xor (value shr 31)) + } + + fun getZigZag64Size(value: Long): Int { + return getVarint64Size((value shl 1) xor (value shr 63)) + } + + fun getInt32Size(fieldNumber: Int, value: Int): Int { + return getTagSize(fieldNumber, WireType.VARINT) + getVarint32Size(value) + } + + fun getUInt32Size(fieldNumber: Int, value: Int): Int { + return getInt32Size(fieldNumber, value) + } + + fun getInt64Size(fieldNumber: Int, value: Long): Int { + return getTagSize(fieldNumber, WireType.VARINT) + getVarint64Size(value) + } + + fun getUInt64Size(fieldNumber: Int, value: Long): Int { + return getInt64Size(fieldNumber, value) + } + + fun getBoolSize(fieldNumber: Int, value: Boolean): Int { + val intValue = if (value) 1 else 0 + return getInt32Size(fieldNumber, intValue) + } + + fun getEnumSize(fieldNumber: Int, value: Int): Int { + return getInt32Size(fieldNumber, value) + } + + fun getSInt32Size(fieldNumber: Int, value: Int): Int { + return getTagSize(fieldNumber, WireType.VARINT) + getZigZag32Size(value) + } + + fun getSInt64Size(fieldNumber: Int, value: Long): Int { + return getTagSize(fieldNumber, WireType.VARINT) + getZigZag64Size(value) + } + + fun getFixed32Size(fieldNumber: Int, value: Long): Int { + return getTagSize(fieldNumber, WireType.FIX_32) + FIXED_32_BYTE_SIZE + } + + fun getFixed64Size(fieldNumber: Int, value: Long): Int { + return getTagSize(fieldNumber, WireType.FIX_64) + FIXED_64_BYTE_SIZE + } + + fun getDoubleSize(fieldNumber: Int, value: Double): Int { + return getTagSize(fieldNumber, WireType.FIX_32) + FIXED_32_BYTE_SIZE + } + + fun getFloatSize(fieldNumber: Int, value: Float): Int { + return getTagSize(fieldNumber, WireType.FIX_64) + FIXED_64_BYTE_SIZE + } + + fun getStringSize(fieldNumber: Int, value: String): Int { + val encodedStringSize = value.toByteArray(Charsets.UTF_8).size //TODO: not sure if it's the best way to do it + return encodedStringSize + getTagSize(fieldNumber, WireType.LENGTH_DELIMITED) + getVarint32Size(encodedStringSize) + } + + fun getBytesSize(fieldNumber: Int, value: ByteArray): Int { + return value.size + getTagSize(fieldNumber, WireType.LENGTH_DELIMITED) + getVarint32Size(value.size) + } } \ No newline at end of file