/* Android IMSI-Catcher Detector | (c) AIMSICD Privacy Project
* -----------------------------------------------------------
* LICENSE: http://git.io/vki47 | TERMS: http://git.io/vki4o
* -----------------------------------------------------------
*/
package com.secupwn.aimsicd.service;
import android.content.Context;
import com.secupwn.aimsicd.utils.RealmHelper;
import java.util.HashMap;
import io.freefair.android.util.logging.AndroidLogger;
import io.freefair.android.util.logging.Logger;
import io.realm.Realm;
import lombok.Cleanup;
/**
* Description: Class that calculates cell signal strength averages and decides if a
* given cell + strength appears to be mysteriously (low or high).
* Signal strengths are shown in units of:
*
* See: http://wiki.opencellid.org/wiki/API#Filtering_of_data
*
* GSM RSSI in dBm in the range of [-51 to -113] or ASU in the range of [0 to 31]
* UMTS RSCP in dBm in the range of [-25 to -121] or ASU in the range of [-5 to 91]
* LTE RSRP in dBm in the range of [-45 to -137] or ASU in the range of [0 to 95]
* CDMA RSSI in dBm in the range of [-75 to -100] or ASU in the range of [1 to 16]
*
* Detection Flowchart:
* https://cloud.githubusercontent.com/assets/2507905/4428863/c85c8366-45d4-11e4-89da-c650cdb56caf.jpg
*
*
* Dependency:
* AIMSICDDbAdapter: getAverageSignalStrength etc..
*
* Issues:
*
* [ ] Need to add RAT detection for each signal, as to above. Since we could have different
* signal due to different RAT, for the same cell (LAC/CID). (@He3556 please confirm.)
* This means that the SQL query will be a little more complicated.
* @author Tor Henning Ueland
*/
public class SignalStrengthTracker {
private final Logger log = AndroidLogger.forClass(SignalStrengthTracker.class);
private static int sleepTimeBetweenSignalRegistration = 60; // [seconds]
private static int minimumIdleTime = 30; // [seconds]
private static int maximumNumberOfDaysSaved = 60; // [days] = 2 months
private static int mysteriousSignalDifference = 10; // [dBm] or [ASU]?
private static int sleepTimeBetweenCleanup = 3600; // [seconds] Once per hour
private Long lastRegistrationTime; // Timestamp for last registration to DB
private Long lastCleanupTime; // Timestamp for last cleanup of DB
private HashMap<Integer, Integer> averageSignalCache = new HashMap<>();
private long lastMovementDetected = 0l; // ??
private RealmHelper mDbHelper;
public SignalStrengthTracker(Context context) {
lastMovementDetected = System.currentTimeMillis();
lastRegistrationTime = System.currentTimeMillis();
lastCleanupTime = System.currentTimeMillis();
mDbHelper = new RealmHelper(context);
}
/**
* Registers a new cell signal strength for future calculation, only values older
* than $sleepTimeBetweenSignalRegistration seconds since last registration, is
* saved for processing.
*
* @param cellID
* @param signalStrength
*/
public void registerSignalStrength(int cellID, int signalStrength) {
// Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC.
// "This method shouldn't be used for measuring timeouts or other elapsed time
// measurements, as changing the system time can affect the results.
// Use nanoTime() for that."
// TODO: We probably need to convert this into seconds for easy use in DB
long now = System.currentTimeMillis(); // [ms]
if (deviceIsMoving()) {
log.info("Ignored signal sample for CID: " + cellID +
" due to device movement. Waiting for " + ((minimumIdleTime * 1000) - (now - lastMovementDetected)) + " ms.");
return;
}
if (now - (sleepTimeBetweenSignalRegistration * 1000) > lastRegistrationTime) {
long diff = now - lastRegistrationTime;
log.info("Scheduling signal strength calculation from CID: " + cellID +
" @ " + signalStrength + " dBm. Last registration was " + diff + "ms ago.");
lastRegistrationTime = now;
//mDbHelper.addSignalStrength(cellID, signalStrength, String.valueOf(System.currentTimeMillis()));
}
if (now - (sleepTimeBetweenCleanup * 1000) > lastCleanupTime) {
log.info("Removing old signal strength entries from DB.");
// cleanupOldData();//
// TODO cleanupOldData() need to change query as now time is a string value
// String query = String.format("DELETE FROM DBi_measure WHERE time < %d", maxTime);
}
}
/**
* Remove Signal Strength data from DB, that is older than N days:
* (days * number of seconds in a day) * seconds to milliseconds
*/
private void cleanupOldData() {
long maxTime = (System.currentTimeMillis() - ((maximumNumberOfDaysSaved * 86400)) * 1000); // [ms] Number of days
//TODO
//mDbHelper.cleanseCellStrengthTables(maxTime);
averageSignalCache.clear();
}
private boolean deviceIsMoving() {
// Is device moving?
return System.currentTimeMillis() - lastMovementDetected < minimumIdleTime * 1000; // [ms]
}
/**
* Uses previously saved calculations and signal measurements to guesstimate if a given signal
* strength for a given cell ID looks mysterious or not.
*
* @param cellID
* @param signalStrength
*/
public boolean isMysterious(int cellID, int signalStrength) {
// If moving, return false
if (deviceIsMoving()) {
log.info("Cannot check signal strength for CID: " + cellID + " because of device movements.");
return false;
}
int storedAvg;
// Cached?
if (averageSignalCache.get(cellID) != null) {
storedAvg = averageSignalCache.get(cellID);
log.debug("Cached average SS for CID: " + cellID + " is: " + storedAvg);
} else {
// Not cached, check DB
@Cleanup Realm realm = Realm.getDefaultInstance();
storedAvg = mDbHelper.getAverageSignalStrength(realm, cellID);
averageSignalCache.put(cellID, storedAvg);
log.debug("Average SS in DB for CID: " + cellID + " is: " + storedAvg);
}
boolean result;
if (storedAvg > signalStrength) {
result = storedAvg - signalStrength > mysteriousSignalDifference;
} else {
result = signalStrength - storedAvg > mysteriousSignalDifference;
}
log.debug("Signal Strength mystery check for CID: " + cellID +
" is " + result + ", avg:" + storedAvg + ", this signal: " + signalStrength);
return result;
}
public void onSensorChanged() {
//log.debug("We are moving...");
lastMovementDetected = System.currentTimeMillis();
}
}