diff options
author | Loïc Collignon <loic.collignon@iot.bzh> | 2017-10-23 10:51:37 +0200 |
---|---|---|
committer | Loïc Collignon <loic.collignon@iot.bzh> | 2017-10-23 10:51:37 +0200 |
commit | 44096523e0c45c6b02840f2fe2aca337510fac28 (patch) | |
tree | dc9c7b2be0750c011fd7c581df65f7b2e0a9ec16 /src | |
parent | 5441251cae0eea3786c327b3b3386eae5bf687db (diff) |
add nfc binding
Change-Id: I1ebf8e803436430490201db533c2a5a04c04295e
Signed-off-by: Loïc Collignon <loic.collignon@iot.bzh>
Diffstat (limited to 'src')
-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 |
7 files changed, 656 insertions, 0 deletions
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; +} |