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

Commit 61e27abf authored by Chalard Jean's avatar Chalard Jean
Browse files

[MS03] Add the contract for the IPMS database.

Test: Some more boilerplate that doesn't exactly need tests as such.
      The important thing is that the database can store and retrieve
      data, not that it creates as specific file or schema.
Bug: 116512211

Change-Id: Ic27e706f15754b34d7bc26626a92d895a15a083d
parent a6fc0b72
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.annotation.NonNull;
public class Status {
    public static final int SUCCESS = 0;

    public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -1;

    public final int resultCode;

    public Status(final int resultCode) {
+143 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.net.ipmemorystore;

import android.annotation.NonNull;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * Encapsulating class for using the SQLite database backing the memory store.
 *
 * This class groups together the contracts and the SQLite helper used to
 * use the database.
 *
 * @hide
 */
public class IpMemoryStoreDatabase {
    /**
     * Contract class for the Network Attributes table.
     */
    public static class NetworkAttributesContract {
        public static final String TABLENAME = "NetworkAttributes";

        public static final String COLNAME_L2KEY = "l2Key";
        public static final String COLTYPE_L2KEY = "TEXT NOT NULL";

        public static final String COLNAME_EXPIRYDATE = "expiryDate";
        // Milliseconds since the Epoch, in true Java style
        public static final String COLTYPE_EXPIRYDATE = "BIGINT";

        public static final String COLNAME_ASSIGNEDV4ADDRESS = "assignedV4Address";
        public static final String COLTYPE_ASSIGNEDV4ADDRESS = "INTEGER";

        // Please note that the group hint is only a *hint*, hence its name. The client can offer
        // this information to nudge the grouping in the decision it thinks is right, but it can't
        // decide for the memory store what is the same L3 network.
        public static final String COLNAME_GROUPHINT = "groupHint";
        public static final String COLTYPE_GROUPHINT = "TEXT";

        public static final String COLNAME_DNSADDRESSES = "dnsAddresses";
        // Stored in marshalled form as is
        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 CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
                + TABLENAME                 + " ("
                + COLNAME_L2KEY             + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, "
                + COLNAME_EXPIRYDATE        + " " + COLTYPE_EXPIRYDATE        + ", "
                + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", "
                + COLNAME_GROUPHINT         + " " + COLTYPE_GROUPHINT         + ", "
                + COLNAME_DNSADDRESSES      + " " + COLTYPE_DNSADDRESSES      + ", "
                + COLNAME_MTU               + " " + COLTYPE_MTU               + ")";
        public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
    }

    /**
     * Contract class for the Private Data table.
     */
    public static class PrivateDataContract {
        public static final String TABLENAME = "PrivateData";

        public static final String COLNAME_L2KEY = "l2Key";
        public static final String COLTYPE_L2KEY = "TEXT NOT NULL";

        public static final String COLNAME_CLIENT = "client";
        public static final String COLTYPE_CLIENT = "TEXT NOT NULL";

        public static final String COLNAME_DATANAME = "dataName";
        public static final String COLTYPE_DATANAME = "TEXT NOT NULL";

        public static final String COLNAME_DATA = "data";
        public static final String COLTYPE_DATA = "BLOB NOT NULL";

        public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
                + TABLENAME        + " ("
                + COLNAME_L2KEY    + " " + COLTYPE_L2KEY    + ", "
                + COLNAME_CLIENT   + " " + COLTYPE_CLIENT   + ", "
                + COLNAME_DATANAME + " " + COLTYPE_DATANAME + ", "
                + COLNAME_DATA     + " " + COLTYPE_DATA     + ", "
                + "PRIMARY KEY ("
                + COLNAME_L2KEY    + ", "
                + COLNAME_CLIENT   + ", "
                + COLNAME_DATANAME + "))";
        public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
    }

    // To save memory when the DB is not used, close it after 30s of inactivity. This is
    // determined manually based on what feels right.
    private static final long IDLE_CONNECTION_TIMEOUT_MS = 30_000;

    /** 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 String DATABASE_FILENAME = "IpMemoryStore.db";

        public DbHelper(@NonNull final Context context) {
            super(context, DATABASE_FILENAME, null, SCHEMA_VERSION);
            setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
        }

        /** Called when the database is created */
        public void onCreate(@NonNull final SQLiteDatabase db) {
            db.execSQL(NetworkAttributesContract.CREATE_TABLE);
            db.execSQL(PrivateDataContract.CREATE_TABLE);
        }

        /** Called when the database is upgraded */
        public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
                final int newVersion) {
            // No upgrade supported yet.
            db.execSQL(NetworkAttributesContract.DROP_TABLE);
            db.execSQL(PrivateDataContract.DROP_TABLE);
            onCreate(db);
        }

        /** Called when the database is downgraded */
        public void onDowngrade(@NonNull final SQLiteDatabase db, final int oldVersion,
                final int newVersion) {
            // Downgrades always nuke all data and recreate an empty table.
            db.execSQL(NetworkAttributesContract.DROP_TABLE);
            db.execSQL(PrivateDataContract.DROP_TABLE);
            onCreate(db);
        }
    }
}
+38 −4
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.server.net.ipmemorystore;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.IIpMemoryStore;
import android.net.ipmemorystore.Blob;
import android.net.ipmemorystore.IOnBlobRetrievedListener;
@@ -27,6 +29,7 @@ import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
import android.net.ipmemorystore.IOnSameNetworkResponseListener;
import android.net.ipmemorystore.IOnStatusListener;
import android.net.ipmemorystore.NetworkAttributesParcelable;
import android.util.Log;

