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