2012年10月21日

[n7]あの画面を出したい

ふと「あの画面を出したい」と思った。
この画面だ。

image

昨日のFukuoka NFCで見せてもらったのだが、2台のNFC Android端末を向かい合わせると、両方ともこの画面になる。
そして、片方がタッチすると送りつける。

私が今のところ作っているのは、PaSoRiからAndroid端末にSNEPでURLなんかを送りつけるアプリだ。
だから、受けとるAndroid端末にはこの画面が出てこない。

 

出したい・・・。


だいたい、当てはついていた。

  • どっちもこの画面になれるのなら、InitiatorやTargetは関係ない
  • タッチしたら、止めることはできない

ということだから、LLCPのSYMMを繰り返している期間だろう。

 

ふむ、当たりだ。
タッチするとCONNECTを送っているので、

  • CONNECTを送信する方が、SNEPクライアント
  • CONNECTを受信した方(CCを送信する方)が、SNEPサーバ

という立ち位置になるようだ。


ふっ、私もずいぶん理解できるようになったもんだ。
今年の初めの方まではさっぱりわからなかったけど、「突然わかる」というのはいつか訪れるもんだな。

 

あとはこの実装を、SNEPサーバになれるようにしてしまいたい。
そして複数のI PDUに対応すれば、Android側でBeamするアプリを作ってNFCだけでPCへファイルを送りつける、というようなものができんかなー、と思っている。
いや、Bluetoothで十分なんだけど、ちょっとやってみたいな、というところだ。

2012年10月14日

[n7]FeliCa Lite用のライブラリを作ろう (2) : 片側認証編

「べ、別にFeliCa Liteのことが好きなわけじゃないんだから」というわけで(なんじゃこりゃ)、FeliCa Lite用ライブラリの続きを作ろう。

前回作ったのは、どちらかといえばNFC-Fの補足ライブラリのようなものであった。
今回は、個別化を移植しよう。

NfcAとNfcFのクラスライブラリは同じ水準かもしれない。
しかし、MifareUltralightがAndroidの標準にあって、Felicaがないのはよろしくない。
Felicaがないのなら、MifareUltraLightもあってはならない、と思うんだけどなあ。
いっそのこと、Type2TagとかType3Tagみたいなクラスにしてしまえばいいのに。


以前も、片側認証のための個別化カード鍵についていろいろ調べた。
特にはまったのが、3DESのエンディアン。
http://hiro99ma.blogspot.jp/2011/09/felica_04.html

FeliCa Liteのユーザーズマニュアル「5.1.1. MAC生成アルゴリズム」にも、添字の大きい方が上位、と書いてある。
なので、まあこれはいいとしよう。

 

それよりも困っているというか悩んでいるというか、個別化マスター鍵の値が1つ違っても、計算するMACの値が一致してしまうのだ。
FeliCa Liteは、IDブロックを読んだときのMACを使っている。
なにが悪いんだろう?

いろいろ調べていたが、DES暗号化は鍵が56bitだから、8byte(64bit)指定しても使われないビットが8bit分あるんじゃなかろうか、と思った。
「1つ違っても」で試したのは一番下のデータだったので、8byteのそれぞれ最下位ビットが使われないと考えるとつじつまが合いそうだ。

JavaでもC#でも同じように動いているということは、APIがだいたいそういうものということか。

 

http://www.arch.cs.kumamoto-u.ac.jp/~kuga/cad/crypt/des/
ここの説明に「パリティ」という説明があった。
使われないのではなく、パリティビットだったのか。。。
理屈はわかったが、「8byteの鍵」って書いてあると、ここまで気が回らないなあ。


そんなわけで、実装方法としては間違っていないようだ。
ただ、個別化マスター鍵の管理方法として、

  • 鍵バージョンと個別化マスター鍵はきっちり管理すること
  • 3DESなので、個別化マスター鍵のA[0~7]、B[8~15]、C[16~23]の各バイトの上位7bitが同じ値にならないようにすること(鍵Aで暗号化→それを鍵Bで復号化→それを鍵Cで暗号化、とするので、AとBが同じだと、Cで暗合しただけと同じになる)
  • 3DESなので、新しい鍵バージョンを追加するときは過去の個別化マスター鍵と比較し、各バイトの上位7bitが一致しないことを確認すること

