libnfc-d

libnfc bindings for DLang
git clone git://xatko.vsos.ethz.ch/libnfc-d.git
Log | Files | Refs

commit 5bf2c990f56c1eee1871ae26188cc89570cc6dad
Author: Dominik Schmidt <dominik@schm1dt.ch>
Date:   Sat, 16 May 2020 15:37:09 +0200

Initial commit

Diffstat:
AMakefile | 33+++++++++++++++++++++++++++++++++
Adocs/.keep | 0
Aexamples/main.d | 24++++++++++++++++++++++++
Asrc-gen/c.d.gen | 14++++++++++++++
Asrc-gen/types.awk | 8++++++++
Asrc/nfc/package.d | 336+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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()); + } +}