• 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.AbstractMessageLite;
9 import com.google.protobuf.ByteString;
10 import com.google.protobuf.CodedInputStream;
11 import com.google.protobuf.ExtensionRegistryLite;
12 import com.google.protobuf.InvalidProtocolBufferException;
13 import com.google.protobuf.MessageLite;
14 import com.google.protobuf.Parser;
15 import com.google.protobuf.conformance.Conformance;
16 import com.google.protobuf_test_messages.edition2023.TestAllTypesEdition2023;
17 import com.google.protobuf_test_messages.edition2023.TestMessagesEdition2023;
18 import com.google.protobuf_test_messages.editions.proto2.TestMessagesProto2Editions;
19 import com.google.protobuf_test_messages.editions.proto3.TestMessagesProto3Editions;
20 import com.google.protobuf_test_messages.proto2.TestMessagesProto2;
21 import com.google.protobuf_test_messages.proto2.TestMessagesProto2.TestAllTypesProto2;
22 import com.google.protobuf_test_messages.proto3.TestMessagesProto3;
23 import com.google.protobuf_test_messages.proto3.TestMessagesProto3.TestAllTypesProto3;
24 import java.nio.ByteBuffer;
25 import java.util.ArrayList;
26 
27 class ConformanceJavaLite {
28   private int testCount = 0;
29 
readFromStdin(byte[] buf, int len)30   private boolean readFromStdin(byte[] buf, int len) throws Exception {
31     int ofs = 0;
32     while (len > 0) {
33       int read = System.in.read(buf, ofs, len);
34       if (read == -1) {
35         return false; // EOF
36       }
37       ofs += read;
38       len -= read;
39     }
40 
41     return true;
42   }
43 
writeToStdout(byte[] buf)44   private void writeToStdout(byte[] buf) throws Exception {
45     System.out.write(buf);
46   }
47 
48   // Returns -1 on EOF (the actual values will always be positive).
readLittleEndianIntFromStdin()49   private int readLittleEndianIntFromStdin() throws Exception {
50     byte[] buf = new byte[4];
51     if (!readFromStdin(buf, 4)) {
52       return -1;
53     }
54     return (buf[0] & 0xff)
55         | ((buf[1] & 0xff) << 8)
56         | ((buf[2] & 0xff) << 16)
57         | ((buf[3] & 0xff) << 24);
58   }
59 
writeLittleEndianIntToStdout(int val)60   private void writeLittleEndianIntToStdout(int val) throws Exception {
61     byte[] buf = new byte[4];
62     buf[0] = (byte) val;
63     buf[1] = (byte) (val >> 8);
64     buf[2] = (byte) (val >> 16);
65     buf[3] = (byte) (val >> 24);
66     writeToStdout(buf);
67   }
68 
69   private enum BinaryDecoderType {
70     BYTE_STRING_DECODER,
71     BYTE_ARRAY_DECODER,
72     ARRAY_BYTE_BUFFER_DECODER,
73     READONLY_ARRAY_BYTE_BUFFER_DECODER,
74     DIRECT_BYTE_BUFFER_DECODER,
75     READONLY_DIRECT_BYTE_BUFFER_DECODER,
76     INPUT_STREAM_DECODER;
77   }
78 
79   private static class BinaryDecoder<T extends MessageLite> {
decode( ByteString bytes, BinaryDecoderType type, Parser<T> parser, ExtensionRegistryLite extensions)80     public T decode(
81         ByteString bytes,
82         BinaryDecoderType type,
83         Parser<T> parser,
84         ExtensionRegistryLite 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, ExtensionRegistryLite extensions)126   private <T extends MessageLite> T parseBinary(
127       ByteString bytes, Parser<T> parser, ExtensionRegistryLite 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         messages.set(i, decoder.decode(bytes, BinaryDecoderType.values()[i], parser, extensions));
147         hasMessage = true;
148       } catch (InvalidProtocolBufferException e) {
149         exceptions.set(i, e);
150         hasException = true;
151       }
152     }
153 
154     if (hasMessage && hasException) {
155       StringBuilder sb =
156           new StringBuilder("Binary decoders disagreed on whether the payload was valid.\n");
157       for (int i = 0; i < BinaryDecoderType.values().length; ++i) {
158         sb.append(BinaryDecoderType.values()[i].name());
159         if (messages.get(i) != null) {
160           sb.append(" accepted the payload.\n");
161         } else {
162           sb.append(" rejected the payload.\n");
163         }
164       }
165       throw new RuntimeException(sb.toString());
166     }
167 
168     if (hasException) {
169       // We do not check if exceptions are equal. Different implementations may return different
170       // exception messages. Throw an arbitrary one out instead.
171       InvalidProtocolBufferException exception = null;
172       for (InvalidProtocolBufferException e : exceptions) {
173         if (exception != null) {
174           exception.addSuppressed(e);
175         } else {
176           exception = e;
177         }
178       }
179       throw exception;
180     }
181 
182     // Fast path comparing all the messages with the first message, assuming equality being
183     // symmetric and transitive.
184     boolean allEqual = true;
185     for (int i = 1; i < messages.size(); ++i) {
186       if (!messages.get(0).equals(messages.get(i))) {
187         allEqual = false;
188         break;
189       }
190     }
191 
192     // Slow path: compare and find out all unequal pairs.
193     if (!allEqual) {
194       StringBuilder sb = new StringBuilder();
195       for (int i = 0; i < messages.size() - 1; ++i) {
196         for (int j = i + 1; j < messages.size(); ++j) {
197           if (!messages.get(i).equals(messages.get(j))) {
198             sb.append(BinaryDecoderType.values()[i].name())
199                 .append(" and ")
200                 .append(BinaryDecoderType.values()[j].name())
201                 .append(" parsed the payload differently.\n");
202           }
203         }
204       }
205       throw new RuntimeException(sb.toString());
206     }
207 
208     return messages.get(0);
209   }
210 
createTestMessage(String messageType)211   private Class<? extends AbstractMessageLite> createTestMessage(String messageType) {
212     switch (messageType) {
213       case "protobuf_test_messages.proto3.TestAllTypesProto3":
214         return TestAllTypesProto3.class;
215       case "protobuf_test_messages.proto2.TestAllTypesProto2":
216         return TestAllTypesProto2.class;
217       case "protobuf_test_messages.editions.TestAllTypesEdition2023":
218         return TestAllTypesEdition2023.class;
219       case "protobuf_test_messages.editions.proto3.TestAllTypesProto3":
220         return TestMessagesProto3Editions.TestAllTypesProto3.class;
221       case "protobuf_test_messages.editions.proto2.TestAllTypesProto2":
222         return TestMessagesProto2Editions.TestAllTypesProto2.class;
223       default:
224         throw new IllegalArgumentException(
225             "Protobuf request has unexpected payload type: " + messageType);
226     }
227   }
228 
createTestFile(String messageType)229   private Class<?> createTestFile(String messageType) {
230     switch (messageType) {
231       case "protobuf_test_messages.proto3.TestAllTypesProto3":
232         return TestMessagesProto3.class;
233       case "protobuf_test_messages.proto2.TestAllTypesProto2":
234         return TestMessagesProto2.class;
235       case "protobuf_test_messages.editions.TestAllTypesEdition2023":
236         return TestMessagesEdition2023.class;
237       case "protobuf_test_messages.editions.proto3.TestAllTypesProto3":
238         return TestMessagesProto3Editions.class;
239       case "protobuf_test_messages.editions.proto2.TestAllTypesProto2":
240         return TestMessagesProto2Editions.class;
241       default:
242         throw new IllegalArgumentException(
243             "Protobuf request has unexpected payload type: " + messageType);
244     }
245   }
246 
247   @SuppressWarnings("unchecked")
doTest(Conformance.ConformanceRequest request)248   private Conformance.ConformanceResponse doTest(Conformance.ConformanceRequest request) {
249     com.google.protobuf.MessageLite testMessage;
250     String messageType = request.getMessageType();
251     switch (request.getPayloadCase()) {
252       case PROTOBUF_PAYLOAD:
253         {
254           try {
255             ExtensionRegistryLite extensions = ExtensionRegistryLite.newInstance();
256             createTestFile(messageType)
257                 .getMethod("registerAllExtensions", ExtensionRegistryLite.class)
258                 .invoke(null, extensions);
259             testMessage =
260                 parseBinary(
261                     request.getProtobufPayload(),
262                     (Parser<AbstractMessageLite>)
263                         createTestMessage(messageType).getMethod("parser").invoke(null),
264                     extensions);
265           } catch (InvalidProtocolBufferException e) {
266             return Conformance.ConformanceResponse.newBuilder()
267                 .setParseError(e.getMessage())
268                 .build();
269           } catch (Exception e) {
270             throw new RuntimeException(e);
271           }
272           break;
273         }
274       case JSON_PAYLOAD:
275         {
276           return Conformance.ConformanceResponse.newBuilder()
277               .setSkipped("Lite runtime does not support JSON format.")
278               .build();
279         }
280       case TEXT_PAYLOAD:
281         {
282           return Conformance.ConformanceResponse.newBuilder()
283               .setSkipped("Lite runtime does not support Text format.")
284               .build();
285         }
286       case PAYLOAD_NOT_SET:
287         {
288           throw new RuntimeException("Request didn't have payload.");
289         }
290       default:
291         {
292           throw new RuntimeException("Unexpected payload case.");
293         }
294     }
295 
296     switch (request.getRequestedOutputFormat()) {
297       case UNSPECIFIED:
298         throw new RuntimeException("Unspecified output format.");
299 
300       case PROTOBUF:
301         return Conformance.ConformanceResponse.newBuilder()
302             .setProtobufPayload(testMessage.toByteString())
303             .build();
304 
305       case JSON:
306         return Conformance.ConformanceResponse.newBuilder()
307             .setSkipped("Lite runtime does not support JSON format.")
308             .build();
309 
310       case TEXT_FORMAT:
311         return Conformance.ConformanceResponse.newBuilder()
312             .setSkipped("Lite runtime does not support Text format.")
313             .build();
314       default:
315         {
316           throw new RuntimeException("Unexpected request output.");
317         }
318     }
319   }
320 
doTestIo()321   private boolean doTestIo() throws Exception {
322     int bytes = readLittleEndianIntFromStdin();
323 
324     if (bytes == -1) {
325       return false; // EOF
326     }
327 
328     byte[] serializedInput = new byte[bytes];
329 
330     if (!readFromStdin(serializedInput, bytes)) {
331       throw new RuntimeException("Unexpected EOF from test program.");
332     }
333 
334     Conformance.ConformanceRequest request =
335         Conformance.ConformanceRequest.parseFrom(serializedInput);
336     Conformance.ConformanceResponse response = doTest(request);
337     byte[] serializedOutput = response.toByteArray();
338 
339     writeLittleEndianIntToStdout(serializedOutput.length);
340     writeToStdout(serializedOutput);
341 
342     return true;
343   }
344 
run()345   public void run() throws Exception {
346     while (doTestIo()) {
347       this.testCount++;
348     }
349 
350     System.err.println(
351         "ConformanceJavaLite: received EOF from test runner after " + this.testCount + " tests");
352   }
353 
main(String[] args)354   public static void main(String[] args) throws Exception {
355     new ConformanceJavaLite().run();
356   }
357 }
358