blob: 72230b4062e115f3ae649b1626d60df82c2d6b51 [file] [log] [blame]
Howard Chen265f8bf2019-08-20 13:00:49 +08001/*
2 * Copyright (C) 2019 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
17package com.android.dynsystem;
18
19import static java.lang.Math.min;
20
21import java.io.BufferedInputStream;
22import java.io.IOException;
23import java.io.InputStream;
24import java.nio.ByteBuffer;
25import java.nio.ByteOrder;
26import java.util.Arrays;
27
28/**
29 * SparseInputStream read from upstream and detects the data format. If the upstream is a valid
30 * sparse data, it will unsparse it on the fly. Otherwise, it just passthrough as is.
31 */
32public class SparseInputStream extends InputStream {
33 static final int FILE_HDR_SIZE = 28;
34 static final int CHUNK_HDR_SIZE = 12;
35
36 /**
37 * This class represents a chunk in the Android sparse image.
38 *
39 * @see system/core/libsparse/sparse_format.h
40 */
41 private class SparseChunk {
42 static final short RAW = (short) 0xCAC1;
43 static final short FILL = (short) 0xCAC2;
44 static final short DONTCARE = (short) 0xCAC3;
45 public short mChunkType;
46 public int mChunkSize;
47 public int mTotalSize;
48 public byte[] fill;
49 public String toString() {
50 return String.format(
51 "type: %x, chunk_size: %d, total_size: %d", mChunkType, mChunkSize, mTotalSize);
52 }
53 }
54
55 private byte[] readFull(InputStream in, int size) throws IOException {
56 byte[] buf = new byte[size];
57 for (int done = 0, n = 0; done < size; done += n) {
58 if ((n = in.read(buf, done, size - done)) < 0) {
59 throw new IOException("Failed to readFull");
60 }
61 }
62 return buf;
63 }
64
65 private ByteBuffer readBuffer(InputStream in, int size) throws IOException {
66 return ByteBuffer.wrap(readFull(in, size)).order(ByteOrder.LITTLE_ENDIAN);
67 }
68
69 private SparseChunk readChunk(InputStream in) throws IOException {
70 SparseChunk chunk = new SparseChunk();
71 ByteBuffer buf = readBuffer(in, CHUNK_HDR_SIZE);
72 chunk.mChunkType = buf.getShort();
73 buf.getShort();
74 chunk.mChunkSize = buf.getInt();
75 chunk.mTotalSize = buf.getInt();
76 return chunk;
77 }
78
79 private BufferedInputStream mIn;
80 private boolean mIsSparse;
81 private long mBlockSize;
82 private long mTotalBlocks;
83 private long mTotalChunks;
84 private SparseChunk mCur;
85 private long mLeft;
86 private int mCurChunks;
87
88 public SparseInputStream(BufferedInputStream in) throws IOException {
89 mIn = in;
90 in.mark(FILE_HDR_SIZE * 2);
91 ByteBuffer buf = readBuffer(mIn, FILE_HDR_SIZE);
92 mIsSparse = (buf.getInt() == 0xed26ff3a);
93 if (!mIsSparse) {
94 mIn.reset();
95 return;
96 }
97 int major = buf.getShort();
98 int minor = buf.getShort();
99
100 if (major > 0x1 || minor > 0x0) {
101 throw new IOException("Unsupported sparse version: " + major + "." + minor);
102 }
103
104 if (buf.getShort() != FILE_HDR_SIZE) {
105 throw new IOException("Illegal file header size");
106 }
107 if (buf.getShort() != CHUNK_HDR_SIZE) {
108 throw new IOException("Illegal chunk header size");
109 }
110 mBlockSize = buf.getInt();
111 if ((mBlockSize & 0x3) != 0) {
112 throw new IOException("Illegal block size, must be a multiple of 4");
113 }
114 mTotalBlocks = buf.getInt();
115 mTotalChunks = buf.getInt();
116 mLeft = mCurChunks = 0;
117 }
118
119 /**
120 * Check if it needs to open a new chunk.
121 *
122 * @return true if it's EOF
123 */
124 private boolean prepareChunk() throws IOException {
125 if (mCur == null || mLeft <= 0) {
126 if (++mCurChunks > mTotalChunks) return true;
127 mCur = readChunk(mIn);
128 if (mCur.mChunkType == SparseChunk.FILL) {
129 mCur.fill = readFull(mIn, 4);
130 }
131 mLeft = mCur.mChunkSize * mBlockSize;
132 }
133 return mLeft == 0;
134 }
135
136 /**
137 * It overrides the InputStream.read(byte[] buf)
138 */
139 public int read(byte[] buf) throws IOException {
140 if (!mIsSparse) {
141 return mIn.read(buf);
142 }
143 if (prepareChunk()) return -1;
144 int n = -1;
145 switch (mCur.mChunkType) {
146 case SparseChunk.RAW:
147 n = mIn.read(buf, 0, (int) min(mLeft, buf.length));
148 mLeft -= n;
149 return n;
150 case SparseChunk.DONTCARE:
151 n = (int) min(mLeft, buf.length);
152 Arrays.fill(buf, 0, n - 1, (byte) 0);
153 mLeft -= n;
154 return n;
155 case SparseChunk.FILL:
156 // The FILL type is rarely used, so use a simple implmentation.
157 return super.read(buf);
158 default:
159 throw new IOException("Unsupported Chunk:" + mCur.toString());
160 }
161 }
162
163 /**
164 * It overrides the InputStream.read()
165 */
166 public int read() throws IOException {
167 if (!mIsSparse) {
168 return mIn.read();
169 }
170 if (prepareChunk()) return -1;
171 int ret = -1;
172 switch (mCur.mChunkType) {
173 case SparseChunk.RAW:
174 ret = mIn.read();
175 break;
176 case SparseChunk.DONTCARE:
177 ret = 0;
178 break;
179 case SparseChunk.FILL:
180 ret = mCur.fill[(4 - ((int) mLeft & 0x3)) & 0x3];
181 break;
182 default:
183 throw new IOException("Unsupported Chunk:" + mCur.toString());
184 }
185 mLeft--;
186 return ret;
187 }
188
189 /**
190 * Get the unsparse size
191 * @return -1 if unknown
192 */
193 public long getUnsparseSize() {
194 if (!mIsSparse) {
195 return -1;
196 }
197 return mBlockSize * mTotalBlocks;
198 }
199}