| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.conscrypt.ct; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| |
| public class Serialization { |
| private Serialization() {} |
| |
| private static final int DER_TAG_MASK = 0x3f; |
| private static final int DER_TAG_OCTET_STRING = 0x4; |
| private static final int DER_LENGTH_LONG_FORM_FLAG = 0x80; |
| |
| public static byte[] readDEROctetString(byte[] input) |
| throws SerializationException { |
| return readDEROctetString(new ByteArrayInputStream(input)); |
| } |
| |
| public static byte[] readDEROctetString(InputStream input) |
| throws SerializationException { |
| int tag = readByte(input) & DER_TAG_MASK; |
| if (tag != DER_TAG_OCTET_STRING) { |
| throw new SerializationException("Wrong DER tag, expected OCTET STRING, got " + tag); |
| } |
| int length; |
| int width = readByte(input); |
| if ((width & DER_LENGTH_LONG_FORM_FLAG) != 0) { |
| length = readNumber(input, width & ~DER_LENGTH_LONG_FORM_FLAG); |
| } else { |
| length = width; |
| } |
| |
| return readFixedBytes(input, length); |
| } |
| |
| public static byte[][] readList(byte[] input, int listWidth, int elemWidth) |
| throws SerializationException { |
| return readList(new ByteArrayInputStream(input), listWidth, elemWidth); |
| } |
| |
| /** |
| * Read a variable length vector of variable sized elements as described by RFC5246 section 4.3. |
| * The vector is prefixed by its total length, in bytes and in big endian format, |
| * so is each element contained in the vector. |
| * @param listWidth the width of the vector's length field, in bytes. |
| * @param elemWidth the width of each element's length field, in bytes. |
| * @throws SerializationException if EOF is encountered. |
| */ |
| public static byte[][] readList(InputStream input, int listWidth, int elemWidth) |
| throws SerializationException { |
| ArrayList<byte[]> result = new ArrayList(); |
| byte[] data = readVariableBytes(input, listWidth); |
| input = new ByteArrayInputStream(data); |
| try { |
| while (input.available() > 0) { |
| result.add(readVariableBytes(input, elemWidth)); |
| } |
| } catch (IOException e) { |
| throw new SerializationException(e); |
| } |
| return result.toArray(new byte[result.size()][]); |
| } |
| |
| /** |
| * Read a length-prefixed sequence of bytes. |
| * The length must be encoded in big endian format. |
| * @param width the width of the length prefix, in bytes. |
| * @throws SerializationException if EOF is encountered, or if {@code width} is negative or |
| * greater than 4 |
| */ |
| public static byte[] readVariableBytes(InputStream input, int width) |
| throws SerializationException { |
| int length = readNumber(input, width); |
| return readFixedBytes(input, length); |
| } |
| |
| /** |
| * Read a fixed number of bytes from the input stream. |
| * @param length the number of bytes to read. |
| * @throws SerializationException if EOF is encountered. |
| */ |
| public static byte[] readFixedBytes(InputStream input, int length) |
| throws SerializationException { |
| try { |
| if (length < 0) { |
| throw new SerializationException("Negative length: " + length); |
| } |
| |
| byte[] data = new byte[length]; |
| int count = input.read(data); |
| if (count < length) { |
| throw new SerializationException("Premature end of input, expected " + length + |
| " bytes, only read " + count); |
| } |
| return data; |
| } catch (IOException e) { |
| throw new SerializationException(e); |
| } |
| } |
| |
| /** |
| * Read a number in big endian format from the input stream. |
| * This methods only supports a width of up to 4 bytes. |
| * @param width the width of the number, in bytes. |
| * @throws SerializationException if EOF is encountered, or if {@code width} is negative or |
| * greater than 4 |
| */ |
| public static int readNumber(InputStream input, int width) throws SerializationException { |
| if (width > 4 || width < 0) { |
| throw new SerializationException("Invalid width: " + width); |
| } |
| |
| int result = 0; |
| for (int i = 0; i < width; i++) { |
| result = (result << 8) | (readByte(input) & 0xFF); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Read a number in big endian format from the input stream. |
| * This methods supports a width of up to 8 bytes. |
| * @param width the width of the number, in bytes. |
| * @throws SerializationException if EOF is encountered. |
| * @throws IllegalArgumentException if {@code width} is negative or greater than 8 |
| */ |
| public static long readLong(InputStream input, int width) throws SerializationException { |
| if (width > 8 || width < 0) { |
| throw new IllegalArgumentException("Invalid width: " + width); |
| } |
| |
| long result = 0; |
| for (int i = 0; i < width; i++) { |
| result = (result << 8) | (readByte(input) & 0xFF); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Read a single byte from the input stream. |
| * @throws SerializationException if EOF is encountered. |
| */ |
| public static byte readByte(InputStream input) throws SerializationException { |
| try { |
| int b = input.read(); |
| if (b == -1) { |
| throw new SerializationException("Premature end of input, could not read byte."); |
| } |
| return (byte)b; |
| } catch (IOException e) { |
| throw new SerializationException(e); |
| } |
| } |
| |
| /** |
| * Write length prefixed sequence of bytes to the ouput stream. |
| * The length prefix is encoded in big endian order. |
| * @param data the data to be written. |
| * @param width the width of the length prefix, in bytes. |
| * @throws SerializationException if the length of {@code data} is too large to fit in |
| * {@code width} bytes or {@code width} is negative. |
| */ |
| public static void writeVariableBytes(OutputStream output, byte[] data, int width) |
| throws SerializationException { |
| writeNumber(output, data.length, width); |
| writeFixedBytes(output, data); |
| } |
| |
| /** |
| * Write a fixed number sequence of bytes to the ouput stream. |
| * @param data the data to be written. |
| */ |
| public static void writeFixedBytes(OutputStream output, byte[] data) |
| throws SerializationException { |
| try { |
| output.write(data); |
| } catch (IOException e) { |
| throw new SerializationException(e); |
| } |
| } |
| |
| /** |
| * Write a number to the output stream. |
| * The number is encoded in big endian order. |
| * @param value the value to be written. |
| * @param width the width of the encoded number, in bytes |
| * @throws SerializationException if the number is too large to fit in {@code width} bytes or |
| * {@code width} is negative. |
| */ |
| public static void writeNumber(OutputStream output, long value, int width) |
| throws SerializationException { |
| if (width < 0) { |
| throw new SerializationException("Negative width: " + width); |
| } |
| if (width < 8 && value >= (1L << (8*width))) { |
| throw new SerializationException("Number too large, " + value + |
| " does not fit in " + width + " bytes"); |
| } |
| |
| try { |
| while (width > 0) { |
| long shift = (width - 1) * 8; |
| // Java behaves weirdly if shifting by more than the variable's size |
| if (shift < Long.SIZE) { |
| output.write((byte)((value >> shift) & 0xFF)); |
| } else { |
| output.write(0); |
| } |
| |
| width --; |
| } |
| } catch (IOException e) { |
| throw new SerializationException(e); |
| } |
| } |
| } |
| |