diff --git a/AfRApay.FTM/AfRApay.FTM.csproj b/AfRApay.FTM/AfRApay.FTM.csproj index d439800..a3e97da 100644 --- a/AfRApay.FTM/AfRApay.FTM.csproj +++ b/AfRApay.FTM/AfRApay.FTM.csproj @@ -7,4 +7,10 @@ enable + + + + + + diff --git a/AfRApay.FTM/Program.cs b/AfRApay.FTM/Program.cs index 83fa4f4..c766294 100644 --- a/AfRApay.FTM/Program.cs +++ b/AfRApay.FTM/Program.cs @@ -1,2 +1,79 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using System.CommandLine; +using PCSC; +using PCSC.Monitoring; +using PCSC.Iso7816; + +var rootCommand = new RootCommand("Fancy Test Machine for AfRApay"); + +var listReadersOption = new Option("--list-readers", "List card readers and exit"); +rootCommand.Add(listReadersOption); + +rootCommand.SetHandler((listReaders) => { + using (var context = ContextFactory.Instance.Establish(SCardScope.System)) { + // We need a card reader or this won't work! + var readerNames = context.GetReaders(); + if (readerNames.Length == 0) { + Console.Error.WriteLine("Error: no card reader detected"); + Environment.Exit(1); + } + + // If --list-readers is passed, list readers and exit. + if (listReaders) { + Console.Error.WriteLine("---------- Connected Readers ----------"); + foreach (var name in readerNames) { + Console.WriteLine(name); + } + Console.Error.WriteLine("---------------------------------------"); + Environment.Exit(0); + } + + // Listen for events on all connected readers. + using (var monitor = MonitorFactory.Instance.Create(SCardScope.System)) { + monitor.Initialized += (_, args) => Console.WriteLine("[ Reader Initialized: {0} ]", args.ReaderName); + monitor.MonitorException += (_, args) => { + Console.Error.WriteLine("! ERROR: {0}", args); + Environment.Exit(1); + }; + monitor.StatusChanged += (_, args) => Console.WriteLine("~ {0} -> {1}", args.LastState, args.NewState); + monitor.CardInserted += (_, args) => { + Console.WriteLine("> TAP: {0}", Convert.ToHexString(args.Atr)); + var reader = new IsoReader(context, args.ReaderName, SCardShareMode.Shared, SCardProtocol.Any); + HandleTap(reader); + }; + monitor.CardRemoved += (_, args) => { + Console.WriteLine("< OFF"); + Console.WriteLine(); // Write a blank line between card taps for readability. + }; + + Console.WriteLine("[ Starting... ]"); + monitor.Start(readerNames); + while (true) { + Console.Read(); + } + } + } +}, listReadersOption); + +return await rootCommand.InvokeAsync(args); + +// Queries a card for data when one is tapped. +static void HandleTap(IsoReader reader) { + // Send a PCSC pseudo-APDU to query the ISO 14443 UID. + var rsp = reader.Transmit(new CommandApdu(IsoCase.Case2Short, SCardProtocol.Any) { + CLA = 0xFF, + Instruction = InstructionCode.GetData, + P1 = 0x00, + P2 = 0x00, + }); + if (!IsSucc(rsp)) { + Console.Error.WriteLine("--> Card Error: SW1={0} SW2={1}", (SW1Code)rsp.SW1, rsp.SW2); + return; + } + var uid = rsp.GetData(); + Console.WriteLine(" UID: {0}", Convert.ToHexString(uid)); +} + +// Was the command successful? +static bool IsSucc(Response rsp) { + return rsp.SW1 == (byte)SW1Code.Normal && rsp.SW2 == 0x00; +} diff --git a/shell.nix b/shell.nix index 5cb47bd..20998be 100644 --- a/shell.nix +++ b/shell.nix @@ -3,8 +3,12 @@ pkgs.mkShell { name = "afrapay"; packages = with pkgs; [ dotnet-sdk_7 + pcsclite # ESP32 tooling (for MateCard) platformio ]; + + # AfRApay.FTM tries to dlopen the pcsclite library at runtime. + LD_LIBRARY_PATH = [ "${pkgs.pcsclite.out}/lib" ]; }