diff options
author | 2018-12-27 20:59:41 +0900 | |
---|---|---|
committer | 2019-01-21 15:21:09 +0900 | |
commit | bf73e66d4db7e2cd235017333260ee2fdceea5ee (patch) | |
tree | d1f92c3215c276ce5e4df17f4fd595bf88678eb6 | |
parent | 91549b6d1be1e0d8d0deb9da45050eb76165a39d (diff) |
[MS08] Read back attributes and blobs.
Test: New tests in IpMemoryStore
Bug: 113554482
Change-Id: I2ddfef0c2ed37459c038f75d1dfc92fdefbf58f5
4 files changed, 341 insertions, 28 deletions
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java index 25522763df09..238f07715ce3 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java @@ -29,8 +29,11 @@ import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.Status; import android.util.Log; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.List; /** @@ -71,7 +74,7 @@ public class IpMemoryStoreDatabase { public static final String COLTYPE_DNSADDRESSES = "BLOB"; public static final String COLNAME_MTU = "mtu"; - public static final String COLTYPE_MTU = "INTEGER"; + public static final String COLTYPE_MTU = "INTEGER DEFAULT -1"; public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLENAME + " (" @@ -122,7 +125,7 @@ public class IpMemoryStoreDatabase { /** The SQLite DB helper */ public static class DbHelper extends SQLiteOpenHelper { // Update this whenever changing the schema. - private static final int SCHEMA_VERSION = 1; + private static final int SCHEMA_VERSION = 2; private static final String DATABASE_FILENAME = "IpMemoryStore.db"; public DbHelper(@NonNull final Context context) { @@ -166,6 +169,21 @@ public class IpMemoryStoreDatabase { return os.toByteArray(); } + @NonNull + private static ArrayList<InetAddress> decodeAddressList(@NonNull final byte[] encoded) { + final ByteArrayInputStream is = new ByteArrayInputStream(encoded); + final ArrayList<InetAddress> addresses = new ArrayList<>(); + int d = -1; + while ((d = is.read()) != -1) { + final byte[] bytes = new byte[d]; + is.read(bytes, 0, d); + try { + addresses.add(InetAddress.getByAddress(bytes)); + } catch (UnknownHostException e) { /* Hopefully impossible */ } + } + return addresses; + } + // Convert a NetworkAttributes object to content values to store them in a table compliant // with the contract defined in NetworkAttributesContract. @NonNull @@ -275,4 +293,80 @@ public class IpMemoryStoreDatabase { toContentValues(key, clientId, name, data), SQLiteDatabase.CONFLICT_REPLACE); return (res == -1) ? Status.ERROR_STORAGE : Status.SUCCESS; } + + @Nullable + static NetworkAttributes retrieveNetworkAttributes(@NonNull final SQLiteDatabase db, + @NonNull final String key) { + final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME, + null, // columns, null means everything + NetworkAttributesContract.COLNAME_L2KEY + " = ?", // selection + new String[] { key }, // selectionArgs + null, // groupBy + null, // having + null); // orderBy + // L2KEY is the primary key ; it should not be possible to get more than one + // result here. 0 results means the key was not found. + if (cursor.getCount() != 1) return null; + cursor.moveToFirst(); + + // Make sure the data hasn't expired + final long expiry = cursor.getLong( + cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE)); + if (expiry < System.currentTimeMillis()) return null; + + final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); + final int assignedV4AddressInt = getInt(cursor, + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0); + final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT); + final byte[] dnsAddressesBlob = + getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); + final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1); + if (0 != assignedV4AddressInt) { + builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt)); + } + builder.setGroupHint(groupHint); + if (null != dnsAddressesBlob) { + builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob)); + } + if (mtu >= 0) { + builder.setMtu(mtu); + } + return builder.build(); + } + + private static final String[] DATA_COLUMN = new String[] { + PrivateDataContract.COLNAME_DATA + }; + @Nullable + static byte[] retrieveBlob(@NonNull final SQLiteDatabase db, @NonNull final String key, + @NonNull final String clientId, @NonNull final String name) { + final Cursor cursor = db.query(PrivateDataContract.TABLENAME, + DATA_COLUMN, // columns + PrivateDataContract.COLNAME_L2KEY + " = ? AND " // selection + + PrivateDataContract.COLNAME_CLIENT + " = ? AND " + + PrivateDataContract.COLNAME_DATANAME + " = ?", + new String[] { key, clientId, name }, // selectionArgs + null, // groupBy + null, // having + null); // orderBy + // The query above is querying by (composite) primary key, so it should not be possible to + // get more than one result here. 0 results means the key was not found. + if (cursor.getCount() != 1) return null; + cursor.moveToFirst(); + return cursor.getBlob(0); // index in the DATA_COLUMN array + } + + // Helper methods + static String getString(final Cursor cursor, final String columnName) { + final int columnIndex = cursor.getColumnIndex(columnName); + return (columnIndex >= 0) ? cursor.getString(columnIndex) : null; + } + static byte[] getBlob(final Cursor cursor, final String columnName) { + final int columnIndex = cursor.getColumnIndex(columnName); + return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null; + } + static int getInt(final Cursor cursor, final String columnName, final int defaultValue) { + final int columnIndex = cursor.getColumnIndex(columnName); + return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue; + } } diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java index f002da851287..444b299d49e6 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java @@ -19,6 +19,7 @@ package com.android.server.net.ipmemorystore; import static android.net.ipmemorystore.Status.ERROR_DATABASE_CANNOT_BE_OPENED; import static android.net.ipmemorystore.Status.ERROR_GENERIC; import static android.net.ipmemorystore.Status.ERROR_ILLEGAL_ARGUMENT; +import static android.net.ipmemorystore.Status.SUCCESS; import static com.android.server.net.ipmemorystore.IpMemoryStoreDatabase.EXPIRY_ERROR; @@ -278,9 +279,32 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { * the query. */ @Override - public void retrieveNetworkAttributes(@NonNull final String l2Key, - @NonNull final IOnNetworkAttributesRetrieved listener) { - // TODO : implement this. + public void retrieveNetworkAttributes(@Nullable final String l2Key, + @Nullable final IOnNetworkAttributesRetrieved listener) { + if (null == listener) return; + mExecutor.execute(() -> { + try { + if (null == l2Key) { + listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null); + return; + } + if (null == mDb) { + listener.onL2KeyResponse(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, + null); + return; + } + try { + final NetworkAttributes attributes = + IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key); + listener.onL2KeyResponse(makeStatus(SUCCESS), l2Key, + null == attributes ? null : attributes.toParcelable()); + } catch (final Exception e) { + listener.onL2KeyResponse(makeStatus(ERROR_GENERIC), l2Key, null); + } + } catch (final RemoteException e) { + // Client at the other end died + } + }); } /** @@ -297,6 +321,28 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { @Override public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId, @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) { - // TODO : implement this. + if (null == listener) return; + mExecutor.execute(() -> { + try { + if (null == l2Key) { + listener.onBlobRetrieved(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, name, null); + return; + } + if (null == mDb) { + listener.onBlobRetrieved(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, + name, null); + return; + } + try { + final Blob b = new Blob(); + b.data = IpMemoryStoreDatabase.retrieveBlob(mDb, l2Key, clientId, name); + listener.onBlobRetrieved(makeStatus(SUCCESS), l2Key, name, b); + } catch (final Exception e) { + listener.onBlobRetrieved(makeStatus(ERROR_GENERIC), l2Key, name, null); + } + } catch (final RemoteException e) { + // Client at the other end died + } + }); } } diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java index a9f9758bb1f8..1fc67a8212ae 100644 --- a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java +++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java @@ -27,6 +27,7 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.lang.reflect.Modifier; import java.net.Inet4Address; import java.net.InetAddress; import java.util.Arrays; @@ -60,6 +61,12 @@ public class ParcelableTests { builder.setMtu(null); in = builder.build(); assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); + + // Verify that this test does not miss any new field added later. + // If any field is added to NetworkAttributes it must be tested here for parceling + // roundtrip. + assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) + .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); } @Test diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java index 00137f83f52e..94bcd28bf009 100644 --- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java +++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java @@ -16,6 +16,9 @@ package com.android.server.net.ipmemorystore; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; @@ -23,8 +26,11 @@ import static org.mockito.Mockito.doReturn; import android.content.Context; import android.net.ipmemorystore.Blob; +import android.net.ipmemorystore.IOnBlobRetrievedListener; +import android.net.ipmemorystore.IOnNetworkAttributesRetrieved; import android.net.ipmemorystore.IOnStatusListener; import android.net.ipmemorystore.NetworkAttributes; +import android.net.ipmemorystore.NetworkAttributesParcelable; import android.net.ipmemorystore.Status; import android.net.ipmemorystore.StatusParcelable; import android.os.IBinder; @@ -41,8 +47,12 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.File; +import java.lang.reflect.Modifier; import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Arrays; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -92,6 +102,59 @@ public class IpMemoryStoreServiceTest { }; } + /** Helper method to make an IOnBlobRetrievedListener */ + private interface OnBlobRetrievedListener { + void onBlobRetrieved(Status status, String l2Key, String name, byte[] data); + } + private IOnBlobRetrievedListener onBlobRetrieved(final OnBlobRetrievedListener functor) { + return new IOnBlobRetrievedListener() { + @Override + public void onBlobRetrieved(final StatusParcelable statusParcelable, + final String l2Key, final String name, final Blob blob) throws RemoteException { + functor.onBlobRetrieved(new Status(statusParcelable), l2Key, name, + null == blob ? null : blob.data); + } + + @Override + public IBinder asBinder() { + return null; + } + }; + } + + /** Helper method to make an IOnNetworkAttributesRetrievedListener */ + private interface OnNetworkAttributesRetrievedListener { + void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attr); + } + private IOnNetworkAttributesRetrieved onNetworkAttributesRetrieved( + final OnNetworkAttributesRetrievedListener functor) { + return new IOnNetworkAttributesRetrieved() { + @Override + public void onL2KeyResponse(final StatusParcelable status, final String l2Key, + final NetworkAttributesParcelable attributes) + throws RemoteException { + functor.onNetworkAttributesRetrieved(new Status(status), l2Key, + null == attributes ? null : new NetworkAttributes(attributes)); + } + + @Override + public IBinder asBinder() { + return null; + } + }; + } + + // Helper method to factorize some boilerplate + private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) { + final CountDownLatch latch = new CountDownLatch(1); + functor.accept(latch); + try { + latch.await(5000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail(timeoutMessage); + } + } + @Test public void testNetworkAttributes() { final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); @@ -102,18 +165,103 @@ public class IpMemoryStoreServiceTest { na.setGroupHint("hint1"); na.setMtu(219); final String l2Key = UUID.randomUUID().toString(); - final CountDownLatch latch = new CountDownLatch(1); - mService.storeNetworkAttributes(l2Key, na.build().toParcelable(), - onStatus(status -> { - assertTrue("Store status not successful : " + status.resultCode, + NetworkAttributes attributes = na.build(); + doLatched("Did not complete storing attributes", latch -> + mService.storeNetworkAttributes(l2Key, attributes.toParcelable(), + onStatus(status -> { + assertTrue("Store status not successful : " + status.resultCode, + status.isSuccess()); + latch.countDown(); + }))); + + doLatched("Did not complete retrieving attributes", latch -> + mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved( + (status, key, attr) -> { + assertTrue("Retrieve network attributes not successful : " + + status.resultCode, status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(attributes, attr); + latch.countDown(); + }))); + + final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder(); + try { + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); + } catch (UnknownHostException e) { /* Still can't happen */ } + final NetworkAttributes attributes2 = na2.build(); + doLatched("Did not complete storing attributes 2", latch -> + mService.storeNetworkAttributes(l2Key, attributes2.toParcelable(), + onStatus(status -> latch.countDown()))); + + doLatched("Did not complete retrieving attributes 2", latch -> + mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved( + (status, key, attr) -> { + assertTrue("Retrieve network attributes not successful : " + + status.resultCode, status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(attributes.assignedV4Address, attr.assignedV4Address); + assertEquals(attributes.groupHint, attr.groupHint); + assertEquals(attributes.mtu, attr.mtu); + assertEquals(attributes2.dnsAddresses, attr.dnsAddresses); + latch.countDown(); + }))); + + doLatched("Did not complete retrieving attributes 3", latch -> + mService.retrieveNetworkAttributes(l2Key + "nonexistent", + onNetworkAttributesRetrieved( + (status, key, attr) -> { + assertTrue("Retrieve network attributes not successful : " + + status.resultCode, status.isSuccess()); + assertEquals(l2Key + "nonexistent", key); + assertNull("Retrieved data not stored", attr); + latch.countDown(); + } + ))); + + // Verify that this test does not miss any new field added later. + // If any field is added to NetworkAttributes it must be tested here for storing + // and retrieving. + assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) + .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); + } + + @Test + public void testInvalidAttributes() { + doLatched("Did not complete storing bad attributes", latch -> + mService.storeNetworkAttributes("key", null, onStatus(status -> { + assertFalse("Success storing on a null key", status.isSuccess()); + assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); latch.countDown(); - })); - try { - latch.await(5000, TimeUnit.SECONDS); - } catch (InterruptedException e) { - fail("Did not complete storing attributes"); - } + }))); + + final NetworkAttributes na = new NetworkAttributes.Builder().setMtu(2).build(); + doLatched("Did not complete storing bad attributes", latch -> + mService.storeNetworkAttributes(null, na.toParcelable(), onStatus(status -> { + assertFalse("Success storing null attributes on a null key", + status.isSuccess()); + assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); + latch.countDown(); + }))); + + doLatched("Did not complete storing bad attributes", latch -> + mService.storeNetworkAttributes(null, null, onStatus(status -> { + assertFalse("Success storing null attributes on a null key", + status.isSuccess()); + assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); + latch.countDown(); + }))); + + doLatched("Did not complete retrieving bad attributes", latch -> + mService.retrieveNetworkAttributes(null, onNetworkAttributesRetrieved( + (status, key, attr) -> { + assertFalse("Success retrieving attributes for a null key", + status.isSuccess()); + assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); + assertNull(key); + assertNull(attr); + }))); } @Test @@ -121,18 +269,36 @@ public class IpMemoryStoreServiceTest { final Blob b = new Blob(); b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 }; final String l2Key = UUID.randomUUID().toString(); - final CountDownLatch latch = new CountDownLatch(1); - mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b, - onStatus(status -> { - assertTrue("Store status not successful : " + status.resultCode, - status.isSuccess()); - latch.countDown(); - })); - try { - latch.await(5000, TimeUnit.SECONDS); - } catch (InterruptedException e) { - fail("Did not complete storing private data"); - } + doLatched("Did not complete storing private data", latch -> + mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b, + onStatus(status -> { + assertTrue("Store status not successful : " + status.resultCode, + status.isSuccess()); + latch.countDown(); + }))); + + doLatched("Did not complete retrieving private data", latch -> + mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved( + (status, key, name, data) -> { + assertTrue("Retrieve blob status not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(name, TEST_DATA_NAME); + Arrays.equals(b.data, data); + latch.countDown(); + }))); + + // Most puzzling error message ever + doLatched("Did not complete retrieving nothing", latch -> + mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME + "2", onBlobRetrieved( + (status, key, name, data) -> { + assertTrue("Retrieve blob status not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(name, TEST_DATA_NAME + "2"); + assertNull(data); + latch.countDown(); + }))); } @Test |