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 com.google.protobuf.AbstractMessage testMessage; 233 boolean isProto3 = 234 request.getMessageType().equals("protobuf_test_messages.proto3.TestAllTypesProto3"); 235 boolean isProto2 = 236 request.getMessageType().equals("protobuf_test_messages.proto2.TestAllTypesProto2"); 237 238 switch (request.getPayloadCase()) { 239 case PROTOBUF_PAYLOAD: 240 { 241 if (isProto3) { 242 try { 243 ExtensionRegistry extensions = ExtensionRegistry.newInstance(); 244 TestMessagesProto3.registerAllExtensions(extensions); 245 testMessage = 246 parseBinary( 247 request.getProtobufPayload(), TestAllTypesProto3.parser(), extensions); 248 } catch (InvalidProtocolBufferException e) { 249 return Conformance.ConformanceResponse.newBuilder() 250 .setParseError(e.getMessage()) 251 .build(); 252 } 253 } else if (isProto2) { 254 try { 255 ExtensionRegistry extensions = ExtensionRegistry.newInstance(); 256 TestMessagesProto2.registerAllExtensions(extensions); 257 testMessage = 258 parseBinary( 259 request.getProtobufPayload(), TestAllTypesProto2.parser(), extensions); 260 } catch (InvalidProtocolBufferException e) { 261 return Conformance.ConformanceResponse.newBuilder() 262 .setParseError(e.getMessage()) 263 .build(); 264 } 265 } else { 266 throw new RuntimeException("Protobuf request doesn't have specific payload type."); 267 } 268 break; 269 } 270 case JSON_PAYLOAD: 271 { 272 try { 273 JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(typeRegistry); 274 if (request.getTestCategory() 275 == Conformance.TestCategory.JSON_IGNORE_UNKNOWN_PARSING_TEST) { 276 parser = parser.ignoringUnknownFields(); 277 } 278 if (isProto3) { 279 TestMessagesProto3.TestAllTypesProto3.Builder builder = 280 TestMessagesProto3.TestAllTypesProto3.newBuilder(); 281 parser.merge(request.getJsonPayload(), builder); 282 testMessage = builder.build(); 283 } else if (isProto2) { 284 TestMessagesProto2.TestAllTypesProto2.Builder builder = 285 TestMessagesProto2.TestAllTypesProto2.newBuilder(); 286 parser.merge(request.getJsonPayload(), builder); 287 testMessage = builder.build(); 288 } else { 289 throw new RuntimeException("Protobuf request doesn't have specific payload type."); 290 } 291 } catch (InvalidProtocolBufferException e) { 292 return Conformance.ConformanceResponse.newBuilder() 293 .setParseError(e.getMessage()) 294 .build(); 295 } 296 break; 297 } 298 case TEXT_PAYLOAD: 299 { 300 if (isProto3) { 301 try { 302 TestMessagesProto3.TestAllTypesProto3.Builder builder = 303 TestMessagesProto3.TestAllTypesProto3.newBuilder(); 304 TextFormat.merge(request.getTextPayload(), builder); 305 testMessage = builder.build(); 306 } catch (TextFormat.ParseException e) { 307 return Conformance.ConformanceResponse.newBuilder() 308 .setParseError(e.getMessage()) 309 .build(); 310 } 311 } else if (isProto2) { 312 try { 313 TestMessagesProto2.TestAllTypesProto2.Builder builder = 314 TestMessagesProto2.TestAllTypesProto2.newBuilder(); 315 TextFormat.merge(request.getTextPayload(), builder); 316 testMessage = builder.build(); 317 } catch (TextFormat.ParseException e) { 318 return Conformance.ConformanceResponse.newBuilder() 319 .setParseError(e.getMessage()) 320 .build(); 321 } 322 } else { 323 throw new RuntimeException("Protobuf request doesn't have specific payload type."); 324 } 325 break; 326 } 327 case PAYLOAD_NOT_SET: 328 { 329 throw new RuntimeException("Request didn't have payload."); 330 } 331 332 default: 333 { 334 throw new RuntimeException("Unexpected payload case."); 335 } 336 } 337 338 switch (request.getRequestedOutputFormat()) { 339 case UNSPECIFIED: 340 throw new RuntimeException("Unspecified output format."); 341 342 case PROTOBUF: 343 { 344 ByteString messageString = testMessage.toByteString(); 345 return Conformance.ConformanceResponse.newBuilder() 346 .setProtobufPayload(messageString) 347 .build(); 348 } 349 350 case JSON: 351 try { 352 return Conformance.ConformanceResponse.newBuilder() 353 .setJsonPayload( 354 JsonFormat.printer().usingTypeRegistry(typeRegistry).print(testMessage)) 355 .build(); 356 } catch (InvalidProtocolBufferException | IllegalArgumentException e) { 357 return Conformance.ConformanceResponse.newBuilder() 358 .setSerializeError(e.getMessage()) 359 .build(); 360 } 361 362 case TEXT_FORMAT: 363 return Conformance.ConformanceResponse.newBuilder() 364 .setTextPayload(TextFormat.printer().printToString(testMessage)) 365 .build(); 366 367 default: 368 { 369 throw new RuntimeException("Unexpected request output."); 370 } 371 } 372 } 373 doTestIo()374 private boolean doTestIo() throws Exception { 375 int bytes = readLittleEndianIntFromStdin(); 376 377 if (bytes == -1) { 378 return false; // EOF 379 } 380 381 byte[] serializedInput = new byte[bytes]; 382 383 if (!readFromStdin(serializedInput, bytes)) { 384 throw new RuntimeException("Unexpected EOF from test program."); 385 } 386 387 Conformance.ConformanceRequest request = 388 Conformance.ConformanceRequest.parseFrom(serializedInput); 389 Conformance.ConformanceResponse response = doTest(request); 390 byte[] serializedOutput = response.toByteArray(); 391 392 writeLittleEndianIntToStdout(serializedOutput.length); 393 writeToStdout(serializedOutput); 394 395 return true; 396 } 397 run()398 public void run() throws Exception { 399 typeRegistry = 400 TypeRegistry.newBuilder() 401 .add(TestMessagesProto3.TestAllTypesProto3.getDescriptor()) 402 .build(); 403 while (doTestIo()) { 404 this.testCount++; 405 } 406 407 System.err.println( 408 "ConformanceJava: received EOF from test runner after " + this.testCount + " tests"); 409 } 410 main(String[] args)411 public static void main(String[] args) throws Exception { 412 new ConformanceJava().run(); 413 } 414 } 415