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.AbstractMessage; 9 import com.google.protobuf.ByteString; 10 import com.google.protobuf.CodedInputStream; 11 import com.google.protobuf.ExtensionRegistry; 12 import com.google.protobuf.InvalidProtocolBufferException; 13 import com.google.protobuf.Parser; 14 import com.google.protobuf.TextFormat; 15 import com.google.protobuf.conformance.Conformance; 16 import com.google.protobuf.util.JsonFormat; 17 import com.google.protobuf.util.JsonFormat.TypeRegistry; 18 import com.google.protobuf_test_messages.edition2023.TestAllTypesEdition2023; 19 import com.google.protobuf_test_messages.edition2023.TestMessagesEdition2023; 20 import com.google.protobuf_test_messages.editions.proto2.TestMessagesProto2Editions; 21 import com.google.protobuf_test_messages.editions.proto3.TestMessagesProto3Editions; 22 import com.google.protobuf_test_messages.proto2.TestMessagesProto2; 23 import com.google.protobuf_test_messages.proto2.TestMessagesProto2.TestAllTypesProto2; 24 import com.google.protobuf_test_messages.proto3.TestMessagesProto3; 25 import com.google.protobuf_test_messages.proto3.TestMessagesProto3.TestAllTypesProto3; 26 import java.nio.ByteBuffer; 27 import java.util.ArrayList; 28 29 class ConformanceJava { 30 private int testCount = 0; 31 private TypeRegistry typeRegistry; 32 readFromStdin(byte[] buf, int len)33 private boolean readFromStdin(byte[] buf, int len) throws Exception { 34 int ofs = 0; 35 while (len > 0) { 36 int read = System.in.read(buf, ofs, len); 37 if (read == -1) { 38 return false; // EOF 39 } 40 ofs += read; 41 len -= read; 42 } 43 44 return true; 45 } 46 writeToStdout(byte[] buf)47 private void writeToStdout(byte[] buf) throws Exception { 48 System.out.write(buf); 49 } 50 51 // Returns -1 on EOF (the actual values will always be positive). readLittleEndianIntFromStdin()52 private int readLittleEndianIntFromStdin() throws Exception { 53 byte[] buf = new byte[4]; 54 if (!readFromStdin(buf, 4)) { 55 return -1; 56 } 57 return (buf[0] & 0xff) 58 | ((buf[1] & 0xff) << 8) 59 | ((buf[2] & 0xff) << 16) 60 | ((buf[3] & 0xff) << 24); 61 } 62 writeLittleEndianIntToStdout(int val)63 private void writeLittleEndianIntToStdout(int val) throws Exception { 64 byte[] buf = new byte[4]; 65 buf[0] = (byte) val; 66 buf[1] = (byte) (val >> 8); 67 buf[2] = (byte) (val >> 16); 68 buf[3] = (byte) (val >> 24); 69 writeToStdout(buf); 70 } 71 72 private enum BinaryDecoderType { 73 BYTE_STRING_DECODER, 74 BYTE_ARRAY_DECODER, 75 ARRAY_BYTE_BUFFER_DECODER, 76 READONLY_ARRAY_BYTE_BUFFER_DECODER, 77 DIRECT_BYTE_BUFFER_DECODER, 78 READONLY_DIRECT_BYTE_BUFFER_DECODER, 79 INPUT_STREAM_DECODER; 80 } 81 82 private static class BinaryDecoder<T extends AbstractMessage> { decode( ByteString bytes, BinaryDecoderType type, Parser<T> parser, ExtensionRegistry extensions)83 public T decode( 84 ByteString bytes, BinaryDecoderType type, Parser<T> parser, ExtensionRegistry 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, ExtensionRegistry extensions)126 private <T extends AbstractMessage> T parseBinary( 127 ByteString bytes, Parser<T> parser, ExtensionRegistry 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 // = BinaryDecoderType.values()[i].parseProto3(bytes); 147 messages.set(i, decoder.decode(bytes, BinaryDecoderType.values()[i], parser, extensions)); 148 hasMessage = true; 149 } catch (InvalidProtocolBufferException e) { 150 exceptions.set(i, e); 151 hasException = true; 152 } 153 } 154 155 if (hasMessage && hasException) { 156 StringBuilder sb = 157 new StringBuilder("Binary decoders disagreed on whether the payload was valid.\n"); 158 for (int i = 0; i < BinaryDecoderType.values().length; ++i) { 159 sb.append(BinaryDecoderType.values()[i].name()); 160 if (messages.get(i) != null) { 161 sb.append(" accepted the payload.\n"); 162 } else { 163 sb.append(" rejected the payload.\n"); 164 } 165 } 166 throw new RuntimeException(sb.toString()); 167 } 168 169 if (hasException) { 170 // We do not check if exceptions are equal. Different implementations may return different 171 // exception messages. Throw an arbitrary one out instead. 172 InvalidProtocolBufferException exception = null; 173 for (InvalidProtocolBufferException e : exceptions) { 174 if (exception != null) { 175 exception.addSuppressed(e); 176 } else { 177 exception = e; 178 } 179 } 180 throw exception; 181 } 182 183 // Fast path comparing all the messages with the first message, assuming equality being 184 // symmetric and transitive. 185 boolean allEqual = true; 186 for (int i = 1; i < messages.size(); ++i) { 187 if (!messages.get(0).equals(messages.get(i))) { 188 allEqual = false; 189 break; 190 } 191 } 192 193 // Slow path: compare and find out all unequal pairs. 194 if (!allEqual) { 195 StringBuilder sb = new StringBuilder(); 196 for (int i = 0; i < messages.size() - 1; ++i) { 197 for (int j = i + 1; j < messages.size(); ++j) { 198 if (!messages.get(i).equals(messages.get(j))) { 199 sb.append(BinaryDecoderType.values()[i].name()) 200 .append(" and ") 201 .append(BinaryDecoderType.values()[j].name()) 202 .append(" parsed the payload differently.\n"); 203 } 204 } 205 } 206 throw new RuntimeException(sb.toString()); 207 } 208 209 return messages.get(0); 210 } 211 createTestMessage(String messageType)212 private Class<? extends AbstractMessage> createTestMessage(String messageType) { 213 switch (messageType) { 214 case "protobuf_test_messages.proto3.TestAllTypesProto3": 215 return TestAllTypesProto3.class; 216 case "protobuf_test_messages.proto2.TestAllTypesProto2": 217 return TestAllTypesProto2.class; 218 case "protobuf_test_messages.editions.TestAllTypesEdition2023": 219 return TestAllTypesEdition2023.class; 220 case "protobuf_test_messages.editions.proto3.TestAllTypesProto3": 221 return TestMessagesProto3Editions.TestAllTypesProto3.class; 222 case "protobuf_test_messages.editions.proto2.TestAllTypesProto2": 223 return TestMessagesProto2Editions.TestAllTypesProto2.class; 224 default: 225 throw new IllegalArgumentException( 226 "Protobuf request has unexpected payload type: " + messageType); 227 } 228 } 229 createTestFile(String messageType)230 private Class<?> createTestFile(String messageType) { 231 switch (messageType) { 232 case "protobuf_test_messages.proto3.TestAllTypesProto3": 233 return TestMessagesProto3.class; 234 case "protobuf_test_messages.proto2.TestAllTypesProto2": 235 return TestMessagesProto2.class; 236 case "protobuf_test_messages.editions.TestAllTypesEdition2023": 237 return TestMessagesEdition2023.class; 238 case "protobuf_test_messages.editions.proto3.TestAllTypesProto3": 239 return TestMessagesProto3Editions.class; 240 case "protobuf_test_messages.editions.proto2.TestAllTypesProto2": 241 return TestMessagesProto2Editions.class; 242 default: 243 throw new IllegalArgumentException( 244 "Protobuf request has unexpected payload type: " + messageType); 245 } 246 } 247 248 @SuppressWarnings("unchecked") doTest(Conformance.ConformanceRequest request)249 private Conformance.ConformanceResponse doTest(Conformance.ConformanceRequest request) { 250 AbstractMessage testMessage; 251 String messageType = request.getMessageType(); 252 253 ExtensionRegistry extensions = ExtensionRegistry.newInstance(); 254 try { 255 createTestFile(messageType) 256 .getMethod("registerAllExtensions", ExtensionRegistry.class) 257 .invoke(null, extensions); 258 } catch (Exception e) { 259 throw new RuntimeException(e); 260 } 261 262 switch (request.getPayloadCase()) { 263 case PROTOBUF_PAYLOAD: 264 { 265 try { 266 testMessage = 267 parseBinary( 268 request.getProtobufPayload(), 269 (Parser<AbstractMessage>) 270 createTestMessage(messageType).getMethod("parser").invoke(null), 271 extensions); 272 } catch (InvalidProtocolBufferException e) { 273 return Conformance.ConformanceResponse.newBuilder() 274 .setParseError(e.getMessage()) 275 .build(); 276 } catch (Exception e) { 277 throw new RuntimeException(e); 278 } 279 break; 280 } 281 case JSON_PAYLOAD: 282 { 283 try { 284 JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(typeRegistry); 285 if (request.getTestCategory() 286 == Conformance.TestCategory.JSON_IGNORE_UNKNOWN_PARSING_TEST) { 287 parser = parser.ignoringUnknownFields(); 288 } 289 AbstractMessage.Builder<?> builder = 290 (AbstractMessage.Builder<?>) 291 createTestMessage(messageType).getMethod("newBuilder").invoke(null); 292 parser.merge(request.getJsonPayload(), builder); 293 testMessage = (AbstractMessage) builder.build(); 294 } catch (InvalidProtocolBufferException e) { 295 return Conformance.ConformanceResponse.newBuilder() 296 .setParseError(e.getMessage()) 297 .build(); 298 } catch (Exception e) { 299 throw new RuntimeException(e); 300 } 301 break; 302 } 303 case TEXT_PAYLOAD: 304 { 305 try { 306 AbstractMessage.Builder<?> builder = 307 (AbstractMessage.Builder<?>) 308 createTestMessage(messageType).getMethod("newBuilder").invoke(null); 309 TextFormat.merge(request.getTextPayload(), extensions, builder); 310 testMessage = (AbstractMessage) builder.build(); 311 } catch (TextFormat.ParseException e) { 312 return Conformance.ConformanceResponse.newBuilder() 313 .setParseError(e.getMessage()) 314 .build(); 315 } catch (Exception e) { 316 throw new RuntimeException(e); 317 } 318 break; 319 } 320 case PAYLOAD_NOT_SET: 321 { 322 throw new IllegalArgumentException("Request didn't have payload."); 323 } 324 325 default: 326 { 327 throw new IllegalArgumentException("Unexpected payload case."); 328 } 329 } 330 331 switch (request.getRequestedOutputFormat()) { 332 case UNSPECIFIED: 333 throw new IllegalArgumentException("Unspecified output format."); 334 335 case PROTOBUF: 336 { 337 ByteString messageString = testMessage.toByteString(); 338 return Conformance.ConformanceResponse.newBuilder() 339 .setProtobufPayload(messageString) 340 .build(); 341 } 342 343 case JSON: 344 try { 345 return Conformance.ConformanceResponse.newBuilder() 346 .setJsonPayload( 347 JsonFormat.printer().usingTypeRegistry(typeRegistry).print(testMessage)) 348 .build(); 349 } catch (InvalidProtocolBufferException | IllegalArgumentException e) { 350 return Conformance.ConformanceResponse.newBuilder() 351 .setSerializeError(e.getMessage()) 352 .build(); 353 } 354 355 case TEXT_FORMAT: 356 return Conformance.ConformanceResponse.newBuilder() 357 .setTextPayload(TextFormat.printer().printToString(testMessage)) 358 .build(); 359 360 default: 361 { 362 throw new IllegalArgumentException("Unexpected request output."); 363 } 364 } 365 } 366 doTestIo()367 private boolean doTestIo() throws Exception { 368 int bytes = readLittleEndianIntFromStdin(); 369 370 if (bytes == -1) { 371 return false; // EOF 372 } 373 374 byte[] serializedInput = new byte[bytes]; 375 376 if (!readFromStdin(serializedInput, bytes)) { 377 throw new RuntimeException("Unexpected EOF from test program."); 378 } 379 380 Conformance.ConformanceRequest request = 381 Conformance.ConformanceRequest.parseFrom(serializedInput); 382 Conformance.ConformanceResponse response = doTest(request); 383 byte[] serializedOutput = response.toByteArray(); 384 385 writeLittleEndianIntToStdout(serializedOutput.length); 386 writeToStdout(serializedOutput); 387 388 return true; 389 } 390 run()391 public void run() throws Exception { 392 typeRegistry = 393 TypeRegistry.newBuilder() 394 .add(TestMessagesProto3.TestAllTypesProto3.getDescriptor()) 395 .add( 396 com.google.protobuf_test_messages.editions.proto3.TestMessagesProto3Editions 397 .TestAllTypesProto3.getDescriptor()) 398 .build(); 399 while (doTestIo()) { 400 this.testCount++; 401 } 402 403 System.err.println( 404 "ConformanceJava: received EOF from test runner after " + this.testCount + " tests"); 405 } 406 main(String[] args)407 public static void main(String[] args) throws Exception { 408 new ConformanceJava().run(); 409 } 410 } 411