というような取り決めにした方がよいだろう。

 

ようやく、片側認証らしきものを追加した。
https://github.com/hirokuma/NfcTest4

知らない分野はまだまだいろいろあるな。

2012年10月13日

[n7]FeliCa Lite用のライブラリを作ろう (1)

「作ろう」って書くと、「レジ袋とペットボトルでペンギンを作ろう」という歌(オフロスキー)を思い出してしまう。

それはどうでもいいとして、前回FeliCa Liteの読み込みをやろうとしたが、MifareUltralightみたいなクラスがないのでめんどくさかった。

誰か作っていそうな気がするけど、勉強がてらFeliCa Lite用のライブラリを作ろう。


(途中省略)


ひとまず、1ブロックのReadとWriteを作った。
ReadはPAD0の読み込みだけ確認。Writeは動作確認すらしていない。

https://github.com/hirokuma/NfcTest4/blob/master/src/com/blogpost/hiro99ma/nfc/FelicaLite.java

 

NfcFクラスを継承して、置き換えて使ってもらえるようなものにしようかと思っていた。
が、NfcFクラスはfinal classで継承できず・・・。
では、その親のBasicTagTechnologyクラスを継承しようとしたが、これはpublicじゃないので見えず・・・。

じゃあ、packageをandroid.nfc.techにすればいいか?とも思ったが、それはだめなような気がするので、is-aは諦めてhas-aの形にした(なんて呼ぶかわからん)。


こういうの、C/C++だといろいろと「強引な」やりかたがあるんだけど、Javaはさすがにできませんな。
まあ、それはその方がいいと思う。


Read

public static byte[] readBlock(int blockNo) throws IOException {
    byte[] buf = new byte[16];
    buf[0] = 16;                    //length
    buf[1] = (byte)0x06;            //Read Without Encryption 
    System.arraycopy(mTag.getId(), 0, buf, 2, 8);
    buf[10] = (byte)0x01;            //service num
    buf[11] = (byte)0x0b;            //service code(lower)
    buf[12] = (byte)0x00;            //service code(upper)
    buf[13] = (byte)0x01;            //blocklist num
    buf[14] = (byte)0x80;            //2byte-blocklist(upper)
    buf[15] = (byte)blockNo;        //2byte-blocklist(lower)
    
    byte[] ret = mNfcF.transceive(buf);
    
    //length check
    if(ret.length != 29) {
        ret = null;
        return null;
    }
    //IDm check
    for(int i=2+0; i<2+8; i++) {
        if(ret[i] != buf[i]) {
            ret = null;
            return null;
        }
    }
    //status flag check
    if((ret[10] != 0x00) || (ret[11] != 0x00)) {
        ret = null;
        return null;
    }
    
    //read data copy
    System.arraycopy(ret, 13, buf, 0, 16);
    ret = null;
    return buf;
}

シンプルに「コマンドデータ作成」「送信」「受信データチェック」「受信データコピー」の順に処理。
まあ、それ以外無いけど。

あ、受信データチェックに応答コマンド値(0x07)を見てないな・・・。
ブロック数をチェックしてないのは、データ長でほぼそれがわかるし、それ以外の値だったとしてもまあいいか、ということで。

サービスコードが0x000b固定なのは、ReadOnlyにしておけばRWでもROでも読めるようだから。
製品仕様なのかな? ドキュメントを読みきれてないけど、動いてるというレベル。


Write

public static boolean writeBlock(int blockNo, byte[] data) throws IOException {
    byte[] buf = new byte[32];
    buf[0] = 32;                    //length
    buf[1] = (byte)0x08;            //Write Without Encryption
    System.arraycopy(mTag.getId(), 0, buf, 2, 8);
    buf[10] = (byte)0x01;            //service num
    buf[11] = (byte)0x09;            //service code(lower)
    buf[12] = (byte)0x00;            //service code(upper)
    buf[13] = (byte)0x01;            //blocklist num
    buf[14] = (byte)0x80;            //2byte-blocklist(upper)
    buf[15] = (byte)blockNo;        //2byte-blocklist(lower)
    System.arraycopy(data, 0, buf, 16, 16);
 
    byte[] ret = mNfcF.transceive(buf);
 
    //length check
    if(ret.length != 12) {
        ret = null;
        return false;
    }
    //IDm check
    for(int i=2+0; i<2+8; i++) {
        if(ret[i] != buf[i]) {
            ret = null;
            return false;
        }
    }
    //status flag check
    if((ret[10] != 0x00) || (ret[11] != 0x00)) {
        ret = null;
        return false;
    }
    ret = null;
    return true;
}

