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