Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 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 | package com.android.utils.blob; |
| 17 | |
Sudheer Shanka | 96b4537 | 2020-02-29 16:49:27 -0800 | [diff] [blame] | 18 | import static com.android.utils.blob.Utils.BUFFER_SIZE_BYTES; |
| 19 | import static com.android.utils.blob.Utils.copy; |
| 20 | |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 21 | import static com.google.common.truth.Truth.assertThat; |
| 22 | |
| 23 | import android.app.blob.BlobHandle; |
| 24 | import android.app.blob.BlobStoreManager; |
| 25 | import android.content.Context; |
| 26 | import android.os.FileUtils; |
| 27 | import android.os.ParcelFileDescriptor; |
| 28 | |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 29 | import java.io.File; |
| 30 | import java.io.FileDescriptor; |
| 31 | import java.io.FileInputStream; |
| 32 | import java.io.FileOutputStream; |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 33 | import java.io.RandomAccessFile; |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 34 | import java.security.MessageDigest; |
| 35 | import java.util.Random; |
| 36 | import java.util.concurrent.TimeUnit; |
| 37 | |
| 38 | public class DummyBlobData { |
| 39 | private static final long DEFAULT_SIZE_BYTES = 10 * 1024L * 1024L; |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 40 | |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 41 | private final Random mRandom; |
| 42 | private final File mFile; |
| 43 | private final long mFileSize; |
Sudheer Shanka | c6c7994 | 2020-03-12 13:20:46 -0700 | [diff] [blame] | 44 | private final CharSequence mLabel; |
Sudheer Shanka | 8992c05 | 2020-05-26 18:10:09 -0700 | [diff] [blame] | 45 | private final long mExpiryDurationMs; |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 46 | |
| 47 | byte[] mFileDigest; |
| 48 | long mExpiryTimeMs; |
| 49 | |
Sudheer Shanka | c0fd5fa | 2020-03-15 23:31:37 -0700 | [diff] [blame] | 50 | private DummyBlobData(Builder builder) { |
Sudheer Shanka | c6c7994 | 2020-03-12 13:20:46 -0700 | [diff] [blame] | 51 | mRandom = new Random(builder.getRandomSeed()); |
| 52 | mFile = new File(builder.getContext().getFilesDir(), builder.getFileName()); |
| 53 | mFileSize = builder.getFileSize(); |
| 54 | mLabel = builder.getLabel(); |
Sudheer Shanka | 8992c05 | 2020-05-26 18:10:09 -0700 | [diff] [blame] | 55 | mExpiryDurationMs = builder.getExpiryDurationMs(); |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 56 | } |
| 57 | |
Sudheer Shanka | c6c7994 | 2020-03-12 13:20:46 -0700 | [diff] [blame] | 58 | public static class Builder { |
| 59 | private final Context mContext; |
| 60 | private int mRandomSeed = 0; |
| 61 | private long mFileSize = DEFAULT_SIZE_BYTES; |
| 62 | private CharSequence mLabel = "Test label"; |
| 63 | private String mFileName = "blob_" + System.nanoTime(); |
Sudheer Shanka | 8992c05 | 2020-05-26 18:10:09 -0700 | [diff] [blame] | 64 | private long mExpiryDurationMs = TimeUnit.DAYS.toMillis(1); |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 65 | |
Sudheer Shanka | c6c7994 | 2020-03-12 13:20:46 -0700 | [diff] [blame] | 66 | public Builder(Context context) { |
| 67 | mContext = context; |
| 68 | } |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 69 | |
Sudheer Shanka | c6c7994 | 2020-03-12 13:20:46 -0700 | [diff] [blame] | 70 | public Context getContext() { |
| 71 | return mContext; |
| 72 | } |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 73 | |
Sudheer Shanka | c6c7994 | 2020-03-12 13:20:46 -0700 | [diff] [blame] | 74 | public Builder setRandomSeed(int randomSeed) { |
| 75 | mRandomSeed = randomSeed; |
| 76 | return this; |
| 77 | } |
| 78 | |
| 79 | public int getRandomSeed() { |
| 80 | return mRandomSeed; |
| 81 | } |
| 82 | |
Sudheer Shanka | f80c8e5 | 2020-04-22 03:36:18 -0700 | [diff] [blame] | 83 | public Builder setFileSize(long fileSize) { |
Sudheer Shanka | c6c7994 | 2020-03-12 13:20:46 -0700 | [diff] [blame] | 84 | mFileSize = fileSize; |
| 85 | return this; |
| 86 | } |
| 87 | |
| 88 | public long getFileSize() { |
| 89 | return mFileSize; |
| 90 | } |
| 91 | |
| 92 | public Builder setLabel(CharSequence label) { |
| 93 | mLabel = label; |
| 94 | return this; |
| 95 | } |
| 96 | |
| 97 | public CharSequence getLabel() { |
| 98 | return mLabel; |
| 99 | } |
| 100 | |
| 101 | public Builder setFileName(String fileName) { |
| 102 | mFileName = fileName; |
| 103 | return this; |
| 104 | } |
| 105 | |
| 106 | public String getFileName() { |
| 107 | return mFileName; |
| 108 | } |
| 109 | |
Sudheer Shanka | 8992c05 | 2020-05-26 18:10:09 -0700 | [diff] [blame] | 110 | public Builder setExpiryDurationMs(long durationMs) { |
| 111 | mExpiryDurationMs = durationMs; |
| 112 | return this; |
| 113 | } |
| 114 | |
| 115 | public long getExpiryDurationMs() { |
| 116 | return mExpiryDurationMs; |
| 117 | } |
| 118 | |
Sudheer Shanka | c6c7994 | 2020-03-12 13:20:46 -0700 | [diff] [blame] | 119 | public DummyBlobData build() { |
| 120 | return new DummyBlobData(this); |
| 121 | } |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 122 | } |
| 123 | |
| 124 | public void prepare() throws Exception { |
| 125 | try (RandomAccessFile file = new RandomAccessFile(mFile, "rw")) { |
| 126 | writeRandomData(file, mFileSize); |
| 127 | } |
| 128 | mFileDigest = FileUtils.digest(mFile, "SHA-256"); |
Sudheer Shanka | 8992c05 | 2020-05-26 18:10:09 -0700 | [diff] [blame] | 129 | mExpiryTimeMs = System.currentTimeMillis() + mExpiryDurationMs; |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 130 | } |
| 131 | |
| 132 | public BlobHandle getBlobHandle() throws Exception { |
Sudheer Shanka | 96b4537 | 2020-02-29 16:49:27 -0800 | [diff] [blame] | 133 | return BlobHandle.createWithSha256(mFileDigest, mLabel, |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 134 | mExpiryTimeMs, "test_tag"); |
| 135 | } |
| 136 | |
| 137 | public long getFileSize() throws Exception { |
| 138 | return mFileSize; |
| 139 | } |
| 140 | |
| 141 | public long getExpiryTimeMillis() { |
| 142 | return mExpiryTimeMs; |
| 143 | } |
| 144 | |
| 145 | public void delete() { |
| 146 | mFile.delete(); |
| 147 | } |
| 148 | |
| 149 | public void writeToSession(BlobStoreManager.Session session) throws Exception { |
| 150 | writeToSession(session, 0, mFileSize); |
| 151 | } |
| 152 | |
| 153 | public void writeToSession(BlobStoreManager.Session session, |
| 154 | long offsetBytes, long lengthBytes) throws Exception { |
| 155 | try (FileInputStream in = new FileInputStream(mFile)) { |
Sudheer Shanka | a2c2087 | 2020-06-21 22:31:26 -0700 | [diff] [blame] | 156 | Utils.writeToSession(session, in, offsetBytes, lengthBytes, lengthBytes); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | public void writeToSession(BlobStoreManager.Session session, |
| 161 | long offsetBytes, long lengthBytes, long allocateBytes) throws Exception { |
| 162 | try (FileInputStream in = new FileInputStream(mFile)) { |
| 163 | Utils.writeToSession(session, in, offsetBytes, lengthBytes, allocateBytes); |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 164 | } |
| 165 | } |
| 166 | |
| 167 | public void writeToFd(FileDescriptor fd, long offsetBytes, long lengthBytes) throws Exception { |
| 168 | try (FileInputStream in = new FileInputStream(mFile)) { |
| 169 | in.getChannel().position(offsetBytes); |
| 170 | try (FileOutputStream out = new FileOutputStream(fd)) { |
| 171 | copy(in, out, lengthBytes); |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | |
Sudheer Shanka | 96b4537 | 2020-02-29 16:49:27 -0800 | [diff] [blame] | 176 | public ParcelFileDescriptor openForRead() throws Exception { |
| 177 | return ParcelFileDescriptor.open(mFile, ParcelFileDescriptor.MODE_READ_ONLY); |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 178 | } |
| 179 | |
| 180 | public void readFromSessionAndVerifyBytes(BlobStoreManager.Session session, |
| 181 | long offsetBytes, int lengthBytes) throws Exception { |
| 182 | final byte[] expectedBytes = new byte[lengthBytes]; |
| 183 | try (FileInputStream in = new FileInputStream(mFile)) { |
| 184 | read(in, expectedBytes, offsetBytes, lengthBytes); |
| 185 | } |
| 186 | |
| 187 | final byte[] actualBytes = new byte[lengthBytes]; |
| 188 | try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream( |
Sudheer Shanka | 2a973ab | 2020-03-25 12:49:30 -0700 | [diff] [blame] | 189 | session.openRead())) { |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 190 | read(in, actualBytes, offsetBytes, lengthBytes); |
| 191 | } |
| 192 | |
| 193 | assertThat(actualBytes).isEqualTo(expectedBytes); |
| 194 | |
| 195 | } |
| 196 | |
| 197 | private void read(FileInputStream in, byte[] buffer, |
| 198 | long offsetBytes, int lengthBytes) throws Exception { |
| 199 | in.getChannel().position(offsetBytes); |
| 200 | in.read(buffer, 0, lengthBytes); |
| 201 | } |
| 202 | |
| 203 | public void readFromSessionAndVerifyDigest(BlobStoreManager.Session session) |
| 204 | throws Exception { |
| 205 | readFromSessionAndVerifyDigest(session, 0, mFile.length()); |
| 206 | } |
| 207 | |
| 208 | public void readFromSessionAndVerifyDigest(BlobStoreManager.Session session, |
| 209 | long offsetBytes, long lengthBytes) throws Exception { |
| 210 | final byte[] actualDigest; |
| 211 | try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream( |
Sudheer Shanka | 2a973ab | 2020-03-25 12:49:30 -0700 | [diff] [blame] | 212 | session.openRead())) { |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 213 | actualDigest = createSha256Digest(in, offsetBytes, lengthBytes); |
| 214 | } |
| 215 | |
| 216 | assertThat(actualDigest).isEqualTo(mFileDigest); |
| 217 | } |
| 218 | |
| 219 | public void verifyBlob(ParcelFileDescriptor pfd) throws Exception { |
| 220 | final byte[] actualDigest; |
| 221 | try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { |
| 222 | actualDigest = FileUtils.digest(in, "SHA-256"); |
| 223 | } |
| 224 | assertThat(actualDigest).isEqualTo(mFileDigest); |
| 225 | } |
| 226 | |
| 227 | private byte[] createSha256Digest(FileInputStream in, long offsetBytes, long lengthBytes) |
| 228 | throws Exception { |
| 229 | final MessageDigest digest = MessageDigest.getInstance("SHA-256"); |
| 230 | in.getChannel().position(offsetBytes); |
| 231 | final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; |
| 232 | long bytesRead = 0; |
| 233 | while (bytesRead < lengthBytes) { |
| 234 | int toRead = (bytesRead + buffer.length <= lengthBytes) |
| 235 | ? buffer.length : (int) (lengthBytes - bytesRead); |
| 236 | toRead = in.read(buffer, 0, toRead); |
| 237 | digest.update(buffer, 0, toRead); |
| 238 | bytesRead += toRead; |
| 239 | } |
| 240 | return digest.digest(); |
| 241 | } |
| 242 | |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 243 | private void writeRandomData(RandomAccessFile file, long fileSize) |
| 244 | throws Exception { |
| 245 | long bytesWritten = 0; |
| 246 | final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; |
| 247 | while (bytesWritten < fileSize) { |
| 248 | mRandom.nextBytes(buffer); |
| 249 | final int toWrite = (bytesWritten + buffer.length <= fileSize) |
| 250 | ? buffer.length : (int) (fileSize - bytesWritten); |
| 251 | file.seek(bytesWritten); |
| 252 | file.write(buffer, 0, toWrite); |
| 253 | bytesWritten += toWrite; |
| 254 | } |
| 255 | } |
| 256 | } |