JavaでBonjourを使ったファイル転送アプリを作る

PutOnPutOn for Macを俺だけは便利に使っていますが、困ったことといえばWindows PCにはファイル転送できないことです。
仕事でWindows PCを使うけれど別にVisual Studioとかを入れているわけでも無し、ここはマルチプラットフォームで動く(はず)のJavaでPutOnと通信できるアプリを作ってみようではありませんか。

JavaBonjourを使ったサービスの公開と発見を行う

Androidだとandroid.net.nsdというものが使えるらしいですし、AppleもなんかJavaAPIを公開していた気がしますが、JmDNSを使ってみることにしました。Apache License 2.0。

// 接続を待ち受けるServerSocketを作っておきます
serverSocket = new ServerSocket();
InetSocketAddress serverSocketAddress = new InetSocketAddress(InetAddress.getLocalHost(), 0);
serverSocket(serverSocketAddress);
InetAddress inetAddress = serverSocket.getInetAddress();
int port = serverSocket.getLocalPort();
// 実際は次の行は別スレッドで実行、待ち受けてます
Socket actualSocket = serverSocket.accept();

// サービスのタイプとか名前とかポートとか設定して公開します
serviceInfo = ServiceInfo.create("_hoge._tcp.local.", tempServerName, port, "Hoge Service");
try {
	jmDNS = JmDNS.create(inetAddress);
	jmDNS.setDelegate(this);
	jmDNS.registerService(serviceInfo);
	serverName = serviceInfo.getName();

// サービスをブラウズもします
	jmDNS.addServiceListener("_hoge._tcp.local.", this);
} catch (IOException e) {
	e.printStackTrace(); //とかなんとか
}

serverSocketに接続が来たらactualSocketのInputStreamとかOutputStreamでなんかします。
ブラウズしてサービスが見つかったら

public void serviceAdded(ServiceEvent serviceEvent) {
	ServiceInfo info = serviceEvent.getInfo();
	String serviceName = info.getName();
	// 以下うんたら
}

でうんたらします。PutOnはPutOn同士のサービスが見つからないと点滅信号を表示しないので、自分から接続する役ではなくてもブラウズはします。

JavaでBlowfishを使った複合化を行う

PutOnは通信内溶をBlowfishで暗号化し、鍵を点滅信号で伝達します。JavaでBlowfishを使った暗号化・複合化をするには、Cipherを使えば出来るみたいです。

byte[] decryptBytesWithKey(byte[] cryptBytes, byte[] keyBytes, byte[] initVectorBytes) {
	byte[] result = null;
	try {
		// Blowfish、CBCモード、PKCS7パディングで
		Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS7Padding");
		// 鍵
		SecretKeySpec key = new SecretKeySpec(keyBytes, "Blowfish");
		// 初期化ベクトル
		IvParameterSpec initVector = new IvParameterSpec(initVectorBytes);
		cipher.init(Cipher.DECRYPT_MODE, key, initVector);
		result = cipher.doFinal(cryptBytes);
			
	} catch (Exception e) {
		e.printStackTrace();
		// とかなんとか
	}
	return result;
}

あ、でも上記のコードはこれだけでは動きません。Java SE 6ではPKCS7パディングが無いらしいんです(暗号化ならPKCS5を使えばいいらしいんですけど。クソッタレですね)。そこで、Bouncy Castleというのを見つけてきました。MIT license。
これのProviderをつっこめばPKCS7Paddingが使えるようですよ。

Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); 

JavaJSONをなんかする

Javaは今7でしたっけ?なんか6ではJSONのパースは標準でついてないらしいんですよ(クソッタレですね)。PutOnはファイルに関するメタデータとかをJSONで送っているのでこれでは困ります。そこでJSONICというのを見つけてきました。Javaのコレクションとか基本クラスとJSONをシンプルに変換できるようですよ。Apache License 2.0。

byte[] infoBytes; //これに復号したbyte[]を入れとく
Charset utf8CharSet = Charset.forName("UTF-8");
String jsonString = new String(infoBytes, utf8CharSet);
HashMap info = JSON.decode(jsonString); // これだけ。簡単ですね
Number fileLengthNumber = (Number) info.get("fileLength");
fileLength = fileLengthNumber.longValue();
String tempFileName = (String) info.get("fileName");

他気づいたこと

そんな感じでPutOnのJava版が出来そうですよ。Windows PCで動くといいけど。
作ってて気づいたのですが、PutOnとPutOn for Macは通常Bluetoothでは通信できません。でもBluetoothテザリングで接続しているiPhoneMac間だとできるんですね。当たり前か。
Javaで書けるってことはAndroidでも出来るのかもしれませんが、なんかAndroidだとマルチキャストがデフォルトで無効とかそういうのがあるようですよ。

JmDNSによるBonjourのサービス解決 - Kazzzの日記
http://d.hatena.ne.jp/Kazzz/20130305/p1