1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2008 Google Inc. All rights reserved. 3 // https://developers.google.com/protocol-buffers/ 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions are 7 // met: 8 // 9 // * Redistributions of source code must retain the above copyright 10 // notice, this list of conditions and the following disclaimer. 11 // * Redistributions in binary form must reproduce the above 12 // copyright notice, this list of conditions and the following disclaimer 13 // in the documentation and/or other materials provided with the 14 // distribution. 15 // * Neither the name of Google Inc. nor the names of its 16 // contributors may be used to endorse or promote products derived from 17 // this software without specific prior written permission. 18 // 19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 import com.google.protobuf.AbstractMessage; 32 import com.google.protobuf.ByteString; 33 import com.google.protobuf.CodedInputStream; 34 import com.google.protobuf.ExtensionRegistry; 35 import com.google.protobuf.InvalidProtocolBufferException; 36 import com.google.protobuf.Parser; 37 import com.google.protobuf.TextFormat; 38 import com.google.protobuf.conformance.Conformance; 39 import com.google.protobuf.util.JsonFormat; 40 import com.google.protobuf.util.JsonFormat.TypeRegistry; 41 import com.google.protobuf_test_messages.proto2.TestMessagesProto2; 42 import com.google.protobuf_test_messages.proto2.TestMessagesProto2.TestAllTypesProto2; 43 import com.google.protobuf_test_messages.proto3.TestMessagesProto3; 44 import com.google.protobuf_test_messages.proto3.TestMessagesProto3.TestAllTypesProto3; 45 import java.nio.ByteBuffer; 46 import java.util.ArrayList; 47 48 class ConformanceJava { 49 private int testCount = 0; 50 private TypeRegistry typeRegistry; 51 readFromStdin(byte[] buf, int len)52 private boolean readFromStdin(byte[] buf, int len) throws Exception { 53 int ofs = 0; 54 while (len > 0) { 55 int read = System.in.read(buf, ofs, len); 56 if (read == -1) { 57 return false; // EOF 58 } 59 ofs += read; 60 len -= read; 61 } 62 63 return true; 64 } 65 writeToStdout(byte[] buf)66 private void writeToStdout(byte[] buf) throws Exception { 67 System.out.write(buf); 68 } 69 70 // Returns -1 on EOF (the actual values will always be positive). readLittleEndianIntFromStdin()71 private int readLittleEndianIntFromStdin() throws Exception { 72 byte[] buf = new byte[4]; 73 if (!readFromStdin(buf, 4)) { 74 return -1; 75 } 76 return (buf[0] & 0xff) 77 | ((buf[1] & 0xff) << 8) 78 | ((buf[2] & 0xff) << 16) 79 | ((buf[3] & 0xff) << 24); 80 } 81 writeLittleEndianIntToStdout(int val)82 private void writeLittleEndianIntToStdout(int val) throws Exception { 83 byte[] buf = new byte[4]; 84 buf[0] = (byte) val; 85 buf[1] = (byte) (val >> 8); 86 buf[2] = (byte) (val >> 16); 87 buf[3] = (byte) (val >> 24); 88 writeToStdout(buf); 89 } 90 91 private enum BinaryDecoderType { 92 BTYE_STRING_DECODER, 93 BYTE_ARRAY_DECODER, 94 ARRAY_BYTE_BUFFER_DECODER, 95 READONLY_ARRAY_BYTE_BUFFER_DECODER, 96 DIRECT_BYTE_BUFFER_DECODER, 97 READONLY_DIRECT_BYTE_BUFFER_DECODER, 98 INPUT_STREAM_DECODER; 99 } 100 101 private static class BinaryDecoder<T extends AbstractMessage> { decode( ByteString bytes, BinaryDecoderType type, Parser<T> parser, ExtensionRegistry extensions)102 public T decode( 103 ByteString bytes, BinaryDecoderType type, Parser<T> parser, ExtensionRegistry extensions) 104 throws InvalidProtocolBufferException { 105 switch (type) { 106 case BTYE_STRING_DECODER: 107 case BYTE_ARRAY_DECODER: 108 return parser.parseFrom(bytes, extensions); 109 case ARRAY_BYTE_BUFFER_DECODER: 110 { 111 ByteBuffer buffer = ByteBuffer.allocate(bytes.size()); 112 bytes.copyTo(buffer); 113 buffer.flip(); 114 return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions); 115 } 116 case READONLY_ARRAY_BYTE_BUFFER_DECODER: 117 { 118 return parser.parseFrom( 119 CodedInputStream.newInstance(bytes.asReadOnlyByteBuffer()), extensions); 120 } 121 case DIRECT_BYTE_BUFFER_DECODER: 122 { 123 ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size()); 124 bytes.copyTo(buffer); 125 buffer.flip(); 126 return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions); 127 } 128 case READONLY_DIRECT_BYTE_BUFFER_DECODER: 129 { 130 ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size()); 131 bytes.copyTo(buffer); 132 buffer.flip(); 133 return parser.parseFrom( 134 CodedInputStream.newInstance(buffer.asReadOnlyBuffer()), extensions); 135 } 136 case INPUT_STREAM_DECODER: 137 { 138 return parser.parseFrom(bytes.newInput(), extensions); 139 } 140 } 141 return null; 142 } 143 } 144 parseBinary( ByteString bytes, Parser<T> parser, ExtensionRegistry extensions)145 private <T extends AbstractMessage> T parseBinary( 146 ByteString bytes, Parser<T> parser, ExtensionRegistry extensions) 147 throws InvalidProtocolBufferException { 148 ArrayList<T> messages = new ArrayList<>(); 149 ArrayList<InvalidProtocolBufferException> exceptions = new ArrayList<>(); 150 151 for (int i = 0; i < BinaryDecoderType.values().length; i++) { 152 messages.add(null); 153 exceptions.add(null); 154 } 155 if (messages.isEmpty()) { 156 throw new RuntimeException("binary decoder types missing"); 157 } 158 159 BinaryDecoder<T> decoder = new BinaryDecoder<>(); 160 161 boolean hasMessage = false; 162 boolean hasException = false; 163 for (int i = 0; i < BinaryDecoderType.values().length; ++i) { 164 try { 165 // = BinaryDecoderType.values()[i].parseProto3(bytes); 166 messages.set(i, decoder.decode(bytes, BinaryDecoderType.values()[i], parser, extensions)); 167 hasMessage = true; 168 } catch (InvalidProtocolBufferException e) { 169 exceptions.set(i, e); 170 hasException = true; 171 } 172 } 173 174 if (hasMessage && hasException) { 175 StringBuilder sb = 176 new StringBuilder("Binary decoders disagreed on whether the payload was valid.\n"); 177 for (int i = 0; i < BinaryDecoderType.values().length; ++i) { 178 sb.append(BinaryDecoderType.values()[i].name()); 179 if (messages.get(i) != null) { 180 sb.append(" accepted the payload.\n"); 181 } else { 182 sb.append(" rejected the payload.\n"); 183 } 184 } 185 throw new RuntimeException(sb.toString()); 186 } 187 188 if (hasException) { 189 // We do not check if exceptions are equal. Different implementations may return different 190 // exception messages. Throw an arbitrary one out instead. 191 InvalidProtocolBufferException exception = null; 192 for (InvalidProtocolBufferException e : exceptions) { 193 if (exception != null) { 194 exception.addSuppressed(e); 195 } else { 196 exception = e; 197 } 198 } 199 throw exception; 200 } 201 202 // Fast path comparing all the messages with the first message, assuming equality being 203 // symmetric and transitive. 204 boolean allEqual = true; 205 for (int i = 1; i < messages.size(); ++i) { 206 if (!messages.get(0).equals(messages.get(i))) { 207 allEqual = false; 208 break; 209 } 210 } 211 212 // Slow path: compare and find out all unequal pairs. 213 if (!allEqual) { 214 StringBuilder sb = new StringBuilder(); 215 for (int i = 0; i < messages.size() - 1; ++i) { 216 for (int j = i + 1; j < messages.size(); ++j) { 217 if (!messages.get(i).equals(messages.get(j))) { 218 sb.append(BinaryDecoderType.values()[i].name()) 219 .append(" and ") 220 .append(BinaryDecoderType.values()[j].name()) 221 .append(" parsed the payload differently.\n"); 222 } 223 } 224 } 225 throw new RuntimeException(sb.toString()); 226 } 227 228 return messages.get(0); 229 } 230 doTest(Conformance.ConformanceRequest request)231 private Conformance.ConformanceResponse doTest(Conformance.ConformanceRequest request) { 232 AbstractMessage testMessage; 233 String messageType = request.getMessageType(); 234 boolean isProto3 = 235 messageType.equals("protobuf_test_messages.proto3.TestAllTypesProto3"); 236 boolean isProto2 = 237 messageType.equals("protobuf_test_messages.proto2.TestAllTypesProto2"); 238 239 switch (request.getPayloadCase()) { 240 case PROTOBUF_PAYLOAD: 241 { 242 if (isProto3) { 243 try { 244 ExtensionRegistry extensions = ExtensionRegistry.newInstance(); 245 TestMessagesProto3.registerAllExtensions(extensions); 246 testMessage = 247 parseBinary( 248 request.getProtobufPayload(), TestAllTypesProto3.parser(), extensions); 249 } catch (InvalidProtocolBufferException e) { 250 return Conformance.ConformanceResponse.newBuilder() 251 .setParseError(e.getMessage()) 252 .build(); 253 } 254 } else if (isProto2) { 255 try { 256 ExtensionRegistry extensions = ExtensionRegistry.newInstance(); 257 TestMessagesProto2.registerAllExtensions(extensions); 258 testMessage = 259 parseBinary( 260 request.getProtobufPayload(), TestAllTypesProto2.parser(), extensions); 261 } catch (InvalidProtocolBufferException e) { 262 return Conformance.ConformanceResponse.newBuilder() 263 .setParseError(e.getMessage()) 264 .build(); 265 } 266 } else { 267 throw new IllegalArgumentException( 268 "Protobuf request has unexpected payload type: " + messageType); 269 } 270 break; 271 } 272 case JSON_PAYLOAD: 273 { 274 try { 275 JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(typeRegistry); 276 if (request.getTestCategory() 277 == Conformance.TestCategory.JSON_IGNORE_UNKNOWN_PARSING_TEST) { 278 parser = parser.ignoringUnknownFields(); 279 } 280 if (isProto3) { 281 TestMessagesProto3.TestAllTypesProto3.Builder builder = 282 TestMessagesProto3.TestAllTypesProto3.newBuilder(); 283 parser.merge(request.getJsonPayload(), builder); 284 testMessage = builder.build(); 285 } else if (isProto2) { 286 TestMessagesProto2.TestAllTypesProto2.Builder builder = 287 TestMessagesProto2.TestAllTypesProto2.newBuilder(); 288 parser.merge(request.getJsonPayload(), builder); 289 testMessage = builder.build(); 290 } else { 291 throw new IllegalArgumentException( 292 "Protobuf request has unexpected payload type: " + messageType); 293 } 294 } catch (InvalidProtocolBufferException e) { 295 return Conformance.ConformanceResponse.newBuilder() 296 .setParseError(e.getMessage()) 297 .build(); 298 } 299 break; 300 } 301 case TEXT_PAYLOAD: 302 { 303 if (isProto3) { 304 try { 305 TestMessagesProto3.TestAllTypesProto3.Builder builder = 306 TestMessagesProto3.TestAllTypesProto3.newBuilder(); 307 TextFormat.merge(request.getTextPayload(), builder); 308 testMessage = builder.build(); 309 } catch (TextFormat.ParseException e) { 310 return Conformance.ConformanceResponse.newBuilder() 311 .setParseError(e.getMessage()) 312 .build(); 313 } 314 } else if (isProto2) { 315 try { 316 TestMessagesProto2.TestAllTypesProto2.Builder builder = 317 TestMessagesProto2.TestAllTypesProto2.newBuilder(); 318 TextFormat.merge(request.getTextPayload(), builder); 319 testMessage = builder.build(); 320 } catch (TextFormat.ParseException e) { 321 return Conformance.ConformanceResponse.newBuilder() 322 .setParseError(e.getMessage()) 323 .build(); 324 } 325 } else { 326 throw new IllegalArgumentException( 327 "Protobuf request has unexpected payload type: " + messageType); 328 } 329 break; 330 } 331 case PAYLOAD_NOT_SET: 332 { 333 throw new IllegalArgumentException("Request didn't have payload."); 334 } 335 336 default: 337 { 338 throw new IllegalArgumentException("Unexpected payload case."); 339 } 340 } 341 342 switch (request.getRequestedOutputFormat()) { 343 case UNSPECIFIED: 344 throw new IllegalArgumentException("Unspecified output format."); 345 346 case PROTOBUF: 347 { 348 ByteString messageString = testMessage.toByteString(); 349 return Conformance.ConformanceResponse.newBuilder() 350 .setProtobufPayload(messageString) 351 .build(); 352 } 353 354 case JSON: 355 try { 356 return Conformance.ConformanceResponse.newBuilder() 357 .setJsonPayload( 358 JsonFormat.printer().usingTypeRegistry(typeRegistry).print(testMessage)) 359 .build(); 360 } catch (InvalidProtocolBufferException | IllegalArgumentException e) { 361 return Conformance.ConformanceResponse.newBuilder() 362 .setSerializeError(e.getMessage()) 363 .build(); 364 } 365 366 case TEXT_FORMAT: 367 return Conformance.ConformanceResponse.newBuilder() 368 .setTextPayload(TextFormat.printer().printToString(testMessage)) 369 .build(); 370 371 default: 372 { 373 throw new IllegalArgumentException("Unexpected request output."); 374 } 375 } 376 } 377 doTestIo()378 private boolean doTestIo() throws Exception { 379 int bytes = readLittleEndianIntFromStdin(); 380 381 if (bytes == -1) { 382 return false; // EOF 383 } 384 385 byte[] serializedInput = new byte[bytes]; 386 387 if (!readFromStdin(serializedInput, bytes)) { 388 throw new RuntimeException("Unexpected EOF from test program."); 389 } 390 391 Conformance.ConformanceRequest request = 392 Conformance.ConformanceRequest.parseFrom(serializedInput); 393 Conformance.ConformanceResponse response = doTest(request); 394 byte[] serializedOutput = response.toByteArray(); 395 396 writeLittleEndianIntToStdout(serializedOutput.length); 397 writeToStdout(serializedOutput); 398 399 return true; 400 } 401 run()402 public void run() throws Exception { 403 typeRegistry = 404 TypeRegistry.newBuilder() 405 .add(TestMessagesProto3.TestAllTypesProto3.getDescriptor()) 406 .build(); 407 while (doTestIo()) { 408 this.testCount++; 409 } 410 411 System.err.println( 412 "ConformanceJava: received EOF from test runner after " + this.testCount + " tests"); 413 } 414 main(String[] args)415 public static void main(String[] args) throws Exception { 416 new ConformanceJava().run(); 417 } 418 } 419