package wifi;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;
import java.util.Scanner;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
/**
* The WiFiClient class provides a GUI for interacting with 802.11~ implementations. It
* can be used to drive either Java-based or C++-based link layers. (Use a CppGUIAdapter
* to interface this with C++ code, and a JavaGUIAdapter to drive Java code.)
* Care is taken to ensure that all GUI-servicing operations occur in the event-dispatching
* thread while all interactions with 802.11~ code occur in separate threads. Thus the
* constructor doesn't actually create the GUI — that happens in buildGUI() which is
* called from the client's run() method. (The run() method also starts up a thread to
* watch for arriving packets from the 802.11~ layer.) The client's run method is invoked
* by main via SwingUtilities.invokeAndWait() to ensure it's executed on the event-dispatching
* thread.
*
* A complete Java implementation looks like:
*
* WiFiClient
* JavaGUIAdapter
* LinkLayer (802.11~ implementation)
* RF
*
*
* A complete C++ implementation is messier, due to the need for additional layers to
* mediate between the Java and C++ code:
*
* WiFiClient (Java)
* CppGUIAdapter (Java)
* cppStubs (C++)
* linklayer (802.11~ implementation in C++)
* RF (C++ JNI layer)
* RF (Java implementation of RF layer)
*
*
* See the project documentation for additional information on compiling an 802.11~ project.
*
* @author Brad Richards
* @version 1.2
*
*/
public class WiFiClient implements ActionListener, Runnable
{
protected JScrollPane textPane; // Holds the message text display
protected JTextArea inputBox; // The message text display itself
// Text is collected in a StringBuilder as well as the JTextArea. This allows us to
// continue buffering text in the StringBuilder even when the window is paused.
protected JTextArea display; // The output text display
protected StringBuffer outputText = new StringBuffer(); // Holds display text
protected JFrame frame; // The frame that holds the display and key panels
protected JButton[] ctrlButtons; // Has to be field so listener can access them
protected JButton[] sendButtons; // Has to be field so listener can access them
protected short[] sendAddrs;
protected JLabel status; // Has to be field so setter can access it
protected boolean paused = false; // Are we paused?
protected short MACaddr; // This station's address
private GUIClientInterface theLinkLayer;// The link layer we're "driving"
protected static int NUM_CTRL_BUTTONS = 4;
protected static int NUM_SEND_BUTTONS = 10;
protected static final int COMMAND = 0;
protected static final int CLEAR = 1;
protected static final int PAUSE = 2;
protected static final int SAVE = 3;
/**
* The constructor builds the GUI and prepares it for use.
*/
public WiFiClient(short MACaddr, GUIClientInterface theLinkLayer) {
this.MACaddr = MACaddr;
this.theLinkLayer = theLinkLayer;
}
/**
* Creates all of the GUI components and registers the event handlers.
* This method should be invoked on the event-dispatching thread.
*/
private void buildGUI() {
ctrlButtons = new JButton[NUM_CTRL_BUTTONS];
sendButtons = new JButton[NUM_SEND_BUTTONS];
sendAddrs = new short[NUM_SEND_BUTTONS];
// Build the overall frame
frame = new JFrame("802.11~ Client [MAC "+MACaddr+"]");
frame.setBackground(Color.LIGHT_GRAY);
frame.setSize(700,500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
// Text input is in the NORTH panel of border layout
JPanel sendingPanel = new JPanel();
sendingPanel.setLayout(new GridLayout(3,0));
sendingPanel.add(new JLabel(" Enter text below. Press a button to transmit text to specified destination.")); // Label's on top
inputBox = new JTextArea(2, 0);
inputBox.setEditable(true);
inputBox.setLineWrap(true);
inputBox.setWrapStyleWord(true);
inputBox.setFont(new Font("Courier", Font.PLAIN, 14));
JPanel inputBoxPanel = new JPanel();
inputBoxPanel.add(inputBox);
inputBox.setText("This is a sample text message.");
sendingPanel.add(inputBox); // Text field's next
JPanel sendButtonPanel = new JPanel();
sendButtonPanel.setLayout(new GridLayout(0, 5));
sendButtons[0] = new JButton("Bcast");
sendButtonPanel.add(sendButtons[0]);
sendButtons[0].addActionListener(this);
sendAddrs[0] = (short)-1;
short[] defaults = theLinkLayer.getDefaultAddrs();
for(int i=1; i defaults.length || defaults[i-1] == Short.MIN_VALUE) { // Leave "blank"
sendButtons[i] = new JButton("MAC "+i);
sendAddrs[i] = Short.MIN_VALUE;
}
else { // Take from defaults
sendButtons[i] = new JButton("MAC "+defaults[i-1]);
sendAddrs[i] = defaults[i-1];
}
sendButtonPanel.add(sendButtons[i]);
sendButtons[i].addActionListener(this);
}
sendingPanel.add(sendButtonPanel); // And, finally, the sending buttons
frame.add(sendingPanel, BorderLayout.NORTH);
// Text is displayed in a scrolling pane in the CENTER of the border layout
display = new JTextArea();
display.setEditable(false);
display.setLineWrap(true);
display.setWrapStyleWord(true);
display.setFont(new Font("Courier", Font.PLAIN, 14));
textPane = new JScrollPane(display); // Wrap TextArea in a ScrollPane
frame.add(textPane, BorderLayout.CENTER);
// Build a panel to house the three ctrlButtons
JPanel controls = new JPanel();
controls.setLayout(new GridLayout(0, NUM_CTRL_BUTTONS));
// Create all of the ctrlButtons and register this object as listener
// for each.
ctrlButtons[COMMAND] = new JButton("Command");
ctrlButtons[CLEAR] = new JButton("Clear");
if (paused)
ctrlButtons[PAUSE] = new JButton("Resume");
else
ctrlButtons[PAUSE] = new JButton("Pause");
ctrlButtons[SAVE] = new JButton("Save");
for(int i=0; i 0) {
String inputString =
JOptionPane.showInputDialog(null,
"Please enter MAC address for this button:",
"Set Address",
JOptionPane.QUESTION_MESSAGE);
if (inputString != null) {
Scanner s = new Scanner(inputString);
if (s.hasNextShort()) {
sendAddrs[i] = s.nextShort();
this.addText("Mapping button to MAC address "+sendAddrs[i]+"\n");
sendButtons[i].setText("MAC "+sendAddrs[i]);
}
else {
this.addText("That wasn't a valid address.\n");
}
}
}
else {
byte[] msg = inputBox.getText().getBytes();
this.addText("Sending text message to MAC "+sendAddrs[i]+": \"");
this.addText(inputBox.getText()+"\"\n");
int result = theLinkLayer.sendOutgoingData(sendAddrs[i], msg);
if (result != msg.length)
{
this.addText("Sent "+result+" bytes, but message was "+msg.length+"!\n");
}
}
return; // We found the button
}
}
// Wasn't a send button. Check the control buttons.
// If it's the COMMAND button, prompt the user for a command integer
// and value. (The value defaults to 0.) Pass the pair of integers
// to the 802.11~ layer and report the result to the output panel.
if (e.getSource() == ctrlButtons[COMMAND]) {
String inputString =
JOptionPane.showInputDialog(null,
"Enter command and value, separated by spaces",
"Enter Command",
JOptionPane.QUESTION_MESSAGE);
if (inputString != null) {
Scanner s = new Scanner(inputString);
int value = 0;
if (s.hasNextInt()) {
int command = s.nextInt();
if (s.hasNextInt())
value = s.nextInt();
int result = theLinkLayer.sendCommand(command, value);
this.addText("Command to 802.11~ layer ("+command+", "+value+") returned "+result+"\n");
}
else {
this.addText("Not a valid command.");
}
}
}
// Clear deletes the text from the buffer and resets the display
else if (e.getSource() == ctrlButtons[CLEAR]) { // CLEAR
outputText.delete(0, outputText.length());
display.setText("");
textPane.getVerticalScrollBar().setValue(Integer.MAX_VALUE);
}
// Pause negates the paused flag, which controls whether text added
// to the display is "posted" immediately. It also changes the text
// on the button.
else if (e.getSource() == ctrlButtons[PAUSE]) {// PAUSE
paused = !paused;
if (paused) {
ctrlButtons[PAUSE].setText("Resume");
}
else {
ctrlButtons[PAUSE].setText("Pause");
display.setText(outputText.toString());
textPane.getVerticalScrollBar().setValue(Integer.MAX_VALUE);
}
}
else if (e.getSource() == ctrlButtons[SAVE])
{
saveToFile();
}
}
/**
* Call this to append text to the scrolling output pane. No newlines are added,
* so be sure to include a "\n" where desired. Text is collected in a StringBuffer
* (outputText) as well as the JTextArea (display). This allows us to continue
* buffering text even when the window output is paused.
*
* @param msg Text to add to the scrolling pane
*/
public synchronized void addText(String msg) {
outputText.append(msg);
// Setting the scroll bar's position sometimes causes a mysterious exception
// to be thrown. If it happens, pause while the output collects in outputText.
// When the user resumes, all of the outputText will be dumped into the display
// pane again.
try {
if (!paused) {
display.append(msg);
// textPane.getVerticalScrollBar().setValue(Integer.MAX_VALUE);
}
} catch (RuntimeException e) {
System.err.println("Exception in addText() -- pausing output");
paused = true;
ctrlButtons[PAUSE].setText("Resume");
}
}
/**
* Prompts user to select an output file, then writes all text from the
* scrolling pane to the file.
*/
private void saveToFile() {
File outputFile = null;
JFileChooser chooser = new JFileChooser();
int returnVal = chooser.showSaveDialog(null);
if(returnVal == JFileChooser.APPROVE_OPTION) {
outputFile = chooser.getSelectedFile();
PrintWriter writer;
try {
writer = new PrintWriter(new FileWriter(outputFile));
writer.println(display.getText());
writer.close();
} catch (IOException e) {
outputText.append("Error writing to file!!");
}
}
}
/**
* The run method should be executed by the event-dispatching thread. it creates
* the GUI and starts a thread to watch for packets arriving for this station.
*/
public void run() {
buildGUI();
(new Thread(new StreamWatcher(this))).start();
}
/**
* We need this inner class to wrap up the code that watches for arriving packets.
* We can't run it in the event-dispatching thread, so this loop can't just go in
* the run() method.
*
* @author Brad Richards
*/
class StreamWatcher implements Runnable {
WiFiClient display;
public StreamWatcher(WiFiClient display) { this.display = display; }
/**
* Block and wait for incoming transmissions. Repeat.
*/
public void run() {
for(;;) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// Do nothing if awakened early
}
byte[] bytes = theLinkLayer.watchForIncomingData();
if (bytes != null && bytes.length >= 2) {
int tmp = ((int)bytes[0]) & 0xFF;
tmp = (tmp << 8) | (((int)bytes[1]) & 0xFF);
short srcAddr = (short)tmp;
String payload = new String(bytes, 2, bytes.length-2);
display.addText("From "+srcAddr+": \""+payload+"\"\n");
}
}
}
}
/**
* The main method selects a MAC address, creates a WiFiClient GUI and associates
* it with the link layer implementation, then waits in an infinite loop watching
* for stream output from the link layer and routing into the GUI display.
*/
public static void main(String[] args) {
Random rand = new Random();
short mac;
// Take MAC on command-line if it's available, or create a random MAC
if (args.length > 0) {
mac = (new Scanner(args[0]).nextShort());
System.out.println("Using MAC address of "+mac+" as requested.");
}
else {
mac = (short)(rand.nextInt(100)+701);
System.out.println("Using a random MAC address: "+mac);
}
// Use the CppGUIAdapter for a C++-based project, JavaGUIAdapter for Java-based project
// GUIClientInterface linkLayer = new CppGUIAdapter();
GUIClientInterface linkLayer = new JavaGUIAdapter();
WiFiClient display = new WiFiClient(mac, linkLayer);
// This mechanism causes the GUI to be created by the event-dispatching thread
try {
SwingUtilities.invokeAndWait(display);
} catch (Exception e1) {
System.err.println("Yikes! Something went wrong when invoking WiFiClient's run() method:");
e1.printStackTrace();
}
// Remind the user how to modify buttons
display.addText("Shift-click a button to change its MAC address.\n");
// Run forever, watching for input from the link layer and adding it to the GUI's
// text display window.
for(;;) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Do nothing if awakened early
}
byte[] bytes = linkLayer.pollForStreamOutput();
if (bytes != null) {
String output = new String(bytes, 0, bytes.length);
display.addText(output);
}
}
}
}