1/*
2 * Copyright (C) 2013 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 android.app.backup;
18
19import android.app.backup.BackupDataInput;
20import android.app.backup.BackupDataOutput;
21import android.content.res.AssetFileDescriptor;
22import android.content.res.AssetManager;
23import android.os.Bundle;
24import android.os.Environment;
25import android.os.ParcelFileDescriptor;
26import android.test.AndroidTestCase;
27import android.test.InstrumentationTestCase;
28import android.util.Base64;
29import android.util.Log;
30import org.json.JSONObject;
31
32import java.io.BufferedReader;
33import java.io.File;
34import java.io.FileInputStream;
35import java.io.FileNotFoundException;
36import java.io.FileOutputStream;
37import java.io.FileReader;
38import java.io.IOException;
39import java.io.InputStream;
40import java.io.InputStreamReader;
41import java.lang.Exception;
42import java.nio.ByteBuffer;
43
44public class BackupDataTest extends AndroidTestCase {
45    private static final String KEY1 = "key1";
46    private static final String KEY2 = "key2a";
47    private static final String KEY3 = "key3bc";
48    private static final String KEY4 = "key4dad";  // variable key lengths to test padding
49    private static final String[] KEYS = {KEY1, KEY2, KEY3, KEY4};
50
51    private static final String DATA1 = "abcdef";
52    private static final String DATA2 = "abcdefg";
53    private static final String DATA3 = "abcdefgh";
54    private static final String DATA4 = "abcdeffhi"; //variable data lengths to test padding
55    private static final String[] DATA = {DATA1, DATA2, DATA3, DATA4};
56    private static final String TAG = "BackupDataTest";
57
58    private File mFile;
59    private ParcelFileDescriptor mDataFile;
60    private File mDirectory;
61    private Bundle mStatusBundle;
62    private AssetManager mAssets;
63
64    @Override
65    protected void setUp() throws Exception {
66        super.setUp();
67        mDirectory = new File(Environment.getExternalStorageDirectory(), "test_data");
68        mDirectory.mkdirs();
69        mAssets = mContext.getAssets();
70    }
71
72    @Override
73    protected void tearDown() throws Exception {
74        super.tearDown();
75        if (mDataFile != null) {
76            mDataFile.close();
77        }
78    }
79
80    public void testSingle() throws IOException {
81        mFile = new File(mDirectory, "backup_mixed_sinlge.dat");
82        openForWriting();
83        BackupDataOutput bdo = new BackupDataOutput(mDataFile.getFileDescriptor());
84
85        writeEntity(bdo, KEY1, DATA1.getBytes());
86
87        mDataFile.close();
88        openForReading();
89
90        BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor());
91        int count = 0;
92        while (bdi.readNextHeader()) {
93            readAndVerifyEntity(bdi, KEY1, DATA1.getBytes());
94            count++;
95        }
96        assertEquals("only one entity in this stream", 1, count);
97    }
98
99    public void testMultiple() throws IOException {
100        mFile = new File(mDirectory, "backup_multiple_test.dat");
101        openForWriting();
102        BackupDataOutput bdo = new BackupDataOutput(mDataFile.getFileDescriptor());
103
104        for(int i = 0; i < KEYS.length; i++) {
105            writeEntity(bdo, KEYS[i], DATA[i].getBytes());
106        }
107
108        mDataFile.close();
109        openForReading();
110
111        BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor());
112        int count = 0;
113        while (bdi.readNextHeader()) {
114            readAndVerifyEntity(bdi, KEYS[count], DATA[count].getBytes());
115            count++;
116        }
117        assertEquals("four entities in this stream", KEYS.length, count);
118    }
119
120    public void testDelete() throws IOException {
121        mFile = new File(mDirectory, "backup_delete_test.dat");
122        openForWriting();
123        BackupDataOutput bdo = new BackupDataOutput(mDataFile.getFileDescriptor());
124
125        for(int i = 0; i < KEYS.length; i++) {
126            deleteEntity(bdo, KEYS[i]);
127        }
128
129        mDataFile.close();
130        openForReading();
131
132        BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor());
133        int count = 0;
134        while (bdi.readNextHeader()) {
135            readAndVerifyDeletedEntity(bdi, KEYS[count]);
136            count++;
137        }
138        assertEquals("four deletes in this stream", KEYS.length, count);
139    }
140
141    public void testMixed() throws IOException {
142        mFile = new File(mDirectory, "backup_mixed_test.dat");
143        openForWriting();
144
145        BackupDataOutput bdo = new BackupDataOutput(mDataFile.getFileDescriptor());
146
147        int i = 0;
148        deleteEntity(bdo, KEYS[i]); i++;
149        writeEntity(bdo, KEYS[i], DATA[i].getBytes()); i++;
150        writeEntity(bdo, KEYS[i], DATA[i].getBytes()); i++;
151        deleteEntity(bdo, KEYS[i]); i++;
152
153        mDataFile.close();
154        openForReading();
155
156        BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor());
157        int out = 0;
158        assertTrue(bdi.readNextHeader());
159        readAndVerifyDeletedEntity(bdi, KEYS[out]); out++;
160        assertTrue(bdi.readNextHeader());
161        readAndVerifyEntity(bdi, KEYS[out], DATA[out].getBytes()); out++;
162        assertTrue(bdi.readNextHeader());
163        readAndVerifyEntity(bdi, KEYS[out], DATA[out].getBytes()); out++;
164        assertTrue(bdi.readNextHeader());
165        readAndVerifyDeletedEntity(bdi, KEYS[out]); out++;
166        assertFalse("four items in this stream",
167                bdi.readNextHeader());
168    }
169
170    public void testReadMockData() throws IOException {
171        copyAssetToFile("backup_mock.dat", "backup_read_mock_test.dat");
172
173        openForReading();
174        BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor());
175        BufferedReader truth = new BufferedReader(new InputStreamReader(
176                mAssets.openFd("backup_mock.gld").createInputStream()));
177        while( bdi.readNextHeader()) {
178            String[] expected = truth.readLine().split(":");
179            byte[] expectedBytes = null;
180            if (expected.length > 1) {
181                expectedBytes = Base64.decode(expected[1], Base64.DEFAULT);
182            }
183            String key = bdi.getKey();
184            int dataSize = bdi.getDataSize();
185
186            assertEquals("wrong key", expected[0], key);
187            assertEquals("wrong length for key " + key,
188                    (expectedBytes == null ? -1: expectedBytes.length), dataSize);
189            if (dataSize != -1) {
190                byte[] buffer = new byte[dataSize];
191                bdi.readEntityData(buffer, 0, dataSize);
192                assertEquals("wrong data for key " + key, expected[1],
193                        Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
194            }
195        }
196        assertNull("there are unused entries in the golden file", truth.readLine());
197    }
198
199    public void testReadRealData() throws IOException {
200        copyAssetToFile("backup_real.dat", "backup_read_real_test.dat");
201
202        openForReading();
203        BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor());
204        BufferedReader truth = new BufferedReader(new InputStreamReader(
205                mAssets.openFd("backup_real.gld").createInputStream()));
206
207        while(bdi.readNextHeader()) {
208            String[] expected = truth.readLine().split(":");
209            byte[] expectedBytes = null;
210            if (expected.length > 1) {
211                expectedBytes = Base64.decode(expected[1], Base64.DEFAULT);
212            }
213            String key = bdi.getKey();
214            int dataSize = bdi.getDataSize();
215
216            assertEquals("wrong key", expected[0], key);
217            assertEquals("wrong length for key " + key,
218                    (expectedBytes == null ? -1: expectedBytes.length), dataSize);
219            if (dataSize != -1) {
220                byte[] buffer = new byte[dataSize];
221                bdi.readEntityData(buffer, 0, dataSize);
222                assertEquals("wrong data for key " + key, expected[1],
223                        Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
224            }
225        }
226        assertNull("there are unused entries in the golden file", truth.readLine());
227    }
228
229    private void copyAssetToFile(String source, String destination) throws IOException {
230        mFile = new File(mDirectory, destination);
231        openForWriting();
232        FileInputStream fileInputStream = mAssets.openFd(source).createInputStream();
233        FileOutputStream fileOutputStream = new FileOutputStream(mDataFile.getFileDescriptor());
234        byte[] copybuffer = new byte[1024];
235        int numBytes = fileInputStream.read(copybuffer);
236        fileOutputStream.write(copybuffer, 0, numBytes);
237        fileOutputStream.close();
238    }
239
240    private void openForWriting() throws FileNotFoundException {
241        mDataFile = ParcelFileDescriptor.open(mFile,
242                ParcelFileDescriptor.MODE_WRITE_ONLY |
243                        ParcelFileDescriptor.MODE_CREATE |
244                        ParcelFileDescriptor.MODE_TRUNCATE);  // Make an empty file if necessary
245    }
246
247    private void openForReading() throws FileNotFoundException {
248        mDataFile = ParcelFileDescriptor.open(mFile,
249                ParcelFileDescriptor.MODE_READ_ONLY |
250                        ParcelFileDescriptor.MODE_CREATE);  // Make an empty file if necessary
251    }
252
253    private void writeEntity(BackupDataOutput bdo, String key, byte[] data) throws IOException {
254        int status = bdo.writeEntityHeader(key, data.length);
255        // documentation says "number of bytes written" but that's not what we get:
256        assertEquals(0, status);
257
258        status = bdo.writeEntityData(data, data.length);
259        // documentation says "number of bytes written" but that's not what we get:
260        assertEquals(0, status);
261    }
262
263    private void deleteEntity(BackupDataOutput bdo, String key) throws IOException {
264        int status = bdo.writeEntityHeader(key, -1);
265        // documentation says "number of bytes written" but that's not what we get:
266        assertEquals(0, status);
267    }
268
269    private void readAndVerifyEntity(BackupDataInput bdi, String expectedKey, byte[] expectedData)
270            throws IOException {
271        assertEquals("Key mismatch",
272                expectedKey, bdi.getKey());
273        assertEquals("data size mismatch",
274                expectedData.length, bdi.getDataSize());
275        byte[] data = new byte[bdi.getDataSize()];
276        bdi.readEntityData(data, 0, bdi.getDataSize());
277        assertEquals("payload size is wrong",
278                expectedData.length, data.length);
279        for (int i = 0; i < data.length; i++) {
280            assertEquals("payload mismatch",
281                    expectedData[i], data[i]);
282        }
283    }
284    private void readAndVerifyDeletedEntity(BackupDataInput bdi, String expectedKey)
285            throws IOException {
286        assertEquals("Key mismatch",
287                expectedKey, bdi.getKey());
288        assertEquals("deletion mis-reported",
289                -1, bdi.getDataSize());
290    }
291}
292