こちらも、基本はReadと同じ。
返すデータが書き込みの成功か失敗になる、というところか。


途中でreturnするのが嫌いな人も多いだろうが、私は関数の最初(パラメータチェック)と最後(結果のチェック)ならreturnで抜けてもいいかな、と思っているので、こうなってる。

 

NfcF#transceive()の戻り値をreturn前にnull代入しているが、そこまでする必要はあるのかな?
いらんと思うけど、知識がない。。
無駄なコードを作り込んでいるような気がしてならん。

Javaがよくわからん・・・

AndroidでNFC-Fのコマンド送受信をするときは、NfcF.transceive()を使う。
こんなメソッドだ。

public byte[] transceive(byte[] data) throws IOException {
    return transceive(data, true);
}

戻ってくるのは、応答した結果のbyte[]。

さて、このbyte[]、どうしたらいいんだろうか?
どっちかだと思う。

  • transceive()以下でメモリ確保してあげてるから、そのまま使っていいよ。
  • 暫定でメモリを渡してるんだから、さっさと自分でコピーしてね。

悩んでいたが、文字にしてみると、前者だろう、ということに気付いた。

 

まだ言語仕様がよくわかっていないが、Javaの引数はCの引数と同じイメージでいいようだ。

void f() {
  byte[] a = new byte[] { 1, 2, 3 };  //「new byte[]」はいらない?
  g(a);
  h(a);
}

void g(byte[] a) {
  a[0] = 4;
}

void h(byte[] b) {
  b = new byte[] { 5, 6, 7 };
}

f()が持つa[]は、g()によって書き換えはされるが、h()によって置き換えられることはない。
だから、相手先にメモリを確保してもらうなら、戻り値でもらうようにするのがよさそうだ。
引数で返してもらうなら、配列にしてしまうしかないのかな?

void f() {
  byte[][] a = new byte[1][];
  a[0] = new byte[] { 1, 2, 3 };
  h(a);
}

void h(byte[][] b) {
  b[0] = new byte[] { 5, 6, 7 };
}

 

なんか、C#の文法とかが入り交じってしまって、コンパイルもしてないから、私が思っているイメージが伝わればいいかな、ということで。


そしてまだよくわかってないものに、System.arraycopy()が挙げられる。

System.arraycopy()は、シャローコピー(shallow copy)だそうだ。
って、みんないってるけど、言語仕様なんだろうか?

package test;
 
public class Test {
    public static void main(String[] args) {
 
        byte[] a = { 1, 2, 3, 4, 5 };
        byte[] b = { 6, 7, 8, 9, 0 };
        byte[] c = new byte[2];
        System.arraycopy(a, 0, c, 0, 1);
        System.arraycopy(b, 0, c, 1, 1);
 
        //最初の値
        System.out.print("a:");
        for(byte k : a) {
            System.out.print(k + " ");
        }
        System.out.println();
        System.out.print("b:");
        for(byte k : b) {
            System.out.print(k + " ");
        }
        System.out.println();
        System.out.print("c:");
        for(byte k : c) {
            System.out.print(k + " ");
        }
        System.out.println();
        
        //書き換え
        c[0] = 10;
        c[1] = 11;
        
        //最後の値
        System.out.println("----------");
 
        System.out.print("a:");
        for(byte k : a) {
            System.out.print(k + " ");
        }
        System.out.println();
        System.out.print("b:");
        for(byte k : b) {
            System.out.print(k + " ");
        }
        System.out.println();
        System.out.print("c:");
        for(byte k : c) {
            System.out.print(k + " ");
        }
        System.out.println();
    }
}

