/*
Copyright (C) 2011 monte
This file is part of PSP NetParty.
PSP NetParty is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package pspnetparty.client.swt.app;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;
import pspnetparty.client.swt.WlanProxyConstants;
import pspnetparty.client.swt.SwtUtils;
import pspnetparty.lib.IniFile;
import pspnetparty.lib.IniSection;
import pspnetparty.lib.Utility;
import pspnetparty.lib.constants.AppConstants;
import pspnetparty.lib.socket.AsyncTcpServer;
import pspnetparty.lib.socket.AsyncUdpServer;
import pspnetparty.lib.socket.IProtocol;
import pspnetparty.lib.socket.IProtocolDriver;
import pspnetparty.lib.socket.IServerListener;
import pspnetparty.lib.socket.ISocketConnection;
import pspnetparty.lib.socket.PacketData;
import pspnetparty.wlan.JnetPcapWlanDevice;
import pspnetparty.wlan.NativeWlanDevice;
import pspnetparty.wlan.WlanDevice;
import pspnetparty.wlan.WlanLibrary;
import pspnetparty.wlan.WlanNetwork;
public class WlanProxyApp {
private static final String INI_SETTING_FILE_NAME = "PlayClient.ini";
private static final String SECTION_LAN_ADAPTERS = "LanAdapters";
private Shell shell;
private Spinner portSpinner;
private Combo adapterCombo;
private Button serverStart;
private Text logText;
private WlanLibrary wlanLibrary;
private IniFile iniFile;
private List<WlanDevice> wlanAdapterList = new ArrayList<WlanDevice>();
private boolean isPacketCapturing = false;
private WlanDevice currentDevice = WlanDevice.NULL;
private Thread packetCaptureThread;
private ByteBuffer captureBuffer = ByteBuffer.allocateDirect(WlanDevice.CAPTURE_BUFFER_SIZE);
private Thread ssidPollingThread;
private AsyncTcpServer tcpServer;
private AsyncUdpServer udpServer;
private ISocketConnection proxyClient = ISocketConnection.NULL;
private boolean isScanNetworkRequested = false;
private String lastSSID = "";
public int sentBytes;
public int capturedBytes;
private Thread cronThread;
private Label statusbarLabel;
public WlanProxyApp() throws IOException {
iniFile = new IniFile(INI_SETTING_FILE_NAME);
shell = new Shell(SwtUtils.DISPLAY);
shell.setText("PSP NetParty WLANアダプタプロキシ");
GridLayout gridLayout;
gridLayout = new GridLayout(5, false);
gridLayout.marginWidth = 2;
gridLayout.marginHeight = 2;
gridLayout.marginBottom = 1;
shell.setLayout(gridLayout);
Label portLabel = new Label(shell, SWT.NONE);
portLabel.setText("ポート");
portLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
portSpinner = new Spinner(shell, SWT.BORDER);
portSpinner.setMinimum(1);
portSpinner.setMaximum(65535);
portSpinner.setSelection(20000);
Label adapterLabel = new Label(shell, SWT.NONE);
adapterLabel.setText("アダプタ");
adapterLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
adapterCombo = new Combo(shell, SWT.BORDER | SWT.READ_ONLY);
adapterCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
serverStart = new Button(shell, SWT.TOGGLE);
serverStart.setText("開始");
logText = new Text(shell, SWT.READ_ONLY | SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
logText.setBackground(SwtUtils.DISPLAY.getSystemColor(SWT.COLOR_WHITE));
logText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 5, 1));
statusbarLabel = new Label(shell, SWT.NONE);
statusbarLabel.setText("通信はありません");
statusbarLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 5, 1));
String software = String.format("%s 通信サーバー バージョン: %s", AppConstants.APP_NAME, AppConstants.VERSION);
appendLog(software, false);
appendLog("プロトコル: " + IProtocol.NUMBER, false);
if (!JnetPcapWlanDevice.LIBRARY.isReady()) {
appendLog("Pcapがインストールされていません", false);
} else if (!NativeWlanDevice.LIBRARY.isReady()) {
appendLog("PcapインストールOK", false);
appendLog("Windowsワイヤレスネットワーク機能がインストールされていません", false);
wlanLibrary = JnetPcapWlanDevice.LIBRARY;
appendLog("SSID機能: Off (jNetPcap)", false);
} else {
appendLog("PcapインストールOK", false);
appendLog("Windowsワイヤレスネットワーク機能OK", false);
wlanLibrary = NativeWlanDevice.LIBRARY;
appendLog("SSID機能: On (PnpWlan)", false);
}
if (wlanLibrary.isReady()) {
adapterCombo.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event event) {
int index = adapterCombo.getSelectionIndex();
int separatorIndex = wlanAdapterList.size() + 1;
int refreshIndex = separatorIndex + 1;
if (index == 0) {
serverStart.setEnabled(false);
} else if (index < separatorIndex) {
serverStart.setEnabled(true);
} else if (index == separatorIndex) {
adapterCombo.select(0);
serverStart.setEnabled(false);
} else if (index == refreshIndex) {
refreshAdapterList();
}
}
});
serverStart.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event event) {
if (serverStart.getSelection()) {
if (startPacketCapturing()) {
serverStart.setText("停止");
adapterCombo.setEnabled(false);
} else {
serverStart.setSelection(false);
}
} else {
serverStart.setEnabled(false);
isPacketCapturing = false;
statusbarLabel.setText("通信はありません");
}
}
});
refreshAdapterList();
initBackgroundThreads();
tcpServer = new AsyncTcpServer(WlanDevice.CAPTURE_BUFFER_SIZE + 10);
udpServer = new AsyncUdpServer();
ProxyProtocol protocol = new ProxyProtocol();
tcpServer.addProtocol(protocol);
udpServer.addProtocol(protocol);
IServerListener listener = new IServerListener() {
@Override
public void log(String message) {
appendLog(message, true);
}
@Override
public void serverStartupFinished() {
}
@Override
public void serverShutdownFinished() {
}
};
tcpServer.addServerListener(listener);
udpServer.addServerListener(listener);
initCaptureBuffer();
} else {
adapterCombo.removeAll();
adapterCombo.add("使用できません");
adapterCombo.select(0);
adapterCombo.setEnabled(false);
portSpinner.setEnabled(false);
serverStart.setEnabled(false);
}
shell.pack();
shell.setMinimumSize(shell.getSize());
shell.setSize(300, 200);
shell.open();
}
private void initCaptureBuffer() {
String c = String.valueOf(WlanProxyConstants.COMMAND_PACKET);
ByteBuffer b = AppConstants.CHARSET.encode(c);
captureBuffer.put(0, b.get());
}
private void appendLog(final String message, final boolean timestamp) {
try {
if (SwtUtils.isNotUIThread()) {
SwtUtils.DISPLAY.asyncExec(new Runnable() {
@Override
public void run() {
appendLog(message, timestamp);
}
});
return;
}
if (logText.getCharCount() > 0)
logText.append("\n");
if (timestamp) {
Date now = new Date();
logText.append(SwtUtils.LOG_DATE_FORMAT.format(now));
logText.append(" - ");
}
logText.append(message);
logText.setTopIndex(logText.getLineCount());
} catch (SWTException e) {
}
}
private void refreshAdapterList() {
adapterCombo.removeAll();
adapterCombo.add("選択されていません");
wlanAdapterList.clear();
try {
wlanLibrary.findDevices(wlanAdapterList);
} catch (RuntimeException e) {
appendLog(Utility.stackTraceToString(e), true);
return;
} catch (UnsatisfiedLinkError e) {
appendLog(Utility.stackTraceToString(e), true);
return;
}
IniSection nicSection = iniFile.getSection(SECTION_LAN_ADAPTERS);
int maxNameLength = 15;
int i = 1;
for (Iterator<WlanDevice> iter = wlanAdapterList.iterator(); iter.hasNext(); i++) {
WlanDevice device = iter.next();
String macAddress = Utility.macAddressToString(device.getHardwareAddress(), 0, true);
String display = nicSection.get(macAddress, "");
if (Utility.isEmpty(display)) {
display = device.getName();
display = display.replace("(Microsoft's Packet Scheduler)", "");
display = display.replaceAll(" {2,}", " ").trim();
nicSection.set(macAddress, display);
} else if (display.equals("")) {
iter.remove();
continue;
}
display += " [" + macAddress + "]";
adapterCombo.add(display);
maxNameLength = Math.max(display.length(), maxNameLength);
}
StringBuilder sb = new StringBuilder(maxNameLength);
for (i = 0; i < maxNameLength; i++)
sb.append('-');
adapterCombo.add(sb.toString());
adapterCombo.add("アダプターリストを再読み込み");
adapterCombo.select(0);
serverStart.setEnabled(false);
}
private boolean startPacketCapturing() {
try {
InetSocketAddress address = new InetSocketAddress(portSpinner.getSelection());
tcpServer.startListening(address);
udpServer.startListening(address);
int index = adapterCombo.getSelectionIndex() - 1;
WlanDevice device = wlanAdapterList.get(index);
device.open();
currentDevice = device;
isPacketCapturing = true;
wakeupThread(packetCaptureThread);
if (wlanLibrary.isSSIDEnabled())
wakeupThread(ssidPollingThread);
wakeupThread(cronThread);
return true;
} catch (RuntimeException e) {
appendLog(Utility.stackTraceToString(e), true);
return false;
} catch (Exception e) {
appendLog(Utility.stackTraceToString(e), true);
return false;
}
}
private void processCapturedPacket() {
// System.out.println(captureBuffer.toString());
// System.out.println(bufferForCapturing.get(0));
capturedBytes += captureBuffer.remaining();
proxyClient.send(captureBuffer);
}
private void initBackgroundThreads() {
packetCaptureThread = new Thread(new Runnable() {
@Override
public void run() {
Runnable prepareCaptureEndAction = new Runnable() {
@Override
public void run() {
try {
isPacketCapturing = false;
serverStart.setEnabled(false);
} catch (SWTException e) {
}
}
};
Runnable captureEndAction = new Runnable() {
@Override
public void run() {
try {
adapterCombo.setEnabled(true);
serverStart.setText("開始");
serverStart.setEnabled(true);
} catch (SWTException e) {
}
}
};
try {
while (!shell.isDisposed()) {
synchronized (packetCaptureThread) {
if (!isPacketCapturing)
packetCaptureThread.wait();
}
try {
while (isPacketCapturing) {
captureBuffer.clear();
captureBuffer.position(1);
int ret = currentDevice.capturePacket(captureBuffer);
if (ret > 0) {
captureBuffer.flip();
processCapturedPacket();
} else if (ret == 0) {
} else {
SwtUtils.DISPLAY.syncExec(prepareCaptureEndAction);
break;
}
}
} catch (Exception e) {
appendLog(Utility.stackTraceToString(e), true);
isPacketCapturing = false;
}
currentDevice.close();
currentDevice = WlanDevice.NULL;
tcpServer.stopListening();
udpServer.stopListening();
SwtUtils.DISPLAY.syncExec(captureEndAction);
}
} catch (SWTException e) {
} catch (Exception e) {
appendLog(Utility.stackTraceToString(e), true);
}
}
}, "PacketCaptureThread");
packetCaptureThread.setDaemon(true);
ssidPollingThread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (!shell.isDisposed()) {
synchronized (ssidPollingThread) {
if (!isPacketCapturing)
ssidPollingThread.wait();
}
try {
while (isPacketCapturing) {
String ssid = currentDevice.getSSID();
if (!ssid.equals(lastSSID)) {
ByteBuffer buf = Utility.encode(WlanProxyConstants.COMMAND_GET_SSID + ssid);
proxyClient.send(buf);
lastSSID = ssid;
}
if (isScanNetworkRequested) {
currentDevice.scanNetwork();
isScanNetworkRequested = false;
}
Thread.sleep(3000);
}
} catch (Exception e) {
}
}
} catch (SWTException e) {
} catch (Exception e) {
}
}
}, "SsidPollingThread");
ssidPollingThread.setDaemon(true);
cronThread = new Thread(new Runnable() {
@Override
public void run() {
Runnable refreshAction = new Runnable() {
@Override
public void run() {
statusbarLabel.setText("PSPからの受信: " + capturedBytes + " バイト | 他参加者からの受信: " + sentBytes + " バイト");
capturedBytes = sentBytes = 0;
}
};
try {
while (!shell.isDisposed()) {
synchronized (cronThread) {
if (!isPacketCapturing)
cronThread.wait();
}
try {
while (isPacketCapturing) {
SwtUtils.DISPLAY.asyncExec(refreshAction);
Thread.sleep(1000);
}
} catch (Exception e) {
}
}
} catch (SWTException e) {
} catch (Exception e) {
}
}
}, "CronThread");
cronThread.setDaemon(true);
}
private void wakeupThread(Thread thread) {
synchronized (thread) {
if (thread.isAlive()) {
thread.notify();
return;
}
}
thread.start();
}
public void startEventLoop() {
while (!shell.isDisposed()) {
if (!SwtUtils.DISPLAY.readAndDispatch()) {
SwtUtils.DISPLAY.sleep();
}
}
try {
SwtUtils.DISPLAY.dispose();
} catch (RuntimeException e) {
}
}
private class ProxyProtocol implements IProtocol {
private ByteBuffer featureBuffer;
private ProxyProtocol() {
String c = String.valueOf(wlanLibrary.isSSIDEnabled() ? WlanProxyConstants.COMMAND_SSID_FEATURE_ENABLED
: WlanProxyConstants.COMMAND_SSID_FEATURE_DISABLED);
featureBuffer = AppConstants.CHARSET.encode(c);
}
@Override
public void log(String message) {
appendLog(message, true);
}
@Override
public String getProtocol() {
return WlanProxyConstants.PROTOCOL;
}
@Override
public IProtocolDriver createDriver(ISocketConnection connection) {
appendLog(" " + connection.getRemoteAddress(), true);
if (proxyClient != ISocketConnection.NULL)
return null;
appendLog("接続されました: " + connection.getRemoteAddress(), true);
featureBuffer.clear();
// System.out.println(featureBuffer);
connection.send(featureBuffer);
connection.send(Utility.encode(WlanProxyConstants.COMMAND_GET_SSID + lastSSID));
proxyClient = connection;
ProxyProtocolDriver driver = new ProxyProtocolDriver();
driver.connection = connection;
return driver;
}
}
private class ProxyProtocolDriver implements IProtocolDriver {
private ISocketConnection connection;
private ArrayList<WlanNetwork> networks = new ArrayList<WlanNetwork>();
@Override
public ISocketConnection getConnection() {
return connection;
}
@Override
public boolean process(PacketData data) {
ByteBuffer buffer = data.getBuffer();
int origLimit = buffer.limit();
buffer.limit(1);
char c = Utility.decode(buffer).charAt(0);
buffer.limit(origLimit);
switch (c) {
case WlanProxyConstants.COMMAND_PACKET:
// System.out.println(buffer);
sentBytes += buffer.remaining();
currentDevice.sendPacket(buffer);
break;
case WlanProxyConstants.COMMAND_SET_SSID:
String ssid = Utility.decode(buffer);
currentDevice.setSSID(ssid);
// System.out.println(ssid);
break;
case WlanProxyConstants.COMMAND_SCAN_NETWORK:
isScanNetworkRequested = true;
break;
case WlanProxyConstants.COMMAND_FIND_NETWORK:
networks.clear();
currentDevice.findNetworks(networks);
StringBuilder sb = new StringBuilder();
sb.append(WlanProxyConstants.COMMAND_FIND_NETWORK);
for (WlanNetwork n : networks) {
sb.append(n.getSsid());
sb.append('\t');
sb.append(n.getRssi());
sb.append('\f');
}
connection.send(Utility.encode(sb));
break;
default:
return false;
}
return true;
}
@Override
public void connectionDisconnected() {
proxyClient = ISocketConnection.NULL;
appendLog("切断されました: " + connection.getRemoteAddress(), true);
}
@Override
public void errorProtocolNumber(String number) {
}
}
public static void main(String[] args) throws Exception {
new WlanProxyApp().startEventLoop();
}
}