Felicaカードのデータ構成を調べるために、C#言語でFelicaカードをデータダンプするプログラムを作成しました。ライブラリは、felicalib.dll を使用し、前回利用した「C#で Felica Library「felicalib.dll」を使用する 」のFelicaLib.csに、少しコードを追加しました。
動作環境
- Windows 7 Professional
- VisualStudio express 2013 for Desptop
- PaSoRi RC-S380
- felicalib-0.4.2.zip
Felicaカードのデータ構成
Felicaカードのデータ構成は、製造ID(IDm)、製造パラメータ(PMm)、ファイル情報となっており、次のように定義されます。詳細情報については、SonyのWebサイトのFelica関連の技術情報にまとめられています。今回は、Felica関連の技術資料に基づき、Felicaカードの構成情報をC#言語を使用して、データダンプします。
- IDm
- FeliCaにおける製造IDを示します。8オクテットで、これは任意に読み取ることができる。
8オクテットのうち上位2オクテットが製造者コード、下位6オクテットがカード識別番号となります。カード識別番号は製造者コードごとに異なる体系となります。 - PMm
- 製造パラメータ(8バイト)と呼ばれます。IDm 以外の個別の値を指定することができます。こちらは FeliCa の種類によって内容が異なります。
- ファイルシステム
- FeliCaは、一枚のカードを複数の目的で利用できるように、データをフォルダーとファイル構造で管理できるファイルシステムを持っています。システムコードは、システムを特定するための2バイトの値で、事業者および使用目的ごとに割り当てられています。ファイルシステムは、「エリア」と「サービス」によって階層状に構成され、次のような階層構造となっています。
- システムコード (System code)
- エリア (area) フォルダーに相当するもの
- サービスコード (Serivce code)
- ブロック単位のデータ (1ブロック16バイト)
Felicaカードのデータダンプ関数の追加
Felicaカードのデータをダンプするための関数「readCard」を次に示します。最初に、製造ID(IDm)、製造パラメータ(PMm)を取得して表示し、次にシステムコードとそれに関連するファイル情報を順次取得して表示しています。
private void readCard(Felica f) { int i, j, k; f.Polling((int)SystemCode.Any); Console.Write("# IDm: "); hexdump(f.IDm(), 8); Console.Write("\n"); Console.Write("# PMm: "); hexdump(f.PMm(), 8); Console.Write("\n\n"); felicat felicaf2 = new felicat(); felicat felicaf1 = f.felica_enum_systemcode(); for (i = 0; i < felicaf1.num_system_code; i++) { int syscode = ((felicaf1.system_code[i]) >> 8) & 0xff | ((felicaf1.system_code[i]) << 8) & 0xff00; Console.Write("# System code: {0:x4}\n", syscode); felicaf2 = f.felica_enum_service(syscode); Console.Write("# Number of area = {0}\n", felicaf2.num_area_code); for (j = 0; j < felicaf2.num_area_code; j++) { Console.Write("# Area: {0:x4} - {1:x4}\n", felicaf2.area_code[j], felicaf2.end_service_code[j]); } Console.Write("# Number of service code = {0}\n", felicaf2.num_service_code); for (j = 0; j < felicaf2.num_service_code; j++) { ushort service = felicaf2.service_code[j]; printserviceinfo(service); for (k = 0; k < 255; k++) { data = f.ReadWithoutEncryption((int)felicaf2.service_code[j], k); if (data == null) break; Console.Write("{0:x4}:{1:x4} ", (int)felicaf2.service_code[j], k); hexdump(data, 16); Console.Write("\n"); } } Console.Write("\n"); } }
Felicaカードから取得したサービスコードに対応した意味を表示するために、printserviceinfo関数を用いて変換します。
static void printserviceinfo(ushort s) { string ident; switch ((s >> 1) & 0xf) { case 0: ident = "Area Code"; break; case 4: ident = "Random Access R/W"; break; case 5: ident = "Random Access Read only"; break; case 6: ident = "Cyclic Access R/W"; break; case 7: ident = "Cyclic Access Read only"; break; case 8: ident = "Purse (Direct)"; break; case 9: ident = "Purse (Cashback/decrement)"; break; case 10: ident = "Purse (Decrement)"; break; case 11: ident = "Purse (Read only)"; break; default: ident = "INVALID or UNKOWN"; break; } Console.Write("# Serivce code ={0:x4} : {1}", s, ident); if ((s & 0x1) == 0) { Console.Write(" (Protected)"); } Console.Write("\n"); }
FelicaLib.csの変更部分
構造体を持つ関数のパラメータを、C#の関数として取り扱うためには、そのメンバの配置方法を変更する必要があります(参考:C#からDLL関数の呼び出し )。Felicaとのインタフェースを行う構造体をC#からアクセスできるように、次のように構造体を定義します。
[StructLayout(LayoutKind.Sequential)] public struct felicat{ public IntPtr p; /**< PaSoRi ハンドル */ public ushort systemcode; /**< システムコード */ [MarshalAs(UnmanagedType.ByValArray, SizeConst= 8)] public byte[] IDm; /**< IDm */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] PMm; /**< PMm */ /* systemcode */ public byte num_system_code; /**< 列挙システムコード数 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public ushort[] system_code; /**< 列挙システムコード */ /* area/service codes */ public byte num_area_code; /**< エリアコード数 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public ushort[] area_code; /**< エリアコード */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public ushort[] end_service_code; /**< エンドサービスコード */ public byte num_service_code; /**< サービスコード数 */ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] public ushort[] service_code; /**< サービスコード */ };
Felicaカードのデータをダンプするために、ダイナミックリンクライブラリ「felicalib.dll」のfelica_enum_systemcode関数とfelica_enum_service関数を、C#で使用できるようにDllImportで宣言します。
[DllImport("felicalib.dll", CallingConvention = CallingConvention.Cdecl)] private extern static IntPtr felica_enum_systemcode(IntPtr p); [DllImport("felicalib.dll", CallingConvention = CallingConvention.Cdecl)] private extern static IntPtr felica_enum_service(IntPtr p, ushort systemcode); private felicat felicai;
FelicaLib.csにfelica_enum_systemcodeメソッドとfelica_enum_serviceを作成し、それぞれ、felica_enum_systemcode関数とfelica_enum_service関数を呼び出し、システムコード、エリアコード、サービスコードを呼び出して、あらかじめ定義した構造体に設定し、C#からアクセスできるようにします。
public felicat felica_enum_systemcode() { felica_free(felicap); felicap = felica_enum_systemcode(pasorip); if (felicap == IntPtr.Zero) { throw new Exception("カード読み取り失敗"); } felicai = (felicat)Marshal.PtrToStructure(felicap, typeof(felicat)); return felicai; } public felicat felica_enum_service(int systemcode) { felica_free(felicap); felicap = felica_enum_service(pasorip, (ushort)systemcode); if (felicap == IntPtr.Zero) { throw new Exception("カード読み取り失敗"); } felicai = (felicat)Marshal.PtrToStructure(felicap, typeof(felicat)); return felicai; }
所有する楽天EdyのICカードを使用して、プログラムを実行したダンプ結果を次に示します。サービスコードが「Protected」になっている部分については、ブロック単位のデータダンプが不可になります。
# IDm: 01 14 b3 8f 11 12 e0 0a # PMm: 01 20 22 04 27 67 4e ff # System code: 811d # Number of area = 3 # Area: 0000 – fffe # Area: 1000 – 123f # Area: 2000 – 213f # Number of service code = 10 # Serivce code =1008 : Random Access R/W (Protected) # Serivce code =100b : Random Access Read only 100b:0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 100b:0001 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 100b:0002 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 100b:0003 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 100b:0004 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # Serivce code =1108 : Random Access R/W (Protected) # Serivce code =110b : Random Access Read only 110b:0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ・ ・ ・ ・