blob: c69934aceb754c9fbd488eb470506ad6a15e1e4c [file] [log] [blame]
/*
* Copyright (C) 2017 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.watchlist;
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.SQLiteOpenHelper;
import android.os.Environment;
import android.util.Pair;
import com.android.internal.util.HexDump;
import java.io.File;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Helper class to process watchlist read / save watchlist reports.
*/
class WatchlistReportDbHelper extends SQLiteOpenHelper {
private static final String TAG = "WatchlistReportDbHelper";
private static final String NAME = "watchlist_report.db";
private static final int VERSION = 2;
private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
private static class WhiteListReportContract {
private static final String TABLE = "records";
private static final String APP_DIGEST = "app_digest";
private static final String CNC_DOMAIN = "cnc_domain";
private static final String TIMESTAMP = "timestamp";
}
private static final String CREATE_TABLE_MODEL = "CREATE TABLE "
+ WhiteListReportContract.TABLE + "("
+ WhiteListReportContract.APP_DIGEST + " BLOB,"
+ WhiteListReportContract.CNC_DOMAIN + " TEXT,"
+ WhiteListReportContract.TIMESTAMP + " INTEGER DEFAULT 0" + " )";
private static final int INDEX_DIGEST = 0;
private static final int INDEX_CNC_DOMAIN = 1;
private static final int INDEX_TIMESTAMP = 2;
private static final String[] DIGEST_DOMAIN_PROJECTION =
new String[] {
WhiteListReportContract.APP_DIGEST,
WhiteListReportContract.CNC_DOMAIN
};
private static WatchlistReportDbHelper sInstance;
/**
* Aggregated watchlist records.
*/
public static class AggregatedResult {
// A list of digests that visited c&c domain or ip before.
final Set<String> appDigestList;
// The c&c domain or ip visited before.
@Nullable final String cncDomainVisited;
// A list of app digests and c&c domain visited.
final HashMap<String, String> appDigestCNCList;
public AggregatedResult(Set<String> appDigestList, String cncDomainVisited,
HashMap<String, String> appDigestCNCList) {
this.appDigestList = appDigestList;
this.cncDomainVisited = cncDomainVisited;
this.appDigestCNCList = appDigestCNCList;
}
}
static File getSystemWatchlistDbFile() {
return new File(Environment.getDataSystemDirectory(), NAME);
}
private WatchlistReportDbHelper(Context context) {
super(context, getSystemWatchlistDbFile().getAbsolutePath(), null, VERSION);
// Memory optimization - close idle connections after 30s of inactivity
setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
}
public static synchronized WatchlistReportDbHelper getInstance(Context context) {
if (sInstance != null) {
return sInstance;
}
sInstance = new WatchlistReportDbHelper(context);
return sInstance;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_MODEL);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO: For now, drop older tables and recreate new ones.
db.execSQL("DROP TABLE IF EXISTS " + WhiteListReportContract.TABLE);
onCreate(db);
}
/**
* Insert new watchlist record.
*
* @param appDigest The digest of an app.
* @param cncDomain C&C domain that app visited.
* @return True if success.
*/
public boolean insertNewRecord(byte[] appDigest, String cncDomain,
long timestamp) {
final SQLiteDatabase db = getWritableDatabase();
final ContentValues values = new ContentValues();
values.put(WhiteListReportContract.APP_DIGEST, appDigest);
values.put(WhiteListReportContract.CNC_DOMAIN, cncDomain);
values.put(WhiteListReportContract.TIMESTAMP, timestamp);
return db.insert(WhiteListReportContract.TABLE, null, values) != -1;
}
/**
* Aggregate all records in database before input timestamp, and return a
* rappor encoded result.
*/
@Nullable
public AggregatedResult getAggregatedRecords(long untilTimestamp) {
final String selectStatement = WhiteListReportContract.TIMESTAMP + " < ?";
final SQLiteDatabase db = getReadableDatabase();
Cursor c = null;
try {
c = db.query(true /* distinct */,
WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement,
new String[]{Long.toString(untilTimestamp)}, null, null,
null, null);
if (c == null) {
return null;
}
final HashSet<String> appDigestList = new HashSet<>();
final HashMap<String, String> appDigestCNCList = new HashMap<>();
String cncDomainVisited = null;
while (c.moveToNext()) {
// We use hex string here as byte[] cannot be a key in HashMap.
String digestHexStr = HexDump.toHexString(c.getBlob(INDEX_DIGEST));
String cncDomain = c.getString(INDEX_CNC_DOMAIN);
appDigestList.add(digestHexStr);
if (cncDomainVisited != null) {
cncDomainVisited = cncDomain;
}
appDigestCNCList.put(digestHexStr, cncDomain);
}
return new AggregatedResult(appDigestList, cncDomainVisited, appDigestCNCList);
} finally {
if (c != null) {
c.close();
}
}
}
/**
* Remove all the records before input timestamp.
*
* @return True if success.
*/
public boolean cleanup(long untilTimestamp) {
final SQLiteDatabase db = getWritableDatabase();
final String clause = WhiteListReportContract.TIMESTAMP + "< " + untilTimestamp;
return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
}
}