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.
So, what to do? Well, we have to make the original data available to users.
That (and motelab compat) require us to have BLOB types for array bytes.
However, this may not be our default VIEW. We will also smash the data into
string tuples. Should we put this in the same table with the blob(s)? I tend
to think no, since you will never want to view both at the same time. Perhaps
they should go in the same table anyway, and we should provide VIEWs
corresponding to what they might want to see. This is easier and reduces the
number of tables per logging set. Plus, we then only have to create a table
for each array to join on the main type table. We can provide VIEWs to do this
which hide the join complexity.
So, there will be a lot of tables (because VIEWs show up as tables) per
discrete run of the logger. Thus, we need a master table that helps the
experimenter keep track of a binding between their description of what the
logging run means, and the numeric id attached to each table. This makes joins
impossible as far as I know (can't somehow interpolate the id into a select as
far as I know). But that's ok; that info is just there for experimenter help.
We will provide an outside command that lists which numeric IDs mean what
descriptions/runs. For each VIEW we provide, we'll also append a "_latest" tag
on the back, which we will programmatically bind to the new table data every
time a new run is started.
For now, the "numeric" id will be a random number, OR an experimenter-assigned
tag (may be alphanumeric). Thus, the ID in the map table must be a varchar.
If they don't make it unique, we'll reuse the old tables!
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment