diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | CMakeLists.txt | 26 | ||||
-rw-r--r-- | conf.d/cmake/config.cmake | 204 | ||||
-rw-r--r-- | htdocs/nfc/AFB-websock.js | 174 | ||||
-rw-r--r-- | htdocs/nfc/binding-debug.css | 61 | ||||
-rw-r--r-- | htdocs/nfc/index.html | 31 | ||||
-rw-r--r-- | htdocs/nfc/nfc-binding.js | 156 | ||||
-rw-r--r-- | src/CMakeLists.txt | 44 | ||||
-rw-r--r-- | src/api.c | 63 | ||||
-rw-r--r-- | src/libnfc_reader.c | 366 | ||||
-rw-r--r-- | src/libnfc_reader.h | 12 | ||||
-rw-r--r-- | src/nfc-binding.c | 113 | ||||
-rw-r--r-- | src/nfc-binding.h | 17 | ||||
-rw-r--r-- | src/stringutils.h | 41 |
15 files changed, 1314 insertions, 0 deletions
@@ -50,3 +50,6 @@ modules.order Module.symvers Mkfile.old dkms.conf + +# app-templates submodule +app-templates/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e07cae1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "conf.d/app-templates"] + path = conf.d/app-templates + url = https://gerrit.automotivelinux.org/gerrit/p/apps/app-templates.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cb875ab --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Loïc Collignon <loic.collignon@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + +CMAKE_MINIMUM_REQUIRED(VERSION 3.3) + +option(USE_LIBNFC "Enable or disable the 'libnfc' usage." ON) + +include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/cmake/config.cmake) + +install(DIRECTORY htdocs/nfc DESTINATION htdocs) +install(DIRECTORY etc DESTINATION ./) diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake new file mode 100644 index 0000000..b9e60d9 --- /dev/null +++ b/conf.d/cmake/config.cmake @@ -0,0 +1,204 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll <fulup@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + +# Project Info +# ------------------ +set(PROJECT_NAME nfc-binding) +set(PROJECT_VERSION "0.1") +set(PROJECT_PRETTY_NAME "NFC Binding") +set(PROJECT_DESCRIPTION "Abstract NFC readers.") +set(PROJECT_URL "https://gerrit.automotivelinux.org/gerrit/apps/app-templates") +set(PROJECT_ICON "icon.png") +set(PROJECT_AUTHOR "Collignon, Loïc") +set(PROJECT_AUTHOR_MAIL "loic.collignon@iot.bzh") +set(PROJECT_LICENSE "APL2.0") +set(PROJECT_LANGUAGES,"C") + +# Where are stored default templates files from submodule or subtree app-templates in your project tree +# relative to the root project directory +set(PROJECT_APP_TEMPLATES_DIR "conf.d/app-templates") + +# Where are stored your external libraries for your project. This is 3rd party library that you don't maintain +# but used and must be built and linked. +# set(PROJECT_LIBDIR "libs") + +# Where are stored data for your application. Pictures, static resources must be placed in that folder. +# set(PROJECT_RESOURCES "data") + +# Which directories inspect to find CMakeLists.txt target files +# set(PROJECT_SRC_DIR_PATTERN "*") + +# Compilation Mode (DEBUG, RELEASE) +# ---------------------------------- +set(CMAKE_BUILD_TYPE "DEBUG") +set(USE_EFENCE 1) + +# Kernel selection if needed. You can choose between a +# mandatory version to impose a minimal version. +# Or check Kernel minimal version and just print a Warning +# about missing features and define a preprocessor variable +# to be used as preprocessor condition in code to disable +# incompatibles features. Preprocessor define is named +# KERNEL_MINIMAL_VERSION_OK. +# +# NOTE*** FOR NOW IT CHECKS KERNEL Yocto environment and +# Yocto SDK Kernel version. +# ----------------------------------------------- +#set (kernel_mandatory_version 4.8) +#set (kernel_minimal_version 4.8) + +# Compiler selection if needed. Impose a minimal version. +# ----------------------------------------------- +set (gcc_minimal_version 4.9) + +# PKG_CONFIG required packages +# ----------------------------- +set (PKG_REQUIRED_LIST + json-c + libsystemd>=222 + afb-daemon + libmicrohttpd>=0.9.55 +) + +# Prefix path where will be installed the files +# Default: /usr/local (need root permission to write in) +# ------------------------------------------------------ +#set(CMAKE_INSTALL_PREFIX $ENV{HOME}/opt) + +# Customize link option +# ----------------------------- +list(APPEND link_libraries -lnfc) + +# Compilation options definition +# Use CMake generator expressions to specify only for a specific language +# Values are prefilled with default options that is currently used. +# Either separate options with ";", or each options must be quoted separately +# DO NOT PUT ALL OPTION QUOTED AT ONCE , COMPILATION COULD FAILED ! +# ---------------------------------------------------------------------------- +#set(COMPILE_OPTIONS +# -Wall +# -Wextra +# -Wconversion +# -Wno-unused-parameter +# -Wno-sign-compare +# -Wno-sign-conversion +# -Werror=maybe-uninitialized +# -Werror=implicit-function-declaration +# -ffunction-sections +# -fdata-sections +# -fPIC +# CACHE STRING "Compilation flags") +#set(C_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C language.") +#set(CXX_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C++ language.") +#set(PROFILING_COMPILE_OPTIONS +# -g +# -O0 +# -pg +# -Wp,-U_FORTIFY_SOURCE +# CACHE STRING "Compilation flags for PROFILING build type.") +#set(DEBUG_COMPILE_OPTIONS +# -g +# -ggdb +# -Wp,-U_FORTIFY_SOURCE +# CACHE STRING "Compilation flags for DEBUG build type.") +#set(CCOV_COMPILE_OPTIONS +# -g +# -O2 +# --coverage +# CACHE STRING "Compilation flags for CCOV build type.") +#set(RELEASE_COMPILE_OPTIONS +# -g +# -O2 +# CACHE STRING "Compilation flags for RELEASE build type.") + +# (BUG!!!) as PKG_CONFIG_PATH does not work [should be an env variable] +# --------------------------------------------------------------------- +set(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}/lib64/pkgconfig ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig) +set(LD_LIBRARY_PATH ${CMAKE_INSTALL_PREFIX}/lib64 ${CMAKE_INSTALL_PREFIX}/lib) + +# Optional location for config.xml.in +# ----------------------------------- +#set(WIDGET_ICON conf.d/wgt/${PROJECT_ICON} CACHE PATH "Path to the widget icon") +#set(WIDGET_CONFIG_TEMPLATE ${CMAKE_CURRENT_SOURCE_DIR}/conf.d/wgt/config.xml.in CACHE PATH "Path to widget config file template (config.xml.in)") + +# Mandatory widget Mimetype specification of the main unit +# -------------------------------------------------------------------------- +# Choose between : +#- text/html : HTML application, +# content.src designates the home page of the application +# +#- application/vnd.agl.native : AGL compatible native, +# content.src designates the relative path of the binary. +# +# - application/vnd.agl.service: AGL service, content.src is not used. +# +#- ***application/x-executable***: Native application, +# content.src designates the relative path of the binary. +# For such application, only security setup is made. +# +set(WIDGET_TYPE MimeType_Not_Set) + +# Mandatory Widget entry point file of the main unit +# -------------------------------------------------------------- +# This is the file that will be executed, loaded, +# at launch time by the application framework. +# +set(WIDGET_ENTRY_POINT EntryPoint_Path_Not_Set) + +# Optional dependencies order +# --------------------------- +#set(EXTRA_DEPENDENCIES_ORDER) + +# Optional Extra global include path +# ----------------------------------- +#set(EXTRA_INCLUDE_DIRS) + +# Optional extra libraries +# ------------------------- +#set(EXTRA_LINK_LIBRARIES) + +# Optional force binding Linking flag +# ------------------------------------ +# set(BINDINGS_LINK_FLAG LinkOptions ) + +# Optional force package prefix generation, like widget +# ----------------------------------------------------- +# set(PKG_PREFIX DestinationPath) + +# Optional Application Framework security token +# and port use for remote debugging. +#------------------------------------------------------------ +set(AFB_TOKEN "" CACHE PATH "Default binder security token") +set(AFB_REMPORT "1234" CACHE PATH "Default binder listening port") + +# Print a helper message when every thing is finished +# ---------------------------------------------------- +set(CLOSING_MESSAGE "Typical binding launch: afb-daemon --port=${AFB_REMPORT} --workdir=${CMAKE_BINARY_DIR}/package --ldpaths=lib --roothttp=htdocs --token=\"${AFB_TOKEN}\" --tracereq=common --verbose") +set(PACKAGE_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt") + +# Optional schema validator about now only XML, LUA and JSON +# are supported +#------------------------------------------------------------ +#set(LUA_CHECKER "luac" "-p" CACHE STRING "LUA compiler") +#set(XML_CHECKER "xmllint" CACHE STRING "XML linter") +#set(JSON_CHECKER "json_verify" CACHE STRING "JSON linter") + +# This include is mandatory and MUST happens at the end +# of this file, else you expose you to unexpected behavior +# ----------------------------------------------------------- +include(${PROJECT_APP_TEMPLATES_DIR}/cmake/common.cmake) diff --git a/htdocs/nfc/AFB-websock.js b/htdocs/nfc/AFB-websock.js new file mode 100644 index 0000000..08a7ffe --- /dev/null +++ b/htdocs/nfc/AFB-websock.js @@ -0,0 +1,174 @@ +AFB = function(base, initialtoken){ + +var urlws = "ws://"+window.location.host+"/"+base; +var urlhttp = "http://"+window.location.host+"/"+base; + +/*********************************************/ +/**** ****/ +/**** AFB_context ****/ +/**** ****/ +/*********************************************/ +var AFB_context; +{ + var UUID = undefined; + var TOKEN = initialtoken; + + var context = function(token, uuid) { + this.token = token; + this.uuid = uuid; + } + + context.prototype = { + get token() {return TOKEN;}, + set token(tok) {if(tok) TOKEN=tok;}, + get uuid() {return UUID;}, + set uuid(id) {if(id) UUID=id;} + }; + + AFB_context = new context(); +} +/*********************************************/ +/**** ****/ +/**** AFB_websocket ****/ +/**** ****/ +/*********************************************/ +var AFB_websocket; +{ + var CALL = 2; + var RETOK = 3; + var RETERR = 4; + var EVENT = 5; + + var PROTO1 = "x-afb-ws-json1"; + + AFB_websocket = function(onopen, onabort) { + var u = urlws; + if (AFB_context.token) { + u = u + '?x-afb-token=' + AFB_context.token; + if (AFB_context.uuid) + u = u + '&x-afb-uuid=' + AFB_context.uuid; + } + this.ws = new WebSocket(u, [ PROTO1 ]); + this.pendings = {}; + this.awaitens = {}; + this.counter = 0; + this.ws.onopen = onopen.bind(this); + this.ws.onerror = onerror.bind(this); + this.ws.onclose = onclose.bind(this); + this.ws.onmessage = onmessage.bind(this); + this.onopen = onopen; + this.onabort = onabort; + this.onclose = onabort; + } + + function onerror(event) { + var f = this.onabort; + if (f) { + delete this.onopen; + delete this.onabort; + f && f(this); + } + this.onerror && this.onerror(this); + } + + function onopen(event) { + var f = this.onopen; + delete this.onopen; + delete this.onabort; + f && f(this); + } + + function onclose(event) { + for (var id in this.pendings) { + var ferr = this.pendings[id].onerror; + ferr && ferr(null, this); + } + this.pendings = {}; + this.onclose && this.onclose(); + } + + function fire(awaitens, name, data) { + var a = awaitens[name]; + if (a) + a.forEach(function(handler){handler(data);}); + var i = name.indexOf("/"); + if (i >= 0) { + a = awaitens[name.substring(0,i)]; + if (a) + a.forEach(function(handler){handler(data);}); + } + a = awaitens["*"]; + if (a) + a.forEach(function(handler){handler(data);}); + } + + function reply(pendings, id, ans, offset) { + if (id in pendings) { + var p = pendings[id]; + delete pendings[id]; + var f = p[offset]; + f(ans); + } + } + + function onmessage(event) { + var obj = JSON.parse(event.data); + var code = obj[0]; + var id = obj[1]; + var ans = obj[2]; + AFB_context.token = obj[3]; + switch (code) { + case RETOK: + reply(this.pendings, id, ans, 0); + break; + case RETERR: + reply(this.pendings, id, ans, 1); + break; + case EVENT: + default: + fire(this.awaitens, id, ans); + break; + } + } + + function close() { + this.ws.close(); + this.onabort(); + } + + function call(method, request) { + return new Promise((function(resolve, reject){ + var id, arr; + do { + id = String(this.counter = 4095 & (this.counter + 1)); + } while (id in this.pendings); + this.pendings[id] = [ resolve, reject ]; + arr = [CALL, id, method, request ]; + if (AFB_context.token) arr.push(AFB_context.token); + this.ws.send(JSON.stringify(arr)); + }).bind(this)); + } + + function onevent(name, handler) { + var id = name; + var list = this.awaitens[id] || (this.awaitens[id] = []); + list.push(handler); + } + + AFB_websocket.prototype = { + close: close, + call: call, + onevent: onevent + }; +} +/*********************************************/ +/**** ****/ +/**** ****/ +/**** ****/ +/*********************************************/ +return { + context: AFB_context, + ws: AFB_websocket +}; +}; + diff --git a/htdocs/nfc/binding-debug.css b/htdocs/nfc/binding-debug.css new file mode 100644 index 0000000..f41c940 --- /dev/null +++ b/htdocs/nfc/binding-debug.css @@ -0,0 +1,61 @@ +#debug-panel { + float: right; +} + +#debug-panel.collapsed { + background-color: transparent; + overflow-x: hidden; +} + +#debug-panel.expanded { + background-color: lightyellow; + max-width: 25%; + overflow-x: scroll; +} + +#debug-panel.collapsed > #debug-panel-collapse { + display: none; +} + +#debug-panel.expanded > #debug-panel-collapse { + display: block; +} + +#debug-panel.collapsed > #debug-panel-expand { + display: block; +} + +#debug-panel.expanded > #debug-panel-expand { + display: none; +} + +#debug-panel.expanded > #debug-panel-content { + display: block; +} + +#debug-panel.collapsed > #debug-panel-content { + display: none; +} + +.json-key { + color: cornflowerblue; + font-weight: bold; +} + +.json-string { + color: crimson; +} + +.json-number { + color: sandybrown; +} + +.json-boolean { + color: fuchsia; + font-weight: bold; +} + +.json-null { + color: black; + font-weight: bold; +} diff --git a/htdocs/nfc/index.html b/htdocs/nfc/index.html new file mode 100644 index 0000000..999dc0d --- /dev/null +++ b/htdocs/nfc/index.html @@ -0,0 +1,31 @@ +<!doctype html> +<html> + <head> + <title>nfc-binding</title> + <meta charset="UTF-8"> + + <!-- + <link rel="stylesheet" type="text/css" href="prettify.css"> + <script type="text/javascript" src="prettify.js"></script> + --> + + <script type="text/javascript" src="AFB-websock.js"></script> + <script type="text/javascript" src="nfc-binding.js"></script> + <link rel="stylesheet" type="text/css" href="binding-debug.css" /> + </head> + + <body onload="init();" id="app-body"> + <div id="debug-panel-container"></div> + <h1>hl-persistence-binding</h1> + <p> + <ul> + <li><button onclick="subscribe();">Subscribe</button></li> + <li><button onclick="unsubscribe();">Unsubscribe</button></li> + <li><button onclick="list_devices();">list-devices</button></li> + <li><button onclick="list_devices_capabilities();">list-device-capabilities</button></li> + <li><button onclick="start_polling();">start-polling</button></li> + <li><button onclick="stop_polling();">stop-polling</button></li> + </ul> + </p> + </body> +</html> diff --git a/htdocs/nfc/nfc-binding.js b/htdocs/nfc/nfc-binding.js new file mode 100644 index 0000000..375d036 --- /dev/null +++ b/htdocs/nfc/nfc-binding.js @@ -0,0 +1,156 @@ +var afb = new AFB("api", "mysecret"); +var ws; + +function add_debbug_panel() { + + if (document.getElementById("debug-panel")) + return; + + var itm = document.getElementById("debug-panel-container"); + if (itm) + { + var pnl = + "<div id=\"debug-panel\" class=\"expanded\">\n" + + " <button id=\"debug-panel-collapse\" onclick=\"debug_panel_collapse();\">></button>\n" + + " <button id=\"debug-panel-expand\" onclick=\"debug_panel_expand();\"><</button>\n" + + " <div id=\"debug-panel-content\">\n" + + " <h1>Debug</h1>\n" + + " <h2>Call</h2><div id=\"debug-panel-call\">\n" + + " <ul>\n" + + " <li><strong>api : </strong><span id=\"debug-panel-call-id\"></span></li>\n" + + " <li><strong>verb : </strong><span id=\"debug-panel-call-verb\"></span></li>\n" + + " <li><strong>query : </strong></li>\n" + + " </ul>\n" + + " <pre id=\"debug-panel-call-query\"></pre>\n" + + " </div>\n" + + " <h2>Response</h2><pre id=\"debug-panel-response\"></pre>\n" + + " <h2>Event</h2><pre id=\"debug-panel-event\"></pre>\n" + + " </div>\n" + + "</div>\n"; + itm.insertAdjacentHTML("afterbegin", pnl); + } +} + +function createClass(name,rules) { + var style = document.createElement('style'); + style.type = 'text/css'; + document.getElementsByTagName('head')[0].appendChild(style); + if(!(style.sheet||{}).insertRule) + (style.styleSheet || style.sheet).addRule(name, rules); + else + style.sheet.insertRule(name+"{"+rules+"}",0); +} + +function syntaxHighlight(json) { + if (typeof json != 'string') + json = JSON.stringify(json, undefined, 2); + + json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { + var cls = 'json-number'; + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'json-key'; + } else { + cls = 'json-string'; + } + } else if (/true|false/.test(match)) { + cls = 'json-boolean'; + } else if (/null/.test(match)) { + cls = 'json-null'; + } + return '<span class="' + cls + '">' + match + '</span>'; + }); +} + +function set_item_html(id, text) +{ + var itm = document.getElementById(id); + if (itm) itm.innerHTML = text; +} + +function set_item_text(id, text) +{ + var itm = document.getElementById(id); + if (itm) itm.innerText = text; +} + +function debug_panel_collapse() { + var pnl = document.getElementById('debug-panel'); + if (pnl) + { + pnl.classList.remove('expanded'); + pnl.classList.add('collapsed'); + } +} + +function debug_panel_expand() { + var pnl = document.getElementById('debug-panel'); + if (pnl) + { + pnl.classList.remove('collapsed'); + pnl.classList.add('expanded'); + } +} + +function init() { + add_debbug_panel(); + ws = new afb.ws(onopen, onabort); +} + +function onopen() { + //callbinder("ll-auth", "getuser", ""); + ws.onevent("*", gotevent); +} + +function onabort() { +} + +function replyok(obj) { + console.log("replyok:" + JSON.stringify(obj)); + set_item_html("debug-panel-response", syntaxHighlight(JSON.stringify(obj, null, 4))); +} + +function replyerr(obj) { + console.log("replyerr:" + JSON.stringify(obj)); + set_item_html("debug-panel-response", syntaxHighlight(JSON.stringify(obj, null, 4))); +} + +function gotevent(obj) { + console.log("gotevent:" + JSON.stringify(obj)); + set_item_html("debug-panel-event", syntaxHighlight(JSON.stringify(obj, null, 4))); +} + +function callbinder(api, verb, query) { + console.log ("subscribe api="+api+" verb="+verb+" query=" +query); + + set_item_text("debug-panel-call-api", api); + set_item_text("debug-panel-call-verb", verb); + set_item_html("debug-panel-call-query", syntaxHighlight(JSON.stringify(query, null, 4))); + + ws.call(api+"/"+verb, query).then(replyok, replyerr); +} + +function subscribe() { + callbinder("nfc", "subscribe", {}); +} + +function unsubscribe() { + callbinder("nfc", "unsubscribe", {}); +} + +function list_devices() { + callbinder("nfc", "list-devices", {}); +} + +function list_devices_capabilities() { + callbinder("nfc", "list-devices-capabilities", {}); +} + +function start_polling() { + callbinder("nfc", "start-polling", {}); +} + +function stop_polling() { + callbinder("nfc", "stop-polling", {}); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..0c9021c --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,44 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Loïc Collignon <loic.collignon@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + +CMAKE_MINIMUM_REQUIRED(VERSION 3.3) + +PROJECT_TARGET_ADD(nfc-binding) + +set(NFC_BINDING_SOURCES api.c nfc-binding.c) + +if (USE_LIBNFC) + set(NFC_BINDING_SOURCES ${NFC_BINDING_SOURCES} libnfc_reader.c) + add_definitions(-DUSE_LIBNFC=1) +endif() +message(STATUS "libnfc enabled: ${USE_LIBNFC}") + +add_library(${TARGET_NAME} MODULE ${NFC_BINDING_SOURCES}) +target_link_libraries(${TARGET_NAME} ${link_libraries}) + +add_custom_command(TARGET ${TARGET_NAME} + PRE_BUILD + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/../package/htdocs + COMMAND cp -rv ${CMAKE_CURRENT_SOURCE_DIR}/../htdocs ${CMAKE_CURRENT_BINARY_DIR}/../package/) + +SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + PREFIX "afb-" + LABELS "BINDING" + LINK_FLAGS ${BINDINGS_LINK_FLAG} + OUTPUT_NAME ${TARGET_NAME}) + diff --git a/src/api.c b/src/api.c new file mode 100644 index 0000000..e508e86 --- /dev/null +++ b/src/api.c @@ -0,0 +1,63 @@ +#include "nfc-binding.h" + +/* +static const struct afb_auth nfc_auths[] = { +}; +*/ + +static const struct afb_verb_v2 nfc_verbs[] = { + { + .verb = "subscribe", + .callback = verb_subscribe, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "unsubscribe", + .callback = verb_unsubscribe, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "list-devices", + .callback = verb_list_devices, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "list-devices-capabilities", + .callback = verb_list_devices_capabilities, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "start-polling", + .callback = verb_start_polling, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "stop-polling", + .callback = verb_stop_polling, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { .verb = NULL } +}; + +const struct afb_binding afbBindingV2 = { + .api = "nfc", + .specification = NULL, + .info = NULL, + .verbs = nfc_verbs, + .preinit = NULL, + .init = init, + .onevent = NULL, + .noconcurrency = 0 +}; diff --git a/src/libnfc_reader.c b/src/libnfc_reader.c new file mode 100644 index 0000000..8b11f0c --- /dev/null +++ b/src/libnfc_reader.c @@ -0,0 +1,366 @@ +#include "nfc-binding.h" + +#include <string.h> +#include <sys/types.h> +#include <pthread.h> + +// FIXME: It compile without these lines, but KDevelop complains about pthread_t being undeclared. +#ifndef _BITS_PTHREADTYPES +typedef unsigned long int pthread_t; +#endif + +#include <nfc/nfc.h> +#include "libnfc_reader.h" +#include "stringutils.h" + +extern struct afb_event on_nfc_read_event; + +#define MAX_NFC_DEVICE_COUNT 8 +#define MAX_NFC_MODULATIONS 8 +#define MAX_NFC_BAUDRATES 8 +#define POLL_NUMBER 0xff +#define POLL_PERIOD 0x05 + +typedef struct libnfc_device_tag +{ + pthread_t poller; + nfc_device* device; + nfc_connstring name; + + nfc_modulation* modulations; + size_t modulations_count; +} libnfc_device; + +typedef struct libnfc_context_tag +{ + nfc_context* context; + libnfc_device* devices; + size_t devices_count; +} libnfc_context; + +static libnfc_context libnfc = { + .context = NULL, + .devices = NULL, + .devices_count = 0 +}; + +void libnfc_polling_error(int code) +{ + switch(code) + { + case NFC_EIO: + AFB_ERROR("libnfc: polling failed with NFC_EIO (%d) code: Input / output error, device may not be usable anymore without re-open it!", code); + break; + case NFC_EINVARG: + AFB_ERROR("libnfc: polling failed with NFC_EINVARG (%d) code: Invalid argument(s)!", code); + break; + case NFC_EDEVNOTSUPP: + AFB_ERROR("libnfc: polling failed with NFC_EDEVNOTSUPP (%d) code: Operation not supported by device!", code); + break; + case NFC_ENOTSUCHDEV: + AFB_ERROR("libnfc: polling failed with NFC_ENOTSUCHDEV (%d) code: No such device!", code); + break; + case NFC_EOVFLOW: + AFB_ERROR("libnfc: polling failed with NFC_EOVFLOW (%d) code: Buffer overflow!", code); + break; + case NFC_ETIMEOUT: + AFB_ERROR("libnfc: polling failed with NFC_ETIMEOUT (%d) code: Operation timed out!", code); + break; + case NFC_EOPABORTED: + AFB_ERROR("libnfc: polling failed with NFC_EOPABORTED (%d) code: Operation aborted (by user)!", code); + break; + case NFC_ENOTIMPL: + AFB_ERROR("libnfc: polling failed with NFC_ENOTIMPL (%d) code: Not (yet) implemented!", code); + break; + case NFC_ETGRELEASED: + AFB_ERROR("libnfc: polling failed with NFC_ETGRELEASED (%d) code: Target released!", code); + break; + case NFC_ERFTRANS: + AFB_ERROR("libnfc: polling failed with NFC_ERFTRANS (%d) code: Error while RF transmission!", code); + break; + case NFC_EMFCAUTHFAIL: + AFB_ERROR("libnfc: polling failed with NFC_EMFCAUTHFAIL (%d) code: MIFARE Classic: authentication failed!", code); + break; + case NFC_ESOFT: + AFB_ERROR("libnfc: polling failed with NFC_ESOFT (%d) code: Software error (allocation, file/pipe creation, etc.)!", code); + break; + case NFC_ECHIP: + AFB_ERROR("libnfc: polling failed with NFC_ECHIP (%d) code: Device's internal chip error!", code); + break; + default: + AFB_ERROR("libnfc: polling failed with unknown code: %d!", code); + break; + } +} + +void* libnfc_reader_main(void* arg) +{ + libnfc_device* device; + nfc_target nt; + int polled_target_count; + + // Read datas + const char* mt; + char* field1; + char* field2; + char* field3; + char* field4; + struct json_object* result; + + device = (libnfc_device*)arg; + + while(device->device) + { + polled_target_count = nfc_initiator_poll_target(device->device, device->modulations, device->modulations_count, POLL_NUMBER, POLL_PERIOD, &nt); + switch(polled_target_count) + { + case 0: + AFB_INFO("libnfc: polling done with no result."); + break; + + case 1: + mt = str_nfc_modulation_type(nt.nm.nmt); + AFB_NOTICE("libnfc: polling done with one result of type %s.", mt); + switch(nt.nm.nmt) + { + case NMT_ISO14443A: + field1 = to_hex_string(nt.nti.nai.abtAtqa, 2); + field2 = to_hex_string(&nt.nti.nai.btSak, 1); + field3 = to_hex_string(nt.nti.nai.abtUid, nt.nti.nai.szUidLen); + field4 = to_hex_string(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen); + + result = json_object_new_object(); + json_object_object_add(result, "Type", json_object_new_string(mt)); + if (field1) json_object_object_add(result, "ATQA", json_object_new_string(field1)); + if (field2) json_object_object_add(result, "SAK", json_object_new_string(field2)); + if (field3) json_object_object_add(result, "UID", json_object_new_string(field3)); + if (field4) json_object_object_add(result, "ATS", json_object_new_string(field4)); + + break; + case NMT_ISO14443B: + field1 = to_hex_string(nt.nti.nbi.abtPupi, 4); + field2 = to_hex_string(nt.nti.nbi.abtApplicationData, 4); + field3 = to_hex_string(nt.nti.nbi.abtProtocolInfo, 3); + field4 = to_hex_string(&nt.nti.nbi.ui8CardIdentifier, 1); + + result = json_object_new_object(); + json_object_object_add(result, "Type", json_object_new_string(mt)); + if (field1) json_object_object_add(result, "PUPI", json_object_new_string(field1)); + if (field2) json_object_object_add(result, "Application Data", json_object_new_string(field2)); + if (field3)json_object_object_add(result, "Protocol Info", json_object_new_string(field3)); + if (field4)json_object_object_add(result, "Card Id", json_object_new_string(field4)); + + break; + default: + AFB_WARNING("libnfc: unsupported modulation type: %s.", mt); + break; + } + + if (result) + { + AFB_NOTICE("libnfc: push tag read event=%s", json_object_to_json_string(result)); + afb_event_push(on_nfc_read_event, result); + } + if (field1) free(field1); + if (field2) free(field2); + if (field3) free(field3); + if (field4) free(field4); + + break; + + default: + if (polled_target_count < 0) + libnfc_polling_error(polled_target_count); + else + AFB_WARNING("libnfc: polling done with unsupported result count: %d.", polled_target_count); + break; + } + } + + return NULL; +} + +/// @brief Start the libnfc context. +/// @return An exit code, @c EXIT_LIBNFC_SUCCESS (zero) on success. +int libnfc_init() +{ + nfc_device* dev; + nfc_connstring connstrings[MAX_NFC_DEVICE_COUNT]; + const nfc_modulation_type* modulations; + const nfc_baud_rate* baudrates; + size_t ref_device_count; + size_t device_idx; + size_t modulation_idx; + + nfc_init(&libnfc.context); + if (libnfc.context == NULL) + { + AFB_ERROR("[libnfc] Initialization failed (malloc)!"); + return EXIT_LIBNFC_NOT_INITIALIZED; + } + + AFB_NOTICE("[libnfc] Using libnfc version: %s.", nfc_version()); + + // Find and register devices + ref_device_count = nfc_list_devices(libnfc.context, connstrings, MAX_NFC_DEVICE_COUNT); + if (!ref_device_count) + { + AFB_ERROR("libnfc: No NFC device found!"); + return EXIT_LIBNFC_NO_DEVICE_FOUND; + } + libnfc.devices_count = ref_device_count; + libnfc.devices = malloc(sizeof(libnfc_device) * libnfc.devices_count); + memset(libnfc.devices, 0, sizeof(libnfc_device) * libnfc.devices_count); + + for(device_idx = 0; device_idx < ref_device_count; ++device_idx) + { + AFB_NOTICE("libnfc: NFC Device found: \"%s\".", connstrings[device_idx]); + strcpy(libnfc.devices[device_idx].name, connstrings[device_idx]); + + // Find and register modulations + dev = nfc_open(libnfc.context, connstrings[device_idx]); + if (dev) + { + if (nfc_device_get_supported_modulation(dev, N_INITIATOR, &modulations)) + { + AFB_ERROR("libnfc: Failed to get supported modulations from '%s'!", connstrings[device_idx]); + } + else + { + // Find and register modulations + modulation_idx = 0; + while(modulations[modulation_idx]) ++modulation_idx; + libnfc.devices[device_idx].modulations_count = modulation_idx; + if (modulation_idx) + { + libnfc.devices[device_idx].modulations = malloc(sizeof(nfc_modulation) * modulation_idx); + memset(libnfc.devices[device_idx].modulations, 0, sizeof(nfc_modulation) * modulation_idx); + + modulation_idx = 0; + while(modulations[modulation_idx]) + { + libnfc.devices[device_idx].modulations[modulation_idx].nmt = modulations[modulation_idx]; + if (!nfc_device_get_supported_baud_rate(dev, modulations[modulation_idx], &baudrates)) + { + // Keep only the first speed which is supposed to be the fastest + libnfc.devices[device_idx].modulations[modulation_idx].nbr = baudrates[0]; + } + + AFB_NOTICE("libnfc: - Modulation '%s' supported at '%s'." + , str_nfc_modulation_type(libnfc.devices[device_idx].modulations[modulation_idx].nmt) + , str_nfc_baud_rate(libnfc.devices[device_idx].modulations[modulation_idx].nbr)); + ++modulation_idx; + } + } + } + nfc_close(dev); + } + } + + return EXIT_LIBNFC_SUCCESS; +} + +/// @brief List devices founds by libnfc. +/// @param[in] result A json object array into which found devices are added. +/// @return An exit code, @c EXIT_LIBNFC_SUCCESS (zero) on success. +int libnfc_list_devices(struct json_object* result) +{ + struct json_object* device; + size_t i; + + for(i = 0; i < libnfc.devices_count; ++i) + { + device = json_object_new_object(); + json_object_object_add(device, "source", json_object_new_string("libnfc")); + json_object_object_add(device, "name", json_object_new_string(libnfc.devices[i].name)); + json_object_array_add(result, device); + } + + return EXIT_LIBNFC_SUCCESS; +} + +int libnfc_list_devices_capabilities(struct json_object* result, struct json_object* devices) +{ + struct json_object* device; + struct json_object* mods; + struct json_object* mod; + size_t i, j; + + for(i = 0; i < libnfc.devices_count; ++i) + { + device = json_object_new_object(); + json_object_object_add(device, "source", json_object_new_string("libnfc")); + json_object_object_add(device, "name", json_object_new_string(libnfc.devices[i].name)); + mods = json_object_new_array(); + + for(j = 0; j < libnfc.devices[i].modulations_count; ++j) + { + mod = json_object_new_object(); + json_object_object_add(mod, "modulation", json_object_new_string(str_nfc_modulation_type(libnfc.devices[i].modulations[j].nmt))); + json_object_object_add(mod, "baudrate", json_object_new_string(str_nfc_baud_rate(libnfc.devices[i].modulations[j].nbr))); + json_object_array_add(mods, mod); + } + + json_object_object_add(device, "modulations", mods); + json_object_array_add(result, device); + } + + return EXIT_LIBNFC_SUCCESS; +} + +int libnfc_start_polling(struct json_object* result, struct json_object* devices) +{ + struct json_object* device; + size_t i; + int r; + + for(i = 0; i < libnfc.devices_count; ++i) + { + device = json_object_new_object(); + json_object_object_add(device, "source", json_object_new_string("libnfc")); + json_object_object_add(device, "name", json_object_new_string(libnfc.devices[i].name)); + if (libnfc.devices[i].device) + { + json_object_object_add(device, "status", json_object_new_string("already polling")); + AFB_NOTICE("libnfc: Device '%s' is already polling.", libnfc.devices[i].name); + } + else + { + libnfc.devices[i].device = nfc_open(libnfc.context, libnfc.devices[i].name); + if (libnfc.devices[i].device) + { + if (nfc_initiator_init(libnfc.devices[i].device) < 0) + { + nfc_close(libnfc.devices[i].device); + libnfc.devices[i].device = NULL; + json_object_object_add(device, "status", json_object_new_string("failed to set initiator mode")); + AFB_ERROR("libnfc: nfc_initiator_init failedfor device '%s'!", libnfc.devices[i].name); + } + else + { + r = pthread_create(&libnfc.devices[i].poller, NULL, libnfc_reader_main, (void*)&libnfc.devices[i]); + if (r) + { + nfc_close(libnfc.devices[i].device); + libnfc.devices[i].device = NULL; + json_object_object_add(device, "status", json_object_new_string("failed to create the polling thread")); + AFB_ERROR("libnfc: pthread_create failed!"); + } + else + { + json_object_object_add(device, "status", json_object_new_string("polling")); + AFB_NOTICE("libnfc: Polling the device '%s'.", libnfc.devices[i].name); + } + } + } + else + { + json_object_object_add(device, "status", json_object_new_string("failed to open device")); + AFB_ERROR("libnfc: Failed to open device '%s'!", libnfc.devices[i].name); + } + } + json_object_array_add(result, device); + } + + return EXIT_LIBNFC_SUCCESS; +} diff --git a/src/libnfc_reader.h b/src/libnfc_reader.h new file mode 100644 index 0000000..1209fda --- /dev/null +++ b/src/libnfc_reader.h @@ -0,0 +1,12 @@ +#pragma once + +#include <json-c/json.h> + +#define EXIT_LIBNFC_SUCCESS 0 +#define EXIT_LIBNFC_NOT_INITIALIZED 1 +#define EXIT_LIBNFC_NO_DEVICE_FOUND 2 + +int libnfc_init(); +int libnfc_list_devices(struct json_object* result); +int libnfc_list_devices_capabilities(struct json_object* result, struct json_object* devices); +int libnfc_start_polling(struct json_object* result, struct json_object* devices); diff --git a/src/nfc-binding.c b/src/nfc-binding.c new file mode 100644 index 0000000..3d96cbb --- /dev/null +++ b/src/nfc-binding.c @@ -0,0 +1,113 @@ +#include "nfc-binding.h" + +#if USE_LIBNFC == 1 +#include "libnfc_reader.h" +#endif + +struct afb_event on_nfc_read_event; + +/// @brief Binding's initialization. +/// @return Exit code, zero on success, non-zero otherwise. +int init() +{ + on_nfc_read_event = afb_daemon_make_event("on-nfc-read"); + if (!afb_event_is_valid(on_nfc_read_event)) + { + AFB_ERROR("Failed to create a valid event!"); + return 1; + } + +#if USE_LIBNFC == 1 + if (libnfc_init()) + { + AFB_ERROR("Failed start libnfc reader!"); + return 2; + } +#endif + + return 0; +} + +/// @brief Get a list of devices. +/// @param[in] req The query. +void verb_subscribe(struct afb_req req) +{ + if (afb_req_subscribe(req, on_nfc_read_event)) afb_req_fail(req, NULL, "Subscription failure!"); + else afb_req_success(req, NULL, "Subscription success!"); +} + +/// @brief Get a list of devices. +/// @param[in] req The query. +void verb_unsubscribe(struct afb_req req) +{ + if (afb_req_unsubscribe(req, on_nfc_read_event)) afb_req_fail(req, NULL, "Unsubscription failure!"); + else afb_req_success(req, NULL, "Unsubscription success!"); +} + +/// @brief Get a list of devices. +/// @param[in] req The query. +void verb_list_devices(struct afb_req req) +{ + struct json_object* result; + + result = json_object_new_array(); + +#if USE_LIBNFC == 1 + if (libnfc_list_devices(result)) + { + afb_req_fail(req, "Failed to get devices list from libnfc!", NULL); + return; + } +#endif + + afb_req_success(req, result, NULL); +} + +/// @brief Get a list of devices capabilities. +/// @param[in] req The query. +void verb_list_devices_capabilities(struct afb_req req) +{ + struct json_object* result; + struct json_object* arg; + + arg = afb_req_json(req); + + result = json_object_new_array(); + +#if USE_LIBNFC == 1 + if (libnfc_list_devices_capabilities(result, arg)) + { + afb_req_fail(req, "Failed to get devices list from libnfc!", NULL); + return; + } +#endif + + afb_req_success(req, result, NULL); +} + +/// @brief Start polling. +/// @param[in] req The query. +void verb_start_polling(struct afb_req req) +{ + struct json_object* result; + struct json_object* arg; + + arg = afb_req_json(req); + + result = json_object_new_array(); + +#if USE_LIBNFC == 1 + if (libnfc_start_polling(result, arg)) + { + afb_req_fail(req, "Failed to get devices list from libnfc!", NULL); + return; + } +#endif + + afb_req_success(req, result, NULL); +} + +void verb_stop_polling(struct afb_req req) +{ + afb_req_fail(req, "Not implemented yet!", NULL); +} diff --git a/src/nfc-binding.h b/src/nfc-binding.h new file mode 100644 index 0000000..a4fcfb4 --- /dev/null +++ b/src/nfc-binding.h @@ -0,0 +1,17 @@ +#pragma once + +#include <json-c/json.h> + +#define AFB_BINDING_VERSION 2 +#include <afb/afb-binding.h> + +// Initializations +int init(); + +// Verbs +void verb_subscribe(struct afb_req req); +void verb_unsubscribe(struct afb_req req); +void verb_list_devices(struct afb_req req); +void verb_list_devices_capabilities(struct afb_req req); +void verb_start_polling(struct afb_req req); +void verb_stop_polling(struct afb_req req); diff --git a/src/stringutils.h b/src/stringutils.h new file mode 100644 index 0000000..824d09d --- /dev/null +++ b/src/stringutils.h @@ -0,0 +1,41 @@ +#pragma once + +#include <stdlib.h> + +/** + * @brief Get a hexadecimal string representation from memory buffer. + * @param[in] src Buffer's pointer. + * @param[in] sz Buffer's size. + * @return A pointer to the result string. Caller is responsible for the result lifetime. + */ +static inline char* to_hex_string(const void* src, long unsigned int sz) +{ + static const char lookup[] = + { + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f' + }; + + const char* source; + char* result; + long unsigned int i; + + result = NULL; + if (src && sz) + { + source = (const char*)src; + result = (char*)malloc(sz * 2 + 1); + if (result) + { + result[sz * 2] = 0; + for (i = 0; i < sz; ++i) + { + result[i * 2] = lookup[(source[i] & 0xf0) >> 4]; + result[i * 2 + 1] = lookup[source[i] & 0x0f]; + } + } + } + return result; +} |