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