1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2008 Google Inc. All rights reserved. 3 // 4 // Use of this source code is governed by a BSD-style 5 // license that can be found in the LICENSE file or at 6 // https://developers.google.com/open-source/licenses/bsd 7 8 import com.google.protobuf.AbstractMessageLite; 9 import com.google.protobuf.ByteString; 10 import com.google.protobuf.CodedInputStream; 11 import com.google.protobuf.ExtensionRegistryLite; 12 import com.google.protobuf.InvalidProtocolBufferException; 13 import com.google.protobuf.MessageLite; 14 import com.google.protobuf.Parser; 15 import com.google.protobuf.conformance.Conformance; 16 import com.google.protobuf_test_messages.edition2023.TestAllTypesEdition2023; 17 import com.google.protobuf_test_messages.edition2023.TestMessagesEdition2023; 18 import com.google.protobuf_test_messages.editions.proto2.TestMessagesProto2Editions; 19 import com.google.protobuf_test_messages.editions.proto3.TestMessagesProto3Editions; 20 import com.google.protobuf_test_messages.proto2.TestMessagesProto2; 21 import com.google.protobuf_test_messages.proto2.TestMessagesProto2.TestAllTypesProto2; 22 import com.google.protobuf_test_messages.proto3.TestMessagesProto3; 23 import com.google.protobuf_test_messages.proto3.TestMessagesProto3.TestAllTypesProto3; 24 import java.nio.ByteBuffer; 25 import java.util.ArrayList; 26 27 class ConformanceJavaLite { 28 private int testCount = 0; 29 readFromStdin(byte[] buf, int len)30 private boolean readFromStdin(byte[] buf, int len) throws Exception { 31 int ofs = 0; 32 while (len > 0) { 33 int read = System.in.read(buf, ofs, len); 34 if (read == -1) { 35 return false; // EOF 36 } 37 ofs += read; 38 len -= read; 39 } 40 41 return true; 42 } 43 writeToStdout(byte[] buf)44 private void writeToStdout(byte[] buf) throws Exception { 45 System.out.write(buf); 46 } 47 48 // Returns -1 on EOF (the actual values will always be positive). readLittleEndianIntFromStdin()49 private int readLittleEndianIntFromStdin() throws Exception { 50 byte[] buf = new byte[4]; 51 if (!readFromStdin(buf, 4)) { 52 return -1; 53 } 54 return (buf[0] & 0xff) 55 | ((buf[1] & 0xff) << 8) 56 | ((buf[2] & 0xff) << 16) 57 | ((buf[3] & 0xff) << 24); 58 } 59 writeLittleEndianIntToStdout(int val)60 private void writeLittleEndianIntToStdout(int val) throws Exception { 61 byte[] buf = new byte[4]; 62 buf[0] = (byte) val; 63 buf[1] = (byte) (val >> 8); 64 buf[2] = (byte) (val >> 16); 65 buf[3] = (byte) (val >> 24); 66 writeToStdout(buf); 67 } 68 69 private enum BinaryDecoderType { 70 BYTE_STRING_DECODER, 71 BYTE_ARRAY_DECODER, 72 ARRAY_BYTE_BUFFER_DECODER, 73 READONLY_ARRAY_BYTE_BUFFER_DECODER, 74 DIRECT_BYTE_BUFFER_DECODER, 75 READONLY_DIRECT_BYTE_BUFFER_DECODER, 76 INPUT_STREAM_DECODER; 77 } 78 79 private static class BinaryDecoder<T extends MessageLite> { decode( ByteString bytes, BinaryDecoderType type, Parser<T> parser, ExtensionRegistryLite extensions)80 public T decode( 81 ByteString bytes, 82 BinaryDecoderType type, 83 Parser<T> parser, 84 ExtensionRegistryLite extensions) 85 throws InvalidProtocolBufferException { 86 switch (type) { 87 case BYTE_STRING_DECODER: 88 case BYTE_ARRAY_DECODER: 89 return parser.parseFrom(bytes, extensions); 90 case ARRAY_BYTE_BUFFER_DECODER: 91 { 92 ByteBuffer buffer = ByteBuffer.allocate(bytes.size()); 93 bytes.copyTo(buffer); 94 buffer.flip(); 95 return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions); 96 } 97 case READONLY_ARRAY_BYTE_BUFFER_DECODER: 98 { 99 return parser.parseFrom( 100 CodedInputStream.newInstance(bytes.asReadOnlyByteBuffer()), extensions); 101 } 102 case DIRECT_BYTE_BUFFER_DECODER: 103 { 104 ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size()); 105 bytes.copyTo(buffer); 106 buffer.flip(); 107 return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions); 108 } 109 case READONLY_DIRECT_BYTE_BUFFER_DECODER: 110 { 111 ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size()); 112 bytes.copyTo(buffer); 113 buffer.flip(); 114 return parser.parseFrom( 115 CodedInputStream.newInstance(buffer.asReadOnlyBuffer()), extensions); 116 } 117 case INPUT_STREAM_DECODER: 118 { 119 return parser.parseFrom(bytes.newInput(), extensions); 120 } 121 } 122 return null; 123 } 124 } 125 parseBinary( ByteString bytes, Parser<T> parser, ExtensionRegistryLite extensions)126 private <T extends MessageLite> T parseBinary( 127 ByteString bytes, Parser<T> parser, ExtensionRegistryLite extensions) 128 throws InvalidProtocolBufferException { 129 ArrayList<T> messages = new ArrayList<>(); 130 ArrayList<InvalidProtocolBufferException> exceptions = new ArrayList<>(); 131 132 for (int i = 0; i < BinaryDecoderType.values().length; i++) { 133 messages.add(null); 134 exceptions.add(null); 135 } 136 if (messages.isEmpty()) { 137 throw new RuntimeException("binary decoder types missing"); 138 } 139 140 BinaryDecoder<T> decoder = new BinaryDecoder<>(); 141 142 boolean hasMessage = false; 143 boolean hasException = false; 144 for (int i = 0; i < BinaryDecoderType.values().length; ++i) { 145 try { 146 messages.set(i, decoder.decode(bytes, BinaryDecoderType.values()[i], parser, extensions)); 147 hasMessage = true; 148 } catch (InvalidProtocolBufferException e) { 149 exceptions.set(i, e); 150 hasException = true; 151 } 152 } 153 154 if (hasMessage && hasException) { 155 StringBuilder sb = 156 new StringBuilder("Binary decoders disagreed on whether the payload was valid.\n"); 157 for (int i = 0; i < BinaryDecoderType.values().length; ++i) { 158 sb.append(BinaryDecoderType.values()[i].name()); 159 if (messages.get(i) != null) { 160 sb.append(" accepted the payload.\n"); 161 } else { 162 sb.append(" rejected the payload.\n"); 163 } 164 } 165 throw new RuntimeException(sb.toString()); 166 } 167 168 if (hasException) { 169 // We do not check if exceptions are equal. Different implementations may return different 170 // exception messages. Throw an arbitrary one out instead. 171 InvalidProtocolBufferException exception = null; 172 for (InvalidProtocolBufferException e : exceptions) { 173 if (exception != null) { 174 exception.addSuppressed(e); 175 } else { 176 exception = e; 177 } 178 } 179 throw exception; 180 } 181 182 // Fast path comparing all the messages with the first message, assuming equality being 183 // symmetric and transitive. 184 boolean allEqual = true; 185 for (int i = 1; i < messages.size(); ++i) { 186 if (!messages.get(0).equals(messages.get(i))) { 187 allEqual = false; 188 break; 189 } 190 } 191 192 // Slow path: compare and find out all unequal pairs. 193 if (!allEqual) { 194 StringBuilder sb = new StringBuilder(); 195 for (int i = 0; i < messages.size() - 1; ++i) { 196 for (int j = i + 1; j < messages.size(); ++j) { 197 if (!messages.get(i).equals(messages.get(j))) { 198 sb.append(BinaryDecoderType.values()[i].name()) 199 .append(" and ") 200 .append(BinaryDecoderType.values()[j].name()) 201 .append(" parsed the payload differently.\n"); 202 } 203 } 204 } 205 throw new RuntimeException(sb.toString()); 206 } 207 208 return messages.get(0); 209 } 210 createTestMessage(String messageType)211 private Class<? extends AbstractMessageLite> createTestMessage(String messageType) { 212 switch (messageType) { 213 case "protobuf_test_messages.proto3.TestAllTypesProto3": 214 return TestAllTypesProto3.class; 215 case "protobuf_test_messages.proto2.TestAllTypesProto2": 216 return TestAllTypesProto2.class; 217 case "protobuf_test_messages.editions.TestAllTypesEdition2023": 218 return TestAllTypesEdition2023.class; 219 case "protobuf_test_messages.editions.proto3.TestAllTypesProto3": 220 return TestMessagesProto3Editions.TestAllTypesProto3.class; 221 case "protobuf_test_messages.editions.proto2.TestAllTypesProto2": 222 return TestMessagesProto2Editions.TestAllTypesProto2.class; 223 default: 224 throw new IllegalArgumentException( 225 "Protobuf request has unexpected payload type: " + messageType); 226 } 227 } 228 createTestFile(String messageType)229 private Class<?> createTestFile(String messageType) { 230 switch (messageType) { 231 case "protobuf_test_messages.proto3.TestAllTypesProto3": 232 return TestMessagesProto3.class; 233 case "protobuf_test_messages.proto2.TestAllTypesProto2": 234 return TestMessagesProto2.class; 235 case "protobuf_test_messages.editions.TestAllTypesEdition2023": 236 return TestMessagesEdition2023.class; 237 case "protobuf_test_messages.editions.proto3.TestAllTypesProto3": 238 return TestMessagesProto3Editions.class; 239 case "protobuf_test_messages.editions.proto2.TestAllTypesProto2": 240 return TestMessagesProto2Editions.class; 241 default: 242 throw new IllegalArgumentException( 243 "Protobuf request has unexpected payload type: " + messageType); 244 } 245 } 246 247 @SuppressWarnings("unchecked") doTest(Conformance.ConformanceRequest request)248 private Conformance.ConformanceResponse doTest(Conformance.ConformanceRequest request) { 249 com.google.protobuf.MessageLite testMessage; 250 String messageType = request.getMessageType(); 251 switch (request.getPayloadCase()) { 252 case PROTOBUF_PAYLOAD: 253 { 254 try { 255 ExtensionRegistryLite extensions = ExtensionRegistryLite.newInstance(); 256 createTestFile(messageType) 257 .getMethod("registerAllExtensions", ExtensionRegistryLite.class) 258 .invoke(null, extensions); 259 testMessage = 260 parseBinary( 261 request.getProtobufPayload(), 262 (Parser<AbstractMessageLite>) 263 createTestMessage(messageType).getMethod("parser").invoke(null), 264 extensions); 265 } catch (InvalidProtocolBufferException e) { 266 return Conformance.ConformanceResponse.newBuilder() 267 .setParseError(e.getMessage()) 268 .build(); 269 } catch (Exception e) { 270 throw new RuntimeException(e); 271 } 272 break; 273 } 274 case JSON_PAYLOAD: 275 { 276 return Conformance.ConformanceResponse.newBuilder() 277 .setSkipped("Lite runtime does not support JSON format.") 278 .build(); 279 } 280 case TEXT_PAYLOAD: 281 { 282 return Conformance.ConformanceResponse.newBuilder() 283 .setSkipped("Lite runtime does not support Text format.") 284 .build(); 285 } 286 case PAYLOAD_NOT_SET: 287 { 288 throw new RuntimeException("Request didn't have payload."); 289 } 290 default: 291 { 292 throw new RuntimeException("Unexpected payload case."); 293 } 294 } 295 296 switch (request.getRequestedOutputFormat()) { 297 case UNSPECIFIED: 298 throw new RuntimeException("Unspecified output format."); 299 300 case PROTOBUF: 301 return Conformance.ConformanceResponse.newBuilder() 302 .setProtobufPayload(testMessage.toByteString()) 303 .build(); 304 305 case JSON: 306 return Conformance.ConformanceResponse.newBuilder() 307 .setSkipped("Lite runtime does not support JSON format.") 308 .build(); 309 310 case TEXT_FORMAT: 311 return Conformance.ConformanceResponse.newBuilder() 312 .setSkipped("Lite runtime does not support Text format.") 313 .build(); 314 default: 315 { 316 throw new RuntimeException("Unexpected request output."); 317 } 318 } 319 } 320 doTestIo()321 private boolean doTestIo() throws Exception { 322 int bytes = readLittleEndianIntFromStdin(); 323 324 if (bytes == -1) { 325 return false; // EOF 326 } 327 328 byte[] serializedInput = new byte[bytes]; 329 330 if (!readFromStdin(serializedInput, bytes)) { 331 throw new RuntimeException("Unexpected EOF from test program."); 332 } 333 334 Conformance.ConformanceRequest request = 335 Conformance.ConformanceRequest.parseFrom(serializedInput); 336 Conformance.ConformanceResponse response = doTest(request); 337 byte[] serializedOutput = response.toByteArray(); 338 339 writeLittleEndianIntToStdout(serializedOutput.length); 340 writeToStdout(serializedOutput); 341 342 return true; 343 } 344 run()345 public void run() throws Exception { 346 while (doTestIo()) { 347 this.testCount++; 348 } 349 350 System.err.println( 351 "ConformanceJavaLite: received EOF from test runner after " + this.testCount + " tests"); 352 } 353 main(String[] args)354 public static void main(String[] args) throws Exception { 355 new ConformanceJavaLite().run(); 356 } 357 } 358