結果

a:1 2 3 4 5
b:6 7 8 9 0
c:1 6
----------
a:1 2 3 4 5
b:6 7 8 9 0
c:10 11

 

あれ?
参照してるんだから、参照先が変わると参照元も変わるんじゃないの??

じゃあ、参照元だけ変更して、参照先はそのままにしてみた。

 

結果

a:1 2 3 4 5
b:6 7 8 9 0
c:1 6
----------
a:10 2 3 4 5
b:11 7 8 9 0
c:1 6

 

変わらん・・・。
ちゃんと値コピーされている。

 

いや、不思議に思ったのだ。
コピーって配列の一部だけコピーすることができるけど、それをわざわざ「ここからここまではこのオブジェクトを参照」「ここからここまでは、あのオブジェクト参照」なんて管理するのは、非常に面倒だし、かえって遅くなるではないか。

 

System.arraycopy()の「shallow」な対象は、配列オブジェクトではなく、配列オブジェクトの要素部分ということなんだな。
上の例では、要素部分はプリミティブなbyte型だから、そのまま値コピーされている。
要素部分がStringみたいな型だったら、それはアドレスをコピーするだけにしていて、その先は同じものを参照しますよ、ということか。

 

 image
要素部分がオブジェクト型の場合は、アドレスだけをコピー

 

image
要素部分がプリミティブ型の場合は、アドレスに相当するのが値なので、値コピー

 

試してよかった・・・。

[n7]USBデバッグモードにしておくと、MTPはだめらしい

私はWindows XPを使っているのだが、Nexus7をUSBでつなぐとデバイスマネージャで「! MTP」と出てくる。
MTPを反転させてるんじゃなくて、ドライバが見つからんよ、という意味だ。

ネットで調べてみると、MediaPlayerのバージョンがどうとか、あれをインストールしたとか、削除してつなぎ直したとか、いろいろ出てくる。
そして、いろいろやったが、変わらん。

もうどうでもいいや、と思って見ていると、USBデバッグ時はMTPできん、というサイトがあった。
Windows XPでNexus7の内部ストレージにアクセスする方法|はるかなぎの搜作記録

USBデバッグモードを解除してつなぎ直すと、あっさりドライバがインストールされて認識した。
なーんだ。
まあ、わかってしまえば大したことじゃなかったけど、それまではさっぱり見当も付かないことってあるよな、と自己弁護した。


ちなみに、Windows7のときも同じような目に会った。
hiro99ma blog: [n7]Windows7でNexus7がMTPとして認識してなかった

[n7]TagLostException (2)

TagLostExceptionをどうたどろうか考えていたら、先生が教えてくれた。
さすがだ。

そんなわけで、transceive()しているデータを見直すことにした。
先に書いておくと、「結果としてその通りだった」ということになる。


NFC-Fの場合、先頭に送信データサイズが1byteあり、それに続けてコマンドが続く。
つまり、送信データサイズは、コマンド長+1になる。

Read w/o Enc(NFC Forumでいうところの、CHECK)は、こんな構成。
説明を簡単にするために、2byteブロックリストで1ブロックだけ読み出すときの例とする。

([0] 送信データサイズ)
[1] コマンド:0x06
[2~9]IDm(NFCID2)
[10]サービス数:0x01
[11~12]サービスコード
[13]ブロック数:0x01
[14~15]ブロックリスト

FeliCaチップに送信するときはもう少しデータを追加するのだが、transceive()にはこれくらいあればいいようだ。

データを見直してみると、どうもサービス数を書き込む配列の添字が間違っていて、IDmと重なっていたようだ。
修正して送信すると、TagLostExceptionは発生しない。
うむ、よかろう。

 

ということは、TagLostExceptionが発生するのは、InCommunicateThruに相当する命令を実行して、コマンド自体は成功したけれども、相手から応答がなくてタイムアウトした場合にも発生すると考えていいのかな。


では、うまくいかなかったPUSH(三者間通信)の方もやってみよう。

