blob: c0a1d1a00491cbac3754d766f6d9a92f02eb926a [file] [log] [blame] [edit]
/*
*
* 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 <string.h>
#include <sys/socket.h>
#include <linux/socket.h>
#include <near/nfc_copy.h>
#include <near/plugin.h>
#include <near/types.h>
#include <near/adapter.h>
#include <near/device.h>
#include <near/ndef.h>
#include <near/tlv.h>
#include <near/snep.h>
#include "near.h"
struct snep_fragment {
uint32_t len;
uint8_t *data;
};
struct p2p_snep_put_req_data {
uint8_t fd;
uint32_t adapter_idx;
uint32_t target_idx;
near_device_io_cb cb;
guint watch;
GSList *fragments;
};
struct p2p_snep_req_frame {
uint8_t version;
uint8_t request;
uint32_t length;
uint8_t ndef[];
} __attribute__((packed));
struct p2p_snep_resp_frame {
uint8_t version;
uint8_t response;
uint32_t length;
uint8_t info[];
} __attribute__((packed));
static GHashTable *snep_client_hash;
/* Callback: free snep data */
static void free_snep_core_client(gpointer data)
{
struct p2p_snep_data *snep_data = data;
DBG("");
g_free(snep_data->nfc_data);
g_free(snep_data);
}
/* Send a short response code */
void near_snep_core_response_noinfo(int client_fd, uint8_t response)
{
struct p2p_snep_resp_frame resp;
DBG("Response 0x%x", response);
resp.version = NEAR_SNEP_VERSION;
resp.response = response;
resp.length = 0;
send(client_fd, &resp, sizeof(resp), 0);
}
/*
* near_snep_core_parse_handover_record
*
* The hr frame should be here BUT:
* The first 4 bytes are the Max Allowed Length
*
* - Because of an Android's BUGs:
* - the Hr frame is not correct; a Hr record
* is embedded in a ... Hr record !!! The author
* used 'Hr' instead of 'cr'
* - The OOB block is badly written:
* - the payload ID should be the same in the 'ac' record
* and the OOB record.
* - The OOB data length bytes must be swapped (Big endian to Little E.)
*
* The hack fixes the first issue (bluetooth.c fixes the second) !
* */
void near_snep_core_parse_handover_record(int client_fd, uint8_t *ndef,
uint32_t nfc_data_length)
{
GList *records;
struct near_ndef_message *msg = NULL;
if (!ndef)
return;
/*
* Bugfix Android: Fix 'cr' instead of 'Hr'
* Bug is in Google:HandoverManager.java:645
*/
if (nfc_data_length > 9 && strncmp((char *)(ndef + 9), "Hr", 2) == 0) {
DBG("Android 4.1.1 found !!!");
*(ndef + 9) = 'c';
}
/* Parse the incoming frame */
records = near_ndef_parse_msg(ndef, nfc_data_length, &msg);
if (!records)
return;
near_ndef_records_free(records);
if (!msg)
return;
near_info("Send SNEP / Hs frame");
near_snep_core_response_with_info(client_fd, NEAR_SNEP_RESP_SUCCESS,
msg->data, msg->length);
g_free(msg->data);
g_free(msg);
}
/*
* This code will read the ndef message.
* return <0 on error
* ==0 if not more bytes
* >0 if there's still some data to read
*/
static int snep_core_read_ndef(int client_fd,
struct p2p_snep_data *snep_data)
{
int bytes_recv, remaining_bytes;
DBG("");
remaining_bytes = snep_data->nfc_data_length -
snep_data->nfc_data_current_length;
bytes_recv = recv(client_fd, snep_data->nfc_data_ptr, remaining_bytes,
MSG_DONTWAIT);
if (bytes_recv < 0) {
near_error("%d %s", bytes_recv, strerror(errno));
/* Some more data should show up */
if (errno == EAGAIN)
return EAGAIN; /* Positive !!*/
goto out;
}
snep_data->nfc_data_current_length += bytes_recv;
snep_data->nfc_data_ptr += bytes_recv;
/* Is the read complete ? */
if (snep_data->nfc_data_length == snep_data->nfc_data_current_length)
return 0;
if (!snep_data->respond_continue) {
snep_data->respond_continue = TRUE;
near_snep_core_response_noinfo(client_fd, NEAR_SNEP_RESP_CONTINUE);
}
return 1;
out:
g_hash_table_remove(snep_client_hash, GINT_TO_POINTER(client_fd));
return -errno; /* Negative on error */
}
static void free_snep_core_fragment(gpointer data)
{
struct snep_fragment *fragment = data;
if (fragment)
g_free(fragment->data);
g_free(fragment);
fragment = NULL;
}
static void free_snep_core_push_data(gpointer userdata, int status)
{
struct p2p_snep_put_req_data *data;
DBG("");
if (!userdata)
return;
data = (struct p2p_snep_put_req_data *) userdata;
close(data->fd);
if (data->cb)
data->cb(data->adapter_idx, data->target_idx, status);
if (data->watch > 0)
g_source_remove(data->watch);
g_slist_free_full(data->fragments, free_snep_core_fragment);
g_free(data);
}
static int snep_core_send_fragment(struct p2p_snep_put_req_data *req)
{
struct snep_fragment *fragment;
int err;
DBG("");
if (!req || !req->fragments ||
g_slist_length(req->fragments) == 0)
return -EINVAL;
fragment = req->fragments->data;
err = send(req->fd, fragment->data, fragment->len, 0);
req->fragments = g_slist_remove(req->fragments, fragment);
g_free(fragment->data);
g_free(fragment);
return err;
}
static int snep_core_push_response(struct p2p_snep_put_req_data *req)
{
struct p2p_snep_resp_frame frame;
uint8_t *ndef;
uint32_t ndef_len;
int bytes_recv, err;
DBG("");
bytes_recv = recv(req->fd, &frame, sizeof(frame), 0);
if (bytes_recv < 0) {
near_error("Read SNEP frame error %d %s", bytes_recv,
strerror(errno));
return bytes_recv;
}
/* Check frame length */
frame.length = g_ntohl(frame.length);
DBG("Response 0x%x %p", frame.response, &frame);
switch (frame.response) {
case NEAR_SNEP_RESP_CONTINUE:
while (g_slist_length(req->fragments) != 0) {
err = snep_core_send_fragment(req);
if (err < 0)
return err;
}
return frame.response;
case NEAR_SNEP_RESP_SUCCESS:
if (frame.length == 0)
return 0;
/* Get the incoming data */
ndef_len = frame.length;
ndef = g_try_malloc0(ndef_len);
if (!ndef)
return -ENOMEM;
bytes_recv = recv(req->fd, ndef, ndef_len, 0);
if (bytes_recv < 0) {
near_error("Read SNEP frame error: %d %s", bytes_recv,
strerror(errno));
return bytes_recv;
}
/* Not enough bytes */
if (bytes_recv < 6)
return -EINVAL;
if (strncmp((char *)(ndef + 3), "Hs", 2) == 0)
near_snep_core_parse_handover_record(req->fd, ndef,
ndef_len);
g_free(ndef);
return 0;
}
return -1;
}
static gboolean snep_core_push_event(GIOChannel *channel,
GIOCondition condition, gpointer data)
{
int err;
DBG("push_event condition 0x%x", condition);
if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
near_error("Error with SNEP channel");
free_snep_core_push_data(data, -1);
return FALSE;
}
err = snep_core_push_response(data);
if (err <= 0) {
free_snep_core_push_data(data, err);
return FALSE;
}
return TRUE;
}
static int snep_core_push_prepare_fragments(struct p2p_snep_put_req_data *req,
struct near_ndef_message *ndef)
{
struct snep_fragment *fragment;
uint32_t max_fragment_len;
DBG("");
max_fragment_len = NEAR_SNEP_REQ_MAX_FRAGMENT_LENGTH;
while (ndef->offset < ndef->length) {
fragment = g_try_malloc0(sizeof(struct snep_fragment));
if (!fragment)
return -ENOMEM;
if (max_fragment_len <= (ndef->length - ndef->offset))
fragment->len = max_fragment_len;
else
fragment->len = ndef->length - ndef->offset;
fragment->data = g_try_malloc0(fragment->len);
if (!fragment->data) {
g_free(fragment);
return -ENOMEM;
}
memcpy(fragment->data, ndef->data + ndef->offset,
fragment->len);
ndef->offset += fragment->len;
req->fragments = g_slist_append(req->fragments, fragment);
}
return 0;
}
static bool snep_core_process_request(int client_fd,
struct p2p_snep_data *snep_data,
near_server_io req_get,
near_server_io req_put)
{
bool ret;
int err;
DBG("request %d", snep_data->request);
/* Now, we process the request code */
switch (snep_data->request) {
case NEAR_SNEP_REQ_PUT:
DBG("NEAR_SNEP_REQ_PUT");
if (req_put)
ret = (*req_put)(client_fd, snep_data);
else {
near_snep_core_response_noinfo(client_fd,
NEAR_SNEP_RESP_NOT_IMPL);
ret = true;
}
/* free and leave */
g_hash_table_remove(snep_client_hash,
GINT_TO_POINTER(client_fd));
break;
case NEAR_SNEP_REQ_GET:
DBG("NEAR_SNEP_REQ_GET");
if (req_get)
ret = (*req_get)(client_fd, snep_data);
else {
near_snep_core_response_noinfo(client_fd,
NEAR_SNEP_RESP_NOT_IMPL);
ret = true;
}
/* If there's some fragments, don't delete before the CONT */
if (!snep_data->req) {
/* free and leave */
DBG("Clean Table");
g_hash_table_remove(snep_client_hash,
GINT_TO_POINTER(client_fd));
}
break;
case NEAR_SNEP_REQ_REJECT:
DBG("NEAR_SNEP_REQ_REJECT");
if (!snep_data->req->fragments) {
near_error("error: NEAR_SNEP_REQ_REJECT but no fragment");
ret = false;
}
else {
ret = true;
}
g_slist_free_full(snep_data->req->fragments,
free_snep_core_fragment);
g_slist_free(snep_data->req->fragments);
g_hash_table_remove(snep_client_hash,
GINT_TO_POINTER(client_fd));
break;
case NEAR_SNEP_REQ_CONTINUE:
/*
* NEAR_SNEP_REQ_CONTINUE indicates that we have to send the
* remaining fragments...
*/
if (!snep_data->req) {
ret = true;
break;
}
DBG("NEAR_SNEP_REQ_CONTINUE");
if (!snep_data->req->fragments) {
near_error("error: NEAR_SNEP_REQ_CONTINUE but no fragment");
ret = false;
goto leave_cont;
}
/* Send fragments, one after the other (no ack expected) */
while (g_slist_length(snep_data->req->fragments) != 0) {
err = snep_core_send_fragment(snep_data->req);
if (err < 0) {
ret = false;
goto leave_cont;
}
}
ret = true;
leave_cont:
/* No more fragment to send, clean memory */
g_slist_free_full(snep_data->req->fragments,
free_snep_core_fragment);
g_slist_free(snep_data->req->fragments);
g_hash_table_remove(snep_client_hash,
GINT_TO_POINTER(client_fd));
break;
default:
near_error("Unsupported SNEP request code");
ret = false;
break;
}
return ret;
}
/*
* SNEP Core: read function
* This function handles SNEP REQUEST codes:
* GET, PUT and CONTINUE (REJECT is not handled).
*
* We read the first 6 bytes (the header) and check
* - the read size ( should be 6 )
* - the version (on MAJOR)
*
* Then, we check snep_data. If it exists, it means that we are in
* a fragment/continue situation (a 1st fragment was sent, and we
* expect a CONTINUE for the remaining bytes).
* If there's no existing snep_data, we create a new one and read the
* missing bytes (llcp removes fragmentation issues)
*
*/
bool near_snep_core_read(int client_fd,
uint32_t adapter_idx, uint32_t target_idx,
near_tag_io_cb cb,
near_server_io req_get,
near_server_io req_put,
gpointer data)
{
struct p2p_snep_data *snep_data;
struct p2p_snep_req_frame frame;
int bytes_recv, ret;
uint32_t ndef_length;
DBG("");
/* Check previous/pending snep_data */
snep_data = g_hash_table_lookup(snep_client_hash,
GINT_TO_POINTER(client_fd));
/*
* If snep data is already there, and there are more bytes to read
* we just go ahead and read more fragments from the client.
*/
if (snep_data &&
snep_data->nfc_data_length !=
snep_data->nfc_data_current_length) {
ret = snep_core_read_ndef(client_fd, snep_data);
if (ret)
return ret;
goto process_request;
}
/*
* We already got something from this client, we should try
* to continue reading.
*/
/* TODO Try with PEEK */
bytes_recv = recv(client_fd, &frame, sizeof(frame), 0);
if (bytes_recv < 0) {
near_error("Read error SNEP %d %s", bytes_recv,
strerror(errno));
return false;
}
/* Check frame size */
if (bytes_recv != sizeof(frame)) {
near_error("Bad frame size: %d", bytes_recv);
return false;
}
/* If major is different, send UNSUPPORTED VERSION */
if (NEAR_SNEP_MAJOR(frame.version) != NEAR_SNEP_MAJOR(NEAR_SNEP_VERSION)) {
near_error("Unsupported version (%d)", frame.version);
near_snep_core_response_noinfo(client_fd, NEAR_SNEP_RESP_VERSION);
return true;
}
/*
* This is a fragmentation SNEP operation since we have pending
* frames. But the ndef length and the current data length are
* identical. So this is a CONTINUE for a fragmented GET, and
* we should just process a CONTINUE frame and send the fragments
* back to the client. This will be done from snep_core_process_request().
*/
if (snep_data) {
snep_data->request = frame.request;
goto process_request;
}
/* This is a new request from the client */
snep_data = g_try_malloc0(sizeof(struct p2p_snep_data));
if (!snep_data)
return false;
/* the whole frame length */
ndef_length = GINT_FROM_BE(frame.length);
snep_data->nfc_data = g_try_malloc0(ndef_length + TLV_SIZE);
if (!snep_data->nfc_data) {
g_free(snep_data);
return false;
}
/* fill the struct */
snep_data->nfc_data_length = ndef_length;
snep_data->nfc_data_ptr = snep_data->nfc_data;
snep_data->adapter_idx = adapter_idx;
snep_data->target_idx = target_idx;
snep_data->request = frame.request;
snep_data->respond_continue = FALSE;
snep_data->cb = cb;
/* Add to the client hash table */
g_hash_table_insert(snep_client_hash,
GINT_TO_POINTER(client_fd), snep_data);
if (ndef_length > 0) {
if ((frame.request == NEAR_SNEP_REQ_GET) ||
(frame.request == NEAR_SNEP_REQ_PUT)) {
/* We should read the missing bytes */
ret = snep_core_read_ndef(client_fd, snep_data);
if (ret)
return ret;
}
}
process_request:
return snep_core_process_request(client_fd, snep_data,
req_get, req_put);
}
/*
* send a response frame with some datas. If the frame is too long, we
* have to fragment the frame, using snep fragmentation protocol.
* Return:
* < 0 if error
* 0 if no fragment;
* > 0 if there's still some fragments
*
*/
static int near_snep_core_response(int fd, struct p2p_snep_put_req_data *req,
uint8_t resp_code, struct near_ndef_message *ndef)
{
struct p2p_snep_req_frame header;
struct snep_fragment *fragment;
uint32_t max_fragment_len;
bool fragmenting;
int err;
int snep_req_header_length, snep_additional_length;
DBG("resp: 0x%02X", resp_code);
max_fragment_len = NEAR_SNEP_REQ_MAX_FRAGMENT_LENGTH;
header.version = NEAR_SNEP_VERSION;
if (resp_code == NEAR_SNEP_REQ_GET) { /* Get for android */
snep_req_header_length = NEAR_SNEP_REQ_GET_HEADER_LENGTH;
snep_additional_length = 4; /* 4 Acceptable Length */
} else {
snep_req_header_length = NEAR_SNEP_REQ_PUT_HEADER_LENGTH;
snep_additional_length = 0;
}
header.length = GUINT32_TO_BE(ndef->length + snep_additional_length);
header.request = resp_code;
fragment = g_try_malloc0(sizeof(struct snep_fragment));
if (!fragment) {
err = -ENOMEM;
goto error;
}
if (max_fragment_len >= (ndef->length + snep_req_header_length)) {
fragment->len = ndef->length + snep_req_header_length;
fragmenting = false;
} else {
fragment->len = max_fragment_len;
fragmenting = true;
}
fragment->data = g_try_malloc0(fragment->len);
if (!fragment->data) {
g_free(fragment);
err = ENOMEM;
goto error;
}
/* Header to data - common header */
memcpy(fragment->data, (uint8_t *)&header, NEAR_SNEP_REQ_PUT_HEADER_LENGTH);
/* if GET, we add the Acceptable length */
if (header.request == NEAR_SNEP_REQ_GET)
near_put_be32(snep_req_header_length,
fragment->data + NEAR_SNEP_REQ_PUT_HEADER_LENGTH);
if (fragmenting) {
memcpy(fragment->data + snep_req_header_length, ndef->data,
max_fragment_len - snep_req_header_length);
ndef->offset = max_fragment_len - snep_req_header_length;
err = snep_core_push_prepare_fragments(req, ndef);
if (err < 0) {
g_free(fragment->data);
g_free(fragment);
goto error;
}
} else {
memcpy(fragment->data + snep_req_header_length,
ndef->data, ndef->length);
}
err = send(fd, fragment->data, fragment->len, 0);
if (err < 0) {
near_error("Sending failed %d", err);
g_free(fragment->data);
g_free(fragment);
goto error;
}
g_free(fragment->data);
g_free(fragment);
return 0;
error:
if (req)
free_snep_core_push_data(req, err);
return err;
}
void near_snep_core_response_with_info(int client_fd, uint8_t response,
uint8_t *data, int length)
{
struct p2p_snep_data *snep_data;
struct p2p_snep_put_req_data *req;
struct near_ndef_message *ndef;
DBG("Response with info 0x%x (len:%d)", response, length);
req = NULL;
ndef = NULL;
/* get the snep data */
snep_data = g_hash_table_lookup(snep_client_hash,
GINT_TO_POINTER(client_fd));
if (!snep_data) {
DBG("snep_data not found");
goto done;
}
/* Prepare the ndef struct */
ndef = g_try_malloc0(sizeof(struct near_ndef_message));
if (!ndef)
goto done;
ndef->data = g_try_malloc0(length);
if (!ndef->data) {
g_free(ndef);
ndef = NULL;
goto done;
}
/* Fill the ndef */
ndef->length = length;
ndef->offset = 0;
memcpy(ndef->data, data, length);
ndef->offset = 0;
/* Now prepare req struct */
req = g_try_malloc0(sizeof(struct p2p_snep_put_req_data));
if (!req)
goto done;
/* Prepare the callback */
snep_data->req = req;
req->fd = client_fd;
req->adapter_idx = snep_data->adapter_idx;
req->target_idx = snep_data->target_idx;
req->cb = snep_data->cb;
/* send it !*/
near_snep_core_response(client_fd, req, response, ndef);
done:
/* If no fragment, free mem */
if (req) {
if (req->fragments == 0) {
g_free(req);
snep_data->req = NULL;
}
}
if (ndef)
g_free(ndef->data);
g_free(ndef);
}
/* SNEP Core: on P2P push */
int near_snep_core_push(int fd, uint32_t adapter_idx, uint32_t target_idx,
struct near_ndef_message *ndef,
near_device_io_cb cb,
gpointer data)
{
struct p2p_snep_put_req_data *req;
GIOChannel *channel;
uint8_t resp_code;
int err;
DBG("");
req = g_try_malloc0(sizeof(struct p2p_snep_put_req_data));
if (!req) {
err = -ENOMEM;
goto error;
}
channel = g_io_channel_unix_new(fd);
g_io_channel_set_close_on_unref(channel, TRUE);
req->fd = fd;
req->adapter_idx = adapter_idx;
req->target_idx = target_idx;
req->cb = cb;
ndef->offset = 0;
req->watch = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_NVAL |
G_IO_ERR, snep_core_push_event,
(gpointer) req);
/* Check if Hr or Hs for Handover over SNEP */
if (*(char *)(ndef->data + 3) == 'H')
resp_code = NEAR_SNEP_REQ_GET; /* Get for android */
else
resp_code = NEAR_SNEP_REQ_PUT;
return near_snep_core_response(fd, req, resp_code, ndef);
error:
free_snep_core_push_data(req, err);
return err;
}
/* SNEP core functions: close */
void near_snep_core_close(int client_fd, int err, gpointer data)
{
struct p2p_snep_data *snep_data;
DBG("");
snep_data = g_hash_table_lookup(snep_client_hash,
GINT_TO_POINTER(client_fd));
if (!snep_data)
return;
snep_data->cb(snep_data->adapter_idx, snep_data->target_idx, err);
g_hash_table_remove(snep_client_hash, GINT_TO_POINTER(client_fd));
}
int __near_snep_core_init(void)
{
snep_client_hash = g_hash_table_new_full(g_direct_hash,
g_direct_equal, NULL,
free_snep_core_client);
return 0;
}
void __near_snep_core_cleanup(void)
{
g_hash_table_destroy(snep_client_hash);
snep_client_hash = NULL;
}