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