Commit 15efae98 authored by David Johnson's avatar David Johnson

The start of the mote logging code.

parent 66ee32fc
import java.io.*;
public class ElabACL {
private String vnodeName;
private String host;
private short port;
private int keylen;
private String key;
public static ElabACL readACL(File aclDir,String vnodeName)
throws FileNotFoundException {
BufferedReader br = null;
File f = new File(aclDir.toString() + File.separator +
vnodeName + ".acl");
if (!f.canRead()) {
throw new FileNotFoundException("could not read " + f.toString());
}
try {
br = new BufferedReader(new FileReader(f));
String line = null;
String h = null;
short p = 0;
int klen = 0;
String k = null;
while ((line = br.readLine()) != null) {
if (line.startsWith("host:")) {
String[] sa = line.split(" ");
h = sa[1];
}
else if (line.startsWith("port:")) {
String[] sa = line.split(" ");
p = (short)Integer.parseInt(sa[1]);
}
else if (line.startsWith("keylen:")) {
String[] sa = line.split(" ");
klen = Integer.parseInt(sa[1]);
}
else if (line.startsWith("key:")) {
String[] sa = line.split(" ");
k = sa[1];
}
else {
;
}
}
return new ElabACL(vnodeName,h,p,klen,k);
}
catch (Exception e) {
System.err.println("Problem while reading ACL " + f.toString());
e.printStackTrace();
}
return null;
}
public ElabACL(String vname,String host,short port,int keylen,String key) {
this.vnodeName = vname;
this.host = host;
this.port = port;
this.keylen = keylen;
this.key = key;
}
public String getVnodeName() {
return this.vnodeName;
}
public String getHost() {
return this.host;
}
public short getPort() {
return this.port;
}
public int getKeyLen() {
return this.keylen;
}
public String getKey() {
return this.key;
}
}
import java.util.*;
//import net.tinyos.message.Message;
public class LogPacket {
private Date timeStamp;
private byte[] data;
private int packetType;
private int crc;
//private Message msg;
public LogPacket(Date time,byte[] data,int packetType,int crc) {
this.timeStamp = time;
this.data = data;
this.packetType = packetType;
this.crc = crc;
//this.msg = null;
}
public Date getTimeStamp() {
return this.timeStamp;
}
public byte[] getData() {
return this.data;
}
public int getLowLevelPacketType() {
return this.packetType;
}
public int getLowLevelCRC() {
return this.crc;
}
}
import java.util.*;
import java.lang.reflect.*;
import javax.sql.*;
import java.net.*;
import java.io.*;
public class MoteLogger {
private int debug;
private File classDir;
private File aclDir;
private String logTag;
private String pid;
private String eid;
private String[] motes;
private SynchQueue packetQueue;
private String dbURL = "jdbc.DriverMysql";
private String dbUser = "root";
private String dbPass = "";
public static void main(String args[]) {
//
// Usage: java MoteLogger
// -M
// -m moteXXX,moteXXX,... (a list of /var/log/tiplogs/moteXXX.acl
// files we should look in to get tip connect info)
// -c directory (of class files)
// -p pid,eid
// -i id tag for this logging session
// -
parseArgsAndRun(args);
}
public static void parseArgsAndRun(String args[]) {
File classDir = null;
String tag = null;
String pid = null;
String eid = null;
File aclDir = new File("/var/log/tiplogs");
int debug = 0;
String[] motes = null;
int i;
for (i = 0; i < args.length; ++i) {
if (args[i].equals("-c")) {
if (++i < args.length && !args[i].startsWith("-")) {
classDir = new File(args[i]);
}
else {
System.err.println("option '-c' must have an argument!");
usage();
}
}
else if (args[i].equals("-i")) {
if (++i < args.length && !args[i].startsWith("-")) {
tag = args[i];
}
else {
System.err.println("option '-i' must have an argument!");
usage();
}
}
else if (args[i].equals("-p")) {
if (++i < args.length && !args[i].startsWith("-")) {
String[] sa = args[i].split(",");
if (sa.length == 2) {
pid = sa[0];
eid = sa[1];
}
else {
System.err.println("argument to '-p' must be of the " +
"form 'pid,eid'!");
usage();
}
}
else {
System.err.println("option '-p' must have an argument!");
usage();
}
}
else if (args[i].equals("-M")) {
if (++i < args.length && !args[i].startsWith("-")) {
aclDir = new File(args[i]);
}
else {
System.err.println("option '-i' must have an argument!");
usage();
}
}
else if (args[i].equals("-d")) {
++debug;
}
else if (args[i].startsWith("-")) {
System.err.println("Improper option '" + args[i] + "'!");
usage();
}
else {
// encountered a mote vnode name...
break;
}
}
// parse the remainder args as mote vnode names
motes = new String[args.length - i];
int j = 0;
while (i < args.length) {
motes[j] = args[i];
++i;
}
//this.motes = motes;
// startup
MoteLogger ml = new MoteLogger(classDir,aclDir,motes,
pid,eid,tag,debug);
ml.run();
}
public static void usage() {
String usage = "" +
"Usage: java MoteLogger -cipMd \n" +
"Options:\n" +
"\t-c <classdir> Directory containing packet-matching " +
"classfiles \n" +
"\t-i <idtag> Alphanumeric tag for this logging set \n" +
"\t-p pid,eid \n" +
//"\t-m <vname,vname,...> (list of vnames present in acl dir) \n"+
"\t-M <acldir> ACL directory \n" +
"\t-d..d Stay in foreground and debug at level specified \n" +
"";
System.err.println(usage);
System.exit(-1);
}
public MoteLogger(File classDir,File aclDir,String[] motes,String pid,
String eid,String tag,int debug) {
this.classDir = classDir;
this.aclDir = aclDir;
this.pid = pid;
this.eid = eid;
this.logTag = tag;
this.debug = debug;
this.motes = motes;
}
// I know, not a thread, but who cares
public void run() {
if (motes == null || motes.length == 0) {
// gotta read any acl files we find in the specified dir:
File[] aclFiles = aclDir.listFiles( new FilenameFilter() {
public boolean accept(File dir, String name) {
if (name != null && name.endsWith(".acl")) {
return true;
}
else {
return false;
}
}
});
if (aclFiles != null) {
motes = new String[aclFiles.length];
for (int i = 0; i < aclFiles.length; ++i) {
System.out.println(aclFiles[i].getName());
String[] sa = aclFiles[i].getName().split("\\.");
motes[i] = sa[0];
}
}
}
if (motes == null || motes.length == 0) {
System.out.println("Could not find any mote ACL files; exiting.");
System.exit(0);
}
// get capture keys
Hashtable acls = new Hashtable();
for (int i = 0; i < motes.length; ++i) {
try {
ElabACL ma = ElabACL.readACL(aclDir,motes[i]);
acls.put(motes[i],ma);
}
catch (Exception e) {
System.err.println("Problem reading ACL for " + motes[i]);
e.printStackTrace();
}
}
// setup queue
packetQueue = new SynchQueue();
// connect to the database
;
// spawn connection threads
for (Enumeration e1 = acls.keys(); e1.hasMoreElements(); ) {
String vNN = (String)e1.nextElement();
ElabACL acl = (ElabACL)acls.get(vNN);
(new MoteLogThread(acl,packetQueue)).start();
}
}
class MoteLogThread extends Thread {
private ElabACL acl;
private SynchQueue q;
private Socket sock;
public MoteLogThread(ElabACL acl,SynchQueue packetQueue) {
this.acl = acl;
this.q = packetQueue;
}
public void run() {
sock = null;
try {
// isn't java just wonderful
sock = new Socket(acl.getHost(),acl.getPort());
System.out.println("Connected to " + sock.getInetAddress() +
":" + sock.getPort() + " for " +
acl.getVnodeName());
}
catch (Exception e) {
System.err.println("Couldn't connect to " + acl.getHost() +
":" + acl.getPort());
e.printStackTrace();
return;
}
// authenticate
// send the 4-byte key len in host order :-(
int keylen = acl.getKeyLen();
byte[] len = new byte[4];
len[3] = (byte)(0xff & (keylen >> 24));
len[2] = (byte)(0xff & (keylen >> 16));
len[1] = (byte)(0xff & (keylen >> 8));
len[0] = (byte)(0xff & keylen);
// now send the key (and pad its length out to 256 bytes)
// aw heck, lets not pad it, who cares... capture won't...
// oops, it does
String key = acl.getKey();
byte[] authBytes = new byte[260];
byte[] oldbkey = null;
try {
oldbkey = key.getBytes("ISO-8859-1");
}
catch (Exception e) {
e.printStackTrace();
}
System.arraycopy(len,0,authBytes,0,len.length);
System.arraycopy(oldbkey,0,authBytes,4,oldbkey.length);
for (int i = oldbkey.length + 4; i < 260; ++i) {
authBytes[i] = 0;
}
//bkey[0] = 0;
//bkey[255] = 0;
System.out.println("Sending keylen " + keylen + " and key " +
key + " bytearraylen = "+key.getBytes().length +
" \n realkey = '"+new String(authBytes)+"'");
try {
OutputStream out = sock.getOutputStream();
out.write(authBytes);
//out.write(bkey);
InputStream in = sock.getInputStream();
byte[] rba = new byte[4];
int retval = 0;
// make sure we get them all...
retval = in.read(rba);
while (retval < 4) {
retval = in.read(rba,retval,rba.length-retval);
}
retval = rba[0];
retval |= rba[1] << 8;
retval |= rba[2] << 16;
retval |= rba[3] << 24;
String msg = null;
if (retval == 0) {
msg = "success.";
}
else if (retval == 1) {
msg = "capture busy.";
}
else {
msg = "failure (" + retval +").";
}
System.out.println("Result of authentication: " + msg);
if (retval != 0) {
return;
}
}
catch (Exception e) {
System.err.println("Problem authenticating for " +
acl.getVnodeName());
e.printStackTrace();
return;
}
// read packets forever:
LogPacket lp = null;
PacketReader pr = null;
try {
pr = new PacketReader(sock.getInputStream());
}
catch (Exception e) {
e.printStackTrace();
return;
}
while (true) {
lp = null;
try {
lp = pr.readPacket();
}
catch (Exception e) {
System.err.println("Problem while reading from " +
acl.getVnodeName());
e.printStackTrace();
}
}
}
}
}
import java.io.*;
import java.util.*;
public final class PacketReader {
/*
* creates a class that decomposes an InputStream into tinyos
* packets. IT DOES NOT REPLY to any packets, since it is meant
* to only be used by our logger. All the logger does is snoop
* packets... we want to let any applications respond as necessary.
* As it turns out, when we snoop the Elab component-generated packets,
* we'll issue any acks then -- or let some special "elab listener" do it.
*
*/
/*
* Here's the relevant protocol info from net.tinyos.packet.Packetizer:
*
* Protocol inspired by, but not identical to, RFC 1663.
* There is currently no protocol establishment phase, and a single
* byte ("packet type") to identify the kind/target/etc of each packet.
*
* The protocol is really, really not aiming for high performance.
*
* There is however a hook for future extensions: implementations are
* required to answer all unknown packet types with a P_UNKNOWN packet.
*
* To summarise the protocol:
* - the two sides (A & B) are connected by a (potentially unreliable)
* byte stream
* - the two sides exchange packets framed by 0x7e (SYNC_BYTE) bytes
* - each packet has the form
* <packet type> <data bytes 1..n> <16-bit crc>
* where the crc (see net.tinyos.util.Crc) covers the packet type
* and bytes 1..n
* - bytes can be escaped by preceding them with 0x7d and their
* value xored with 0x20; 0x7d and 0x7e bytes must be escaped,
* 0x00 - 0x1f and 0x80-0x9f may be optionally escaped
* - There are currently 5 packet types:
* P_PACKET_NO_ACK: A user-packet, with no ack required
* P_PACKET_ACK: A user-packet with a prefix byte, ack required.
* The receiver must send a P_ACK packet with the prefix byte
* as its contents.
* P_ACK: ack for a previous P_PACKET_ACK packet
* P_UNKNOWN: unknown packet type received. On reception of an
* unknown packet type, the receicer must send a P_UNKNOWN packet,
* the first byte must be the unknown packet type.
* - Packets that are greater than a (private) MTU are silently dropped.
*/
/* see http://www.tinyos.net/tinyos-1.x/doc/serialcomm/description.html */
final static int SYNC_BYTE = 0x7e;
final static int ESCAPE_BYTE = 0x7d;
final static int MTU = 256;
// don't need this cause we're not responding.
//final static int ACK_TIMEOUT = 1000; // in milliseconds
final static int P_ACK = 64;
final static int P_PACKET_ACK = 65;
final static int P_PACKET_NO_ACK = 66;
final static int P_UNKNOWN = 255;
private InputStream in;
public PacketReader(InputStream in) {
this.in = in;
}
// this method is meant to be called repeatedly, at a high rate.
// if "arrangements" in the caller (like synchronization with a queue)
// make fast, repetitious calls impossible, the timestamps will get off.
// if this is a concern, this class should be threaded itself and send
// notifications to the thread that does the logging, or implement a
// queue for that application. That's the right way to do it, but
// we're not dealing with high throughput links here! So speed is
// the better half of quality here... or something like that.
public LogPacket readPacket() throws IOException {
// just implement the simple packet protocol, reading
// byte by byte for now -- ick!
// obviously, this method is NOT thread-safe.
byte[] buf = new byte[MTU];
int currentOffset = 0;
boolean isEscaped = false;
boolean sync = false;
while (true) {
if (currentOffset >= buf.length) {
// mtu reached, toss it.
for (int i = 0; i < buf.length; ++i) {
buf[i] = 0;
}
currentOffset = 0;
}
// read byte -- let the exception fly through to the caller
byte b = (byte)in.read();
if (b == ESCAPE_BYTE) {
isEscaped = true;
}
else if (b == SYNC_BYTE) {
if (isEscaped && sync) {
// ex-xor it
b ^= 0x20;
buf[currentOffset++] = b;
}
else if (isEscaped) {
// we're trying to sync via an escaped data byte, bad us
// i.e., we're coming in in the middle of a packet.
}
else if (sync) {
// end of packet
break;
}
else {
sync = true;
}
}
else {
// normal data byte:
if (isEscaped) {
b ^= 0x20;
}
buf[currentOffset++] = b;
}
}
// we've got a packet, with only the SYNC_BYTEs and ESCAPE_BYTEs
// stripped out. Now we have to check the packet type and the crc.
int crc = 0;
int packetType = 0;
byte[] retval;
Date t = new Date();
LogPacket lp = null;
crc = (0xff & buf[buf.length-1]) << 8;
crc |= 0xff & buf[buf.length-2];
packetType = buf[0];
if (buf[0] == P_PACKET_NO_ACK) {
// no "prefix" byte
retval = new byte[buf.length-3];
System.arraycopy(buf,1,retval,0,retval.length);
return new LogPacket(t,retval,packetType,crc);
}
else if (buf[0] == P_PACKET_ACK) {
retval = new byte[buf.length-4];
System.arraycopy(buf,2,retval,0,retval.length);
return new LogPacket(t,retval,packetType,crc);
}
else if (buf[0] == P_ACK || buf[0] == P_UNKNOWN) {
// do nothing for now; this is only sent by receiver on
// receipt of an unknown packet type anyway
;
}
else {
// XXX: might want to log these in the future...
}
return lp;
}
}
import java.util.LinkedList;
public class SynchQueue extends LinkedList {
public SynchQueue() {
super();
}
public synchronized void queueAdd(Object o) {
this.addFirst(o);
}
public synchronized Object queueRemove(Object o) {
return this.removeLast();
}
}
How we will generate sql
------------------------
To make viewing data easy, we will duplicate packet data amongst several
subtables for each packet type.
For packets that do not use arrays, there's really no problem. Each field
becomes a column. Structures are flattened out (but column names reflect the
struct -- just as mig does the transform). Unions will act the same way here.
However, when packets use arrays, we need to provide a good way to view the
"list"-type data. Motelab handles this by dumping the whole byte array into a
blob column. We can do better. There are a variety of options. First, we can
smash the byte data into a string of basic types or tuples. We can store this
string either in a column next to the blob, and provide VIEWs to help the user
see what they want. Second, we can fragment the msg table: non-array fields
become separate tables, with parent packet ids and index values. This provides
a clean sql way to extract data programmatically (via a join on the parent
packet table), but is less nice visually. Of course, for multiple arrays, the
join will become uglier. Third, we can smash out each item into a single
column in the case of a basic type, or a set of columns for a struct. However,
this simply creates a MASSIVELY long table, which is not visually helpful
either. This, in fact, is a TERRIBLE option.