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 ・ ・ ・ ・