1 /*
2  * Copyright 2018 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.versionedparcelable;
18 
19 import android.os.Bundle;
20 import android.os.IBinder;
21 import android.os.IInterface;
22 import android.os.Parcelable;
23 
24 import androidx.annotation.RestrictTo;
25 import androidx.collection.SimpleArrayMap;
26 
27 import org.jspecify.annotations.NonNull;
28 import org.jspecify.annotations.Nullable;
29 
30 import java.io.ByteArrayOutputStream;
31 import java.io.DataInputStream;
32 import java.io.DataOutputStream;
33 import java.io.FilterInputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
37 import java.lang.reflect.Method;
38 import java.nio.charset.Charset;
39 import java.util.Set;
40 
41 /**
42  */
43 @RestrictTo(RestrictTo.Scope.LIBRARY)
44 class VersionedParcelStream extends VersionedParcel {
45 
46     private static final @NonNull Charset UTF_16 = Charset.forName("UTF-16");
47 
48     // Supported types held inside a bundle. These cannot be added to or changed once shipped.
49     private static final int TYPE_NULL = 0;
50     private static final int TYPE_SUB_BUNDLE = 1;
51     private static final int TYPE_SUB_PERSISTABLE_BUNDLE = 2;
52     private static final int TYPE_STRING = 3;
53     private static final int TYPE_STRING_ARRAY = 4;
54     private static final int TYPE_BOOLEAN = 5;
55     private static final int TYPE_BOOLEAN_ARRAY = 6;
56     private static final int TYPE_DOUBLE = 7;
57     private static final int TYPE_DOUBLE_ARRAY = 8;
58     private static final int TYPE_INT = 9;
59     private static final int TYPE_INT_ARRAY = 10;
60     private static final int TYPE_LONG = 11;
61     private static final int TYPE_LONG_ARRAY = 12;
62     private static final int TYPE_FLOAT = 13;
63     private static final int TYPE_FLOAT_ARRAY = 14;
64 
65     private final @Nullable DataInputStream mMasterInput;
66     private final @Nullable DataOutputStream mMasterOutput;
67 
68     private @Nullable DataInputStream mCurrentInput;
69     private @Nullable DataOutputStream mCurrentOutput;
70     private @Nullable FieldBuffer mFieldBuffer;
71     private boolean mIgnoreParcelables;
72 
73     int mCount = 0;
74     private int mFieldId = -1;
75     int mFieldSize = -1;
76 
VersionedParcelStream(@ullable InputStream input, @Nullable OutputStream output)77     VersionedParcelStream(@Nullable InputStream input, @Nullable OutputStream output) {
78         this(input, output, new SimpleArrayMap<String, Method>(),
79                 new SimpleArrayMap<String, Method>(), new SimpleArrayMap<String, Class<?>>());
80     }
81 
VersionedParcelStream( @ullable InputStream input, @Nullable OutputStream output, @NonNull SimpleArrayMap<String, Method> readCache, @NonNull SimpleArrayMap<String, Method> writeCache, @NonNull SimpleArrayMap<String, Class<?>> parcelizerCache )82     private VersionedParcelStream(
83             @Nullable InputStream input,
84             @Nullable OutputStream output,
85             @NonNull SimpleArrayMap<String, Method> readCache,
86             @NonNull SimpleArrayMap<String, Method> writeCache,
87             @NonNull SimpleArrayMap<String, Class<?>> parcelizerCache
88     ) {
89         super(readCache, writeCache, parcelizerCache);
90         mMasterInput = input != null ? new DataInputStream(new FilterInputStream(input) {
91             @Override
92             public int read() throws IOException {
93                 if (mFieldSize != -1 && mCount >= mFieldSize) {
94                     throw new IOException();
95                 }
96                 int read = super.read();
97                 mCount += 1;
98                 return read;
99             }
100 
101             @Override
102             public int read(byte[] b, int off, int len) throws IOException {
103                 if (mFieldSize != -1 && mCount >= mFieldSize) {
104                     throw new IOException();
105                 }
106                 int read = super.read(b, off, len);
107                 if (read > 0) {
108                     mCount += read;
109                 }
110                 return read;
111             }
112 
113             @Override
114             public long skip(long n) throws IOException {
115                 if (mFieldSize != -1 && mCount >= mFieldSize) {
116                     throw new IOException();
117                 }
118                 long skip = super.skip(n);
119                 if (skip > 0) {
120                     mCount += (int) skip;
121                 }
122                 return skip;
123             }
124         }) : null;
125         mMasterOutput = output != null ? new DataOutputStream(output) : null;
126         mCurrentInput = mMasterInput;
127         mCurrentOutput = mMasterOutput;
128     }
129 
130     @Override
isStream()131     public boolean isStream() {
132         return true;
133     }
134 
135     /**
136      */
137     @Override
setSerializationFlags(boolean allowSerialization, boolean ignoreParcelables)138     public void setSerializationFlags(boolean allowSerialization, boolean ignoreParcelables) {
139         if (!allowSerialization) {
140             throw new RuntimeException("Serialization of this object is not allowed");
141         }
142         mIgnoreParcelables = ignoreParcelables;
143     }
144 
145     @Override
closeField()146     public void closeField() {
147         if (mFieldBuffer != null) {
148             try {
149                 if (mFieldBuffer.mOutput.size() != 0) {
150                     mFieldBuffer.flushField();
151                 }
152             } catch (IOException e) {
153                 throw new ParcelException(e);
154             }
155             mFieldBuffer = null;
156         }
157     }
158 
159     @Override
createSubParcel()160     protected @NonNull VersionedParcel createSubParcel() {
161         return new VersionedParcelStream(mCurrentInput, mCurrentOutput, mReadCache, mWriteCache,
162                 mParcelizerCache);
163     }
164 
165     @Override
readField(int fieldId)166     public boolean readField(int fieldId) {
167         try {
168             while (true) {
169                 if (mFieldId == fieldId) {
170                     return true;
171                 }
172                 if (String.valueOf(mFieldId).compareTo(String.valueOf(fieldId)) > 0) {
173                     return false;
174                 }
175                 if (mCount < mFieldSize) {
176                     mMasterInput.skip(mFieldSize - mCount);
177                 }
178                 mFieldSize = -1;
179                 int fieldInfo = mMasterInput.readInt();
180                 mCount = 0;
181                 int size = fieldInfo & 0xffff;
182                 if (size == 0xffff) {
183                     size = mMasterInput.readInt();
184                 }
185                 int id = (fieldInfo >> 16) & 0xffff;
186                 mFieldId = id;
187                 mFieldSize = size;
188             }
189         } catch (IOException e) {
190         }
191         return false;
192     }
193 
194     @Override
setOutputField(int fieldId)195     public void setOutputField(int fieldId) {
196         closeField();
197         mFieldBuffer = new FieldBuffer(fieldId, mMasterOutput);
198         mCurrentOutput = mFieldBuffer.mDataStream;
199     }
200 
201     @Override
writeByteArray(byte @Nullable [] b)202     public void writeByteArray(byte @Nullable [] b) {
203         try {
204             if (b != null) {
205                 mCurrentOutput.writeInt(b.length);
206                 mCurrentOutput.write(b);
207             } else {
208                 mCurrentOutput.writeInt(-1);
209             }
210         } catch (IOException e) {
211             throw new ParcelException(e);
212         }
213     }
214 
215     @Override
writeByteArray(byte @Nullable [] b, int offset, int len)216     public void writeByteArray(byte @Nullable [] b, int offset, int len) {
217         try {
218             if (b != null) {
219                 mCurrentOutput.writeInt(len);
220                 mCurrentOutput.write(b, offset, len);
221             } else {
222                 mCurrentOutput.writeInt(-1);
223             }
224         } catch (IOException e) {
225             throw new ParcelException(e);
226         }
227     }
228 
229     @Override
writeCharSequence(@ullable CharSequence charSequence)230     protected void writeCharSequence(@Nullable CharSequence charSequence) {
231         if (!mIgnoreParcelables) {
232             throw new RuntimeException("CharSequence cannot be written to an OutputStream");
233         }
234     }
235 
236     @Override
writeInt(int val)237     public void writeInt(int val) {
238         try {
239             mCurrentOutput.writeInt(val);
240         } catch (IOException e) {
241             throw new ParcelException(e);
242         }
243     }
244 
245     @Override
writeLong(long val)246     public void writeLong(long val) {
247         try {
248             mCurrentOutput.writeLong(val);
249         } catch (IOException e) {
250             throw new ParcelException(e);
251         }
252 
253     }
254 
255     @Override
writeFloat(float val)256     public void writeFloat(float val) {
257         try {
258             mCurrentOutput.writeFloat(val);
259         } catch (IOException e) {
260             throw new ParcelException(e);
261         }
262 
263     }
264 
265     @Override
writeDouble(double val)266     public void writeDouble(double val) {
267         try {
268             mCurrentOutput.writeDouble(val);
269         } catch (IOException e) {
270             throw new ParcelException(e);
271         }
272 
273     }
274 
275     @Override
writeString(@ullable String val)276     public void writeString(@Nullable String val) {
277         try {
278             if (val != null) {
279                 byte[] bytes = val.getBytes(UTF_16);
280                 mCurrentOutput.writeInt(bytes.length);
281                 mCurrentOutput.write(bytes);
282             } else {
283                 mCurrentOutput.writeInt(-1);
284             }
285         } catch (IOException e) {
286             throw new ParcelException(e);
287         }
288     }
289 
290     @Override
writeBoolean(boolean val)291     public void writeBoolean(boolean val) {
292         try {
293             mCurrentOutput.writeBoolean(val);
294         } catch (IOException e) {
295             throw new ParcelException(e);
296         }
297     }
298 
299     @Override
writeStrongBinder(@ullable IBinder val)300     public void writeStrongBinder(@Nullable IBinder val) {
301         if (!mIgnoreParcelables) {
302             throw new RuntimeException("Binders cannot be written to an OutputStream");
303         }
304     }
305 
306     @Override
writeParcelable(@ullable Parcelable p)307     public void writeParcelable(@Nullable Parcelable p) {
308         if (!mIgnoreParcelables) {
309             throw new RuntimeException("Parcelables cannot be written to an OutputStream");
310         }
311     }
312 
313     @Override
writeStrongInterface(@ullable IInterface val)314     public void writeStrongInterface(@Nullable IInterface val) {
315         if (!mIgnoreParcelables) {
316             throw new RuntimeException("Binders cannot be written to an OutputStream");
317         }
318     }
319 
320     @Override
readStrongBinder()321     public @Nullable IBinder readStrongBinder() {
322         return null;
323     }
324 
325     @Override
326     @SuppressWarnings("TypeParameterUnusedInFormals")
readParcelable()327     public <T extends Parcelable> @Nullable T readParcelable() {
328         return null;
329     }
330 
331     @Override
readInt()332     public int readInt() {
333         try {
334             return mCurrentInput.readInt();
335         } catch (IOException e) {
336             throw new ParcelException(e);
337         }
338     }
339 
340     @Override
readLong()341     public long readLong() {
342         try {
343             return mCurrentInput.readLong();
344         } catch (IOException e) {
345             throw new ParcelException(e);
346         }
347     }
348 
349     @Override
readFloat()350     public float readFloat() {
351         try {
352             return mCurrentInput.readFloat();
353         } catch (IOException e) {
354             throw new ParcelException(e);
355         }
356     }
357 
358     @Override
readDouble()359     public double readDouble() {
360         try {
361             return mCurrentInput.readDouble();
362         } catch (IOException e) {
363             throw new ParcelException(e);
364         }
365     }
366 
367     @Override
readString()368     public @Nullable String readString() {
369         try {
370             int len = mCurrentInput.readInt();
371             if (len > 0) {
372                 byte[] bytes = new byte[len];
373                 mCurrentInput.readFully(bytes);
374                 return new String(bytes, UTF_16);
375             } else {
376                 return null;
377             }
378         } catch (IOException e) {
379             throw new ParcelException(e);
380         }
381     }
382 
383     @Override
readByteArray()384     public byte @Nullable [] readByteArray() {
385         try {
386             int len = mCurrentInput.readInt();
387             if (len > 0) {
388                 byte[] bytes = new byte[len];
389                 mCurrentInput.readFully(bytes);
390                 return bytes;
391             } else {
392                 return null;
393             }
394         } catch (IOException e) {
395             throw new ParcelException(e);
396         }
397     }
398 
399     @Override
readCharSequence()400     protected @Nullable CharSequence readCharSequence() {
401         return null;
402     }
403 
404     @Override
readBoolean()405     public boolean readBoolean() {
406         try {
407             return mCurrentInput.readBoolean();
408         } catch (IOException e) {
409             throw new ParcelException(e);
410         }
411     }
412 
413     @Override
414     @SuppressWarnings("deprecation")
writeBundle(Bundle val)415     public void writeBundle(Bundle val) {
416         try {
417             if (val != null) {
418                 Set<String> keys = val.keySet();
419                 mCurrentOutput.writeInt(keys.size());
420                 for (String key : keys) {
421                     writeString(key);
422                     Object o = val.get(key);
423                     writeObject(o);
424                 }
425             } else {
426                 mCurrentOutput.writeInt(-1);
427             }
428         } catch (IOException e) {
429             throw new ParcelException(e);
430         }
431     }
432 
433     @Override
readBundle()434     public @Nullable Bundle readBundle() {
435         int size = readInt();
436         if (size < 0) {
437             return null;
438         }
439         Bundle b = new Bundle();
440         for (int i = 0; i < size; i++) {
441             String key = readString();
442             readObject(readInt(), key, b);
443         }
444         return b;
445     }
446 
writeObject(@ullable Object o)447     private void writeObject(@Nullable Object o) {
448         if (o == null) {
449             writeInt(TYPE_NULL);
450         } else if (o instanceof Bundle) {
451             writeInt(TYPE_SUB_BUNDLE);
452             writeBundle((Bundle) o);
453         } else if (o instanceof String) {
454             writeInt(TYPE_STRING);
455             writeString((String) o);
456         } else if (o instanceof String[]) {
457             writeInt(TYPE_STRING_ARRAY);
458             writeArray((String[]) o);
459         } else if (o instanceof Boolean) {
460             writeInt(TYPE_BOOLEAN);
461             writeBoolean((Boolean) o);
462         } else if (o instanceof boolean[]) {
463             writeInt(TYPE_BOOLEAN_ARRAY);
464             writeBooleanArray((boolean[]) o);
465         } else if (o instanceof Double) {
466             writeInt(TYPE_DOUBLE);
467             writeDouble((Double) o);
468         } else if (o instanceof double[]) {
469             writeInt(TYPE_DOUBLE_ARRAY);
470             writeDoubleArray((double[]) o);
471         } else if (o instanceof Integer) {
472             writeInt(TYPE_INT);
473             writeInt((Integer) o);
474         } else if (o instanceof int[]) {
475             writeInt(TYPE_INT_ARRAY);
476             writeIntArray((int[]) o);
477         } else if (o instanceof Long) {
478             writeInt(TYPE_LONG);
479             writeLong((Long) o);
480         } else if (o instanceof long[]) {
481             writeInt(TYPE_LONG_ARRAY);
482             writeLongArray((long[]) o);
483         } else if (o instanceof Float) {
484             writeInt(TYPE_FLOAT);
485             writeFloat((Float) o);
486         } else if (o instanceof float[]) {
487             writeInt(TYPE_FLOAT_ARRAY);
488             writeFloatArray((float[]) o);
489         } else {
490             throw new IllegalArgumentException("Unsupported type " + o.getClass());
491         }
492     }
493 
readObject(int type, @Nullable String key, @NonNull Bundle b)494     private void readObject(int type, @Nullable String key, @NonNull Bundle b) {
495         switch (type) {
496             case TYPE_NULL:
497                 b.putParcelable(key, null);
498                 break;
499             case TYPE_SUB_BUNDLE:
500                 b.putBundle(key, readBundle());
501                 break;
502             case TYPE_SUB_PERSISTABLE_BUNDLE:
503                 b.putBundle(key, readBundle());
504                 break;
505             case TYPE_STRING:
506                 b.putString(key, readString());
507                 break;
508             case TYPE_STRING_ARRAY:
509                 b.putStringArray(key, readArray(new String[0]));
510                 break;
511             case TYPE_BOOLEAN:
512                 b.putBoolean(key, readBoolean());
513                 break;
514             case TYPE_BOOLEAN_ARRAY:
515                 b.putBooleanArray(key, readBooleanArray());
516                 break;
517             case TYPE_DOUBLE:
518                 b.putDouble(key, readDouble());
519                 break;
520             case TYPE_DOUBLE_ARRAY:
521                 b.putDoubleArray(key, readDoubleArray());
522                 break;
523             case TYPE_INT:
524                 b.putInt(key, readInt());
525                 break;
526             case TYPE_INT_ARRAY:
527                 b.putIntArray(key, readIntArray());
528                 break;
529             case TYPE_LONG:
530                 b.putLong(key, readLong());
531                 break;
532             case TYPE_LONG_ARRAY:
533                 b.putLongArray(key, readLongArray());
534                 break;
535             case TYPE_FLOAT:
536                 b.putFloat(key, readFloat());
537                 break;
538             case TYPE_FLOAT_ARRAY:
539                 b.putFloatArray(key, readFloatArray());
540                 break;
541             default:
542                 throw new RuntimeException("Unknown type " + type);
543         }
544     }
545 
546     // This uses extra buffers at the moment, but makes the code really clean.
547     // TODO: Use less buffers
548     private static class FieldBuffer {
549 
550         final @NonNull ByteArrayOutputStream mOutput = new ByteArrayOutputStream();
551         final @NonNull DataOutputStream mDataStream = new DataOutputStream(mOutput);
552         private final int mFieldId;
553         private final @NonNull DataOutputStream mTarget;
554 
FieldBuffer(int fieldId, @NonNull DataOutputStream target)555         FieldBuffer(int fieldId, @NonNull DataOutputStream target) {
556             mFieldId = fieldId;
557             mTarget = target;
558         }
559 
flushField()560         void flushField() throws IOException {
561             mDataStream.flush();
562             int size = mOutput.size();
563             int fieldInfo = (mFieldId << 16) | (size >= 0xffff ? 0xffff : size);
564             mTarget.writeInt(fieldInfo);
565             if (size >= 0xffff) {
566                 mTarget.writeInt(size);
567             }
568             mOutput.writeTo(mTarget);
569         }
570     }
571 
572 }
573