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