Merge changes I2ddfef0c,I49bee0c9
* changes: [MS08] Read back attributes and blobs. [MS07] Implement storeNetworkAttributes and storeBlob.
This commit is contained in:
@@ -18,6 +18,8 @@ package android.net.ipmemorystore;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* A parcelable status representing the result of an operation.
|
||||
* Parcels as StatusParceled.
|
||||
@@ -26,7 +28,10 @@ import android.annotation.NonNull;
|
||||
public class Status {
|
||||
public static final int SUCCESS = 0;
|
||||
|
||||
public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -1;
|
||||
public static final int ERROR_GENERIC = -1;
|
||||
public static final int ERROR_ILLEGAL_ARGUMENT = -2;
|
||||
public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -3;
|
||||
public static final int ERROR_STORAGE = -4;
|
||||
|
||||
public final int resultCode;
|
||||
|
||||
@@ -34,7 +39,8 @@ public class Status {
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
Status(@NonNull final StatusParcelable parcelable) {
|
||||
@VisibleForTesting
|
||||
public Status(@NonNull final StatusParcelable parcelable) {
|
||||
this(parcelable.resultCode);
|
||||
}
|
||||
|
||||
@@ -55,7 +61,12 @@ public class Status {
|
||||
public String toString() {
|
||||
switch (resultCode) {
|
||||
case SUCCESS: return "SUCCESS";
|
||||
case ERROR_GENERIC: return "GENERIC ERROR";
|
||||
case ERROR_ILLEGAL_ARGUMENT: return "ILLEGAL ARGUMENT";
|
||||
case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED";
|
||||
// "DB storage error" is not very helpful but SQLite does not provide specific error
|
||||
// codes upon store failure. Thus this indicates SQLite returned some error upon store
|
||||
case ERROR_STORAGE: return "DATABASE STORAGE ERROR";
|
||||
default: return "Unknown value ?!";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,25 @@
|
||||
package android.net.ipmemorystore;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
/** {@hide} */
|
||||
public class Utils {
|
||||
/** Pretty print */
|
||||
public static String blobToString(final Blob blob) {
|
||||
final StringBuilder sb = new StringBuilder("Blob : [");
|
||||
if (blob.data.length <= 24) {
|
||||
appendByteArray(sb, blob.data, 0, blob.data.length);
|
||||
public static String blobToString(@Nullable final Blob blob) {
|
||||
return "Blob : " + byteArrayToString(null == blob ? null : blob.data);
|
||||
}
|
||||
|
||||
/** Pretty print */
|
||||
public static String byteArrayToString(@Nullable final byte[] data) {
|
||||
if (null == data) return "null";
|
||||
final StringBuilder sb = new StringBuilder("[");
|
||||
if (data.length <= 24) {
|
||||
appendByteArray(sb, data, 0, data.length);
|
||||
} else {
|
||||
appendByteArray(sb, blob.data, 0, 16);
|
||||
appendByteArray(sb, data, 0, 16);
|
||||
sb.append("...");
|
||||
appendByteArray(sb, blob.data, blob.data.length - 8, blob.data.length);
|
||||
appendByteArray(sb, data, data.length - 8, data.length);
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
|
||||
@@ -17,9 +17,24 @@
|
||||
package com.android.server.net.ipmemorystore;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.NetworkUtils;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Encapsulating class for using the SQLite database backing the memory store.
|
||||
@@ -30,6 +45,8 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||
* @hide
|
||||
*/
|
||||
public class IpMemoryStoreDatabase {
|
||||
private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Contract class for the Network Attributes table.
|
||||
*/
|
||||
@@ -57,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 + " ("
|
||||
@@ -108,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) {
|
||||
@@ -140,4 +157,216 @@ public class IpMemoryStoreDatabase {
|
||||
onCreate(db);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static byte[] encodeAddressList(@NonNull final List<InetAddress> addresses) {
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
for (final InetAddress address : addresses) {
|
||||
final byte[] b = address.getAddress();
|
||||
os.write(b.length);
|
||||
os.write(b, 0, b.length);
|
||||
}
|
||||
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
|
||||
private static ContentValues toContentValues(@NonNull final String key,
|
||||
@Nullable final NetworkAttributes attributes, final long expiry) {
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(NetworkAttributesContract.COLNAME_L2KEY, key);
|
||||
values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry);
|
||||
if (null != attributes) {
|
||||
if (null != attributes.assignedV4Address) {
|
||||
values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
|
||||
NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
|
||||
}
|
||||
if (null != attributes.groupHint) {
|
||||
values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
|
||||
}
|
||||
if (null != attributes.dnsAddresses) {
|
||||
values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
|
||||
encodeAddressList(attributes.dnsAddresses));
|
||||
}
|
||||
if (null != attributes.mtu) {
|
||||
values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
// Convert a byte array into content values to store it in a table compliant with the
|
||||
// contract defined in PrivateDataContract.
|
||||
@NonNull
|
||||
private static ContentValues toContentValues(@NonNull final String key,
|
||||
@NonNull final String clientId, @NonNull final String name,
|
||||
@NonNull final byte[] data) {
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(PrivateDataContract.COLNAME_L2KEY, key);
|
||||
values.put(PrivateDataContract.COLNAME_CLIENT, clientId);
|
||||
values.put(PrivateDataContract.COLNAME_DATANAME, name);
|
||||
values.put(PrivateDataContract.COLNAME_DATA, data);
|
||||
return values;
|
||||
}
|
||||
|
||||
private static final String[] EXPIRY_COLUMN = new String[] {
|
||||
NetworkAttributesContract.COLNAME_EXPIRYDATE
|
||||
};
|
||||
static final int EXPIRY_ERROR = -1; // Legal values for expiry are positive
|
||||
|
||||
static final String SELECT_L2KEY = NetworkAttributesContract.COLNAME_L2KEY + " = ?";
|
||||
|
||||
// Returns the expiry date of the specified row, or one of the error codes above if the
|
||||
// row is not found or some other error
|
||||
static long getExpiry(@NonNull final SQLiteDatabase db, @NonNull final String key) {
|
||||
final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
|
||||
EXPIRY_COLUMN, // columns
|
||||
SELECT_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 EXPIRY_ERROR;
|
||||
cursor.moveToFirst();
|
||||
return cursor.getLong(0); // index in the EXPIRY_COLUMN array
|
||||
}
|
||||
|
||||
static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive
|
||||
|
||||
// Returns the relevance of the specified row, or one of the error codes above if the
|
||||
// row is not found or some other error
|
||||
static int getRelevance(@NonNull final SQLiteDatabase db, @NonNull final String key) {
|
||||
final long expiry = getExpiry(db, key);
|
||||
return expiry < 0 ? (int) expiry : RelevanceUtils.computeRelevanceForNow(expiry);
|
||||
}
|
||||
|
||||
// If the attributes are null, this will only write the expiry.
|
||||
// Returns an int out of Status.{SUCCESS,ERROR_*}
|
||||
static int storeNetworkAttributes(@NonNull final SQLiteDatabase db, @NonNull final String key,
|
||||
final long expiry, @Nullable final NetworkAttributes attributes) {
|
||||
final ContentValues cv = toContentValues(key, attributes, expiry);
|
||||
db.beginTransaction();
|
||||
try {
|
||||
// Unfortunately SQLite does not have any way to do INSERT OR UPDATE. Options are
|
||||
// to either insert with on conflict ignore then update (like done here), or to
|
||||
// construct a custom SQL INSERT statement with nested select.
|
||||
final long resultId = db.insertWithOnConflict(NetworkAttributesContract.TABLENAME,
|
||||
null, cv, SQLiteDatabase.CONFLICT_IGNORE);
|
||||
if (resultId < 0) {
|
||||
db.update(NetworkAttributesContract.TABLENAME, cv, SELECT_L2KEY, new String[]{key});
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
return Status.SUCCESS;
|
||||
} catch (SQLiteException e) {
|
||||
// No space left on disk or something
|
||||
Log.e(TAG, "Could not write to the memory store", e);
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
return Status.ERROR_STORAGE;
|
||||
}
|
||||
|
||||
// Returns an int out of Status.{SUCCESS,ERROR_*}
|
||||
static int storeBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
|
||||
@NonNull final String clientId, @NonNull final String name,
|
||||
@NonNull final byte[] data) {
|
||||
final long res = db.insertWithOnConflict(PrivateDataContract.TABLENAME, null,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
|
||||
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;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
@@ -28,7 +35,12 @@ import android.net.ipmemorystore.IOnL2KeyResponseListener;
|
||||
import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
|
||||
import android.net.ipmemorystore.IOnSameNetworkResponseListener;
|
||||
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.net.ipmemorystore.Utils;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -45,6 +57,7 @@ import java.util.concurrent.Executors;
|
||||
public class IpMemoryStoreService extends IIpMemoryStore.Stub {
|
||||
private static final String TAG = IpMemoryStoreService.class.getSimpleName();
|
||||
private static final int MAX_CONCURRENT_THREADS = 4;
|
||||
private static final boolean DBG = true;
|
||||
|
||||
@NonNull
|
||||
final Context mContext;
|
||||
@@ -114,6 +127,11 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub {
|
||||
if (mDb != null) mDb.close();
|
||||
}
|
||||
|
||||
/** Helper function to make a status object */
|
||||
private StatusParcelable makeStatus(final int code) {
|
||||
return new Status(code).toParcelable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store network attributes for a given L2 key.
|
||||
*
|
||||
@@ -128,11 +146,27 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub {
|
||||
* Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
|
||||
* If the call failed, the L2 key will be null.
|
||||
*/
|
||||
// Note that while l2Key and attributes are non-null in spirit, they are received from
|
||||
// another process. If the remote process decides to ignore everything and send null, this
|
||||
// process should still not crash.
|
||||
@Override
|
||||
public void storeNetworkAttributes(@NonNull final String l2Key,
|
||||
@NonNull final NetworkAttributesParcelable attributes,
|
||||
public void storeNetworkAttributes(@Nullable final String l2Key,
|
||||
@Nullable final NetworkAttributesParcelable attributes,
|
||||
@Nullable final IOnStatusListener listener) {
|
||||
// TODO : implement this.
|
||||
// Because the parcelable is 100% mutable, the thread may not see its members initialized.
|
||||
// Therefore either an immutable object is created on this same thread before it's passed
|
||||
// to the executor, or there need to be a write barrier here and a read barrier in the
|
||||
// remote thread.
|
||||
final NetworkAttributes na = null == attributes ? null : new NetworkAttributes(attributes);
|
||||
mExecutor.execute(() -> {
|
||||
try {
|
||||
final int code = storeNetworkAttributesAndBlobSync(l2Key, na,
|
||||
null /* clientId */, null /* name */, null /* data */);
|
||||
if (null != listener) listener.onComplete(makeStatus(code));
|
||||
} catch (final RemoteException e) {
|
||||
// Client at the other end died
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,16 +175,63 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub {
|
||||
* @param l2Key The L2 key for this network.
|
||||
* @param clientId The ID of the client.
|
||||
* @param name The name of this data.
|
||||
* @param data The data to store.
|
||||
* @param blob The data to store.
|
||||
* @param listener The listener that will be invoked to return the answer, or null if the
|
||||
* is not interested in learning about success/failure.
|
||||
* Through the listener, returns a status to indicate success or failure.
|
||||
*/
|
||||
@Override
|
||||
public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
|
||||
@NonNull final String name, @NonNull final Blob data,
|
||||
public void storeBlob(@Nullable final String l2Key, @Nullable final String clientId,
|
||||
@Nullable final String name, @Nullable final Blob blob,
|
||||
@Nullable final IOnStatusListener listener) {
|
||||
// TODO : implement this.
|
||||
final byte[] data = null == blob ? null : blob.data;
|
||||
mExecutor.execute(() -> {
|
||||
try {
|
||||
final int code = storeNetworkAttributesAndBlobSync(l2Key,
|
||||
null /* NetworkAttributes */, clientId, name, data);
|
||||
if (null != listener) listener.onComplete(makeStatus(code));
|
||||
} catch (final RemoteException e) {
|
||||
// Client at the other end died
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for storeNetworkAttributes and storeBlob.
|
||||
*
|
||||
* Either attributes or none of clientId, name and data may be null. This will write the
|
||||
* passed data if non-null, and will write attributes if non-null, but in any case it will
|
||||
* bump the relevance up.
|
||||
* Returns a success code from Status.
|
||||
*/
|
||||
private int storeNetworkAttributesAndBlobSync(@Nullable final String l2Key,
|
||||
@Nullable final NetworkAttributes attributes,
|
||||
@Nullable final String clientId,
|
||||
@Nullable final String name, @Nullable final byte[] data) {
|
||||
if (null == l2Key) return ERROR_ILLEGAL_ARGUMENT;
|
||||
if (null == attributes && null == data) return ERROR_ILLEGAL_ARGUMENT;
|
||||
if (null != data && (null == clientId || null == name)) return ERROR_ILLEGAL_ARGUMENT;
|
||||
if (null == mDb) return ERROR_DATABASE_CANNOT_BE_OPENED;
|
||||
try {
|
||||
final long oldExpiry = IpMemoryStoreDatabase.getExpiry(mDb, l2Key);
|
||||
final long newExpiry = RelevanceUtils.bumpExpiryDate(
|
||||
oldExpiry == EXPIRY_ERROR ? System.currentTimeMillis() : oldExpiry);
|
||||
final int errorCode =
|
||||
IpMemoryStoreDatabase.storeNetworkAttributes(mDb, l2Key, newExpiry, attributes);
|
||||
// If no blob to store, the client is interested in the result of storing the attributes
|
||||
if (null == data) return errorCode;
|
||||
// Otherwise it's interested in the result of storing the blob
|
||||
return IpMemoryStoreDatabase.storeBlob(mDb, l2Key, clientId, name, data);
|
||||
} catch (Exception e) {
|
||||
if (DBG) {
|
||||
Log.e(TAG, "Exception while storing for key {" + l2Key
|
||||
+ "} ; NetworkAttributes {" + (null == attributes ? "null" : attributes)
|
||||
+ "} ; clientId {" + (null == clientId ? "null" : clientId)
|
||||
+ "} ; name {" + (null == name ? "null" : name)
|
||||
+ "} ; data {" + Utils.byteArrayToString(data) + "}", e);
|
||||
}
|
||||
}
|
||||
return ERROR_GENERIC;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,13 +16,30 @@
|
||||
|
||||
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;
|
||||
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;
|
||||
import android.os.RemoteException;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -30,41 +47,267 @@ 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;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** Unit tests for {@link IpMemoryStoreServiceTest}. */
|
||||
/** Unit tests for {@link IpMemoryStoreService}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class IpMemoryStoreServiceTest {
|
||||
private static final String TEST_CLIENT_ID = "testClientId";
|
||||
private static final String TEST_DATA_NAME = "testData";
|
||||
|
||||
@Mock
|
||||
Context mMockContext;
|
||||
private Context mMockContext;
|
||||
private File mDbFile;
|
||||
|
||||
private IpMemoryStoreService mService;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
doReturn(new File("/tmp/test.db")).when(mMockContext).getDatabasePath(anyString());
|
||||
final Context context = InstrumentationRegistry.getContext();
|
||||
final File dir = context.getFilesDir();
|
||||
mDbFile = new File(dir, "test.db");
|
||||
doReturn(mDbFile).when(mMockContext).getDatabasePath(anyString());
|
||||
mService = new IpMemoryStoreService(mMockContext);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mService.shutdown();
|
||||
mDbFile.delete();
|
||||
}
|
||||
|
||||
/** Helper method to make a vanilla IOnStatusListener */
|
||||
private IOnStatusListener onStatus(Consumer<Status> functor) {
|
||||
return new IOnStatusListener() {
|
||||
@Override
|
||||
public void onComplete(final StatusParcelable statusParcelable) throws RemoteException {
|
||||
functor.accept(new Status(statusParcelable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 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 IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
|
||||
// TODO : implement this
|
||||
final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
|
||||
try {
|
||||
na.setAssignedV4Address(
|
||||
(Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4}));
|
||||
} catch (UnknownHostException e) { /* Can't happen */ }
|
||||
na.setGroupHint("hint1");
|
||||
na.setMtu(219);
|
||||
final String l2Key = UUID.randomUUID().toString();
|
||||
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();
|
||||
})));
|
||||
|
||||
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
|
||||
public void testPrivateData() {
|
||||
final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
|
||||
// TODO : implement this
|
||||
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();
|
||||
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
|
||||
public void testFindL2Key() {
|
||||
final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
|
||||
// TODO : implement this
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsSameNetwork() {
|
||||
final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
|
||||
// TODO : implement this
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user