• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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