/**
 * Implementation for the IP memory store.
@@ -37,10 +40,41 @@ import android.net.ipmemorystore.NetworkAttributesParcelable;
 * @hide
 */
public class IpMemoryStoreService extends IIpMemoryStore.Stub {
    private static final String TAG = IpMemoryStoreService.class.getSimpleName();

    @NonNull
    final Context mContext;
    @Nullable
    final SQLiteDatabase mDb;

    /**
     * Construct an IpMemoryStoreService object.
     * This constructor will block on disk access to open the database.
     * @param context the context to access storage with.
     */
    public IpMemoryStoreService(@NonNull final Context context) {
        // Note that constructing the service will access the disk and block
        // for some time, but it should make no difference to the clients. Because
        // the interface is one-way, clients fire and forget requests, and the callback
        // will get called eventually in any case, and the framework will wait for the
        // service to be created to deliver subsequent requests.
        // Avoiding this would mean the mDb member can't be final, which means the service would
        // have to test for nullity, care for failure, and allow for a wait at every single access,
        // which would make the code a lot more complex and require all methods to possibly block.
        mContext = context;
        SQLiteDatabase db;
        final IpMemoryStoreDatabase.DbHelper helper = new IpMemoryStoreDatabase.DbHelper(context);
        try {
            db = helper.getWritableDatabase();
            if (null == db) Log.e(TAG, "Unexpected null return of getWriteableDatabase");
        } catch (final SQLException e) {
            Log.e(TAG, "Can't open the Ip Memory Store database", e);
            db = null;
        } catch (final Exception e) {
            Log.wtf(TAG, "Impossible exception Ip Memory Store database", e);
            db = null;
        }
        mDb = db;
    }

    /**
@@ -61,7 +95,7 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub {
    public void storeNetworkAttributes(@NonNull final String l2Key,
            @NonNull final NetworkAttributesParcelable attributes,
            @Nullable final IOnStatusListener listener) {
        // TODO : implement this
        // TODO : implement this.
    }

    /**
@@ -79,7 +113,7 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub {
    public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
            @NonNull final String name, @NonNull final Blob data,
            @Nullable final IOnStatusListener listener) {
        // TODO : implement this
        // TODO : implement this.
    }

    /**
@@ -99,7 +133,7 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub {
    @Override
    public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
            @NonNull final IOnL2KeyResponseListener listener) {
        // TODO : implement this
        // TODO : implement this.
    }

    /**
@@ -114,7 +148,7 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub {
    @Override
    public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
            @NonNull final IOnSameNetworkResponseListener listener) {
        // TODO : implement this
        // TODO : implement this.
    }

    /**
+6 −0
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.server.net.ipmemorystore;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;

import android.content.Context;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -26,6 +29,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.File;

/** Unit tests for {@link IpMemoryStoreServiceTest}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -36,6 +41,7 @@ public class IpMemoryStoreServiceTest {
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        doReturn(new File("/tmp/test.db")).when(mMockContext).getDatabasePath(anyString());
    }

    @Test