blob: f48463f5ae63343d12fe47afc4cee3e53af429d0 [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.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Pair;
import com.android.internal.util.HexDump;
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.
Set<String> appDigestList;
// The c&c domain or ip visited before.
String cncDomainVisited;
// A list of app digests and c&c domain visited.
HashMap<String, String> appDigestCNCList;
}
private WatchlistReportDbHelper(Context context) {
super(context, WatchlistSettings.getSystemWatchlistFile(NAME).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 the records in database, and return a rappor encoded result.
*/
public AggregatedResult getAggregatedRecords() {
final long twoDaysBefore = getTwoDaysBeforeTimestamp();
final long yesterday = getYesterdayTimestamp();
final String selectStatement = WhiteListReportContract.TIMESTAMP + " >= ? AND " +
WhiteListReportContract.TIMESTAMP + " <= ?";
final SQLiteDatabase db = getReadableDatabase();
Cursor c = null;
try {
c = db.query(true /* distinct */,
WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement,
new String[]{"" + twoDaysBefore, "" + yesterday}, null, null,
null, null);
if (c == null || c.getCount() == 0) {
return null;
}
final AggregatedResult result = new AggregatedResult();
result.cncDomainVisited = null;
// After aggregation, each digest maximum will have only 1 record.
result.appDigestList = new HashSet<>();
result.appDigestCNCList = new HashMap<>();
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);
result.appDigestList.add(digestHexStr);
if (result.cncDomainVisited != null) {
result.cncDomainVisited = cncDomain;
}
result.appDigestCNCList.put(digestHexStr, cncDomain);
}
return result;
} finally {
if (c != null) {
c.close();
}
}
}
/**
* Remove all the records before yesterday.
*
* @return True if success.
*/
public boolean cleanup() {
final SQLiteDatabase db = getWritableDatabase();
final long twoDaysBefore = getTwoDaysBeforeTimestamp();
final String clause = WhiteListReportContract.TIMESTAMP + "< " + twoDaysBefore;
return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
}
static long getTwoDaysBeforeTimestamp() {
return getMidnightTimestamp(2);
}
static long getYesterdayTimestamp() {
return getMidnightTimestamp(1);
}
static long getMidnightTimestamp(int daysBefore) {
java.util.Calendar date = new GregorianCalendar();
// reset hour, minutes, seconds and millis
date.set(java.util.Calendar.HOUR_OF_DAY, 0);
date.set(java.util.Calendar.MINUTE, 0);
date.set(java.util.Calendar.SECOND, 0);
date.set(java.util.Calendar.MILLISECOND, 0);
date.add(java.util.Calendar.DAY_OF_MONTH, -daysBefore);
return date.getTimeInMillis();
}
}