commit 5bf2c990f56c1eee1871ae26188cc89570cc6dad
Author: Dominik Schmidt <dominik@schm1dt.ch>
Date: Sat, 16 May 2020 15:37:09 +0200
Initial commit
Diffstat:
6 files changed, 415 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,33 @@
+DMD ?= dmd
+DFLAGS ?= -g
+_DFLAGS = $(DFLAGS) -Isrc -L-lnfc -Dd=docs/
+INSTALL ?= install
+DESTDIR ?=
+PREFIX ?= /usr/local
+LIBDIR ?= lib
+INCDIR ?= include/d
+DOCDIR ?= share/docs/libnfc-d
+
+SRC = src/nfc/package.d src/nfc/c.d
+
+all: libnfc-d.a
+
+src/nfc/c.d: src-gen/c.d.gen src-gen/types.awk
+ bash $< > $@
+
+libnfc-d.a: $(patsubst %.d,%.o,$(SRC))
+ $(DMD) $(_DFLAGS) -lib -of=$@ $^
+
+examples/main: examples/main.o libnfc-d.a
+ $(DMD) $(_DFLAGS) -of=$@ $^
+
+%.o: %.d
+ $(DMD) $(_DFLAGS) -D -c -of=$@ $^
+
+install: libnfc-d.a $(SRC)
+ mkdir -p $(DESTDIR)$(PREFIX)/$(LIBDIR)
+ $(INSTALL) -m 644 libnfc-d.a $(DESTDIR)$(PREFIX)/$(LIBDIR)/libnfc-d.a
+ mkdir -p $(DESTDIR)$(PREFIX)/$(INCDIR)/nfc
+ $(INSTALL) -m 644 -t $(DESTDIR)$(PREFIX)/$(INCDIR)/nfc src/nfc/*.d
+ mkdir -p $(DESTDIR)$(PREFIX)/$(DOCDIR)
+ $(INSTALL) -m 644 -t $(DESTDIR)$(PREFIX)/$(DOCDIR) docs/package.html
diff --git a/docs/.keep b/docs/.keep
diff --git a/examples/main.d b/examples/main.d
@@ -0,0 +1,24 @@
+import nfc;
+import std.stdio;
+
+void main(){
+ ubyte[] a = null;
+ writeln(a[0..0]);
+
+ writeln("libnfc version ", nfcVersion());
+
+ Context c=new Context();
+
+ auto d = c.open();
+ d.init();
+
+ writeln("Device name: ", d.name);
+
+ Target t;
+ t.baud = t.Baud.B106;
+ auto params = &t.ISO14443A();
+
+ d.selectPassive(t);
+ writeln("The following ISO14443A tag was found: ", t.toString());
+
+}
diff --git a/src-gen/c.d.gen b/src-gen/c.d.gen
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+(
+cd "$(dirname "$0")"
+
+echo "module nfc.c;"
+echo "extern(C):"
+awk -f types.awk '/usr/include/nfc/nfc-types.h'
+echo "alias nfc_connstring = char[NFC_BUFSIZE_CONNSTRING];"
+echo "enum NFC {"
+awk '/#define/ {gsub("NFC_","",$2); print "\t" $2 " = "$3 ",\n"}' /usr/include/nfc/nfc.h
+echo "}"
+awk '/^NFC_EXPORT/ {gsub("const char","const(char)"); gsub("NFC_EXPORT ", ""); gsub("ATTRIBUTE_NONNULL\\([0-9]\\)",""); gsub("nfc_connstring","char*"); print $0;}' '/usr/include/nfc/nfc.h'
+) | sed 's/uint8_t/ubyte/g; s/\([^ ]\+\) \+\([^ \[]\+\)\[\([0-9]*\)\]/\1[\3] \2/g; s/\*\*const/\*\*/g; s/uint32_t/uint/g; s/(void)/()/g; s/\[\]/*/g'
diff --git a/src-gen/types.awk b/src-gen/types.awk
@@ -0,0 +1,8 @@
+BEGIN{started=0}
+/\}/ {gsub(";","",$2); print val " " $2 " {" buffer "\n}"; started=0; buffer="";}
+started == 1 {
+ buffer = buffer "\n" $0;
+}
+/#define/ { if(length($3)>0){print "enum " $2 " = " $3 ";"}}
+/typedef.*[^;]$/ {started=1; val=$2; buffer=""}
+/typedef struct.*;$/ {print "alias " $3 " = void;"}
diff --git a/src/nfc/package.d b/src/nfc/package.d
@@ -0,0 +1,336 @@
+/** A D-based wrapper of some libnfc-functionality
+ *
+ * Examples:
+ * -------------
+ * import nfc;
+ *
+ * writeln("libnfc version ", nfcVersion());
+ *
+ * Context c=new Context();
+ *
+ * auto d = c.open();
+ * d.init();
+ *
+ * writeln("Device name: ", d.name);
+ *
+ * Target t;
+ * t.baud = t.Baud.B106;
+ * auto params = &t.ISO14443A();
+ *
+ * d.selectPassive(t);
+ * writeln("The following ISO14443A tag was found: ", t.toString());
+ * -------------
+ */
+
+module nfc;
+import nfc.c;
+import std.string;
+import std.meta;
+import std.conv:to;
+import std.traits;
+import std.exception;
+import std.array : Appender;
+import core.time : Duration, dur;
+import std.algorithm.mutation:copy;
+
+
+private auto toConnString(Range)(Range r) if(isSomeString!Range){
+ nfc_connstring cs;
+ r.copy(cs[]);
+ return cs;
+}
+
+/** Return the libnfc version string
+ */
+string nfcVersion(){
+ return cast(string)(nfc_version().fromStringz());
+}
+
+unittest{
+ assert(nfcVersion().length > 0);
+}
+
+class NfcException : Exception{
+ this(A...)(A a){
+ super(a);
+ }
+}
+
+/** The main class encapsulating the libnfc context
+ *
+ * This class needs to be instantiated before doing anything else.
+ * It can split off devices by using Context.open
+*/
+
+class Context{
+ nfc_context *ctx;
+
+ this(){
+ nfc_init(&ctx);
+ if(ctx is null){
+ throw new Exception("Malloc");
+ }
+ }
+
+ ~this(){
+ nfc_exit(ctx);
+ ctx = null;
+ }
+
+ /** Open the default device on the system
+ */
+ Device open(){
+ return new Device(enforce!NfcException(nfc_open(ctx, cast(char*)null), "Could not open default NFC device"));
+ }
+
+ /** Open a device given a connection string
+ */
+ Device open(string connstring){
+ return new Device(enforce!NfcException(nfc_open(ctx, connstring.toConnString.ptr), "Could not open NFC device "~connstring));
+ }
+}
+
+unittest{
+ Context c = new Context();
+}
+
+/** The class denoting the target of a NFC device
+ *
+ * This encapsulates both the modulation and target struct of libnfc.
+ * The modulation can be set by the .baud property, the modulation type
+ * by calling the corresponding function (e.g. for ISO14443A t.ISO14443A()).
+ * This call additionally returns a reference to the configuration struct.
+ *
+ * For all the possibilities, refer to the struct nfc_modulation_type
+ */
+struct Target{
+ private nfc_target nt;
+
+ alias nt this;
+
+ static private string modulationName(string name)(){
+ return name[4..$-5].toUpper();
+ }
+
+ static private string modulationType(string name)(){
+ return "NMT_"~modulationName!name;
+ }
+
+ template ModulationType(string name){
+ alias ModulationType = mixin("typeof(nt.nti."~name~")");
+ }
+
+ static private string funcDef(string name)(){
+ alias T = ModulationType!name;
+ return "ref "~modulationName!(T.stringof)~"(){
+ nt.nm.nmt = nfc_modulation_type."~modulationType!(T.stringof)~";
+ return nt.nti."~name~";
+ }";
+ }
+
+ static foreach(T; FieldNameTuple!(typeof(nt.nti))){
+ mixin(funcDef!T);
+ }
+
+ /**
+ * The possible baud rates for communication
+ */
+ enum Baud{
+ UNDEF = nfc_baud_rate.NBR_UNDEFINED,
+ B106 = nfc_baud_rate.NBR_106,
+ B212 = nfc_baud_rate.NBR_212,
+ B424 = nfc_baud_rate.NBR_424,
+ B847 = nfc_baud_rate.NBR_847,
+ }
+
+ /**
+ * Returns the baud rate
+ */
+ Baud baud() const{
+ return cast(Baud) nt.nm.nbr;
+ }
+
+ /**
+ * Sets the baud rate
+ */
+ Baud baud(Baud b){
+ nt.nm.nbr = cast(nfc_baud_rate)b;
+ return baud();
+ }
+
+ /**
+ * Returns the modulation struct
+ */
+ auto modulation(){
+ return nt.nm;
+ }
+
+ /**
+ * Returns a pointer to the c target struct
+ */
+ auto ptr(){
+ return &nt;
+ }
+
+ /**
+ * Prints the internal variables independant of the modulation type
+ */
+ void toString(scope void delegate(const(char)[]) sink) const{
+ string card = nt.nm.nmt.to!string;
+ string baud = baud().to!string;
+ sink(card[4..$]);
+ sink(", Baud ");
+ sink(baud);
+
+ void printer(T)(T t){
+ foreach(mem; FieldNameTuple!T){
+ auto r = mixin("t."~mem);
+ sink("\n\t");
+ sink(mem);
+ sink(": ");
+ sink(r.to!string);
+ }
+
+ }
+
+ lbrk:switch(nt.nm.nmt){
+ static foreach(name; FieldNameTuple!(typeof(nt.nti))){
+ mixin(`case nfc_modulation_type.`~modulationType!((ModulationType!name).stringof)~`:
+ printer(nt.nti.`~name~`);
+ break lbrk;`);
+ }
+ default:
+ sink("Not implemented");
+ break;
+ }
+ }
+
+ string toString() const{
+ Appender!string app;
+ toString(x=>app~=x);
+ return app.data;
+ }
+}
+
+/** A nfc reader/writer device
+ *
+ * This is typically instantiated via the Context class, using Context.open
+ */
+class Device{
+ private nfc_device *dev;
+
+
+ /** Exception thrown on error
+ */
+ class NfcException : Exception{
+ this(string msg, string file=__FILE__, size_t line = __LINE__, Throwable next=null){
+ super(msg~": "~error(), file, line, next);
+ }
+ }
+
+ private int check(int retval, string msg="Call to libnfc failed", string file=__FILE__, size_t line=__LINE__){
+ if(retval < NFC.SUCCESS){
+ throw new NfcException(msg, file, line);
+ }
+ return retval;
+ }
+
+ package this(nfc_device *dev){
+ assert(dev !is null);
+ this.dev = dev;
+ }
+
+ ~this(){
+ nfc_close(dev);
+ dev = null;
+ }
+
+ /** Returns the device name
+ */
+ string name(){
+ auto s = nfc_device_get_name(dev);
+ return cast(string)(s.fromStringz());
+ }
+
+ /** Initializes the initiator
+ *
+ * Params:
+ * secure = Whether to initialize a secure element or not
+ */
+ void init(bool secure = false){
+ if(secure){
+ check(nfc_initiator_init_secure_element(dev));
+ }
+ else{
+ check(nfc_initiator_init(dev));
+ }
+ }
+
+ /** Returns a list of at most ntargets targets found by the device
+ */
+ Target[] listPassiveTargets(Target t, int ntargets){
+ return listPassiveTargets(t, new Target[ntargets]);
+ }
+
+ /** Reads all targets found by the device into the given array
+ */
+ Target[] listPassiveTargets(Target t, Target[] targets){
+ auto ret = check(nfc_initiator_list_passive_targets(dev, t.modulation, cast(nfc_target*)targets.ptr, targets.length));
+ return targets[0..ret];
+ }
+
+ /** Checks whether a target is still present
+ *
+ * The target needs to be selected prior to calling this function
+ */
+ bool targetIsPresent(Target t){
+ auto ret = nfc_initiator_target_is_present(dev, t.ptr);
+ return ret == NFC.SUCCESS;
+ }
+
+ /** Transmits the bytes in send and reads the bytes in receive
+ *
+ * Params:
+ * send = The bytes to send, or null if none
+ * receive = The bytes to receive, or null if none
+ * timeout = The time to wait for the response, infinite if zero
+ */
+ ubyte[] transceive(ubyte[] send, ubyte[] receive = null, Duration timeout=Duration.zero){
+ int ret = check(nfc_initiator_transceive_bytes(dev,
+ send.ptr,
+ send.length,
+ receive.ptr,
+ receive.length,
+ cast(int)timeout.total!"msecs"
+ ));
+
+ return receive[0..ret];
+ }
+
+ /** Selects a device for communication
+ */
+ int selectPassive(ref Target t, ubyte[] initData = null){
+ int ret = check(nfc_initiator_select_passive_target(dev, t.modulation, initData.ptr, initData.length, t.ptr));
+ return ret;
+ }
+
+ /** Polls for devices, and returns the number of devices found, as
+ * well as one of them in t
+ *
+ * Params:
+ * modulations = an array of modulations to try
+ * t = The first found target
+ */
+ int poll(nfc_modulation[] modulations, out Target t, ubyte polling=0xFF, Duration period=dur!"seconds"(1)){
+ auto period_byte = period.total!"msecs"/150;
+ assert(period_byte >= 1 && period_byte <= 0xF);
+ int ret = check(nfc_initiator_poll_target(dev, modulations.ptr, modulations.length, polling, cast(ubyte)period_byte, t.ptr));
+ return ret;
+ }
+
+ /** Returns the last error from the device */
+ private string error() const{
+ auto s = nfc_strerror(dev);
+ return cast(string)(s.fromStringz());
+ }
+}