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