// 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.IOException; import java.util.Iterator; import java.util.Map.Entry; /** Schema used for proto2 messages using message_set_wireformat. */ @CheckReturnValue final class MessageSetSchema implements Schema { private final MessageLite defaultInstance; private final UnknownFieldSchema unknownFieldSchema; private final boolean hasExtensions; private final ExtensionSchema extensionSchema; private MessageSetSchema( UnknownFieldSchema unknownFieldSchema, ExtensionSchema extensionSchema, MessageLite defaultInstance) { this.unknownFieldSchema = unknownFieldSchema; this.hasExtensions = extensionSchema.hasExtensions(defaultInstance); this.extensionSchema = extensionSchema; this.defaultInstance = defaultInstance; } static MessageSetSchema newSchema( UnknownFieldSchema unknownFieldSchema, ExtensionSchema extensionSchema, MessageLite defaultInstance) { return new MessageSetSchema(unknownFieldSchema, extensionSchema, defaultInstance); } @SuppressWarnings("unchecked") @Override public T newInstance() { // TODO(b/248560713) decide if we're keeping support for Full in schema classes and handle this // better. if (defaultInstance instanceof GeneratedMessageLite) { return (T) ((GeneratedMessageLite) defaultInstance).newMutableInstance(); } else { return (T) defaultInstance.newBuilderForType().buildPartial(); } } @Override public boolean equals(T message, T other) { Object messageUnknown = unknownFieldSchema.getFromMessage(message); Object otherUnknown = unknownFieldSchema.getFromMessage(other); if (!messageUnknown.equals(otherUnknown)) { return false; } if (hasExtensions) { FieldSet messageExtensions = extensionSchema.getExtensions(message); FieldSet otherExtensions = extensionSchema.getExtensions(other); return messageExtensions.equals(otherExtensions); } return true; } @Override public int hashCode(T message) { int hashCode = unknownFieldSchema.getFromMessage(message).hashCode(); if (hasExtensions) { FieldSet extensions = extensionSchema.getExtensions(message); hashCode = (hashCode * 53) + extensions.hashCode(); } return hashCode; } @Override public void mergeFrom(T message, T other) { SchemaUtil.mergeUnknownFields(unknownFieldSchema, message, other); if (hasExtensions) { SchemaUtil.mergeExtensions(extensionSchema, message, other); } } @SuppressWarnings("unchecked") @Override public void writeTo(T message, Writer writer) throws IOException { FieldSet extensions = extensionSchema.getExtensions(message); Iterator iterator = extensions.iterator(); while (iterator.hasNext()) { Entry extension = (Entry) iterator.next(); FieldSet.FieldDescriptorLite fd = (FieldSet.FieldDescriptorLite) extension.getKey(); if (fd.getLiteJavaType() != WireFormat.JavaType.MESSAGE || fd.isRepeated() || fd.isPacked()) { throw new IllegalStateException("Found invalid MessageSet item."); } if (extension instanceof LazyField.LazyEntry) { writer.writeMessageSetItem( fd.getNumber(), ((LazyField.LazyEntry) extension).getField().toByteString()); } else { writer.writeMessageSetItem(fd.getNumber(), extension.getValue()); } } writeUnknownFieldsHelper(unknownFieldSchema, message, writer); } /** * A helper method for wildcard capture of {@code unknownFieldSchema}. See: * https://docs.oracle.com/javase/tutorial/java/generics/capture.html */ private void writeUnknownFieldsHelper( UnknownFieldSchema unknownFieldSchema, T message, Writer writer) throws IOException { unknownFieldSchema.writeAsMessageSetTo(unknownFieldSchema.getFromMessage(message), writer); } @SuppressWarnings("ReferenceEquality") @Override public void mergeFrom( T message, byte[] data, int position, int limit, ArrayDecoders.Registers registers) throws IOException { // TODO(b/248560713) decide if we're keeping support for Full in schema classes and handle this // better. UnknownFieldSetLite unknownFields = ((GeneratedMessageLite) message).unknownFields; if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) { unknownFields = UnknownFieldSetLite.newInstance(); ((GeneratedMessageLite) message).unknownFields = unknownFields; } final FieldSet extensions = ((GeneratedMessageLite.ExtendableMessage) message).ensureExtensionsAreMutable(); GeneratedMessageLite.GeneratedExtension extension = null; while (position < limit) { position = ArrayDecoders.decodeVarint32(data, position, registers); final int startTag = registers.int1; if (startTag != WireFormat.MESSAGE_SET_ITEM_TAG) { if (WireFormat.getTagWireType(startTag) == WireFormat.WIRETYPE_LENGTH_DELIMITED) { extension = (GeneratedMessageLite.GeneratedExtension) extensionSchema.findExtensionByNumber( registers.extensionRegistry, defaultInstance, WireFormat.getTagFieldNumber(startTag)); if (extension != null) { position = ArrayDecoders.decodeMessageField( Protobuf.getInstance().schemaFor( extension.getMessageDefaultInstance().getClass()), data, position, limit, registers); extensions.setField(extension.descriptor, registers.object1); } else { position = ArrayDecoders.decodeUnknownField( startTag, data, position, limit, unknownFields, registers); } } else { position = ArrayDecoders.skipField(startTag, data, position, limit, registers); } continue; } int typeId = 0; ByteString rawBytes = null; while (position < limit) { position = ArrayDecoders.decodeVarint32(data, position, registers); final int tag = registers.int1; final int number = WireFormat.getTagFieldNumber(tag); final int wireType = WireFormat.getTagWireType(tag); switch (number) { case WireFormat.MESSAGE_SET_TYPE_ID: if (wireType == WireFormat.WIRETYPE_VARINT) { position = ArrayDecoders.decodeVarint32(data, position, registers); typeId = registers.int1; // TODO(b/248560713) decide if we're keeping support for Full in schema classes and // handle this better. extension = (GeneratedMessageLite.GeneratedExtension) extensionSchema.findExtensionByNumber( registers.extensionRegistry, defaultInstance, typeId); continue; } break; case WireFormat.MESSAGE_SET_MESSAGE: if (extension != null) { position = ArrayDecoders.decodeMessageField( Protobuf.getInstance().schemaFor( extension.getMessageDefaultInstance().getClass()), data, position, limit, registers); extensions.setField(extension.descriptor, registers.object1); continue; } else { if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) { position = ArrayDecoders.decodeBytes(data, position, registers); rawBytes = (ByteString) registers.object1; continue; } break; } default: break; } if (tag == WireFormat.MESSAGE_SET_ITEM_END_TAG) { break; } position = ArrayDecoders.skipField(tag, data, position, limit, registers); } if (rawBytes != null) { unknownFields.storeField( WireFormat.makeTag(typeId, WireFormat.WIRETYPE_LENGTH_DELIMITED), rawBytes); } } if (position != limit) { throw InvalidProtocolBufferException.parseFailure(); } } @Override public void mergeFrom(T message, Reader reader, ExtensionRegistryLite extensionRegistry) throws IOException { mergeFromHelper(unknownFieldSchema, extensionSchema, message, reader, extensionRegistry); } /** * A helper method for wildcard capture of {@code unknownFieldSchema}. See: * https://docs.oracle.com/javase/tutorial/java/generics/capture.html */ private > void mergeFromHelper( UnknownFieldSchema unknownFieldSchema, ExtensionSchema extensionSchema, T message, Reader reader, ExtensionRegistryLite extensionRegistry) throws IOException { UB unknownFields = unknownFieldSchema.getBuilderFromMessage(message); FieldSet extensions = extensionSchema.getMutableExtensions(message); try { while (true) { final int number = reader.getFieldNumber(); if (number == Reader.READ_DONE) { return; } if (parseMessageSetItemOrUnknownField( reader, extensionRegistry, extensionSchema, extensions, unknownFieldSchema, unknownFields)) { continue; } // Done reading. return; } } finally { unknownFieldSchema.setBuilderToMessage(message, unknownFields); } } @Override public void makeImmutable(T message) { unknownFieldSchema.makeImmutable(message); extensionSchema.makeImmutable(message); } private > boolean parseMessageSetItemOrUnknownField( Reader reader, ExtensionRegistryLite extensionRegistry, ExtensionSchema extensionSchema, FieldSet extensions, UnknownFieldSchema unknownFieldSchema, UB unknownFields) throws IOException { int startTag = reader.getTag(); if (startTag != WireFormat.MESSAGE_SET_ITEM_TAG) { if (WireFormat.getTagWireType(startTag) == WireFormat.WIRETYPE_LENGTH_DELIMITED) { Object extension = extensionSchema.findExtensionByNumber( extensionRegistry, defaultInstance, WireFormat.getTagFieldNumber(startTag)); if (extension != null) { extensionSchema.parseLengthPrefixedMessageSetItem( reader, extension, extensionRegistry, extensions); return true; } else { return unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader); } } else { return reader.skipField(); } } // The wire format for MessageSet is: // message MessageSet { // repeated group Item = 1 { // required uint32 typeId = 2; // required bytes message = 3; // } // } // "typeId" is the extension's field number. The extension can only be // a message type, where "message" contains the encoded bytes of that // message. // // In practice, we will probably never see a MessageSet item in which // the message appears before the type ID, or where either field does not // appear exactly once. However, in theory such cases are valid, so we // should be prepared to accept them. int typeId = 0; ByteString rawBytes = null; // If we encounter "message" before "typeId" Object extension = null; // Read bytes from input, if we get it's type first then parse it eagerly, // otherwise we store the raw bytes in a local variable. loop: while (true) { final int number = reader.getFieldNumber(); if (number == Reader.READ_DONE) { break; } final int tag = reader.getTag(); if (tag == WireFormat.MESSAGE_SET_TYPE_ID_TAG) { typeId = reader.readUInt32(); extension = extensionSchema.findExtensionByNumber(extensionRegistry, defaultInstance, typeId); continue; } else if (tag == WireFormat.MESSAGE_SET_MESSAGE_TAG) { if (extension != null) { extensionSchema.parseLengthPrefixedMessageSetItem( reader, extension, extensionRegistry, extensions); continue; } // We haven't seen a type ID yet or we want parse message lazily. rawBytes = reader.readBytes(); continue; } else { if (!reader.skipField()) { break loop; // End of group } } } if (reader.getTag() != WireFormat.MESSAGE_SET_ITEM_END_TAG) { throw InvalidProtocolBufferException.invalidEndTag(); } // If there are any rawBytes left, it means the message content appears before the type ID. if (rawBytes != null) { if (extension != null) { // We known the type // TODO(xiaofeng): Instead of reading into a temporary ByteString, maybe there is a way // to read directly from Reader to the submessage? extensionSchema.parseMessageSetItem(rawBytes, extension, extensionRegistry, extensions); } else { unknownFieldSchema.addLengthDelimited(unknownFields, typeId, rawBytes); } } return true; } @Override public final boolean isInitialized(T message) { FieldSet extensions = extensionSchema.getExtensions(message); return extensions.isInitialized(); } @Override public int getSerializedSize(T message) { int size = 0; size += getUnknownFieldsSerializedSize(unknownFieldSchema, message); if (hasExtensions) { size += extensionSchema.getExtensions(message).getMessageSetSerializedSize(); } return size; } private int getUnknownFieldsSerializedSize( UnknownFieldSchema schema, T message) { UT unknowns = schema.getFromMessage(message); return schema.getSerializedSizeAsMessageSet(unknowns); } }