これも似たような間違いをしていて、IDmとデータ部が重なっていたり、データ長が違ったりしていた。
なんでそういう間違いが多いかというと、AndroidからPaSoRiを操作するライブラリを作ったとき、先頭のデータ長はライブラリで自動的に突っ込むようにしていたためだ。
それを、データ長付きのパラメータに変更したとき、いろいろ間違ったというところだ。
やれやれ。

 

修正して送信すると、TagLostExceptionは発生しない。
うむ、よかろう。
では携帯電話の方は・・・。

出てない。
エラーにならないのに・・・。

transceive()の戻り値を見ると、コマンドは通っているがエラーが返ってきているっぽい。
つまり、個別部のフォーマットが間違っているとかなのか?

ああ、IDmと個別部数の間に、パラメータ長がいるんだ。
修正している間に、間違って消してしまったらしい。
パラメータ長を追加すると、うまくいきましたとさ。
めでたしめでたし。

https://github.com/hirokuma/NfcTest3


今日わかった、transceive()によるTagLostException

  • Targetから応答がなかった場合に発生する
  • Targetから応答があれば、データの中身がエラーであっても成功したことになる。
    つまり「行って返るまで」の責任を負っている。

2012年10月12日

[n7]TagLostException (1)

まずは、基礎的なアプリのサンプルをたくさんつくって、後はサンプルから使い回すようなことを考えている。

簡単そうな三者間通信をやってみよう。


エラーが出る。

android.nfc.TagLostException: Tag was lost.
    at android.nfc.TransceiveResult.getResponseOrThrow(TransceiveResult.java:48)
    at android.nfc.tech.BasicTagTechnology.transceive(BasicTagTechnology.java:151)
    at android.nfc.tech.NfcF.transceive(NfcF.java:119)

"Tag was lost"とあるから、捕捉していたNFCカードを見失ったのか?
でも、カードは机の上に置いて、Nexus7はその上に置いているので、カードを見失ったとか、R/Wの範囲外になったというのは考えにくいように思う。

 

Pollingならうまくいくかと思ったが、そうでもなかった。
うーむ。
・・・あ、サイズを付け忘れていたのでやったら、Pollingは通った。
でも、Read w/o Encryptionはだめなんだよなぁ。


NFC-Aのコマンド(Read)は、特に問題なく通ったように見える。

うーん・・・・んんん?

 

まあ、まだ間違った認識を持っているのはありえるだろうね。

[a]インストール失敗の条件はいずれ調べたい(いずれ・・・)

いつもお世話になってるやましたさんに「permission revokeはpermission剥奪だけのはず」と教えてもらった。

そうか、permissionをrevokeする、というだけなんだ・・・。

 

もしかして、mmでビルドしてるからなのか?と思ったが関係ないし、mm用の自分用署名を使ってるからかと思ったが、それも関係が無かった。

なんだ、なんなんだ・・・?

 

今日、なにげなくデベロッパーコンソールを見ると、有効インストール数が1つ!
そうか、インストールできる人もいるんだ!

ってことは、環境なのか・・・。
Nexus7にはSDスロットがないから、とか、そんな理由なのかなぁ。

 

まあ、一人でもインストールできるならいいや、と私は細かいことを忘れることにした。

いつかね、いつか調べよう(たぶんそれも忘れる)。

[android]permission revokeとインストール失敗は関係ない?

