Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bf73e66d authored by Chalard Jean's avatar Chalard Jean
Browse files

[MS08] Read back attributes and blobs.

Test: New tests in IpMemoryStore
Bug: 113554482

Change-Id: I2ddfef0c2ed37459c038f75d1dfc92fdefbf58f5
parent 91549b6d
Loading
Loading
Loading
Loading
+96 −2
Original line number Diff line number Diff line
@@ -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;
    }
}
+50 −4
Original line number Diff line number Diff line
@@ -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
            }
        });
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -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
+188 −22
Original line number Diff line number Diff line
@@ -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(),
        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 {
            latch.await(5000, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            fail("Did not complete storing attributes");
            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();
                })));

        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);
        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();
                }));
        try {
            latch.await(5000, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            fail("Did not complete storing private data");
        }
                        })));

        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