diff options
Diffstat (limited to 'src')
3 files changed, 152 insertions, 1 deletions
diff --git a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java index be338e5..0b05a5b 100644 --- a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java +++ b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java @@ -602,6 +602,64 @@ public class IpMemoryStoreDatabase { return bestKey; } + /** + * Delete a single entry by key. + * + * If |needWipe| is true, the data will be wiped from disk immediately. Otherwise, it will + * only be marked deleted, and overwritten by subsequent writes or reclaimed during the next + * maintenance window. + * Note that wiping data is a very expensive operation. This is meant for clients that need + * this data gone from disk immediately for security reasons. Functionally it makes no + * difference at all. + */ + static StatusAndCount delete(@NonNull final SQLiteDatabase db, @NonNull final String l2key, + final boolean needWipe) { + return deleteEntriesWithColumn(db, + NetworkAttributesContract.COLNAME_L2KEY, l2key, needWipe); + } + + /** + * Delete all entries that have a particular cluster value. + * + * If |needWipe| is true, the data will be wiped from disk immediately. Otherwise, it will + * only be marked deleted, and overwritten by subsequent writes or reclaimed during the next + * maintenance window. + * Note that wiping data is a very expensive operation. This is meant for clients that need + * this data gone from disk immediately for security reasons. Functionally it makes no + * difference at all. + */ + static StatusAndCount deleteCluster(@NonNull final SQLiteDatabase db, + @NonNull final String cluster, final boolean needWipe) { + return deleteEntriesWithColumn(db, + NetworkAttributesContract.COLNAME_CLUSTER, cluster, needWipe); + } + + // Delete all entries where the given column has the given value. + private static StatusAndCount deleteEntriesWithColumn(@NonNull final SQLiteDatabase db, + @NonNull final String column, @NonNull final String value, final boolean needWipe) { + db.beginTransaction(); + int deleted = 0; + try { + deleted = db.delete(NetworkAttributesContract.TABLENAME, + column + "= ?", new String[] { value }); + db.setTransactionSuccessful(); + } catch (SQLiteException e) { + Log.e(TAG, "Could not delete from the memory store", e); + // Unclear what might have happened ; deleting records is not supposed to be able + // to fail barring a syntax error in the SQL query. + return new StatusAndCount(Status.ERROR_UNKNOWN, 0); + } finally { + db.endTransaction(); + } + + if (needWipe) { + final int vacuumStatus = vacuum(db); + // This is a problem for the client : return the failure + if (Status.SUCCESS != vacuumStatus) return new StatusAndCount(vacuumStatus, deleted); + } + return new StatusAndCount(Status.SUCCESS, deleted); + } + // Drops all records that are expired. Relevance has decayed to zero of these records. Returns // an int out of Status.{SUCCESS, ERROR_*} static int dropAllExpiredRecords(@NonNull final SQLiteDatabase db) { @@ -708,4 +766,13 @@ public class IpMemoryStoreDatabase { final int columnIndex = cursor.getColumnIndex(columnName); return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue; } + private static int vacuum(@NonNull final SQLiteDatabase db) { + try { + db.execSQL("VACUUM"); + return Status.SUCCESS; + } catch (SQLiteException e) { + // Vacuuming may fail from lack of storage, because it makes a copy of the database. + return Status.ERROR_STORAGE; + } + } } diff --git a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java index cd29e0d..ae9c875 100644 --- a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java +++ b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java @@ -33,6 +33,7 @@ import android.net.ipmemorystore.IOnBlobRetrievedListener; import android.net.ipmemorystore.IOnL2KeyResponseListener; import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener; import android.net.ipmemorystore.IOnSameL3NetworkResponseListener; +import android.net.ipmemorystore.IOnStatusAndCountListener; import android.net.ipmemorystore.IOnStatusListener; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.NetworkAttributesParcelable; @@ -116,7 +117,11 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { // TODO : investigate replacing this scheme with a scheme where each thread has its own // instance of the database, as it may be faster. It is likely however that IpMemoryStore // operations are mostly IO-bound anyway, and additional contention is unlikely to bring - // benefits. Alternatively, a read-write lock might increase throughput. + // benefits. Alternatively, a read-write lock might increase throughput. Also if doing + // this work, care must be taken around the privacy-preserving VACUUM operations as + // VACUUM will fail if there are other open transactions at the same time, and using + // multiple threads will open the possibility of this failure happening, threatening + // the privacy guarantees. mExecutor = Executors.newSingleThreadExecutor(); RegularMaintenanceJobService.schedule(mContext, this); } @@ -405,6 +410,56 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { } /** + * Delete a single entry. + * + * @param l2Key The L2 key of the entry to delete. + * @param needWipe Whether the data must be wiped from disk immediately for security reasons. + * This is very expensive and makes no functional difference ; only pass + * true if security requires this data must be removed from disk immediately. + * @param listener A listener that will be invoked to inform of the completion of this call, + * or null if the client is not interested in learning about success/failure. + * returns (through the listener) A status to indicate success and the number of deleted records + */ + public void delete(@NonNull final String l2Key, final boolean needWipe, + @Nullable final IOnStatusAndCountListener listener) { + mExecutor.execute(() -> { + try { + final StatusAndCount res = IpMemoryStoreDatabase.delete(mDb, l2Key, needWipe); + if (null != listener) listener.onComplete(makeStatus(res.status), res.count); + } catch (final RemoteException e) { + // Client at the other end died + } + }); + } + + /** + * Delete all entries in a cluster. + * + * This method will delete all entries in the memory store that have the cluster attribute + * passed as an argument. + * + * @param cluster The cluster to delete. + * @param needWipe Whether the data must be wiped from disk immediately for security reasons. + * This is very expensive and makes no functional difference ; only pass + * true if security requires this data must be removed from disk immediately. + * @param listener A listener that will be invoked to inform of the completion of this call, + * or null if the client is not interested in learning about success/failure. + * returns (through the listener) A status to indicate success and the number of deleted records + */ + public void deleteCluster(@NonNull final String cluster, final boolean needWipe, + @Nullable final IOnStatusAndCountListener listener) { + mExecutor.execute(() -> { + try { + final StatusAndCount res = + IpMemoryStoreDatabase.deleteCluster(mDb, cluster, needWipe); + if (null != listener) listener.onComplete(makeStatus(res.status), res.count); + } catch (final RemoteException e) { + // Client at the other end died + } + }); + } + + /** * Wipe the data in IpMemoryStore database upon network factory reset. */ @Override diff --git a/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java b/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java new file mode 100644 index 0000000..2cbe843 --- /dev/null +++ b/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 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.connectivity.ipmemorystore; + +/** + * Small data class to wrap a Status and an int. + */ +public class StatusAndCount { + public final int status; + public final int count; + public StatusAndCount(final int status, final int count) { + this.status = status; + this.count = count; + } +} |