気力がないので、logcatだけ貼っておく。
とにかく、EjectSD4が失敗するのはpermission revokeのせいだと思っていたが、そうでもない、ということのようだ。

 I/ActivityManager(  355): START {dat=file:///storage/sdcard0/Download/IKAKU_Widget.apk cmp=com.android.packageinstaller/.InstallAppProgress (has extras) u=0} from pid 7018
D/dalvikvm(  355): GC_FOR_ALLOC freed 1032K, 27% free 14840K/20103K, paused 45ms, total 49ms
W/ActivityManager(  355): No content provider found for permission revoke: file:///storage/sdcard0/Download/IKAKU_Widget.apk
W/ActivityManager(  355): No content provider found for permission revoke: file:///storage/sdcard0/Download/IKAKU_Widget.apk
I/ActivityManager(  355): Displayed com.android.packageinstaller/.InstallAppProgress: +78ms
W/ActivityManager(  355): Unable to start service Intent { act=com.extscreen.service }: not found
W/ActivityManager(  355): Unable to start service Intent { act=com.extscreen.service }: not found
I/PackageManager(  355): Running dexopt on: jp.typepad.hirokuma.IKAKU_Widget
D/dalvikvm( 7890): DexOpt: load 8ms, verify+opt 41ms, 321884 bytes
I/ActivityManager(  355): Force stopping package jp.typepad.hirokuma.IKAKU_Widget uid=10081
D/PackageManager(  355): New package installed in /data/app/jp.typepad.hirokuma.IKAKU_Widget-1.apk
I/InputReader(  355): Reconfiguring input devices.  changes=0x00000010




2012年10月10日

[java]shallow copy

#(2012/10/13追記) System.arraycopy()についてよくわかってないことを書いてますが、後日調査しました。

NFCアプリを作るならば、NFCカードへのアクセスは避けられまい。
どうやってやるのか調べたところ(というほどでもないが)、MIFARE Ultralightの例があった。

なるほど、writePage()か。
いやいや、その前に「MifareUltralight」ってクラス名かい!
念のためにNfcAなどを見てみると、transceive()しかなかった。
うーん、それなら「Felica」クラスもあっていいと思うんだけどなぁ。


まあ、手段があるならいいや。
いつものようにバイト配列にコマンドを書いて送るだけだ。

・・・と思ったが、transceive()の引数はbyte[]だった。
長さの指定が無いということは、引数のlengthがそのままデータ長になるんだろう。
それだと、バッファとして用意したbyte[]にデータを突っ込むだけではだめだ。

 

他の人がどうやってるか知らないが、私はだいたい、NFC R/Wに対してWRITEバッファとREADバッファを1つずつ持つように作っている。
無線なので「同時読み書き」が発生しないからバッファは1つでも足りるのだけど、デバッグに便利だし、めんどくさいのでそうしている。
もったいないとは思うのだが、このくらいは許してくれる・・・と思っている。

そのバッファサイズは、FeliCaの最大にあわせて265byteにしている(たしか)。
プリアンブルとポストアンブルは固定だから4byteくらい削れるんじゃないの、といわれそうだが・・・・すまん、めんどくさかったのだ。


さて、本題だ。

私がR/Wに転送するときは、バッファにデータを詰めて、データとデータ長を渡すようにしている。
C/C++の配列にはデータ長がないので、ユーザが管理するのが普通だからだ。

しかし、transceive()はbyte[]のみで、lengthは渡せない。
これは困った。

Javaだとヒープに確保するんだから直前までサイズを固定しなければいいじゃないの、といわれそうだが、何となく慣れなくて・・・。
新しい革袋を用意すべきだとは思うのだが、私にはまだその準備ができていないのだ。

じゃあ、データを作った後になればサイズがわかるから、そのときにまた領域を確保してコピーすればいいやん、という考え方もあるが・・・もったいない。
ほら、我々の世代は「資源は有限、資源を大切に」と育てられた世代ではないか。
違うlengthを得るためだけにコピーするのは、心苦しい。
Genericとか、そんなしくみはいらず、とにかくバッファがほしいだけなんだけどなぁ。

 

と思って調べていると、JavaFAQというページがあった。
ここの「S006-Q09」に、こんな質問があった。

コピーした配列の要素を書き換えると元の配列でも書き換わってしまうのですが?

その回答が、こう。

配列をコピーした際に、配列の要素がコピーされていないからです。

 

おお、これこれ!
こういうのを期待していたんだよ。
shallow copyって、COWで書き込むまでのつなぎってイメージがあったけど、こういうときに使うものなのかな。


shallow copyなら、コピー先のnewはいらないんじゃないの?と思ってやってみた。

だめだった。
うーん、領域をコピーせんといかんのなら、なんか意味が無いなあ。

 

このnewは私に覚悟を問うているのかも。
「貴様に、これだけのメモリを確保する意気込みはあるのか」とか。
ないんだけど、ないんだけど・・・。

 

newすると当然うまくいくんだが、なんだかなぁ。
実際に使用するまでは、メモリ確保したふりをするだけだったらいいんだけど。