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
31import com.google.protobuf.AbstractMessage;
32import com.google.protobuf.ByteString;
33import com.google.protobuf.CodedInputStream;
34import com.google.protobuf.ExtensionRegistry;
35import com.google.protobuf.InvalidProtocolBufferException;
36import com.google.protobuf.Parser;
37import com.google.protobuf.TextFormat;
38import com.google.protobuf.conformance.Conformance;
39import com.google.protobuf.util.JsonFormat;
40import com.google.protobuf.util.JsonFormat.TypeRegistry;
41import com.google.protobuf_test_messages.proto2.TestMessagesProto2;
42import com.google.protobuf_test_messages.proto2.TestMessagesProto2.TestAllTypesProto2;
43import com.google.protobuf_test_messages.proto3.TestMessagesProto3;
44import com.google.protobuf_test_messages.proto3.TestMessagesProto3.TestAllTypesProto3;
45import java.nio.ByteBuffer;
46import java.util.ArrayList;
47
48class ConformanceJava {
49  private int testCount = 0;
50  private TypeRegistry typeRegistry;
51
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
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).
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
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 <MessageType extends AbstractMessage> {
102    public MessageType decode (ByteString bytes, BinaryDecoderType type,
103        Parser <MessageType> parser, ExtensionRegistry extensions)
104      throws InvalidProtocolBufferException {
105      switch (type) {
106        case BTYE_STRING_DECODER:
107          return parser.parseFrom(bytes, extensions);
108        case BYTE_ARRAY_DECODER:
109          return parser.parseFrom(bytes.toByteArray(), extensions);
110        case ARRAY_BYTE_BUFFER_DECODER: {
111          ByteBuffer buffer = ByteBuffer.allocate(bytes.size());
112          bytes.copyTo(buffer);
113          buffer.flip();
114          try {
115            return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions);
116          } catch (InvalidProtocolBufferException e) {
117            throw e;
118          }
119        }
120        case READONLY_ARRAY_BYTE_BUFFER_DECODER: {
121          try {
122            return parser.parseFrom(
123                CodedInputStream.newInstance(bytes.asReadOnlyByteBuffer()), extensions);
124          } catch (InvalidProtocolBufferException e) {
125            throw e;
126          }
127        }
128        case DIRECT_BYTE_BUFFER_DECODER: {
129          ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size());
130          bytes.copyTo(buffer);
131          buffer.flip();
132          try {
133            return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions);
134          } catch (InvalidProtocolBufferException e) {
135            throw e;
136          }
137        }
138        case READONLY_DIRECT_BYTE_BUFFER_DECODER: {
139          ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size());
140          bytes.copyTo(buffer);
141          buffer.flip();
142          try {
143            return parser.parseFrom(
144                CodedInputStream.newInstance(buffer.asReadOnlyBuffer()), extensions);
145          } catch (InvalidProtocolBufferException e) {
146            throw e;
147          }
148        }
149        case INPUT_STREAM_DECODER: {
150          try {
151            return parser.parseFrom(bytes.newInput(), extensions);
152          } catch (InvalidProtocolBufferException e) {
153            throw e;
154          }
155        }
156        default :
157          return null;
158      }
159    }
160  }
161
162  private <MessageType extends AbstractMessage> MessageType parseBinary(
163      ByteString bytes, Parser <MessageType> parser, ExtensionRegistry extensions)
164      throws InvalidProtocolBufferException {
165    ArrayList <MessageType> messages = new ArrayList <MessageType> ();
166    ArrayList <InvalidProtocolBufferException> exceptions =
167        new ArrayList <InvalidProtocolBufferException>();
168
169    for (int i = 0; i < BinaryDecoderType.values().length; i++) {
170      messages.add(null);
171      exceptions.add(null);
172    }
173    BinaryDecoder <MessageType> decoder = new BinaryDecoder <MessageType> ();
174
175    boolean hasMessage = false;
176    boolean hasException = false;
177    for (int i = 0; i < BinaryDecoderType.values().length; ++i) {
178      try {
179        //= BinaryDecoderType.values()[i].parseProto3(bytes);
180        messages.set(i, decoder.decode(bytes, BinaryDecoderType.values()[i], parser, extensions));
181        hasMessage = true;
182      } catch (InvalidProtocolBufferException e) {
183        exceptions.set(i, e);
184        hasException = true;
185      }
186    }
187
188    if (hasMessage && hasException) {
189      StringBuilder sb =
190          new StringBuilder("Binary decoders disagreed on whether the payload was valid.\n");
191      for (int i = 0; i < BinaryDecoderType.values().length; ++i) {
192        sb.append(BinaryDecoderType.values()[i].name());
193        if (messages.get(i) != null) {
194          sb.append(" accepted the payload.\n");
195        } else {
196          sb.append(" rejected the payload.\n");
197        }
198      }
199      throw new RuntimeException(sb.toString());
200    }
201
202    if (hasException) {
203      // We do not check if exceptions are equal. Different implementations may return different
204      // exception messages. Throw an arbitrary one out instead.
205      throw exceptions.get(0);
206    }
207
208    // Fast path comparing all the messages with the first message, assuming equality being
209    // symmetric and transitive.
210    boolean allEqual = true;
211    for (int i = 1; i < messages.size(); ++i) {
212      if (!messages.get(0).equals(messages.get(i))) {
213        allEqual = false;
214        break;
215      }
216    }
217
218    // Slow path: compare and find out all unequal pairs.
219    if (!allEqual) {
220      StringBuilder sb = new StringBuilder();
221      for (int i = 0; i < messages.size() - 1; ++i) {
222        for (int j = i + 1; j < messages.size(); ++j) {
223          if (!messages.get(i).equals(messages.get(j))) {
224            sb.append(BinaryDecoderType.values()[i].name())
225                .append(" and ")
226                .append(BinaryDecoderType.values()[j].name())
227                .append(" parsed the payload differently.\n");
228          }
229        }
230      }
231      throw new RuntimeException(sb.toString());
232    }
233
234    return messages.get(0);
235  }
236
237  private Conformance.ConformanceResponse doTest(Conformance.ConformanceRequest request) {
238    com.google.protobuf.AbstractMessage testMessage;
239    boolean isProto3 =
240        request.getMessageType().equals("protobuf_test_messages.proto3.TestAllTypesProto3");
241    boolean isProto2 =
242        request.getMessageType().equals("protobuf_test_messages.proto2.TestAllTypesProto2");
243
244    switch (request.getPayloadCase()) {
245      case PROTOBUF_PAYLOAD: {
246        if (isProto3) {
247          try {
248            ExtensionRegistry extensions = ExtensionRegistry.newInstance();
249            TestMessagesProto3.registerAllExtensions(extensions);
250            testMessage = parseBinary(request.getProtobufPayload(), TestAllTypesProto3.parser(), extensions);
251          } catch (InvalidProtocolBufferException e) {
252            return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
253          }
254        } else if (isProto2) {
255          try {
256            ExtensionRegistry extensions = ExtensionRegistry.newInstance();
257            TestMessagesProto2.registerAllExtensions(extensions);
258            testMessage = parseBinary(request.getProtobufPayload(), TestAllTypesProto2.parser(), extensions);
259          } catch (InvalidProtocolBufferException e) {
260            return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
261          }
262        } else {
263          throw new RuntimeException("Protobuf request doesn't have specific payload type.");
264        }
265        break;
266      }
267      case JSON_PAYLOAD: {
268        try {
269          JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(typeRegistry);
270          if (request.getTestCategory()
271              == Conformance.TestCategory.JSON_IGNORE_UNKNOWN_PARSING_TEST) {
272            parser = parser.ignoringUnknownFields();
273          }
274          if (isProto3) {
275            TestMessagesProto3.TestAllTypesProto3.Builder builder =
276                TestMessagesProto3.TestAllTypesProto3.newBuilder();
277            parser.merge(request.getJsonPayload(), builder);
278            testMessage = builder.build();
279          } else if (isProto2) {
280            TestMessagesProto2.TestAllTypesProto2.Builder builder =
281                TestMessagesProto2.TestAllTypesProto2.newBuilder();
282            parser.merge(request.getJsonPayload(), builder);
283            testMessage = builder.build();
284          } else {
285            throw new RuntimeException("Protobuf request doesn't have specific payload type.");
286          }
287        } catch (InvalidProtocolBufferException e) {
288          return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
289        }
290        break;
291      }
292      case TEXT_PAYLOAD: {
293        if (isProto3) {
294          try {
295            TestMessagesProto3.TestAllTypesProto3.Builder builder =
296                TestMessagesProto3.TestAllTypesProto3.newBuilder();
297            TextFormat.merge(request.getTextPayload(), builder);
298            testMessage = builder.build();
299          } catch (TextFormat.ParseException e) {
300              return Conformance.ConformanceResponse.newBuilder()
301                  .setParseError(e.getMessage())
302                  .build();
303          }
304        } else if (isProto2) {
305          try {
306            TestMessagesProto2.TestAllTypesProto2.Builder builder =
307                TestMessagesProto2.TestAllTypesProto2.newBuilder();
308            TextFormat.merge(request.getTextPayload(), builder);
309            testMessage = builder.build();
310          } catch (TextFormat.ParseException e) {
311              return Conformance.ConformanceResponse.newBuilder()
312                  .setParseError(e.getMessage())
313                  .build();
314          }
315        } else {
316          throw new RuntimeException("Protobuf request doesn't have specific payload type.");
317        }
318        break;
319      }
320      case PAYLOAD_NOT_SET: {
321        throw new RuntimeException("Request didn't have payload.");
322      }
323
324      default: {
325        throw new RuntimeException("Unexpected payload case.");
326      }
327    }
328
329    switch (request.getRequestedOutputFormat()) {
330      case UNSPECIFIED:
331        throw new RuntimeException("Unspecified output format.");
332
333      case PROTOBUF: {
334        ByteString MessageString = testMessage.toByteString();
335        return Conformance.ConformanceResponse.newBuilder().setProtobufPayload(MessageString).build();
336      }
337
338      case JSON:
339        try {
340          return Conformance.ConformanceResponse.newBuilder().setJsonPayload(
341              JsonFormat.printer().usingTypeRegistry(typeRegistry).print(testMessage)).build();
342        } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
343          return Conformance.ConformanceResponse.newBuilder().setSerializeError(
344              e.getMessage()).build();
345        }
346
347      case TEXT_FORMAT:
348        return Conformance.ConformanceResponse.newBuilder().setTextPayload(
349            TextFormat.printToString(testMessage)).build();
350
351      default: {
352        throw new RuntimeException("Unexpected request output.");
353      }
354    }
355  }
356
357  private boolean doTestIo() throws Exception {
358    int bytes = readLittleEndianIntFromStdin();
359
360    if (bytes == -1) {
361      return false;  // EOF
362    }
363
364    byte[] serializedInput = new byte[bytes];
365
366    if (!readFromStdin(serializedInput, bytes)) {
367      throw new RuntimeException("Unexpected EOF from test program.");
368    }
369
370    Conformance.ConformanceRequest request =
371        Conformance.ConformanceRequest.parseFrom(serializedInput);
372    Conformance.ConformanceResponse response = doTest(request);
373    byte[] serializedOutput = response.toByteArray();
374
375    writeLittleEndianIntToStdout(serializedOutput.length);
376    writeToStdout(serializedOutput);
377
378    return true;
379  }
380
381  public void run() throws Exception {
382    typeRegistry = TypeRegistry.newBuilder().add(
383        TestMessagesProto3.TestAllTypesProto3.getDescriptor()).build();
384    while (doTestIo()) {
385      this.testCount++;
386    }
387
388    System.err.println("ConformanceJava: received EOF from test runner after " +
389        this.testCount + " tests");
390  }
391
392  public static void main(String[] args) throws Exception {
393    new ConformanceJava().run();
394  }
395}
396