1 /*
2  * Copyright (C) 2008 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 com.android.internal.util;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 
21 /**
22  * An object that provides bitwise incremental write access to a byte array.
23  *
24  * This is useful, for example, when writing a series of fields that
25  * may not be aligned on byte boundaries.
26  *
27  * NOTE -- This class is not threadsafe.
28  */
29 @android.ravenwood.annotation.RavenwoodKeepWholeClass
30 public class BitwiseOutputStream {
31 
32     // The byte array being written to, which will be grown as needed.
33     private byte[] mBuf;
34 
35     // The current position offset, in bits, from the msb in byte 0.
36     private int mPos;
37 
38     // The last bit offset, given the current buf length.
39     private int mEnd;
40 
41     /**
42      * An exception to report access problems.
43      */
44     public static class AccessException extends Exception {
AccessException(String s)45         public AccessException(String s) {
46             super("BitwiseOutputStream access failed: " + s);
47         }
48     }
49 
50     /**
51      * Create object from hint at desired size.
52      *
53      * @param startingLength initial internal byte array length in bytes
54      */
55     @UnsupportedAppUsage
BitwiseOutputStream(int startingLength)56     public BitwiseOutputStream(int startingLength) {
57         mBuf = new byte[startingLength];
58         mEnd = startingLength << 3;
59         mPos = 0;
60     }
61 
62     /**
63      * Return byte array containing accumulated data, sized to just fit.
64      *
65      * @return newly allocated byte array
66      */
67     @UnsupportedAppUsage
toByteArray()68     public byte[] toByteArray() {
69         int len = (mPos >>> 3) + ((mPos & 0x07) > 0 ? 1 : 0);  // &7==%8
70         byte[] newBuf = new byte[len];
71         System.arraycopy(mBuf, 0, newBuf, 0, len);
72         return newBuf;
73     }
74 
75     /**
76      * Allocate a new internal buffer, if needed.
77      *
78      * @param bits additional bits to be accommodated
79      */
possExpand(int bits)80     private void possExpand(int bits) {
81         if ((mPos + bits) < mEnd) return;
82         byte[] newBuf = new byte[(mPos + bits) >>> 2];
83         System.arraycopy(mBuf, 0, newBuf, 0, mEnd >>> 3);
84         mBuf = newBuf;
85         mEnd = newBuf.length << 3;
86     }
87 
88     /**
89      * Write some data and increment the current position.
90      *
91      * The 8-bit limit on access to bitwise streams is intentional to
92      * avoid endianness issues.
93      *
94      * @param bits the amount of data to write (gte 0, lte 8)
95      * @param data to write, will be masked to expose only bits param from lsb
96      */
97     @UnsupportedAppUsage
write(int bits, int data)98     public void write(int bits, int data) throws AccessException {
99         if ((bits < 0) || (bits > 8)) {
100             throw new AccessException("illegal write (" + bits + " bits)");
101         }
102         possExpand(bits);
103         data &= (-1 >>> (32 - bits));
104         int index = mPos >>> 3;
105         int offset = 16 - (mPos & 0x07) - bits;  // &7==%8
106         data <<= offset;
107         mPos += bits;
108         mBuf[index] |= data >>> 8;
109         if (offset < 8) mBuf[index + 1] |= data & 0xFF;
110     }
111 
112     /**
113      * Write data in bulk from a byte array and increment the current position.
114      *
115      * @param bits the amount of data to write
116      * @param arr the byte array containing data to be written
117      */
118     @UnsupportedAppUsage
writeByteArray(int bits, byte[] arr)119     public void writeByteArray(int bits, byte[] arr) throws AccessException {
120         for (int i = 0; i < arr.length; i++) {
121             int increment = Math.min(8, bits - (i << 3));
122             if (increment > 0) {
123                 write(increment, (byte)(arr[i] >>> (8 - increment)));
124             }
125         }
126     }
127 
128     /**
129      * Increment the current position, implicitly writing zeros.
130      *
131      * @param bits the amount by which to increment the position
132      */
skip(int bits)133     public void skip(int bits) {
134         possExpand(bits);
135         mPos += bits;
136     }
137 }
138