blob: 5e46087ae532b9b71145d911f97264a4c20b051e [file] [log] [blame]
/*
*
* neard - Near Field Communication manager
*
* Copyright (C) 2012 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 <stdint.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/socket.h>
#include <near/nfc_copy.h>
#include <near/types.h>
#include <near/log.h>
#include <near/adapter.h>
#include <near/device.h>
#include <near/tag.h>
#include <near/ndef.h>
#include <near/tlv.h>
#include "p2p.h"
#define NDEF_HR_MSG_MIN_LENGTH 0x06
#define HR_HEADER_SIZE 6 /* header (1) + type len (1) +
* payload len (1) + rec type (2) 'Hx'
* + version (1)
*/
#define RECORD_TYPE_WKT_ALTERNATIVE_CARRIER 0x0a
#define FRAME_TYPE_OFFSET 3
enum loop_stage_flag {
STATE_MAIN_NDEF = 0x00,
STATE_CFG_RECORD = 0x01,
};
static GHashTable *hr_ndef_hash = NULL;
struct extra_ndef {
uint8_t *ndef;
uint8_t length;
};
struct hr_ndef {
uint8_t *ndef;
uint16_t cur_ptr;
int cur_record_len;
int missing_bytes;
uint32_t adapter_idx;
uint32_t target_idx;
near_tag_io_cb cb;
int extra_ndef_count;
int block_free_size;
bool cfg_record_state;
bool in_extra_read;
};
struct hr_push_client {
uint8_t fd;
uint32_t adapter_idx;
uint32_t target_idx;
near_device_io_cb cb;
guint watch;
};
static void free_hr_ndef(gpointer data)
{
struct hr_ndef *ndef = data;
if (ndef)
g_free(ndef->ndef);
g_free(ndef);
}
static void handover_close(int client_fd, int err, gpointer data)
{
struct hr_ndef *ndef;
DBG("");
ndef = g_hash_table_lookup(hr_ndef_hash, GINT_TO_POINTER(client_fd));
if (!ndef)
return;
g_hash_table_remove(hr_ndef_hash, GINT_TO_POINTER(client_fd));
}
/* Parse an incoming handover buffer*/
static int handover_ndef_parse(int client_fd, struct hr_ndef *ndef)
{
int err;
GList *records;
struct near_ndef_message *msg = NULL;
DBG("");
if ((!ndef->ndef) ||
(ndef->cur_ptr < NDEF_HR_MSG_MIN_LENGTH)) {
err = -EINVAL;
goto fail;
}
/* call the global parse function */
records = near_ndef_parse_msg(ndef->ndef, ndef->cur_ptr, &msg);
if (!records) {
err = -ENOMEM;
goto fail;
}
near_ndef_records_free(records);
if (msg) {
near_info("Send Hs frame");
err = send(client_fd, msg->data, msg->length, MSG_DONTWAIT);
g_free(msg->data);
g_free(msg);
} else {
err = 0;
}
return err;
fail:
near_error("ndef parsing failed %d", err);
handover_close(client_fd, 0, NULL);
return err;
}
static bool handover_recv_error(void)
{
near_error("%s", strerror(errno));
if (errno == EAGAIN)
return true;
return false;
}
/* Add extra records right after the end of the "Hr" ndef record */
static bool handover_read_cfg_records(int client_fd,
uint32_t adapter_idx, uint32_t target_idx,
near_tag_io_cb cb)
{
struct hr_ndef *ndef;
uint8_t *new_ndef;
int bytes_recv;
int ndef_size;
int err;
ndef = g_hash_table_lookup(hr_ndef_hash, GINT_TO_POINTER(client_fd));
if (!ndef) {
near_error("hr_ndef should exist");
return false;
}
if (ndef->in_extra_read) {
/* Next prepare read to complete the Hr */
new_ndef = g_try_realloc(ndef->ndef, ndef->cur_record_len +
NDEF_HR_MSG_MIN_LENGTH);
if (!new_ndef)
return false;
ndef->ndef = new_ndef;
/* Read header bytes */
bytes_recv = recv(client_fd, ndef->ndef + ndef->cur_ptr,
NDEF_HR_MSG_MIN_LENGTH, MSG_DONTWAIT);
if (bytes_recv < 0)
return handover_recv_error();
/* Now, check the ndef payload size plus header bytes */
ndef_size = near_ndef_record_length(ndef->ndef + ndef->cur_ptr,
bytes_recv);
if (ndef_size < 0)
goto fail;
ndef->cur_ptr += bytes_recv;
ndef->missing_bytes = ndef_size - bytes_recv;
/* Next prepare read to complete the NDEF */
new_ndef = g_try_realloc(ndef->ndef, ndef->cur_record_len
+ ndef_size);
if (!new_ndef)
return false;
ndef->ndef = new_ndef;
ndef->cur_record_len += ndef_size;
ndef->in_extra_read = false;
return true;
}
/* Read remaining bytes */
bytes_recv = recv(client_fd, ndef->ndef + ndef->cur_ptr,
ndef->missing_bytes, MSG_DONTWAIT);
if (bytes_recv < 0)
return handover_recv_error();
ndef->cur_ptr += bytes_recv;
ndef->missing_bytes -= bytes_recv;
/* Is the NDEF read complete ? */
if (ndef->missing_bytes)
return true; /* more bytes to come... */
if (ndef->extra_ndef_count > 0)
ndef->extra_ndef_count--;
ndef->in_extra_read = true;
if (ndef->extra_ndef_count == 0) {
/* All the bytes are read so now, parse the frame */
err = handover_ndef_parse(client_fd, ndef);
if (err > 0) {
/* clean memory */
handover_close(client_fd, 0, NULL);
return true;
}
return false;
}
/* Process the next NDEF */
return true;
fail:
near_error("Handover read NDEFs failed");
return false;
}
static bool handover_read_hr(int client_fd,
uint32_t adapter_idx, uint32_t target_idx, near_tag_io_cb cb)
{
int bytes_recv;
int extra_ndefs;
struct hr_ndef *ndef;
DBG("");
ndef = g_hash_table_lookup(hr_ndef_hash, GINT_TO_POINTER(client_fd));
if (!ndef)
return false;
/* Read remaining bytes */
bytes_recv = recv(client_fd, ndef->ndef + ndef->cur_ptr,
ndef->missing_bytes, MSG_DONTWAIT);
if (bytes_recv < 0)
return handover_recv_error();
ndef->cur_ptr += bytes_recv;
ndef->missing_bytes -= bytes_recv;
/* Is the ndef "Hr" read complete or should we loop */
if (ndef->missing_bytes)
return true;
/*
* The first NDEF frame is read. We now should determine how many
* extra records follow the NDEF frame.
* We skip the first 6 bytes (Hr header) to jump on the first record
*/
extra_ndefs = near_ndef_count_records(ndef->ndef + HR_HEADER_SIZE,
ndef->cur_record_len - HR_HEADER_SIZE,
RECORD_TYPE_WKT_ALTERNATIVE_CARRIER);
if (extra_ndefs < 0)
goto fail;
/* There's still some extra ndefs to read */
ndef->extra_ndef_count = extra_ndefs;
/* End of Handover message - now process extra records */
ndef->in_extra_read = true;
ndef->cfg_record_state = true;
/* But, if there's no ac record, we jump to the parsing */
if (ndef->extra_ndef_count == 0) {
handover_ndef_parse(client_fd, ndef);
return false;
}
return true;
fail:
near_error("Handover read failed");
return false;
}
static bool handover_read_initialize(int client_fd,
uint32_t adapter_idx, uint32_t target_idx, near_tag_io_cb cb)
{
int bytes_recv;
struct hr_ndef *ndef;
DBG("");
/* Allocate the ndef structure */
ndef = g_try_malloc0(sizeof(struct hr_ndef));
if (!ndef)
goto fail;
/* Allocate and read frame header (6 bytes) */
ndef->ndef = g_try_malloc0(NDEF_HR_MSG_MIN_LENGTH);
if (!ndef->ndef)
goto fail;
/* Initialize default values */
ndef->cur_ptr = 0;
ndef->cur_record_len = -1;
ndef->adapter_idx = adapter_idx;
ndef->target_idx = target_idx;
ndef->cb = cb;
ndef->cfg_record_state = false;
g_hash_table_insert(hr_ndef_hash, GINT_TO_POINTER(client_fd), ndef);
/* Read header bytes (6) */
bytes_recv = recv(client_fd, ndef->ndef,
NDEF_HR_MSG_MIN_LENGTH, MSG_DONTWAIT);
if (bytes_recv < 0)
return handover_recv_error();
/* Now, check the ndef payload size plus header bytes */
ndef->cur_record_len = near_ndef_record_length(ndef->ndef, bytes_recv);
if (ndef->cur_record_len < 0)
goto fail;
ndef->cur_ptr += bytes_recv;
ndef->missing_bytes = ndef->cur_record_len - bytes_recv;
if (ndef->cur_record_len == NDEF_HR_MSG_MIN_LENGTH) {
handover_ndef_parse(client_fd, ndef);
return false;
}
DBG("Handover frame size is %d", ndef->cur_ptr);
/* Next prepare read to complete the read */
ndef->ndef = g_try_realloc(ndef->ndef, ndef->cur_record_len);
if (!ndef->ndef)
goto fail;
return true;
fail:
free_hr_ndef(ndef);
return false;
}
/*
* This function is a "dispatcher", to read Hr/Hs messages,
* and/or additional NDEF messages
*/
static bool handover_read(int client_fd,
uint32_t adapter_idx, uint32_t target_idx,
near_tag_io_cb cb,
gpointer data)
{
struct hr_ndef *ndef;
ndef = g_hash_table_lookup(hr_ndef_hash, GINT_TO_POINTER(client_fd));
if (!ndef) {
/* First call: allocate and read header bytes */
return handover_read_initialize(client_fd, adapter_idx,
target_idx, cb);
}
if (ndef->cfg_record_state) {
return handover_read_cfg_records(client_fd, adapter_idx,
target_idx, cb);
}
return handover_read_hr(client_fd, adapter_idx, target_idx, cb);
}
static void free_hr_push_client(struct hr_push_client *client, int status)
{
DBG("");
handover_close(client->fd, 0, NULL);
if (client->cb)
client->cb(client->adapter_idx, client->target_idx, status);
if (client->watch > 0)
g_source_remove(client->watch);
g_free(client);
}
static gboolean handover_push_event(GIOChannel *channel,
GIOCondition condition, gpointer data)
{
bool ret;
struct hr_push_client *client = (struct hr_push_client *) data;
DBG("condition 0x%x", condition);
if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
near_error("Error with Handover client");
free_hr_push_client(client, -EIO);
return FALSE;
}
ret = handover_read(client->fd,
client->adapter_idx, client->target_idx,
client->cb, data);
if (!ret)
free_hr_push_client(client, 0);
return ret;
}
static int handover_push(int client_fd,
uint32_t adapter_idx, uint32_t target_idx,
struct near_ndef_message *ndef,
near_device_io_cb cb,
gpointer data)
{
int err;
struct hr_push_client *client;
GIOChannel *channel;
DBG("");
client = g_try_malloc0(sizeof(struct hr_push_client));
if (!client)
return -ENOMEM;
channel = g_io_channel_unix_new(client_fd);
g_io_channel_set_close_on_unref(channel, TRUE);
client->fd = client_fd;
client->adapter_idx = adapter_idx;
client->target_idx = target_idx;
client->cb = cb;
client->watch = g_io_add_watch(channel,
G_IO_IN | G_IO_HUP | G_IO_NVAL |
G_IO_ERR, handover_push_event,
(gpointer) client);
g_io_channel_unref(channel);
err = send(client_fd, ndef->data, ndef->length, MSG_DONTWAIT);
if (err < 0) {
free_hr_push_client(client, err);
g_io_channel_unref(channel);
}
return err;
}
struct near_p2p_driver handover_driver = {
.name = "Handover",
.service_name = NEAR_DEVICE_SN_HANDOVER,
.fallback_service_name = NEAR_DEVICE_SN_SNEP,
.sock_type = SOCK_STREAM,
.read = handover_read,
.push = handover_push,
.close = handover_close,
};
int handover_init(void)
{
hr_ndef_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, free_hr_ndef);
return near_p2p_register(&handover_driver);
}
void handover_exit(void)
{
near_p2p_unregister(&handover_driver);
g_hash_table_destroy(hr_ndef_hash);
hr_ndef_hash = NULL;
}