package de.fun2code.android.piratebox.handler;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import sunlabs.brazil.server.Handler;
import sunlabs.brazil.server.Request;
import sunlabs.brazil.server.Server;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.IntentSender.SendIntentException;
import android.preference.PreferenceManager;
import android.util.Log;
import de.fun2code.android.piratebox.Constants;
import de.fun2code.android.piratebox.PirateBoxService;
import de.fun2code.android.piratebox.database.DatabaseHandler;
/**
* Handler that counts user sessions and connections (MAC addresses).
* To count user sessions the SHA1-Hash of the following combination is used:<br/>
* {@code REMOTE_ADDRESS + USER_AGENT_STRING + DATE}
* <br/>
* To get the MAC address that corresponds to an IP number, the file
* {@literal /proc/net/arp} is inspected.
*
* @author joschi
*
*/
public class ConnectionCountHandler implements Handler {
private String prefix;
private static final String ARP_FILE = "/proc/net/arp";
private static Set<String> sha1Hashes = new HashSet<String>();
private static Set<String> macAddresses = new HashSet<String>();
private DatabaseHandler db;
private SharedPreferences prefs;
@Override
public boolean init(Server server, String prefix) {
this.prefix = prefix;
sha1Hashes.clear();
macAddresses.clear();
try {
// Init database
db = new DatabaseHandler(PirateBoxService.getService());
// Get preferences
prefs = PreferenceManager.getDefaultSharedPreferences(PirateBoxService.getService());
}
catch(Exception e) {
e.printStackTrace();
return false;
}
return true;
}
@Override
public boolean respond(Request request) throws IOException {
String day = new SimpleDateFormat("yyyy-MM-dd", Locale.US).format(new Date());
String sha1 = getSha1Hash(request, day);
/*
* If cookie is not in list of cookies the file /proc/net/arp
* has to be inspected.
*/
if(sha1 != null && !sha1Hashes.contains(sha1)) {
sha1Hashes.add(sha1);
String mac = getMacAddress(request);
/*
* If MAC address is not in list, add it an send a broadcast
*/
if(mac != null && !macAddresses.contains(mac)) {
macAddresses.add(mac);
sendConnectionBroadcast();
}
/*
* Write visitor data to db if statistics are eanabled
*/
if(prefs.getBoolean(Constants.PREF_ENABLE_STATISTICS, true)) {
db.insertVisitor(day, sha1);
}
}
return false;
}
/**
* Returns the SHA-1 hash of the following information:<br/>
* {@code REMOTE_ADDRESS + USER_AGENT_STRING + DATE}
* <br/>
* This is done the same was as in LibraryBoy.
* <br/>
* see: {@link https://github.com/LibraryBox-Dev/LibraryBox-core/blob/master/customization/www_librarybox/vc_counter.php}
*
* @param request
* @return
*/
private String getSha1Hash(Request request, String day) {
String remoteAddr = request.getSocket().getInetAddress().getHostAddress();
String userAgent = request.headers.get("User-Agent");
return sha1(remoteAddr + userAgent + day);
}
/**
* Calculates SHA-1 hash
* <br/>
* Code taken from: {@link http://www.androidsnippets.com/sha-1-hash-function}
*
* @param s String to use for hash calculation
* @return SHA-1 hex value
*/
public String sha1(String s) {
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
Log.e(Constants.TAG, e.toString());
}
digest.reset();
byte[] data = digest.digest(s.getBytes());
return String.format("%0" + (data.length * 2) + "X", new BigInteger(1,
data));
}
/**
* Retrieves the IP number of the request and returns the matching MAC
* address by inspecting /proc/net/arp.
* If no MAC address can be found {@code null} is returned.
*
* @param request Request to use
* @return MAC address or {@code null} if the MAC address could not be found
*/
private String getMacAddress(Request request) {
String mac = null;
BufferedReader br = null;
try {
String ip = request.sock.getInetAddress().toString().replaceAll("[^1-9.]", "");
File file = new File(ARP_FILE);
br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null) {
if(line.matches("^" + ip + "\\s+.*$")) {
mac = line.split("\\s+")[3];
break;
}
}
}
catch(Exception e) {
Log.e(Constants.TAG, "Can't get MAC address: " + e.toString());
}
finally {
if(br != null) {
try {
br.close();
} catch (IOException e) {
Log.e(Constants.TAG, e.toString());
}
}
}
return mac;
}
/**
* Returns the current total connection count
*
* @return number of total connections
*/
public static int getConnectionCount() {
return macAddresses.size();
}
/**
* Clears the connection counter
*/
public static void clearConnectionCount() {
sha1Hashes.clear();
macAddresses.clear();
// Send a status request broadcast to inform listeners of new status
Intent intentRequest = new Intent(Constants.BROADCAST_INTENT_STATUS_REQUEST);
PirateBoxService.getService().sendBroadcast(intentRequest);
}
/**
* Sends a {@literal de.fun2code.android.piratebox.broadcast.intent.CONNECTION}
* broadcast
*/
private void sendConnectionBroadcast() {
Intent intentConnection = new Intent(Constants.BROADCAST_INTENT_CONNECTION);
intentConnection.putExtra(Constants.INTENT_CONNECTION_EXTRA_NUMBER, macAddresses.size());
PirateBoxService.getService().sendBroadcast(intentConnection);
}
}