1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.core.impl.utils;
18 
19 import java.io.ByteArrayInputStream;
20 import java.io.DataInput;
21 import java.io.DataInputStream;
22 import java.io.EOFException;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.nio.ByteOrder;
26 
27 /**
28  * An input stream to parse EXIF data area, which can be written in either little or big endian
29  * order.
30  */
31 // Note: This class is adapted from {@link androidx.exifinterface.media.ExifInterface}
32 final class ByteOrderedDataInputStream extends InputStream implements DataInput {
33     private static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
34     private static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
35 
36     private final DataInputStream mDataInputStream;
37     private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
38     @SuppressWarnings("WeakerAccess") /* synthetic access */
39     final int mLength;
40     @SuppressWarnings("WeakerAccess") /* synthetic access */
41             int mPosition;
42 
ByteOrderedDataInputStream(InputStream in)43     ByteOrderedDataInputStream(InputStream in) throws IOException {
44         this(in, ByteOrder.BIG_ENDIAN);
45     }
46 
ByteOrderedDataInputStream(InputStream in, ByteOrder byteOrder)47     ByteOrderedDataInputStream(InputStream in, ByteOrder byteOrder) throws IOException {
48         mDataInputStream = new DataInputStream(in);
49         mLength = mDataInputStream.available();
50         mPosition = 0;
51         // TODO (b/142218289): Need to handle case where input stream does not support mark
52         mDataInputStream.mark(mLength);
53         mByteOrder = byteOrder;
54     }
55 
ByteOrderedDataInputStream(byte[] bytes)56     ByteOrderedDataInputStream(byte[] bytes) throws IOException {
57         this(new ByteArrayInputStream(bytes));
58     }
59 
setByteOrder(ByteOrder byteOrder)60     public void setByteOrder(ByteOrder byteOrder) {
61         mByteOrder = byteOrder;
62     }
63 
seek(long byteCount)64     public void seek(long byteCount) throws IOException {
65         if (mPosition > byteCount) {
66             mPosition = 0;
67             mDataInputStream.reset();
68             // TODO (b/142218289): Need to handle case where input stream does not support mark
69             mDataInputStream.mark(mLength);
70         } else {
71             byteCount -= mPosition;
72         }
73 
74         if (skipBytes((int) byteCount) != (int) byteCount) {
75             throw new IOException("Couldn't seek up to the byteCount");
76         }
77     }
78 
peek()79     public int peek() {
80         return mPosition;
81     }
82 
83     @Override
available()84     public int available() throws IOException {
85         return mDataInputStream.available();
86     }
87 
88     @Override
read()89     public int read() throws IOException {
90         ++mPosition;
91         return mDataInputStream.read();
92     }
93 
94     @Override
read(byte[] b, int off, int len)95     public int read(byte[] b, int off, int len) throws IOException {
96         int bytesRead = mDataInputStream.read(b, off, len);
97         mPosition += bytesRead;
98         return bytesRead;
99     }
100 
101     @Override
readUnsignedByte()102     public int readUnsignedByte() throws IOException {
103         ++mPosition;
104         return mDataInputStream.readUnsignedByte();
105     }
106 
107     @Override
readLine()108     public String readLine() {
109         throw new UnsupportedOperationException("readLine() not implemented.");
110     }
111 
112     @Override
readBoolean()113     public boolean readBoolean() throws IOException {
114         ++mPosition;
115         return mDataInputStream.readBoolean();
116     }
117 
118     @Override
readChar()119     public char readChar() throws IOException {
120         mPosition += 2;
121         return mDataInputStream.readChar();
122     }
123 
124     @Override
readUTF()125     public String readUTF() throws IOException {
126         mPosition += 2;
127         return mDataInputStream.readUTF();
128     }
129 
130     @Override
readFully(byte[] buffer, int offset, int length)131     public void readFully(byte[] buffer, int offset, int length) throws IOException {
132         mPosition += length;
133         if (mPosition > mLength) {
134             throw new EOFException();
135         }
136         if (mDataInputStream.read(buffer, offset, length) != length) {
137             throw new IOException("Couldn't read up to the length of buffer");
138         }
139     }
140 
141     @Override
readFully(byte[] buffer)142     public void readFully(byte[] buffer) throws IOException {
143         mPosition += buffer.length;
144         if (mPosition > mLength) {
145             throw new EOFException();
146         }
147         if (mDataInputStream.read(buffer, 0, buffer.length) != buffer.length) {
148             throw new IOException("Couldn't read up to the length of buffer");
149         }
150     }
151 
152     @Override
readByte()153     public byte readByte() throws IOException {
154         ++mPosition;
155         if (mPosition > mLength) {
156             throw new EOFException();
157         }
158         int ch = mDataInputStream.read();
159         if (ch < 0) {
160             throw new EOFException();
161         }
162         return (byte) ch;
163     }
164 
165     @Override
readShort()166     public short readShort() throws IOException {
167         mPosition += 2;
168         if (mPosition > mLength) {
169             throw new EOFException();
170         }
171         int ch1 = mDataInputStream.read();
172         int ch2 = mDataInputStream.read();
173         if ((ch1 | ch2) < 0) {
174             throw new EOFException();
175         }
176         if (mByteOrder == LITTLE_ENDIAN) {
177             return (short) ((ch2 << 8) + ch1);
178         } else if (mByteOrder == BIG_ENDIAN) {
179             return (short) ((ch1 << 8) + ch2);
180         }
181         throw new IOException("Invalid byte order: " + mByteOrder);
182     }
183 
184     @Override
readInt()185     public int readInt() throws IOException {
186         mPosition += 4;
187         if (mPosition > mLength) {
188             throw new EOFException();
189         }
190         int ch1 = mDataInputStream.read();
191         int ch2 = mDataInputStream.read();
192         int ch3 = mDataInputStream.read();
193         int ch4 = mDataInputStream.read();
194         if ((ch1 | ch2 | ch3 | ch4) < 0) {
195             throw new EOFException();
196         }
197         if (mByteOrder == LITTLE_ENDIAN) {
198             return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + ch1);
199         } else if (mByteOrder == BIG_ENDIAN) {
200             return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
201         }
202         throw new IOException("Invalid byte order: " + mByteOrder);
203     }
204 
205     @Override
skipBytes(int byteCount)206     public int skipBytes(int byteCount) throws IOException {
207         int totalSkip = Math.min(byteCount, mLength - mPosition);
208         int skipped = 0;
209         while (skipped < totalSkip) {
210             skipped += mDataInputStream.skipBytes(totalSkip - skipped);
211         }
212         mPosition += skipped;
213         return skipped;
214     }
215 
216     @Override
readUnsignedShort()217     public int readUnsignedShort() throws IOException {
218         mPosition += 2;
219         if (mPosition > mLength) {
220             throw new EOFException();
221         }
222         int ch1 = mDataInputStream.read();
223         int ch2 = mDataInputStream.read();
224         if ((ch1 | ch2) < 0) {
225             throw new EOFException();
226         }
227         if (mByteOrder == LITTLE_ENDIAN) {
228             return ((ch2 << 8) + ch1);
229         } else if (mByteOrder == BIG_ENDIAN) {
230             return ((ch1 << 8) + ch2);
231         }
232         throw new IOException("Invalid byte order: " + mByteOrder);
233     }
234 
readUnsignedInt()235     public long readUnsignedInt() throws IOException {
236         return readInt() & 0xffffffffL;
237     }
238 
239     @Override
readLong()240     public long readLong() throws IOException {
241         mPosition += 8;
242         if (mPosition > mLength) {
243             throw new EOFException();
244         }
245         int ch1 = mDataInputStream.read();
246         int ch2 = mDataInputStream.read();
247         int ch3 = mDataInputStream.read();
248         int ch4 = mDataInputStream.read();
249         int ch5 = mDataInputStream.read();
250         int ch6 = mDataInputStream.read();
251         int ch7 = mDataInputStream.read();
252         int ch8 = mDataInputStream.read();
253         if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) {
254             throw new EOFException();
255         }
256         if (mByteOrder == LITTLE_ENDIAN) {
257             return (((long) ch8 << 56) + ((long) ch7 << 48) + ((long) ch6 << 40)
258                     + ((long) ch5 << 32) + ((long) ch4 << 24) + ((long) ch3 << 16)
259                     + ((long) ch2 << 8) + (long) ch1);
260         } else if (mByteOrder == BIG_ENDIAN) {
261             return (((long) ch1 << 56) + ((long) ch2 << 48) + ((long) ch3 << 40)
262                     + ((long) ch4 << 32) + ((long) ch5 << 24) + ((long) ch6 << 16)
263                     + ((long) ch7 << 8) + (long) ch8);
264         }
265         throw new IOException("Invalid byte order: " + mByteOrder);
266     }
267 
268     @Override
readFloat()269     public float readFloat() throws IOException {
270         return Float.intBitsToFloat(readInt());
271     }
272 
273     @Override
readDouble()274     public double readDouble() throws IOException {
275         return Double.longBitsToDouble(readLong());
276     }
277 
278     @Override
mark(int readlimit)279     public void mark(int readlimit) {
280         synchronized (mDataInputStream) {
281             mDataInputStream.mark(readlimit);
282         }
283     }
284 
getLength()285     public int getLength() {
286         return mLength;
287     }
288 }
289