blob: d93a885490952a2149e04e1b9f2cf0f758af10ca [file] [log] [blame]
/*
*
* neard - Near Field Communication manager
*
* Copyright (C) 2011 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <glib.h>
#include <gdbus.h>
#include "near.h"
enum record_tnf {
RECORD_TNF_EMPTY = 0x00,
RECORD_TNF_WELLKNOWN = 0x01,
RECORD_TNF_MIME = 0x02,
RECORD_TNF_URI = 0x03,
RECORD_TNF_EXTERNAL = 0x04,
RECORD_TNF_UNKNOWN = 0x05,
RECORD_TNF_UNCHANGED = 0x06,
RECORD_TNF_RESERVED = 0x07,
};
#define RECORD_ACTION_DO 0x00
#define RECORD_ACTION_SAVE 0x01
#define RECORD_ACTION_EDIT 0x02
#define RECORD_MB_BIT(val) ((val & 0x80) >> 7)
#define RECORD_ME_BIT(val) ((val & 0x40) >> 6)
#define RECORD_CF_BIT(val) ((val & 0x20) >> 5)
#define RECORD_SR_BIT(val) ((val & 0x10) >> 4)
#define RECORD_IL_BIT(val) ((val & 0x8) >> 3)
#define RECORD_TNF_BIT(val) (val & 0x7)
#define NDEF_MSG_MIN_LENGTH 0x03
#define NDEF_PAYLOAD_LENGTH_OFFSET 0x02
#define RECORD_MB 0x80
#define RECORD_ME 0x40
#define RECORD_CF 0x20
#define RECORD_SR 0x10
#define RECORD_IL 0x08
#define RECORD_TNF_EMPTY_SET(val) ((val & ~0x7) | RECORD_TNF_EMPTY)
#define RECORD_TNF_WKT_SET(val) ((val & ~0x7) | RECORD_TNF_WELLKNOWN)
#define RECORD_TNF_MIME_SET(val) ((val & ~0x7) | RECORD_TNF_MIME)
#define RECORD_TNF_URI_SET(val) ((val & ~0x7) | RECORD_TNF_URI)
#define RECORD_TNF_EXTERNAL_SET(val) ((val & ~0x7) | RECORD_TNF_EXTERNAL)
#define RECORD_TNF_UKNOWN_SET(val) ((val & ~0x7) | RECORD_TNF_UNKNOWN)
#define RECORD_TNF_UNCHANGED_SET(val) ((val & ~0x7) | RECORD_TNF_UNCHANGED)
#define NDEF_MSG_SHORT_RECORD_MAX_LENGTH 0xFF
#define NDEF_TEXT_RECORD_TYPE_NAME_HEX_VALUE 0x54
#define NDEF_TEXT_RECORD_UTF16_STATUS 0x80
#define AC_CPS_MASK 0x03
enum record_type {
RECORD_TYPE_WKT_SMART_POSTER = 0x01,
RECORD_TYPE_WKT_URI = 0x02,
RECORD_TYPE_WKT_TEXT = 0x03,
RECORD_TYPE_WKT_SIZE = 0x04,
RECORD_TYPE_WKT_TYPE = 0x05,
RECORD_TYPE_WKT_ACTION = 0x06,
RECORD_TYPE_WKT_HANDOVER_REQUEST = 0x07,
RECORD_TYPE_WKT_HANDOVER_SELECT = 0x08,
RECORD_TYPE_WKT_HANDOVER_CARRIER = 0x09,
RECORD_TYPE_WKT_ALTERNATIVE_CARRIER = 0x0a,
RECORD_TYPE_WKT_COLLISION_RESOLUTION = 0x0b,
RECORD_TYPE_WKT_ERROR = 0x0c,
RECORD_TYPE_MIME_TYPE = 0x0d,
RECORD_TYPE_EXT_AAR = 0x0e,
RECORD_TYPE_EMPTY = 0xfd,
RECORD_TYPE_UNKNOWN = 0xfe,
RECORD_TYPE_ERROR = 0xff
};
#define RECORD_TYPE_WKT "urn:nfc:wkt:"
#define RECORD_TYPE_EXTERNAL "urn:nfc:ext:"
#define AAR_STRING "android.com:pkg"
struct near_ndef_record_header {
uint8_t mb;
uint8_t me;
uint8_t cf;
uint8_t sr;
uint8_t il;
uint8_t tnf;
uint8_t il_length;
uint8_t *il_field;
uint32_t payload_len;
uint32_t offset;
uint8_t type_len;
enum record_type rec_type;
char *type_name;
uint32_t header_len;
};
struct near_ndef_text_payload {
char *encoding;
char *language_code;
char *data;
};
struct near_ndef_uri_payload {
uint8_t identifier;
uint32_t field_length;
uint8_t *field;
};
struct near_ndef_sp_payload {
struct near_ndef_uri_payload *uri;
uint8_t number_of_title_records;
struct near_ndef_text_payload **title_records;
uint32_t size; /* from Size record*/
char *type; /* from Type record*/
char *action;
/* TODO add icon and other records fields*/
};
struct near_ndef_mime_payload {
char *type;
struct {
enum handover_carrier carrier_type;
uint16_t properties; /* e.g.: NO_PAIRING_KEY */
} handover;
};
/* Handover record definitions */
/* alternative record (AC) length based on cdr length without adata */
#define AC_RECORD_PAYLOAD_LEN(cdr_len) (3 + cdr_len)
struct near_ndef_ac_payload {
enum carrier_power_state cps; /* carrier power state */
uint8_t cdr_len; /* carrier data reference length */
uint8_t *cdr; /* carrier data reference */
uint8_t adata_refcount; /* auxiliary data reference count */
/* !: if adata_refcount == 0, then there's no data reference */
uint16_t **adata; /* auxiliary data reference */
};
/* Default Handover version */
#define HANDOVER_VERSION 0x12
#define HANDOVER_MAJOR(version) (((version) >> 4) & 0xf)
#define HANDOVER_MINOR(version) ((version) & 0xf)
/* General Handover Request/Select record */
struct near_ndef_ho_payload {
uint8_t version; /* version id */
uint16_t collision_record; /* collision record */
uint8_t number_of_ac_payloads; /* At least 1 ac is needed */
struct near_ndef_ac_payload **ac_payloads;
/* Optional records */
uint16_t *err_record; /* not NULL if present */
uint8_t number_of_cfg_payloads; /* extra NDEF records */
struct near_ndef_mime_payload **cfg_payloads;
};
struct near_ndef_aar_payload {
char *package;
};
struct near_ndef_record {
char *path;
struct near_ndef_record_header *header;
/* specific payloads */
struct near_ndef_text_payload *text;
struct near_ndef_uri_payload *uri;
struct near_ndef_sp_payload *sp;
struct near_ndef_mime_payload *mime;
struct near_ndef_ho_payload *ho; /* handover payload */
struct near_ndef_aar_payload *aar;
char *type;
uint8_t *data;
size_t data_len;
};
static DBusConnection *connection = NULL;
static inline void fillb8(uint8_t *ptr, uint32_t len)
{
(*(uint8_t *)(ptr)) = ((uint8_t)(len));
}
static inline void fillb16(uint8_t *ptr, uint32_t len)
{
fillb8((ptr), (uint16_t)(len) >> 8);
fillb8((uint8_t *)(ptr) + 1, len);
}
static inline void fillb32(uint8_t *ptr, uint32_t len)
{
fillb16((ptr), (uint32_t)(len) >> 16);
fillb16((uint8_t *)(ptr) + 2, (uint32_t)(len));
}
char *__near_ndef_record_get_path(struct near_ndef_record *record)
{
return record->path;
}
char *__near_ndef_record_get_type(struct near_ndef_record *record)
{
return record->type;
}
uint8_t *__near_ndef_record_get_data(struct near_ndef_record *record,
size_t *len)
{
*len = record->data_len;
return record->data;
}
uint8_t *__near_ndef_record_get_payload(struct near_ndef_record *record,
size_t *len)
{
*len = record->header->payload_len;
return record->data + record->header->header_len;
}
void __near_ndef_append_records(DBusMessageIter *iter, GList *records)
{
GList *list;
DBG("");
for (list = records; list; list = list->next) {
struct near_ndef_record *record = list->data;
uint8_t *data;
size_t data_len;
data = __near_ndef_record_get_data(record, &data_len);
if (!data)
continue;
dbus_message_iter_append_fixed_array(iter, DBUS_TYPE_BYTE,
&data, data_len);
}
}
static const char *uri_prefixes[NFC_MAX_URI_ID + 1] = {
"",
"http://www.",
"https://www.",
"http://",
"https://",
"tel:",
"mailto:",
"ftp://anonymous:anonymous@",
"ftp://ftp.",
"ftps://",
"sftp://",
"smb://",
"nfs://",
"ftp://",
"dav://",
"news:",
"telnet://",
"imap:",
"rstp://",
"urn:",
"pop:",
"sip:",
"sips:",
"tftp:",
"btspp://",
"btl2cap://",
"btgoep://",
"tcpobex://",
"irdaobex://",
"file://",
"urn:epc:id:",
"urn:epc:tag:",
"urn:epc:pat:",
"urn:epc:raw:",
"urn:epc:",
"urn:nfc:",
};
const char *__near_ndef_get_uri_prefix(uint8_t id)
{
if (id > NFC_MAX_URI_ID)
return NULL;
return uri_prefixes[id];
}
static gboolean property_get_type(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *user_data)
{
struct near_ndef_record *record = user_data;
const char *type;
DBG("");
switch (record->header->rec_type) {
case RECORD_TYPE_WKT_SIZE:
case RECORD_TYPE_WKT_TYPE:
case RECORD_TYPE_WKT_ACTION:
case RECORD_TYPE_WKT_ALTERNATIVE_CARRIER:
case RECORD_TYPE_WKT_COLLISION_RESOLUTION:
case RECORD_TYPE_WKT_ERROR:
case RECORD_TYPE_UNKNOWN:
case RECORD_TYPE_ERROR:
case RECORD_TYPE_EMPTY:
type = NULL;
break;
case RECORD_TYPE_WKT_TEXT:
type = "Text";
break;
case RECORD_TYPE_WKT_URI:
type = "URI";
break;
case RECORD_TYPE_WKT_SMART_POSTER:
type = "SmartPoster";
break;
case RECORD_TYPE_WKT_HANDOVER_REQUEST:
type = "HandoverRequest";
break;
case RECORD_TYPE_WKT_HANDOVER_SELECT:
type = "HandoverSelect";
break;
case RECORD_TYPE_WKT_HANDOVER_CARRIER:
type = "HandoverCarrier";
break;
case RECORD_TYPE_MIME_TYPE:
type = "MIME";
break;
case RECORD_TYPE_EXT_AAR:
type = "AAR";
break;
}
if (!type)
return FALSE;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &type);
return TRUE;
}
static gboolean text_exists(const GDBusPropertyTable *property, void *data)
{
struct near_ndef_record *record = data;
DBG("");
if (record->text)
return TRUE;
if (record->sp && record->sp->title_records)
return TRUE;
DBG("No text");
return FALSE;
}
static const char *get_text_payload(const GDBusPropertyTable *property,
struct near_ndef_text_payload *text)
{
if (!strcmp(property->name, "Encoding"))
return text->encoding;
else if (!strcmp(property->name, "Language"))
return text->language_code;
else if (!strcmp(property->name, "Representation"))
return text->data;
else
return NULL;
}
static gboolean property_get_text(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *user_data)
{
struct near_ndef_record *record = user_data;
const char *text;
DBG("");
if (record->text) {
text = get_text_payload(property, record->text);
if (!text)
return FALSE;
DBG("text %s", text);
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &text);
return TRUE;
}
if (record->sp && record->sp->title_records) {
int i;
for (i = 0; i < record->sp->number_of_title_records; i++) {
text = get_text_payload(property,
record->sp->title_records[i]);
if (!text)
continue;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
&text);
}
return TRUE;
}
return FALSE;
}
static gboolean uri_exists(const GDBusPropertyTable *property, void *data)
{
struct near_ndef_record *record = data;
DBG("");
if (record->uri)
return TRUE;
if (record->sp && record->sp->uri)
return TRUE;
DBG("No URI");
return FALSE;
}
static gboolean property_get_uri(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *user_data)
{
struct near_ndef_record *record = user_data;
struct near_ndef_uri_payload *uri;
char *value;
const char *prefix = NULL;
DBG("");
if (record->uri)
uri = record->uri;
else if (record->sp && record->sp->uri)
uri = record->sp->uri;
else
return FALSE;
if (uri->identifier > NFC_MAX_URI_ID) {
near_error("Invalid URI identifier 0x%x", uri->identifier);
return FALSE;
}
prefix = uri_prefixes[uri->identifier];
DBG("URI prefix %s", prefix);
value = g_strdup_printf("%s%.*s", prefix, uri->field_length,
uri->field);
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &value);
g_free(value);
return TRUE;
}
static gboolean sp_action_exists(const GDBusPropertyTable *property,
void *data)
{
struct near_ndef_record *record = data;
DBG("");
if (record->sp && record->sp->action)
return TRUE;
DBG("No SmartPoster action");
return FALSE;
}
static gboolean sp_mime_exists(const GDBusPropertyTable *property, void *data)
{
struct near_ndef_record *record = data;
DBG("");
if (record->sp && record->sp->type)
return TRUE;
DBG("No SmartPoster MIME type");
return FALSE;
}
static gboolean sp_size_exists(const GDBusPropertyTable *property, void *data)
{
struct near_ndef_record *record = data;
DBG("");
if (record->sp && record->sp->size)
return TRUE;
DBG("No SmartPoster size");
return FALSE;
}
static gboolean property_get_action(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *user_data)
{
struct near_ndef_record *record = user_data;
DBG("%s", record->sp->action);
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &record->sp->action);
return TRUE;
}
static gboolean property_get_mime_type(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *user_data)
{
struct near_ndef_record *record = user_data;
DBG("%s", record->sp->type);
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &record->sp->type);
return TRUE;
}
static gboolean property_get_size(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *user_data)
{
struct near_ndef_record *record = user_data;
DBG("%d", record->sp->size);
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &record->sp->size);
return TRUE;
}
static gboolean mime_exists(const GDBusPropertyTable *property, void *data)
{
struct near_ndef_record *record = data;
DBG("");
if (record->mime)
return TRUE;
DBG("No MIME");
return FALSE;
}
static gboolean property_get_mime(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *user_data)
{
struct near_ndef_record *record = user_data;
DBG("");
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &record->mime->type);
return TRUE;
}
static gboolean aar_exists(const GDBusPropertyTable *property, void *data)
{
struct near_ndef_record *record = data;
DBG("");
if (record->aar)
return TRUE;
DBG("No AAR");
return FALSE;
}
static gboolean property_get_aar(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *user_data)
{
struct near_ndef_record *record = user_data;
DBG("");
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &record->aar->package);
return TRUE;
}
static const GDBusPropertyTable record_properties[] = {
{ "Type", "s", property_get_type },
{ "Encoding", "s", property_get_text, NULL, text_exists },
{ "Language", "s", property_get_text, NULL, text_exists },
{ "Representation", "s", property_get_text, NULL, text_exists},
{ "URI", "s", property_get_uri, NULL, uri_exists },
{ "Action", "s", property_get_action, NULL, sp_action_exists },
{ "MIMEType", "s", property_get_mime_type, NULL, sp_mime_exists },
{ "Size", "u", property_get_size, NULL, sp_size_exists },
{ "MIME", "s", property_get_mime, NULL, mime_exists },
{ "AAR", "s", property_get_aar, NULL, aar_exists },
{ }
};
static void free_text_payload(struct near_ndef_text_payload *text)
{
if (!text)
return;
g_free(text->encoding);
g_free(text->language_code);
g_free(text->data);
g_free(text);
}
static void free_uri_payload(struct near_ndef_uri_payload *uri)
{
if (!uri)
return;
g_free(uri->field);
g_free(uri);
}
static void free_sp_payload(struct near_ndef_sp_payload *sp)
{
uint8_t i;
if (!sp)
return;
free_uri_payload(sp->uri);
if (sp->title_records) {
for (i = 0; i < sp->number_of_title_records; i++)
free_text_payload(sp->title_records[i]);
}
g_free(sp->title_records);
g_free(sp->type);
g_free(sp->action);
g_free(sp);
}
static void free_mime_payload(struct near_ndef_mime_payload *mime)
{
if (!mime)
return;
g_free(mime->type);
g_free(mime);
}
static void free_ac_payload(struct near_ndef_ac_payload *ac)
{
if (!ac)
return;
g_free(ac->cdr);
g_free(ac->adata);
g_free(ac);
}
static void free_ho_payload(struct near_ndef_ho_payload *ho)
{
int i;
if (!ho)
return;
if (ho->ac_payloads) {
for (i = 0; i < ho->number_of_ac_payloads; i++)
free_ac_payload(ho->ac_payloads[i]);
}
g_free(ho->ac_payloads);
g_free(ho);
}
static void free_aar_payload(struct near_ndef_aar_payload *aar)
{
if (!aar)
return;
g_free(aar->package);
g_free(aar);
}
static void free_ndef_record(struct near_ndef_record *record)
{
if (!record)
return;
g_free(record->path);
if (record->header) {
switch (record->header->rec_type) {
case RECORD_TYPE_WKT_SIZE:
case RECORD_TYPE_WKT_TYPE:
case RECORD_TYPE_WKT_ACTION:
case RECORD_TYPE_WKT_HANDOVER_CARRIER:
case RECORD_TYPE_WKT_ALTERNATIVE_CARRIER:
case RECORD_TYPE_WKT_COLLISION_RESOLUTION:
case RECORD_TYPE_WKT_ERROR:
case RECORD_TYPE_EMPTY:
case RECORD_TYPE_UNKNOWN:
case RECORD_TYPE_ERROR:
break;
case RECORD_TYPE_WKT_HANDOVER_REQUEST:
case RECORD_TYPE_WKT_HANDOVER_SELECT:
free_ho_payload(record->ho);
break;
case RECORD_TYPE_WKT_TEXT:
free_text_payload(record->text);
break;
case RECORD_TYPE_WKT_URI:
free_uri_payload(record->uri);
break;
case RECORD_TYPE_WKT_SMART_POSTER:
free_sp_payload(record->sp);
break;
case RECORD_TYPE_MIME_TYPE:
free_mime_payload(record->mime);
break;
case RECORD_TYPE_EXT_AAR:
free_aar_payload(record->aar);
break;
}
g_free(record->header->il_field);
g_free(record->header->type_name);
}
g_free(record->header);
g_free(record->type);
g_free(record->data);
g_free(record);
}
static void free_ndef_message(struct near_ndef_message *msg)
{
if (!msg)
return;
g_free(msg->data);
g_free(msg);
}
void __near_ndef_record_free(struct near_ndef_record *record)
{
g_dbus_unregister_interface(connection, record->path,
NFC_RECORD_INTERFACE);
free_ndef_record(record);
}
static char *action_to_string(uint8_t action)
{
switch (action) {
case RECORD_ACTION_DO:
return "Do";
case RECORD_ACTION_SAVE:
return "Save";
case RECORD_ACTION_EDIT:
return "Edit";
default:
near_error("Unknown action 0x%x", action);
return NULL;
}
}
static enum record_type get_external_record_type(uint8_t *type,
size_t type_length)
{
DBG("");
if (strncmp((char *) type, BT_MIME_STRING_2_0,
sizeof(BT_MIME_STRING_2_0) - 1) == 0)
return RECORD_TYPE_MIME_TYPE;
else if (strncmp((char *) type, AAR_STRING,
sizeof(AAR_STRING) - 1) == 0)
return RECORD_TYPE_EXT_AAR;
else
return RECORD_TYPE_UNKNOWN;
}
static enum record_type get_record_type(enum record_tnf tnf,
uint8_t *type, size_t type_length)
{
DBG("");
switch (tnf) {
case RECORD_TNF_URI:
break;
case RECORD_TNF_EMPTY:
if (type_length != 0)
return RECORD_TYPE_ERROR;
else
return RECORD_TYPE_EMPTY;
break;
case RECORD_TNF_UNKNOWN:
if (type_length != 0)
return RECORD_TYPE_ERROR;
else
return RECORD_TYPE_UNKNOWN;
break;
case RECORD_TNF_UNCHANGED:
if (type_length != 0)
return RECORD_TYPE_ERROR;
else
return RECORD_TNF_UNCHANGED;
break;
case RECORD_TNF_RESERVED:
return RECORD_TYPE_ERROR;
case RECORD_TNF_WELLKNOWN:
if (type_length == 1) {
if (type[0] == 'T')
return RECORD_TYPE_WKT_TEXT;
else if (type[0] == 'U')
return RECORD_TYPE_WKT_URI;
else if (type[0] == 's')
return RECORD_TYPE_WKT_SIZE;
else if (type[0] == 't')
return RECORD_TYPE_WKT_TYPE;
else
return RECORD_TYPE_UNKNOWN;
} else if (type_length == 2) {
if (strncmp((char *)type, "Sp", 2) == 0)
return RECORD_TYPE_WKT_SMART_POSTER;
else if (strncmp((char *) type, "Hr", 2) == 0)
return RECORD_TYPE_WKT_HANDOVER_REQUEST;
else if (strncmp((char *) type, "Hs", 2) == 0)
return RECORD_TYPE_WKT_HANDOVER_SELECT;
else if (strncmp((char *) type, "Hc", 2) == 0)
return RECORD_TYPE_WKT_HANDOVER_CARRIER;
else if (strncmp((char *) type, "ac", 2) == 0)
return RECORD_TYPE_WKT_ALTERNATIVE_CARRIER;
else if (strncmp((char *) type, "cr", 2) == 0)
return RECORD_TYPE_WKT_COLLISION_RESOLUTION;
else
return RECORD_TYPE_UNKNOWN;
} else if (type_length == 3) {
if (strncmp((char *)type, "act", 3) == 0)
return RECORD_TYPE_WKT_ACTION;
else if (strncmp((char *)type, "err", 3) == 0)
return RECORD_TYPE_WKT_ERROR;
else
return RECORD_TYPE_UNKNOWN;
}
case RECORD_TNF_MIME:
return RECORD_TYPE_MIME_TYPE;
case RECORD_TNF_EXTERNAL:
return get_external_record_type(type, type_length);
}
return RECORD_TYPE_UNKNOWN;
}
static int build_record_type_string(struct near_ndef_record *rec)
{
uint8_t tnf;
DBG("");
if (!rec || !rec->header)
return -EINVAL;
tnf = rec->header->tnf;
if (rec->header->rec_type == RECORD_TYPE_WKT_SMART_POSTER) {
rec->type = g_strdup_printf(RECORD_TYPE_WKT "U");
return 0;
}
switch (tnf) {
case RECORD_TNF_EMPTY:
case RECORD_TNF_UNKNOWN:
case RECORD_TNF_UNCHANGED:
case RECORD_TNF_RESERVED:
return -EINVAL;
case RECORD_TNF_URI:
case RECORD_TNF_MIME:
rec->type = g_strndup(rec->header->type_name,
rec->header->type_len);
break;
case RECORD_TNF_WELLKNOWN:
rec->type = g_strdup_printf(RECORD_TYPE_WKT "%s",
rec->header->type_name);
break;
case RECORD_TNF_EXTERNAL:
rec->type = g_strdup_printf(RECORD_TYPE_EXTERNAL "%s",
rec->header->type_name);
break;
}
return 0;
}
static uint8_t validate_record_begin_and_end_bits(uint8_t *msg_mb,
uint8_t *msg_me, uint8_t rec_mb,
uint8_t rec_me)
{
DBG("");
if (!msg_mb || !msg_me)
return 0;
/* Validating record header begin and end bits
* eg: Single record: [mb:1,me:1]
* Two records: [mb:1,me:0 - mb:0,me:1]
* Three or more records [mb:1,me:0 - mb:0,me:0 .. mb:0,me:1]
**/
if (rec_mb == 1) {
if (*msg_mb != 1)
*msg_mb = rec_mb;
else
return -EINVAL;
}
if (rec_me == 1) {
if (*msg_me != 1) {
if (*msg_mb == 1)
*msg_me = rec_me;
else
return -EINVAL;
} else
return -EINVAL;
}
return 0;
}
/*
* Parse the ndef record header and cache the begin, end, chunkflag,
* short-record and type-name-format bits. ID length and field, record
* type, payload length and offset (where payload byte starts in input
* parameter). Validate offset for every step forward against total
* available length.
*/
static struct near_ndef_record_header *parse_record_header(uint8_t *rec,
uint32_t offset, uint32_t length)
{
struct near_ndef_record_header *rec_header = NULL;
uint8_t *type = NULL;
uint32_t header_len = 0;
DBG("length %d", length);
if (!rec || offset >= length)
return NULL;
/* This check is for empty record. */
if ((length - offset) < NDEF_MSG_MIN_LENGTH)
return NULL;
rec_header = g_try_malloc0(sizeof(struct near_ndef_record_header));
if (!rec_header)
return NULL;
rec_header->mb = RECORD_MB_BIT(rec[offset]);
rec_header->me = RECORD_ME_BIT(rec[offset]);
rec_header->sr = RECORD_SR_BIT(rec[offset]);
rec_header->il = RECORD_IL_BIT(rec[offset]);
rec_header->tnf = RECORD_TNF_BIT(rec[offset]);
DBG("mb %d me %d sr %d il %d tnf %d",
rec_header->mb, rec_header->me, rec_header->sr,
rec_header->il, rec_header->tnf);
offset++;
rec_header->type_len = rec[offset++];
header_len = 2; /* type length + header bits */
if (rec_header->sr == 1) {
rec_header->payload_len = rec[offset++];
header_len++;
} else {
rec_header->payload_len = near_get_be32(rec + offset);
offset += 4;
header_len += 4;
if ((offset + rec_header->payload_len) > length)
goto fail;
}
DBG("payload length %d", rec_header->payload_len);
if (rec_header->il == 1) {
rec_header->il_length = rec[offset++];
header_len++;
if ((offset + rec_header->payload_len) > length)
goto fail;
}
if (rec_header->type_len > 0) {
if ((offset + rec_header->type_len) > length)
goto fail;
type = g_try_malloc0(rec_header->type_len);
if (!type)
goto fail;
memcpy(type, rec + offset, rec_header->type_len);
offset += rec_header->type_len;
header_len += rec_header->type_len;
if ((offset + rec_header->payload_len) > length)
goto fail;
}
if (rec_header->il_length > 0) {
if ((offset + rec_header->il_length) > length)
goto fail;
rec_header->il_field = g_try_malloc0(rec_header->il_length);
if (!rec_header->il_field)
goto fail;
memcpy(rec_header->il_field, rec + offset,
rec_header->il_length);
offset += rec_header->il_length;
header_len += rec_header->il_length;
if ((offset + rec_header->payload_len) > length)
goto fail;
}
rec_header->rec_type = get_record_type(rec_header->tnf, type,
rec_header->type_len);
rec_header->offset = offset;
rec_header->header_len = header_len;
rec_header->type_name = g_strndup((char *) type, rec_header->type_len);
/* Check that NDEF record is not corrupted */
if (length != (rec_header->header_len + rec_header->payload_len))
rec_header->rec_type = RECORD_TYPE_ERROR;
g_free(type);
return rec_header;
fail:
near_error("parsing record header failed");
g_free(type);
g_free(rec_header->il_field);
g_free(rec_header->type_name);
g_free(rec_header);
return NULL;
}
static struct near_ndef_text_payload *
parse_text_payload(uint8_t *payload, uint32_t length)
{
struct near_ndef_text_payload *text_payload = NULL;
uint8_t status, lang_length;
uint32_t offset;
DBG("");
if (!payload)
return NULL;
offset = 0;
text_payload = g_try_malloc0(sizeof(struct near_ndef_text_payload));
if (!text_payload)
return NULL;
/* 0x80 is used to get 7th bit value (0th bit is LSB) */
status = ((payload[offset] & 0x80) >> 7);
text_payload->encoding = (status == 0) ?
g_strdup("UTF-8") : g_strdup("UTF-16");
/* 0x3F is used to get 5th-0th bits value (0th bit is LSB) */
lang_length = (payload[offset] & 0x3F);
offset++;
if (lang_length > 0) {
if ((offset + lang_length) >= length)
goto fail;
text_payload->language_code = g_strndup(
(char *)(payload + offset),
lang_length);
} else {
text_payload->language_code = NULL;
}
offset += lang_length;
if ((length - lang_length - 1) > 0) {
text_payload->data = g_strndup((char *)(payload + offset),
length - lang_length - 1);
} else {
text_payload->data = NULL;
}
if (offset >= length)
goto fail;
DBG("Encoding '%s'", text_payload->encoding);
DBG("Language Code '%s'", text_payload->language_code);
DBG("Data '%s'", text_payload->data);
return text_payload;
fail:
near_error("text payload parsing failed");
free_text_payload(text_payload);
return NULL;
}
static struct near_ndef_uri_payload *
parse_uri_payload(uint8_t *payload, uint32_t length)
{
struct near_ndef_uri_payload *uri_payload = NULL;
uint32_t index, offset;
DBG("");
if (!payload)
return NULL;
offset = 0;
uri_payload = g_try_malloc0(sizeof(struct near_ndef_uri_payload));
if (!uri_payload)
return NULL;
uri_payload->identifier = payload[offset];
offset++;
if (uri_payload->identifier > NFC_MAX_URI_ID) {
near_error("Invalid URI identifier '0x%X'",
uri_payload->identifier);
goto fail;
}
uri_payload->field_length = length - 1;
if (uri_payload->field_length > 0) {
uri_payload->field = g_try_malloc0(uri_payload->field_length);
if (!uri_payload->field)
goto fail;
memcpy(uri_payload->field, payload + offset,
uri_payload->field_length);
for (index = 0; index < uri_payload->field_length; index++) {
/* URI Record Type Definition 1.0 [3.2.3]
* Any character value within the URI between
* (and including) 0 and 31 SHALL be recorded as
* an error, and the URI record to be discarded */
if (uri_payload->field[index] <= 31)
goto fail;
}
}
DBG("Identifier '0X%X'", uri_payload->identifier);
DBG("Field '%.*s'", uri_payload->field_length, uri_payload->field);
return uri_payload;
fail:
near_error("uri payload parsing failed");
free_uri_payload(uri_payload);
return NULL;
}
/*
* Validate titles records language code in Smartposter.
* There must not be two or more records with the same language identifier.
*/
static int8_t validate_language_code_in_sp_record(GSList *titles)
{
uint8_t i, j, length;
struct near_ndef_text_payload *title1, *title2;
DBG("");
if (!titles)
return -EINVAL;
length = g_slist_length(titles);
for (i = 0; i < length; i++) {
title1 = g_slist_nth_data(titles, i);
for (j = i + 1; j < length; j++) {
title2 = g_slist_nth_data(titles, j);
if ((!title1->language_code) &&
(!title2->language_code))
continue;
if (g_strcmp0(title1->language_code,
title2->language_code) == 0)
return -EINVAL;
}
}
return 0;
}
static struct near_ndef_sp_payload *
parse_sp_payload(uint8_t *payload, uint32_t length)
{
struct near_ndef_sp_payload *sp_payload = NULL;
struct near_ndef_record_header *rec_header = NULL;
uint8_t mb = 0, me = 0, i;
uint32_t offset;
GSList *titles = NULL, *temp;
DBG("");
if (!payload)
return NULL;
offset = 0;
sp_payload = g_try_malloc0(sizeof(struct near_ndef_sp_payload));
if (!sp_payload)
return NULL;
while (offset < length) {
DBG("Record header : 0x%x", payload[offset]);
rec_header = parse_record_header(payload, offset, length);
if (!rec_header)
goto fail;
if (validate_record_begin_and_end_bits(&mb, &me,
rec_header->mb, rec_header->me) != 0) {
DBG("validate mb me failed");
goto fail;
}
offset = rec_header->offset;
switch (rec_header->rec_type) {
case RECORD_TYPE_WKT_SMART_POSTER:
case RECORD_TYPE_WKT_HANDOVER_REQUEST:
case RECORD_TYPE_WKT_HANDOVER_SELECT:
case RECORD_TYPE_WKT_HANDOVER_CARRIER:
case RECORD_TYPE_WKT_ALTERNATIVE_CARRIER:
case RECORD_TYPE_WKT_COLLISION_RESOLUTION:
case RECORD_TYPE_MIME_TYPE:
case RECORD_TYPE_WKT_ERROR:
case RECORD_TYPE_EXT_AAR:
case RECORD_TYPE_UNKNOWN:
break;
case RECORD_TYPE_EMPTY:
case RECORD_TYPE_ERROR:
goto fail;
case RECORD_TYPE_WKT_URI:
/* URI record should be only one. */
if (sp_payload->uri)
goto fail;
sp_payload->uri = parse_uri_payload(payload + offset,
rec_header->payload_len);
if (!sp_payload->uri)
goto fail;
break;
case RECORD_TYPE_WKT_TEXT:
/*
* Title records can zero or more. First fill the
* records in list and validate language identifier
* and then cache them into sp record structure.
*/
{
struct near_ndef_text_payload *title;
title = parse_text_payload(payload + offset,
rec_header->payload_len);
if (!title)
goto fail;
titles = g_slist_append(titles, title);
}
break;
case RECORD_TYPE_WKT_SIZE:
/*
* If payload length is not exactly 4 bytes
* then record is wrong.
*/
if (rec_header->payload_len != 4)
goto fail;
sp_payload->size = near_get_be32(payload + offset);
break;
case RECORD_TYPE_WKT_TYPE:
if (rec_header->payload_len > 0) {
sp_payload->type = g_try_malloc0(
rec_header->payload_len);
if (!sp_payload->type)
goto fail;
sp_payload->type = g_strndup(
(char *) payload + offset,
rec_header->payload_len);
}
break;
case RECORD_TYPE_WKT_ACTION:
/*
* If the action record exists, payload should be
* single byte, otherwise consider it as error.
*/
if (rec_header->payload_len != 1)
goto fail;
sp_payload->action =
g_strdup(action_to_string(payload[offset]));
break;
}
offset += rec_header->payload_len;
g_free(rec_header->il_field);
g_free(rec_header->type_name);
g_free(rec_header);
rec_header = NULL;
}
/*
* Code to fill smart poster record structure from
* 'titles' list.
*/
if (!titles)
return sp_payload;
if (validate_language_code_in_sp_record(titles) != 0) {
DBG("language code validation failed");
goto fail;
}
temp = titles;
sp_payload->number_of_title_records = g_slist_length(temp);
sp_payload->title_records = g_try_malloc0(
sp_payload->number_of_title_records *
sizeof(struct near_ndef_text_payload *));
if (!sp_payload->title_records)
goto fail;
for (i = 0; i < sp_payload->number_of_title_records; i++) {
sp_payload->title_records[i] = temp->data;
temp = temp->next;
}
g_slist_free(titles);
titles = NULL;
return sp_payload;
fail:
near_error("smart poster payload parsing failed");
if (rec_header) {
g_free(rec_header->type_name);
g_free(rec_header->il_field);
g_free(rec_header);
}
free_sp_payload(sp_payload);
g_slist_free(titles);
return NULL;
}
static void correct_eir_len(struct carrier_data *data)
{
/*
* Android 4.1 BUG - OOB EIR length should be in LE, but is in BE.
* Fortunately payload length is 1 byte so this can be detected and
* corrected before sending it to handover agent.
*/
if (data->data[0] == 0) {
DBG("EIR length in BE");
data->data[0] = data->data[1];
data->data[1] = 0;
}
/*
* Some Nokia BH-505 report total OOB block length without length field
* size.
*/
if (data->data[0] == data->size - 2) {
DBG("EIR length without length field size");
data->data[0] += 2;
}
}
static int process_mime_type(struct near_ndef_mime_payload *mime,
struct carrier_data *c_data)
{
int err = -EINVAL;
DBG("");
if (!mime || !c_data)
return -EINVAL;
switch (mime->handover.carrier_type) {
case NEAR_CARRIER_BLUETOOTH:
err = __near_agent_handover_push_data(HO_AGENT_BT, c_data);
if (err == -ESRCH)
err = __near_bluetooth_parse_oob_record(c_data,
&mime->handover.properties, true);
break;
case NEAR_CARRIER_WIFI:
err = __near_agent_handover_push_data(HO_AGENT_WIFI, c_data);
break;
case NEAR_CARRIER_EMPTY:
case NEAR_CARRIER_UNKNOWN:
break;
}
return err;
}
static struct near_ndef_mime_payload *parse_mime_type(
struct near_ndef_record *record, uint8_t *ndef_data,
size_t ndef_length, size_t offset,
uint32_t payload_length, struct carrier_data **c_data)
{
struct near_ndef_mime_payload *mime;
struct carrier_data *c_temp;
DBG("");
if (!c_data || !ndef_data ||
((offset + payload_length) > ndef_length))
return NULL;
mime = g_try_malloc0(sizeof(struct near_ndef_mime_payload));
if (!mime)
return NULL;
c_temp = g_try_malloc0(sizeof(struct carrier_data));
if (!c_temp) {
g_free(mime);
return NULL;
}
mime->type = g_strdup(record->header->type_name);
DBG("MIME Type '%s'", mime->type);
if (strcmp(mime->type, BT_MIME_STRING_2_1) == 0) {
mime->handover.carrier_type = NEAR_CARRIER_BLUETOOTH;
c_temp->type = BT_MIME_V2_1;
c_temp->size = record->header->payload_len;
memcpy(c_temp->data, ndef_data + offset, c_temp->size);
correct_eir_len(c_temp);
} else if (strcmp(mime->type, BT_MIME_STRING_2_0) == 0) {
mime->handover.carrier_type = NEAR_CARRIER_BLUETOOTH;
c_temp->type = BT_MIME_V2_0;
c_temp->size = record->header->payload_len;
memcpy(c_temp->data, ndef_data + offset, c_temp->size);
} else if (strcmp(mime->type, WIFI_WSC_MIME_STRING) == 0) {
mime->handover.carrier_type = NEAR_CARRIER_WIFI;
c_temp->type = WIFI_WSC_MIME;
c_temp->size = record->header->payload_len;
memcpy(c_temp->data, ndef_data + offset, c_temp->size);
} else {
g_free(c_temp);
c_temp = NULL;
*c_data = NULL;
return mime;
}
*c_data = c_temp;
return mime;
}
/* Set the MB bit in message header */
static uint8_t near_ndef_set_mb(uint8_t *hdr, bool first_rec)
{
/* Reset bits 0x40 */
*hdr &= (0xFF & (~RECORD_MB));
/* Set if needed */
if (first_rec)
*hdr |= RECORD_MB;
return *hdr;
}
/* Set the MB/ME bit in message header */
static uint8_t near_ndef_set_me(uint8_t *hdr, bool last_rec)
{
/* Reset bits 0x80 */
*hdr &= (0xFF & (~RECORD_ME));
/* Set if needed */
if (last_rec)
*hdr |= RECORD_ME;
return *hdr;
}
/* Set the MB/ME bit in message header */
static uint8_t near_ndef_set_mb_me(uint8_t *hdr, bool first_rec,
bool last_rec)
{
near_ndef_set_mb(hdr, first_rec);
return near_ndef_set_me(hdr, last_rec);
}
/* Caller should put own payload starting from offset value */
static struct near_ndef_message *ndef_message_alloc_complete(char *type_name,
uint32_t payload_len,
char *payload_id,
uint8_t payload_id_len,
enum record_tnf tnf,
bool first_rec,
bool last_rec)
{
struct near_ndef_message *msg;
uint8_t hdr = 0, type_len, sr_bit, il_bit, id_len;
msg = g_try_malloc0(sizeof(struct near_ndef_message));
if (!msg)
return NULL;
msg->length = 0;
msg->offset = 0;
msg->length++; /* record header*/
msg->length++; /* type name length byte*/
type_len = (type_name) ? strlen(type_name) : 0;
id_len = (payload_id) ? payload_id_len : 0;
sr_bit = (payload_len <= NDEF_MSG_SHORT_RECORD_MAX_LENGTH)
? TRUE : FALSE;
il_bit = (payload_id) ? TRUE : FALSE;
msg->length += (sr_bit) ? 1 : 4;
msg->length += (il_bit) ? 1 : 0;
msg->length += type_len;
msg->length += payload_len;
msg->length += id_len;
msg->data = g_try_malloc0(msg->length);
if (!msg->data)
goto fail;
/* Set MB ME bits */
hdr = near_ndef_set_mb_me(&hdr, first_rec, last_rec);
if (sr_bit)
hdr |= RECORD_SR;
hdr = RECORD_TNF_WKT_SET(hdr);
if (il_bit)
hdr |= RECORD_IL;
switch (tnf) {
case RECORD_TNF_EMPTY:
hdr = RECORD_TNF_EMPTY_SET(hdr);
break;
case RECORD_TNF_URI:
hdr = RECORD_TNF_URI_SET(hdr);
break;
case RECORD_TNF_EXTERNAL:
hdr = RECORD_TNF_EXTERNAL_SET(hdr);
break;
case RECORD_TNF_UNKNOWN:
hdr = RECORD_TNF_UKNOWN_SET(hdr);
break;
case RECORD_TNF_UNCHANGED:
hdr = RECORD_TNF_UNCHANGED_SET(hdr);
break;
case RECORD_TNF_WELLKNOWN:
hdr = RECORD_TNF_WKT_SET(hdr);
break;
case RECORD_TNF_MIME:
hdr = RECORD_TNF_MIME_SET(hdr);
break;
case RECORD_TNF_RESERVED:
goto fail;
}
msg->data[msg->offset++] = hdr;
msg->data[msg->offset++] = type_len;
if (sr_bit) {
msg->data[msg->offset++] = payload_len;
} else {
fillb32((msg->data + msg->offset), payload_len);
msg->offset += 4;
}
if (il_bit)
msg->data[msg->offset++] = payload_id_len;
if (type_name) {
memcpy(msg->data + msg->offset, type_name, type_len);
msg->offset += type_len;
}
if (il_bit) {
memcpy(msg->data + msg->offset, payload_id, payload_id_len);
msg->offset += payload_id_len;
}
return msg;
fail:
near_error("ndef message struct allocation failed");
free_ndef_message(msg);
return NULL;
}
/*
* This is a wrapper to ndef_message_alloc, as, in most cases,
* there's no payload id, and MB=TRUE and ME=TRUE. Default type name format
* is also set to RECORD_TNF_WELLKNOWN
*
*/
static struct near_ndef_message *ndef_message_alloc(char *type_name,
uint32_t payload_len)
{
return ndef_message_alloc_complete(type_name, payload_len,
NULL, 0,
RECORD_TNF_WELLKNOWN,
true, true);
}
static enum carrier_power_state get_cps(uint8_t data)
{
/* enum carrier_power_state values match binary format */
return data & AC_CPS_MASK;
}
static struct near_ndef_ac_payload *parse_ac_payload(uint8_t *payload,
uint32_t length)
{
struct near_ndef_ac_payload *ac_payload = NULL;
uint32_t offset;
DBG("");
if (!payload)
return NULL;
offset = 0;
ac_payload = g_try_malloc0(sizeof(struct near_ndef_ac_payload));
if (!ac_payload)
goto fail;
/* Carrier flag */
ac_payload->cps = get_cps(payload[offset]);
offset++;
/* Carrier data reference length */
ac_payload->cdr_len = payload[offset];
offset++;
if (ac_payload->cdr_len == 0)
goto fail;
/* Carrier data reference */
ac_payload->cdr = g_try_malloc0(ac_payload->cdr_len + 1);
if (!ac_payload->cdr)
goto fail;
memcpy(ac_payload->cdr, payload + offset, ac_payload->cdr_len);
offset = offset + ac_payload->cdr_len;
/* Auxiliary data reference count */
ac_payload->adata_refcount = payload[offset];
offset++;
if (ac_payload->adata_refcount == 0)
return ac_payload;
/* save the auxiliary data reference */
ac_payload->adata = g_try_malloc0(
ac_payload->adata_refcount * sizeof(uint16_t));
if (!ac_payload->adata)
goto fail;
memcpy(ac_payload->adata, payload + offset,
ac_payload->adata_refcount * sizeof(uint16_t));
/* and leave */
return ac_payload;
fail:
near_error("ac payload parsing failed");
free_ac_payload(ac_payload);
return NULL;
}
/* carrier power state & carrier reference */
static struct near_ndef_message *near_ndef_prepare_ac_message(uint8_t cps,
char *cdr,
uint8_t cdr_len)
{
struct near_ndef_message *ac_msg;
/* alloc "ac" message minus adata*/
ac_msg = ndef_message_alloc_complete("ac",
AC_RECORD_PAYLOAD_LEN(cdr_len),
NULL, 0,
RECORD_TNF_WELLKNOWN,
true, true);
if (!ac_msg)
return NULL;
/* Prepare ac message */
ac_msg->data[ac_msg->offset++] = cps;
ac_msg->data[ac_msg->offset++] = cdr_len;
memcpy(ac_msg->data + ac_msg->offset, cdr, cdr_len); /* cdr */
ac_msg->offset += cdr_len;
ac_msg->data[ac_msg->offset] = 0; /* adata ref count */
/* Check if we want an empty record */
if (*cdr == 0x00)
ac_msg->length = 0;
return ac_msg;
}
/* Collision Record message */
static struct near_ndef_message *near_ndef_prepare_cr_message(uint16_t cr_id)
{
struct near_ndef_message *cr_msg;
cr_msg = ndef_message_alloc_complete("cr", sizeof(uint16_t),
NULL, 0,
RECORD_TNF_WELLKNOWN,
true, true);
if (!cr_msg)
return NULL;
/* Prepare ac message */
near_put_be16(cr_id, cr_msg->data + cr_msg->offset);
return cr_msg;
}
static struct near_ndef_message *near_ndef_prepare_cfg_message(char *mime_type,
uint8_t *data, int data_len,
char *cdr, uint8_t cdr_len)
{
struct near_ndef_message *msg = NULL;
DBG(" %s", mime_type);
if (!mime_type)
return NULL;
msg = ndef_message_alloc_complete(mime_type, data_len, cdr, cdr_len,
RECORD_TNF_MIME, true, true);
if (!msg)
return NULL;
/* store data */
if (data)
memcpy(msg->data + msg->offset, data, data_len);
return msg;
}
/*
* Prepare alternative carrier and configuration records
* (e.g. bluetooth or wifi or Hc)
*/
static int near_ndef_prepare_ac_and_cfg_records(enum record_type type,
enum handover_carrier carrier,
struct near_ndef_message **ac,
struct near_ndef_message **cfg,
struct near_ndef_mime_payload *mime,
struct carrier_data *remote_carrier)
{
struct carrier_data *local_carrier;
char cdr;
char *mime_type, *carrier_string;
uint16_t prop;
DBG("");
if (!ac || !cfg)
return -EINVAL;
switch (carrier) {
case NEAR_CARRIER_BLUETOOTH:
cdr = '0';
carrier_string = "Bluetooth";
mime_type = BT_MIME_STRING_2_1;
local_carrier = __near_agent_handover_request_data(
HO_AGENT_BT, remote_carrier);
if (local_carrier)
break;
prop = (mime) ? mime->handover.properties :
OOB_PROPS_EMPTY;
local_carrier = __near_bluetooth_local_get_properties(prop);
break;
case NEAR_CARRIER_WIFI:
cdr = '1';
carrier_string = "WiFi-WSC";
mime_type = WIFI_WSC_MIME_STRING;
local_carrier = __near_agent_handover_request_data(
HO_AGENT_WIFI, remote_carrier);
break;
case NEAR_CARRIER_EMPTY:
case NEAR_CARRIER_UNKNOWN:
default:
return -EINVAL;
}
/*
* WiFi has to be handled a bit differently when we're trying
* to send the first Hr or Hs (i.e. remote_carrier == NULL).
* There are 2 invalid combinations:
* a) AP mode and sending an Hr, as we won't be able to
* initiate the association.
* b) STA mode and sending an Hs, as the AP won't be able
* initiate the association.
*
* a) is detected when local_carrier != NULL and record_type is
* Handover Request.
* b) is detected when local_carrier == NULL and record_type is
* Handover Select.
* In those 2 cases we return an error.
*/
if (carrier == NEAR_CARRIER_WIFI && !remote_carrier) {
if (local_carrier &&
type == RECORD_TYPE_WKT_HANDOVER_REQUEST) {
g_free(local_carrier);
return -EINVAL;
}
if (!local_carrier &&
type == RECORD_TYPE_WKT_HANDOVER_SELECT)
return -EINVAL;
if (!local_carrier &&
type == RECORD_TYPE_WKT_HANDOVER_REQUEST) {
*cfg = near_ndef_prepare_cfg_message(mime_type,
NULL, 0,
&cdr, sizeof(cdr));
*ac = near_ndef_prepare_ac_message(CPS_UNKNOWN, &cdr,
sizeof(cdr));
return 0;
}
}
if (!local_carrier) {
DBG("Unable to retrieve local carrier %s data", carrier_string);
return -ESRCH;
}
*cfg = near_ndef_prepare_cfg_message(mime_type, local_carrier->data,
local_carrier->size,
&cdr, sizeof(cdr));
*ac = near_ndef_prepare_ac_message(local_carrier->state,
&cdr, sizeof(cdr));
g_free(local_carrier);
if (!*cfg || !*ac) {
free_ndef_message(*ac);
free_ndef_message(*cfg);
return -ENOMEM;
}
return 0;
}
static void free_ndef_list(gpointer data)
{
struct near_ndef_message *msg = data;
free_ndef_message(msg);
}
static struct near_ndef_message *prepare_handover_message_header(char *type,
uint32_t msg_len, uint32_t payload_len)
{
struct near_ndef_message *ho_msg;
ho_msg = ndef_message_alloc(type, msg_len);
if (!ho_msg)
return NULL;
/*
* The handover payload length is not the *real* length.
* The PL refers to the NDEF record, not the extra ones.
* So, we have to fix the payload length in the header.
*/
ho_msg->data[NDEF_PAYLOAD_LENGTH_OFFSET] = payload_len;
near_ndef_set_mb_me(ho_msg->data, true, false);
/* Add version */
ho_msg->data[ho_msg->offset++] = HANDOVER_VERSION;
return ho_msg;
}
static uint32_t ndef_message_list_length(GList *list)
{
struct near_ndef_message *msg;
uint32_t length = 0;
if (!list)
return 0;
while (list) {
msg = list->data;
length += msg->length;
list = list->next;
}
return length;
}
static void copy_ac_records(struct near_ndef_message *ho, GList *acs)
{
GList *temp = acs;
struct near_ndef_message *ac;
if (!ho || !temp)
return;
while (temp) {
ac = temp->data;
memcpy(ho->data + ho->offset, ac->data, ac->length);
/*
* AC records are part of handover message payload,
* so modifying offset.
*/
ho->offset += ac->length;
temp = temp->next;
}
}
static void copy_cfg_records(struct near_ndef_message *ho, GList *cfgs)
{
GList *temp = cfgs;
struct near_ndef_message *cfg;
uint32_t offset;
if (!ho || !temp)
return;
offset = ho->offset;
while (temp) {
cfg = temp->data;
memcpy(ho->data + offset, cfg->data, cfg->length);
/*
* Configuration records (e.g. bt or wifi) records are not part
* of handover payload, they are consecutive ndef msgs. So
* here we are not modifying ho->offset.
*/
offset += cfg->length;
temp = temp->next;
}
}
static void set_mb_me_to_false(gpointer data, gpointer user_data)
{
struct near_ndef_message *msg = data;
near_ndef_set_mb_me(msg->data, false, false);
}
static struct near_ndef_message *near_ndef_prepare_empty_hs_message(void)
{
struct near_ndef_message *hs_msg;
struct near_ndef_message *ac_msg;
char cdr = 0x00;
uint32_t hs_length;
DBG("");
ac_msg = near_ndef_prepare_ac_message(CPS_UNKNOWN, &cdr, sizeof(cdr));
if (!ac_msg)
return NULL;
hs_length = 1;
hs_length += ac_msg->length;
hs_msg = prepare_handover_message_header("Hs", hs_length, hs_length);
if (!hs_msg)
goto fail;
near_ndef_set_mb_me(hs_msg->data, true, true);
memcpy(hs_msg->data + hs_msg->offset, ac_msg->data, ac_msg->length);
hs_msg->offset += ac_msg->length;
if (hs_msg->offset > hs_msg->length)
goto fail;
free_ndef_message(ac_msg);
return hs_msg;
fail:
free_ndef_message(ac_msg);
free_ndef_message(hs_msg);
return NULL;
}
static struct near_ndef_message *near_ndef_prepare_hs_reply(
GSList *remote_mimes,
GSList *remote_cfgs)
{
struct near_ndef_message *hs_msg = NULL;
struct near_ndef_message *ac_msg;
struct near_ndef_message *cfg_msg;
struct near_ndef_mime_payload *remote_mime;
struct carrier_data *remote_cfg;
GList *ac_msgs = NULL, *cfg_msgs = NULL, *temp;
GSList *mime_iter, *cfg_iter;
uint8_t hs_length, hs_pl_length, num_of_carriers;
int ret = -EINVAL;
DBG("");
/*
* Preparing empty Hs message in case remote devices has zero
* alternative carries or unknown mime types or unknown
* configuration data.
*/
if ((!remote_mimes || !remote_cfgs))
return near_ndef_prepare_empty_hs_message();
mime_iter = remote_mimes;
cfg_iter = remote_cfgs;
while (mime_iter) {
remote_mime = mime_iter->data;
remote_cfg = cfg_iter->data;
if (!remote_mime || !remote_cfg)
goto fail;
ret = near_ndef_prepare_ac_and_cfg_records(
RECORD_TYPE_WKT_HANDOVER_SELECT,
remote_mime->handover.carrier_type,
&ac_msg, &cfg_msg,
remote_mime, remote_cfg);
if (ret == 0) {
ac_msgs = g_list_append(ac_msgs, ac_msg);
cfg_msgs = g_list_append(cfg_msgs, cfg_msg);
}
mime_iter = mime_iter->next;
cfg_iter = cfg_iter->next;
}
if (g_list_length(ac_msgs) == 0) {
DBG("no alternative carriers, so preparing empty Hs message");
return near_ndef_prepare_empty_hs_message();
}
/* Prepare Hs message */
hs_pl_length = 1;
/* Alternative carriers are part of handover record payload length */
hs_pl_length += ndef_message_list_length(ac_msgs);
hs_length = hs_pl_length;
/* Configuration records are part of handover message length */
hs_length += ndef_message_list_length(cfg_msgs);
hs_msg = prepare_handover_message_header("Hs", hs_length, hs_pl_length);
if (!hs_msg)
goto fail;
num_of_carriers = g_list_length(ac_msgs);
if (num_of_carriers == 1) {
/* only one message */
ac_msg = ac_msgs->data;
near_ndef_set_mb_me(ac_msg->data, true, true);
} else if (num_of_carriers > 1) {
g_list_foreach(ac_msgs, set_mb_me_to_false, NULL);
/* first message */
temp = g_list_first(ac_msgs);
ac_msg = temp->data;
near_ndef_set_mb_me(ac_msg->data, true, false);
/* last message */
temp = g_list_last(ac_msgs);
ac_msg = temp->data;
near_ndef_set_mb_me(ac_msg->data, false, true);
}
g_list_foreach(cfg_msgs, set_mb_me_to_false, NULL);
temp = g_list_last(cfg_msgs);
cfg_msg = temp->data;
near_ndef_set_mb_me(cfg_msg->data, false, true);
/* copy acs */
copy_ac_records(hs_msg, ac_msgs);
if (hs_msg->offset > hs_msg->length)
goto fail;
/*
* copy cfgs, cfg (associated to the ac) records length
* (bt or wifi) is not part of Hs initial size.
*/
copy_cfg_records(hs_msg, cfg_msgs);
DBG("Hs message preparation is done");
g_list_free_full(ac_msgs, free_ndef_list);
g_list_free_full(cfg_msgs, free_ndef_list);
return hs_msg;
fail:
near_error("handover Hs message preparation failed");
g_list_free_full(ac_msgs, free_ndef_list);
g_list_free_full(cfg_msgs, free_ndef_list);
free_ndef_message(hs_msg);
return NULL;
}
static enum handover_carrier string2carrier(char *carrier)
{
if (strcasecmp(carrier, NEAR_HANDOVER_AGENT_BLUETOOTH) == 0)
return NEAR_CARRIER_BLUETOOTH;
if (strcasecmp(carrier, NEAR_HANDOVER_AGENT_WIFI) == 0)
return NEAR_CARRIER_WIFI;
return NEAR_CARRIER_UNKNOWN;
}
static struct near_ndef_message *
near_ndef_prepare_ho_message(enum record_type type, GSList *carriers)
{
struct near_ndef_message *ho_msg = NULL;
struct near_ndef_message *cr_msg = NULL;
struct near_ndef_message *ac_msg;
struct near_ndef_message *cfg_msg;
GList *ac_msgs = NULL, *cfg_msgs = NULL, *temp;
uint16_t collision;
uint8_t ho_length, ho_pl_length;
int ret = -EINVAL;
char *str_type;
DBG("");
switch (type) {
case RECORD_TYPE_WKT_HANDOVER_REQUEST:
str_type = "Hr";
break;
case RECORD_TYPE_WKT_HANDOVER_SELECT:
str_type = "Hs";
break;
default:
return NULL;
}
/* Hr message should have at least one carrier */
while (carriers) {
ret = near_ndef_prepare_ac_and_cfg_records(
type,
string2carrier(carriers->data),
&ac_msg, &cfg_msg, NULL, NULL);
if (ret == 0) {
ac_msgs = g_list_append(ac_msgs, ac_msg);
cfg_msgs = g_list_append(cfg_msgs, cfg_msg);
}
carriers = carriers->next;
}
if (g_list_length(ac_msgs) == 0) {
DBG("no alternative carriers to prepare Hr message");
goto fail;
}
/* Prepare Hr message */
ho_pl_length = 1;
/* Prepare collision resolution record MB=1 ME=0, only for Hr */
if (type == RECORD_TYPE_WKT_HANDOVER_REQUEST) {
collision = GUINT16_TO_BE(g_random_int_range(0, G_MAXUINT16 + 1));
cr_msg = near_ndef_prepare_cr_message(collision);
if (!cr_msg)
goto fail;
near_ndef_set_mb_me(cr_msg->data, true, false);
ho_pl_length += cr_msg->length;
}
/* Alternative carriers are part of handover record payload length */
ho_pl_length += ndef_message_list_length(ac_msgs);
ho_length = ho_pl_length;
/* Configuration records are part of handover message length */
ho_length += ndef_message_list_length(cfg_msgs);
ho_msg = prepare_handover_message_header(str_type,
ho_length, ho_pl_length);
if (!ho_msg)
goto fail;
g_list_foreach(ac_msgs, set_mb_me_to_false, NULL);
/* last message */
temp = g_list_last(ac_msgs);
ac_msg = temp->data;
near_ndef_set_mb_me(ac_msg->data, false, true);
/*
* Hs record payloads do not have collision recore, the first record
* will be the first ac record.
*/
if (type == RECORD_TYPE_WKT_HANDOVER_SELECT) {
temp = g_list_first(ac_msgs);
ac_msg = temp->data;
near_ndef_set_mb_me(ac_msg->data, true, false);
}
g_list_foreach(cfg_msgs, set_mb_me_to_false, NULL);
temp = g_list_last(cfg_msgs);
cfg_msg = temp->data;
near_ndef_set_mb_me(cfg_msg->data, false, true);
if (type == RECORD_TYPE_WKT_HANDOVER_REQUEST) {
/* copy cr */
memcpy(ho_msg->data + ho_msg->offset,
cr_msg->data, cr_msg->length);
ho_msg->offset += cr_msg->length;
if (ho_msg->offset > ho_msg->length)
goto fail;
}
/* copy acs */
copy_ac_records(ho_msg, ac_msgs);
if (ho_msg->offset > ho_msg->length)
goto fail;
/*
* copy cfgs, cfg (associated to the ac) records length
* (bt or wifi) is not part of Hr initial size.
*/
copy_cfg_records(ho_msg, cfg_msgs);
DBG("Handover message preparation is done");
free_ndef_message(cr_msg);
g_list_free_full(ac_msgs, free_ndef_list);
g_list_free_full(cfg_msgs, free_ndef_list);
return ho_msg;
fail:
near_error("handover record preparation failed");
g_list_free_full(ac_msgs, free_ndef_list);
g_list_free_full(cfg_msgs, free_ndef_list);
free_ndef_message(cr_msg);
free_ndef_message(ho_msg);
return NULL;
}
/* Code to fill hr record structure from acs and mimes lists */
static int near_fill_ho_payload(struct near_ndef_ho_payload *ho,
GSList *acs, GSList *mimes)
{
int rec_count;
int i;
GSList *temp;
rec_count = g_slist_length(acs);
ho->ac_payloads = g_try_malloc0(rec_count *
sizeof(struct near_ndef_ac_payload *));
if (!ho->ac_payloads)
goto fail;
temp = acs;
for (i = 0; i < rec_count; i++) {
ho->ac_payloads[i] = temp->data;
temp = temp->next;
}
ho->number_of_ac_payloads = rec_count;
g_slist_free(acs);
/* Same process for cfg mimes */
rec_count = g_slist_length(mimes);
ho->cfg_payloads = g_try_malloc0(rec_count *
sizeof(struct near_ndef_mime_payload *));
if (!ho->cfg_payloads)
goto fail;
temp = mimes;
for (i = 0; i < rec_count; i++) {
ho->cfg_payloads[i] = temp->data;
temp = temp->next;
}
ho->number_of_cfg_payloads = rec_count;
g_slist_free(mimes);
return 0;
fail:
g_free(ho->ac_payloads);
g_free(ho->cfg_payloads);
ho->ac_payloads = NULL;
ho->cfg_payloads = NULL;
return -ENOMEM;
}
/*
* This function will parse an handover record payload, retrieving sub records
* like (ac, cr, er) but it will also get the associated ndefs
* (eg: handover carrier record, mime type for BT)
* In a handover frame, only the following types are expected:
* RECORD_TYPE_WKT_HANDOVER_CARRIER:
* RECORD_TYPE_WKT_COLLISION_RESOLUTION
* RECORD_TYPE_MIME_TYPE
* RECORD_TYPE_WKT_ALTERNATIVE_CARRIER
*/
static struct near_ndef_ho_payload *parse_ho_payload(enum record_type rec_type,
uint8_t *payload, uint32_t ho_length, size_t frame_length,
uint8_t ho_mb, uint8_t ho_me, struct near_ndef_message **reply)
{
struct near_ndef_ho_payload *ho_payload = NULL;
struct near_ndef_ac_payload *ac = NULL;
struct near_ndef_mime_payload *mime = NULL;
struct carrier_data *c_data;
struct near_ndef_record *trec = NULL;
GSList *acs = NULL, *mimes = NULL, *c_datas = NULL;
uint8_t mb = 0, me = 0, i;
uint32_t offset;
int16_t count_ac = 0;
bool action = false, status;
DBG("");
if (!payload)
return NULL;
offset = 0;
/* Create the handover record payload */
ho_payload = g_try_malloc0(sizeof(struct near_ndef_ho_payload));
if (!ho_payload)
return NULL;
/* Version is the first mandatory field of hr payload */
ho_payload->version = payload[offset];
/* If major is different, reply with an empty Hs */
if (HANDOVER_MAJOR(ho_payload->version) !=
HANDOVER_MAJOR(HANDOVER_VERSION)) {
near_error("Unsupported version (%d)", ho_payload->version);
/* Skip parsing and return an empty record */
if (reply)
*reply = near_ndef_prepare_empty_hs_message();
return ho_payload;
}
offset = offset + 1;
/* We should work on the whole frame */
ho_length = frame_length;
while (offset < ho_length) {
/* Create local record for mime parsing */
trec = g_try_malloc0(sizeof(struct near_ndef_record));
if (!trec)
return NULL;
trec->header = parse_record_header(payload, offset, ho_length);
if (!trec->header)
goto fail;
offset = trec->header->offset;
switch (trec->header->rec_type) {
case RECORD_TYPE_WKT_SMART_POSTER:
case RECORD_TYPE_WKT_SIZE:
case RECORD_TYPE_WKT_TEXT:
case RECORD_TYPE_WKT_TYPE:
case RECORD_TYPE_WKT_ACTION:
case RECORD_TYPE_WKT_URI:
case RECORD_TYPE_WKT_HANDOVER_REQUEST:
case RECORD_TYPE_WKT_HANDOVER_SELECT:
case RECORD_TYPE_WKT_ERROR:
case RECORD_TYPE_EXT_AAR:
case RECORD_TYPE_UNKNOWN:
break;
case RECORD_TYPE_EMPTY:
case RECORD_TYPE_ERROR:
goto fail;
case RECORD_TYPE_WKT_HANDOVER_CARRIER:
DBG("HANDOVER_CARRIER");
/*
* TODO process Hc record too !!!
* Used for Wifi session
*/
break;
case RECORD_TYPE_MIME_TYPE:
DBG("TYPE_MIME_TYPE");
/* check mb/me bits */
if (validate_record_begin_and_end_bits(&ho_mb, &ho_me,
trec->header->mb, trec->header->me) != 0) {
DBG("validate mb me failed");
goto fail;
}
/*
* In Handover, the mime type gives bluetooth handover
* or WiFi configuration data.
* If we initiated the session, the received Hs frame
* is the signal to launch the pairing.
*/
if (rec_type == RECORD_TYPE_WKT_HANDOVER_SELECT)
action = true;
else
action = false;
/* HO payload for reply creation */
trec->ho = ho_payload;
mime = parse_mime_type(trec, payload, frame_length,
offset, trec->header->payload_len,
&c_data);
trec->ho = NULL;
if (!mime || !c_data)
goto fail;
/* add the mime to the list */
mimes = g_slist_append(mimes, mime);
/* add the carrier data to the list */
c_datas = g_slist_append(c_datas, c_data);
count_ac--;
if (count_ac == 0)
offset = ho_length;
break;
case RECORD_TYPE_WKT_COLLISION_RESOLUTION:
DBG("COLLISION_RESOLUTION");
/* check nested mb/me bits */
if (validate_record_begin_and_end_bits(&mb, &me,
trec->header->mb, trec->header->me) != 0) {
DBG("validate mb me failed");
goto fail;
}
ho_payload->collision_record =
near_get_be16(payload + offset);
break;
case RECORD_TYPE_WKT_ALTERNATIVE_CARRIER:
DBG("ALTERNATIVE_CARRIER");
/* check nested mb/me bits */
if (validate_record_begin_and_end_bits(&mb, &me,
trec->header->mb, trec->header->me) != 0) {
DBG("validate mb me failed");
goto fail;
}
ac = parse_ac_payload(payload + offset,
trec->header->payload_len);
if (!ac)
goto fail;
acs = g_slist_append(acs, ac);
/* TODO check if adata are present */
count_ac++;
break;
}
offset += trec->header->payload_len;
g_free(trec->header->il_field);
g_free(trec->header->type_name);
g_free(trec->header);
trec->header = NULL;
g_free(trec);
trec = NULL;
}
/*
* In case of multiple carriers, handover with any carrier
* gets done then leave the loop.
*/
if (action) {
status = false;
count_ac = g_slist_length(mimes);
for (i = 0; i < count_ac; i++) {
if (process_mime_type(g_slist_nth_data(mimes, i),
g_slist_nth_data(c_datas, i)) == 0) {
status = true;
break;
}
}
if (!status) {
DBG("could not process alternative carriers");
goto fail;
}
} else if (reply) {
/* This is a Hs with no cfg and no Ac: No reply and fail */
if (rec_type == RECORD_TYPE_WKT_HANDOVER_SELECT &&
g_slist_length(acs) == 0) {
*reply = NULL;
goto fail;
}
/* Prepare Hs, it depends upon Hr message carrier types */
*reply = near_ndef_prepare_hs_reply(mimes, c_datas);
if (!*reply) {
DBG("error in preparing in HS record");
goto fail;
}
}
if ((!acs) || (!mimes))
return ho_payload;
/* Save the records */
if (near_fill_ho_payload(ho_payload, acs, mimes) < 0)
goto fail;
DBG("handover payload parsing complete");
g_slist_free_full(c_datas, g_free);
return ho_payload;
fail:
near_error("handover payload parsing failed");
if (trec) {
if (trec->header) {
g_free(trec->header->type_name);
g_free(trec->header->il_field);
g_free(trec->header);
}
g_free(trec);
}
g_slist_free_full(c_datas, g_free);
free_ho_payload(ho_payload);
return NULL;
}
static struct near_ndef_aar_payload *
parse_aar_payload(uint8_t *payload, uint32_t length)
{
struct near_ndef_aar_payload *aar_payload = NULL;
DBG("");
if (!payload)
return NULL;
aar_payload = g_try_malloc0(sizeof(struct near_ndef_uri_payload));
if (!aar_payload)
return NULL;
aar_payload->package = g_strndup((char *)payload, length);
if (!aar_payload->package) {
near_error("AAR payload parsing failed");
free_aar_payload(aar_payload);
return NULL;
}
DBG("AAR package %s", aar_payload->package);
return aar_payload;
}
int __near_ndef_record_register(struct near_ndef_record *record, char *path)
{
record->path = path;
g_dbus_register_interface(connection, record->path,
NFC_RECORD_INTERFACE,
NULL, NULL, record_properties,
record, NULL);
return 0;
}
/*
* These functions parse a specific type record (id or mime) to find the
* associated string.
*/
bool near_ndef_record_cmp_id(struct near_ndef_record *rec1,
struct near_ndef_record *rec2)
{
DBG("");
if ((!rec1) || (!rec2))
return false;
if ((!rec1->header) || (!rec2->header))
return false;
/* usual checks */
if ((!rec1->header->il_field) ||
(!rec2->header->il_field))
return false;
if (memcmp(rec1->header->il_field, rec2->header->il_field,
(rec1->header->il_length) > (rec2->header->il_length)
? (rec1->header->il_length) :
(rec2->header->il_length)) != 0)
return false;
return true;
}
bool near_ndef_record_cmp_mime(struct near_ndef_record *rec1,
struct near_ndef_record *rec2)
{
DBG("");
if ((!rec1) || (!rec2))
return false;
if ((!rec1->header) || (!rec2->header))
return false;
/* usual checks */
if ((!rec1->mime) || (!rec2->mime))
return false;
if ((!rec1->mime->type) || (!rec2->mime->type))
return false;
if (strlen(rec1->mime->type) != strlen(rec2->mime->type))
return false;
if ((g_strcmp0(rec1->mime->type, rec2->mime->type) != 0))
return false;
return true;
}
/* helper to get the record data length */
size_t near_ndef_data_length(struct near_ndef_record *rec)
{
if (!rec)
return 0;
else
return rec->data_len;
}
/* helper to get the record data pointer */
uint8_t *near_ndef_data_ptr(struct near_ndef_record *rec)
{
if (!rec)
return NULL;
else
return rec->data;
}
GList *near_ndef_parse_msg(uint8_t *ndef_data, size_t ndef_length,
struct near_ndef_message **reply)
{
GList *records;
uint8_t p_mb = 0, p_me = 0, *record_start;
size_t offset = 0;
struct near_ndef_record *record = NULL;
struct carrier_data *c_data;
DBG("");
records = NULL;
if (!ndef_data ||
ndef_length < NDEF_MSG_MIN_LENGTH)
goto fail;
while (offset < ndef_length) {
DBG("Record Header : 0x%X", ndef_data[offset]);
record = g_try_malloc0(sizeof(struct near_ndef_record));
if (!record)
goto fail;
record->header = parse_record_header(ndef_data, offset,
ndef_length);
if (!record->header)
goto fail;
if (validate_record_begin_and_end_bits(&p_mb, &p_me,
record->header->mb,
record->header->me) != 0) {
DBG("validate mb me failed");
goto fail;
}
record_start = ndef_data + offset;
offset = record->header->offset;
switch (record->header->rec_type) {
case RECORD_TYPE_WKT_SIZE:
case RECORD_TYPE_WKT_TYPE:
case RECORD_TYPE_WKT_ACTION:
case RECORD_TYPE_WKT_HANDOVER_CARRIER:
case RECORD_TYPE_WKT_ALTERNATIVE_CARRIER:
case RECORD_TYPE_WKT_COLLISION_RESOLUTION:
case RECORD_TYPE_WKT_ERROR:
case RECORD_TYPE_UNKNOWN:
break;
case RECORD_TYPE_EMPTY:
break;
case RECORD_TYPE_ERROR:
goto fail;
case RECORD_TYPE_WKT_HANDOVER_REQUEST:
case RECORD_TYPE_WKT_HANDOVER_SELECT:
/*
* Handover frame are a little bit special as the NDEF
* length (specified in the header) is not the real
* frame size. The complete frame includes extra NDEF
* following the initial handover NDEF
*/
record->ho = parse_ho_payload(record->header->rec_type,
ndef_data + offset,
record->header->payload_len,
ndef_length - offset,
record->header->mb, record->header->me,
reply);
if (!record->ho)
goto fail;
/* the complete frame is processed, break the loop */
record->header->payload_len = ndef_length;
break;
case RECORD_TYPE_WKT_TEXT:
record->text = parse_text_payload(ndef_data + offset,
record->header->payload_len);
if (!record->text)
goto fail;
break;
case RECORD_TYPE_WKT_URI:
record->uri = parse_uri_payload(ndef_data + offset,
record->header->payload_len);
if (!record->uri)
goto fail;
break;
case RECORD_TYPE_WKT_SMART_POSTER:
record->sp = parse_sp_payload(
ndef_data + offset,
record->header->payload_len);
if (!record->sp)
goto fail;
break;
case RECORD_TYPE_MIME_TYPE:
record->mime = parse_mime_type(record, ndef_data,
ndef_length, offset,
record->header->payload_len,
&c_data);
if (!record->mime)
goto fail;
/* No carrier data, move on */
if (!c_data)
break;
if (process_mime_type(record->mime, c_data) < 0) {
g_free(c_data);
c_data = NULL;
goto fail;
}
g_free(c_data);
c_data = NULL;
break;
case RECORD_TYPE_EXT_AAR:
record->aar = parse_aar_payload(ndef_data + offset,
record->header->payload_len);
if (!record->aar)
goto fail;
break;
}
record->data_len = record->header->header_len +
record->header->payload_len;
record->data = g_try_malloc0(record->data_len);
if (!record->data)
goto fail;
memcpy(record->data, record_start, record->data_len);
records = g_list_append(records, record);
build_record_type_string(record);
offset += record->header->payload_len;
}
return records;
fail:
near_error("ndef parsing failed");
free_ndef_record(record);
return records;
}
void near_ndef_records_free(GList *records)
{
GList *list;
for (list = records; list; list = list->next) {
struct near_ndef_record *record = list->data;
__near_ndef_record_free(record);
}
g_list_free(records);
}
/*
* Compute ndef records length, even though the submitted frame is incomplete.
* This code is used in the handover read function, as we have to "guess" the
* final frame size.
*
* Message size for SR=1 is:
* 1 : ndef rec header (offset 0)
* x : record type length (offset 1)
* y : payload length (offset 2) 1 byte ONLY if SR=1
* if SR=0: (4bytes) 32 bits
* z : payload id length (offset 3) ONLY if il_length=1
* */
int near_ndef_record_length(uint8_t *ndef_in, size_t ndef_in_length)
{
int ndef_size; /* default size for NDEF hdr + rec typ len + payl */
size_t offset;
uint8_t hdr;
DBG("");
if (ndef_in_length < 3)
return -EINVAL;
ndef_size = 3;
offset = 0;
/* save header byte */
hdr = ndef_in[offset];
offset++;
/* header->type_len */
ndef_size += ndef_in[offset++];
/* header->payload_len */
if (RECORD_SR_BIT(hdr) == 1) {
ndef_size += ndef_in[offset++];
} else {
ndef_size += near_get_be32(ndef_in + offset);
offset += 4;
if (offset >= ndef_in_length)
return -ERANGE;
}
/* header->il */
ndef_size += RECORD_IL_BIT(hdr);
/* header->il_length */
if (RECORD_IL_BIT(hdr) == 1)
ndef_size += ndef_in[offset++];
DBG("near_ndef_message_length is %d", ndef_size);
return ndef_size;
}
int near_ndef_count_records(uint8_t *ndef_in, size_t ndef_in_length,
uint8_t record_type)
{
uint8_t p_mb = 0, p_me = 0;
int err;
size_t offset;
struct near_ndef_record *record = NULL;
int counted_records = 0 ;
DBG("");
offset = 0;
if (!ndef_in || ndef_in_length < NDEF_MSG_MIN_LENGTH) {
err = -EINVAL;
goto fail;
}
while (offset < ndef_in_length) {
record = g_try_malloc0(sizeof(struct near_ndef_record));
if (!record) {
err = -ENOMEM;
goto fail;
}
/* Create a record */
record->header = parse_record_header(ndef_in, offset,
ndef_in_length);
if (!record->header) {
err = -EINVAL;
goto fail;
}
/* Validate MB ME */
if (validate_record_begin_and_end_bits(&p_mb, &p_me,
record->header->mb,
record->header->me) != 0) {
DBG("validate mb me failed");
err = -EINVAL;
goto fail;
}
/* Is this what we want ? */
if (record->header->rec_type == record_type)
counted_records++;
/* Jump to the next record */
offset = record->header->offset + record->header->payload_len;
free_ndef_record(record);
}
DBG("Type %d Records found: %d", record_type, counted_records);
return counted_records;
fail:
near_error("ndef counting failed");
free_ndef_record(record);
return err;
}
/* Possible encoding "UTF-8" or "UTF-16" */
struct near_ndef_message *near_ndef_prepare_text_record(char *encoding,
char *language_code, char *text)
{
struct near_ndef_message *msg;
uint32_t text_len, payload_length;
uint8_t code_len, status = 0;
DBG("");
/* Validate input parameters*/
if (((g_strcmp0(encoding, "UTF-8") != 0) &&
(g_strcmp0(encoding, "UTF-16") != 0)) ||
(!language_code) ||
(!text)) {
return NULL;
}
code_len = strlen(language_code);
text_len = strlen(text);
payload_length = 1 + code_len + text_len;
msg = ndef_message_alloc("T", payload_length);
if (!msg)
return NULL;
if (g_strcmp0(encoding, "UTF-16") == 0)
status |= NDEF_TEXT_RECORD_UTF16_STATUS;
status = status | code_len;
msg->data[msg->offset++] = status;
if (code_len > 0)
memcpy(msg->data + msg->offset, language_code, code_len);
msg->offset += code_len;
if (text_len > 0)
memcpy(msg->data + msg->offset, text, text_len);
msg->offset += text_len;
if (msg->offset > msg->length)
goto fail;
return msg;
fail:
near_error("text record preparation failed");
free_ndef_message(msg);
return NULL;
}
struct near_ndef_message *near_ndef_prepare_uri_record(uint8_t identifier,
uint32_t field_length, uint8_t *field)
{
struct near_ndef_message *msg = NULL;
uint32_t payload_length;
DBG("");
/* Validate input parameters*/
if ((field_length == 0 && field) ||
(field_length != 0 && !field)) {
return NULL;
}
payload_length = field_length + 1;
msg = ndef_message_alloc("U", payload_length);
if (!msg)
return NULL;
msg->data[msg->offset++] = identifier;
if (field_length > 0) {
memcpy(msg->data + msg->offset, field, field_length);
msg->offset += field_length;
}
if (msg->offset > msg->length)
goto fail;
return msg;
fail:
near_error("uri record preparation failed");
free_ndef_message(msg);
return NULL;
}
struct near_ndef_message *
near_ndef_prepare_smartposter_record(uint8_t uri_identifier,
uint32_t uri_field_length,
uint8_t *uri_field)
{
struct near_ndef_message *msg = NULL, *uri = NULL;
/* URI is mandatory in Smartposter */
uri = near_ndef_prepare_uri_record(uri_identifier, uri_field_length,
uri_field);
if (!uri)
goto fail;
/* URI record length is equal to payload length of Sp record */
msg = ndef_message_alloc("Sp", uri->length);
if (!msg)
goto fail;
memcpy(msg->data + msg->offset, uri->data, uri->length);
msg->offset += uri->length;
if (msg->offset > msg->length)
goto fail;
free_ndef_message(uri);
return msg;
fail:
near_error("smartposter record preparation failed");
free_ndef_message(uri);
free_ndef_message(msg);
return NULL;
}
static char *get_text_field(DBusMessage *msg, char *text)
{
DBusMessageIter iter, arr_iter;
char *uri = NULL;
DBG("");
if (!text)
return NULL;
dbus_message_iter_init(msg, &iter);
dbus_message_iter_recurse(&iter, &arr_iter);
while (dbus_message_iter_get_arg_type(&arr_iter) !=
DBUS_TYPE_INVALID) {
const char *key;
DBusMessageIter ent_iter;
DBusMessageIter var_iter;
dbus_message_iter_recurse(&arr_iter, &ent_iter);
dbus_message_iter_get_basic(&ent_iter, &key);
dbus_message_iter_next(&ent_iter);
dbus_message_iter_recurse(&ent_iter, &var_iter);
switch (dbus_message_iter_get_arg_type(&var_iter)) {
case DBUS_TYPE_STRING:
if (g_strcmp0(key, text) == 0)
dbus_message_iter_get_basic(&var_iter, &uri);
break;
}
dbus_message_iter_next(&arr_iter);
}
return uri;
}
static inline char *get_uri_field(DBusMessage *msg)
{
return get_text_field(msg, "URI");
}
static GSList *get_carrier_field(DBusMessage *msg)
{
char *carrier;
char **arr;
GSList *carriers = NULL;
uint8_t num_of_carriers, i;
DBG("");
carrier = get_text_field(msg, "Carrier");
if (!carrier)
return NULL;
arr = g_strsplit(carrier, ",", NEAR_CARRIER_MAX);
num_of_carriers = g_strv_length(arr);
for (i = 0; i < num_of_carriers; i++)
carriers = g_slist_append(carriers, g_strdup(arr[i]));
g_strfreev(arr);
return carriers;
}
static struct near_ndef_message *build_text_record(DBusMessage *msg)
{
DBusMessageIter iter, arr_iter;
char *cod = NULL, *lang = NULL, *rep = NULL;
DBG("");
dbus_message_iter_init(msg, &iter);
dbus_message_iter_recurse(&iter, &arr_iter);
while (dbus_message_iter_get_arg_type(&arr_iter) !=
DBUS_TYPE_INVALID) {
const char *key;
DBusMessageIter ent_iter;
DBusMessageIter var_iter;
dbus_message_iter_recurse(&arr_iter, &ent_iter);
dbus_message_iter_get_basic(&ent_iter, &key);
dbus_message_iter_next(&ent_iter);
dbus_message_iter_recurse(&ent_iter, &var_iter);
switch (dbus_message_iter_get_arg_type(&var_iter)) {
case DBUS_TYPE_STRING:
if (g_strcmp0(key, "Encoding") == 0)
dbus_message_iter_get_basic(&var_iter, &cod);
else if (g_strcmp0(key, "Language") == 0)
dbus_message_iter_get_basic(&var_iter, &lang);
else if (g_strcmp0(key, "Representation") == 0)
dbus_message_iter_get_basic(&var_iter, &rep);
break;
}
dbus_message_iter_next(&arr_iter);
}
return near_ndef_prepare_text_record(cod, lang, rep);
}
static struct near_ndef_message *build_uri_record(DBusMessage *msg)
{
char *uri = NULL;
const char *uri_prefix = NULL;
uint8_t id_len, i, id;
uint32_t uri_len;
DBG("");
uri = get_uri_field(msg);
if (!uri)
return NULL;
id = 0;
id_len = 0;
for (i = 1; i <= NFC_MAX_URI_ID; i++) {
uri_prefix = __near_ndef_get_uri_prefix(i);
if (uri_prefix &&
g_str_has_prefix(uri, uri_prefix)) {
id = i;
id_len = strlen(uri_prefix);
break;
}
}
DBG("%d %d\n", i, id_len);
uri_len = strlen(uri) - id_len;
return near_ndef_prepare_uri_record(id, uri_len,
(uint8_t *)(uri + id_len));
}
static struct near_ndef_message *build_sp_record(DBusMessage *msg)
{
char *uri = NULL;
const char *uri_prefix;
uint8_t id_len, i;
uint32_t uri_len;
DBG("");
/*
* Currently this function supports only mandatory URI record,
* TODO: Other records support.
*/
uri = get_uri_field(msg);
if (!uri)
return NULL;
for (i = 1; i <= NFC_MAX_URI_ID; i++) {
uri_prefix = __near_ndef_get_uri_prefix(i);
if (uri_prefix &&
g_str_has_prefix(uri, uri_prefix))
break;
}
if (!uri_prefix) {
i = 0;
id_len = 0;
} else
id_len = strlen(uri_prefix);
uri_len = strlen(uri) - id_len;
return near_ndef_prepare_smartposter_record(i, uri_len,
(uint8_t *)(uri + id_len));
}
static struct near_ndef_message *build_ho_record(enum record_type type,
DBusMessage *msg)
{
struct near_ndef_message *ho;
GSList *carriers;
DBG("");
carriers = get_carrier_field(msg);
ho = near_ndef_prepare_ho_message(type, carriers);
g_slist_free_full(carriers, g_free);
return ho;
}
static int fill_wifi_wsc_data(uint8_t *tlv, uint16_t id,
uint16_t data_len, uint8_t *data)
{
int offset = 0;
if (!tlv || !data)
return 0;
fillb16(tlv, id);
offset += WIFI_WSC_ID_LENGTH;
fillb16(tlv + offset, data_len);
offset += WIFI_WSC_ID_DATA_LENGTH;
memcpy(tlv + offset, data, data_len);
offset += data_len;
return offset;
}
static void get_wsc_data(DBusMessageIter iter, char **ssid, char **pass)
{
DBG("");
*ssid = NULL;
*pass = NULL;
while (dbus_message_iter_get_arg_type(&iter) !=
DBUS_TYPE_INVALID) {
const char *key;
DBusMessageIter ent_iter;
DBusMessageIter var_iter;
dbus_message_iter_recurse(&iter, &ent_iter);
dbus_message_iter_get_basic(&ent_iter, &key);
dbus_message_iter_next(&ent_iter);
dbus_message_iter_recurse(&ent_iter, &var_iter);
switch (dbus_message_iter_get_arg_type(&var_iter)) {
case DBUS_TYPE_STRING:
if (g_strcmp0(key, "SSID") == 0)
dbus_message_iter_get_basic(&var_iter, ssid);
else if (g_strcmp0(key, "Passphrase") == 0)
dbus_message_iter_get_basic(&var_iter, pass);
break;
}
dbus_message_iter_next(&iter);
}
}
struct near_ndef_message *near_ndef_prepare_wsc_record(char *ssid,
char *passphrase)
{
uint16_t ssid_len, pass_len, key_type;
uint8_t temp_key[2];
uint8_t *tlv;
uint32_t tlv_len, offset;
struct near_ndef_message *mime;
/* At least SSID is required in case of open network */
if (!ssid)
return NULL;
DBG("SSID %s Passphrase %s", ssid, passphrase);
/* Prepare TLV from ssid and passphrasse */
ssid_len = strlen(ssid);
if (passphrase) {
pass_len = strlen(passphrase);
key_type = WIFI_WSC_KEY_PSK;
} else {
pass_len = 0;
key_type = WIFI_WSC_KEY_OPEN;
}
/* add ssid length */
tlv_len = WIFI_WSC_ID_LENGTH + WIFI_WSC_ID_DATA_LENGTH + ssid_len;
/* add authentication type length */
tlv_len += WIFI_WSC_ID_LENGTH + WIFI_WSC_ID_DATA_LENGTH + 2;
/* add network key length */
if (passphrase)
tlv_len += WIFI_WSC_ID_LENGTH +
WIFI_WSC_ID_DATA_LENGTH + pass_len;
tlv = g_try_malloc0(tlv_len);
if (!tlv)
return NULL;
offset = 0;
/* copy SSID */
offset += fill_wifi_wsc_data(tlv, WIFI_WSC_ID_SSID,
ssid_len, (uint8_t *) ssid);
/*
* copy authentication type
* copy key type to temp key array to maintain endianess
* and fill wsc data
*/
fillb16(temp_key, key_type);
offset += fill_wifi_wsc_data(tlv + offset, WIFI_WSC_ID_AUTH_TYPE,
WIFI_WSC_ID_DATA_LENGTH,
(uint8_t *) temp_key);
/* copy Network Key */
if (passphrase)
offset += fill_wifi_wsc_data(tlv + offset, WIFI_WSC_ID_KEY,
pass_len,
(uint8_t *) passphrase);
mime = ndef_message_alloc_complete(WIFI_WSC_MIME_STRING, tlv_len, NULL,
0, RECORD_TNF_MIME, true,
true);
if (!mime) {
g_free(tlv);
return NULL;
}
memcpy(mime->data + mime->offset, tlv, tlv_len);
g_free(tlv);
return mime;
}
static char *get_mime_payload_data(DBusMessageIter iter,
char **payload, int *payload_len)
{
DBG("");
if (!payload || !payload_len) {
near_error("Payload %p payload_len %p", payload, payload_len);
return NULL;
}
while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) {
const char *key;
DBusMessageIter ent_iter;
DBusMessageIter var_iter;
DBusMessageIter arr_iter;
dbus_message_iter_recurse(&iter, &ent_iter);
dbus_message_iter_get_basic(&ent_iter, &key);
dbus_message_iter_next(&ent_iter);
dbus_message_iter_recurse(&ent_iter, &var_iter);
if (g_strcmp0(key, "Payload") == 0) {
if (dbus_message_iter_get_arg_type(&var_iter) ==
DBUS_TYPE_ARRAY &&
dbus_message_iter_get_element_type(&var_iter) ==
DBUS_TYPE_BYTE) {
dbus_message_iter_recurse(&var_iter,
&arr_iter);
dbus_message_iter_get_fixed_array(
&arr_iter, payload,
payload_len);
} else {
near_error("Unexpected payload type");
return NULL;
}
}
dbus_message_iter_next(&iter);
}
return *payload;
}
static struct near_ndef_message *near_ndef_prepare_mime_payload_record(
char *type, char *payload, int payload_len)
{
struct near_ndef_message *mime;
DBG("Payload %*s", payload_len, payload);
mime = ndef_message_alloc_complete(type, payload_len, NULL, 0,
RECORD_TNF_MIME, true, true);
if (!mime) {
near_error("Failed to alloc NDEF message");
return NULL;
}
memcpy(mime->data + mime->offset, payload, payload_len);
return mime;
}
static struct near_ndef_message *build_mime_record(DBusMessage *msg)
{
DBusMessageIter iter, arr_iter;
char *key, *mime_str, *ssid, *passphrase;
DBG("");
dbus_message_iter_init(msg, &iter);
dbus_message_iter_recurse(&iter, &arr_iter);
while (dbus_message_iter_get_arg_type(&arr_iter) !=
DBUS_TYPE_INVALID) {
DBusMessageIter ent_iter;
DBusMessageIter var_iter;
dbus_message_iter_recurse(&arr_iter, &ent_iter);
dbus_message_iter_get_basic(&ent_iter, &key);
if (g_strcmp0(key, "MIME") == 0) {
dbus_message_iter_next(&ent_iter);
dbus_message_iter_recurse(&ent_iter, &var_iter);
dbus_message_iter_get_basic(&var_iter, &mime_str);
if (g_strcmp0(mime_str, WIFI_WSC_MIME_STRING) == 0) {
struct near_ndef_message *mime;
struct carrier_data *carrier;
get_wsc_data(arr_iter, &ssid, &passphrase);
if (ssid)
return near_ndef_prepare_wsc_record(
ssid, passphrase);
/*
* If we did not get an SSID and optionally
* a passphrase from the DBus message, then
* we try to get one from the WiFi-WSC agent.
*/
carrier = __near_agent_handover_request_data(
HO_AGENT_WIFI, NULL);
if (!carrier)
return NULL;
mime = ndef_message_alloc_complete(
WIFI_WSC_MIME_STRING, carrier->size,
NULL, 0, RECORD_TNF_MIME, true, true);
if (!mime) {
g_free(carrier);
return NULL;
}
memcpy(mime->data + mime->offset,
carrier->data, carrier->size);
g_free(carrier);
return mime;
} else {
/*
* Expect data is set in the Payload field of
* message.
*/
DBusMessageIter payload_iter;
char *payload;
int payload_len;
DBG("mime string %s", mime_str);
dbus_message_iter_recurse(&iter, &payload_iter);
if (!get_mime_payload_data(payload_iter,
&payload, &payload_len))
return NULL;
return near_ndef_prepare_mime_payload_record(
mime_str, payload, payload_len);
}
}
dbus_message_iter_next(&arr_iter);
}
return NULL;
}
struct near_ndef_message *__ndef_build_from_message(DBusMessage *msg)
{
DBusMessageIter iter;
DBusMessageIter arr_iter;
struct near_ndef_message *ndef;
DBG("");
dbus_message_iter_init(msg, &iter);
dbus_message_iter_recurse(&iter, &arr_iter);
ndef = NULL;
while (dbus_message_iter_get_arg_type(&arr_iter) !=
DBUS_TYPE_INVALID) {
const char *key, *value;
DBusMessageIter ent_iter;
DBusMessageIter var_iter;
dbus_message_iter_recurse(&arr_iter, &ent_iter);
dbus_message_iter_get_basic(&ent_iter, &key);
if (g_strcmp0(key, "Type") != 0) {
dbus_message_iter_next(&arr_iter);
continue;
}
dbus_message_iter_next(&ent_iter);
dbus_message_iter_recurse(&ent_iter, &var_iter);
switch (dbus_message_iter_get_arg_type(&var_iter)) {
case DBUS_TYPE_STRING:
dbus_message_iter_get_basic(&var_iter, &value);
if (g_strcmp0(value, "Text") == 0) {
ndef = build_text_record(msg);
break;
} else if (g_strcmp0(value, "URI") == 0) {
ndef = build_uri_record(msg);
break;
} else if (g_strcmp0(value, "SmartPoster") == 0) {
ndef = build_sp_record(msg);
break;
} else if (g_strcmp0(value, "Handover") == 0) {
ndef = build_ho_record(
RECORD_TYPE_WKT_HANDOVER_REQUEST, msg);
break;
} else if (g_strcmp0(value, "StaticHandover") == 0) {
ndef = build_ho_record(
RECORD_TYPE_WKT_HANDOVER_SELECT, msg);
break;
} else if (g_strcmp0(value, "MIME") == 0) {
ndef = build_mime_record(msg);
break;
} else {
near_error("%s not supported", value);
ndef = NULL;
break;
}
break;
}
dbus_message_iter_next(&arr_iter);
}
return ndef;
}
int __near_ndef_init(void)
{
DBG("");
connection = near_dbus_get_connection();
return 0;
}
void __near_ndef_cleanup(void)
{
}