blob: 13702ade3e1430b79390ef473e53c688fc676f81 [file] [log] [blame] [edit]
/*
*
* 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 <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <poll.h>
#include <sys/socket.h>
#include <linux/socket.h>
#include <near/nfc_copy.h>
#include <near/plugin.h>
#include <near/log.h>
#include <near/types.h>
#include <near/tag.h>
#include <near/device.h>
#include <near/adapter.h>
#include <near/tlv.h>
#include <near/ndef.h>
#include "p2p.h"
static GSList *driver_list = NULL;
static GList *server_list = NULL;
struct p2p_data {
struct near_p2p_driver *driver;
uint32_t adapter_idx;
uint32_t target_idx;
near_device_io_cb cb;
int fd;
guint watch;
struct p2p_data *server;
GList *client_list;
};
/* common function for socket binding */
static int __p2p_bind(struct p2p_data *server_data, GIOFunc listener)
{
int err, fd = server_data->fd;
struct sockaddr_nfc_llcp addr;
GIOChannel *channel;
struct near_p2p_driver *driver = server_data->driver;
DBG("Binding %s", driver->name);
memset(&addr, 0, sizeof(struct sockaddr_nfc_llcp));
addr.sa_family = AF_NFC;
addr.dev_idx = server_data->adapter_idx;
addr.nfc_protocol = NFC_PROTO_NFC_DEP;
addr.service_name_len = strlen(driver->service_name);
strcpy(addr.service_name, driver->service_name);
err = bind(fd, (struct sockaddr *) &addr,
sizeof(struct sockaddr_nfc_llcp));
if (err < 0) {
if (errno == EADDRINUSE) {
DBG("%s is already bound", driver->name);
err = 0;
goto out_err;
}
near_error("%s bind failed %d %d", driver->name, err, errno);
goto out_err;
}
if (server_data->driver->sock_type == SOCK_STREAM) {
err = listen(fd, 10);
if (err < 0) {
near_error("%s listen failed %d", driver->name, err);
goto out_err;
}
}
channel = g_io_channel_unix_new(fd);
g_io_channel_set_close_on_unref(channel, TRUE);
server_data->watch = g_io_add_watch(channel,
G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
listener, (gpointer) server_data);
g_io_channel_unref(channel);
return 0;
out_err:
close(fd);
return err;
}
static gboolean p2p_listener_event(GIOChannel *channel, GIOCondition condition,
gpointer user_data);
struct p2p_connect {
uint32_t adapter_idx;
uint32_t target_idx;
guint watch;
struct near_p2p_driver *driver;
struct near_ndef_message *ndef;
near_device_io_cb cb;
};
static gboolean p2p_client_event(GIOChannel *channel, GIOCondition condition,
gpointer user_data)
{
struct p2p_data *client_data = user_data;
DBG("condition 0x%x", condition);
if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
int err;
struct p2p_data *server_data = client_data->server;
if (client_data->watch > 0)
g_source_remove(client_data->watch);
client_data->watch = 0;
if (condition & (G_IO_NVAL | G_IO_ERR))
err = -EIO;
else
err = 0;
if (client_data->driver->close)
client_data->driver->close(client_data->fd, err,
client_data->driver->user_data);
near_error("%s client channel closed",
client_data->driver->name);
if (server_data)
server_data->client_list =
g_list_remove(server_data->client_list,
client_data);
if (client_data->driver->single_connection) {
server_data->fd = socket(AF_NFC, SOCK_STREAM,
NFC_SOCKPROTO_LLCP);
if (server_data->fd > 0)
__p2p_bind(server_data, p2p_listener_event);
}
if (client_data->driver->sock_type == SOCK_STREAM)
g_free(client_data);
return FALSE;
}
if (client_data->driver->new_client)
return true;
return client_data->driver->read(client_data->fd,
client_data->adapter_idx,
client_data->target_idx,
client_data->cb,
client_data->driver->user_data);
}
static void free_client_data(gpointer data)
{
struct p2p_data *client_data;
DBG("");
client_data = (struct p2p_data *) data;
if (client_data->driver->close)
client_data->driver->close(client_data->fd, 0,
client_data->driver->user_data);
if (client_data->watch > 0)
g_source_remove(client_data->watch);
g_free(client_data);
}
static void free_server_data(gpointer data)
{
struct p2p_data *server_data;
DBG("");
server_data = (struct p2p_data *)data;
if (server_data->watch > 0)
g_source_remove(server_data->watch);
server_data->watch = 0;
g_list_free_full(server_data->client_list, free_client_data);
server_data->client_list = NULL;
DBG("Closing server socket");
close(server_data->fd);
g_free(server_data);
}
static gboolean p2p_listener_event(GIOChannel *channel, GIOCondition condition,
gpointer user_data)
{
struct sockaddr_nfc_llcp client_addr;
socklen_t client_addr_len;
int server_fd, client_fd;
struct p2p_data *client_data, *server_data = user_data;
GIOChannel *client_channel;
struct near_p2p_driver *driver = server_data->driver;
DBG("condition 0x%x", condition);
server_fd = g_io_channel_unix_get_fd(channel);
if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
if (server_data->watch > 0)
g_source_remove(server_data->watch);
server_data->watch = 0;
g_list_free_full(server_data->client_list, free_client_data);
server_data->client_list = NULL;
near_error("Error with %s server channel", driver->name);
return FALSE;
}
client_addr_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *) &client_addr,
&client_addr_len);
if (client_fd < 0) {
near_error("accept failed %d", client_fd);
return FALSE;
}
DBG("client dsap %d ssap %d",
client_addr.dsap, client_addr.ssap);
DBG("target idx %d", client_addr.target_idx);
client_data = g_try_malloc0(sizeof(struct p2p_data));
if (!client_data) {
close(client_fd);
return FALSE;
}
client_data->driver = server_data->driver;
client_data->adapter_idx = server_data->adapter_idx;
client_data->target_idx = client_addr.target_idx;
client_data->fd = client_fd;
client_data->cb = server_data->cb;
client_data->server = server_data;
client_channel = g_io_channel_unix_new(client_fd);
g_io_channel_set_close_on_unref(client_channel, TRUE);
/* This would enable passthru active */
if (server_data->driver->new_client)
server_data->driver->new_client(
client_data->driver->service_name, client_fd,
server_data->driver->user_data);
client_data->watch = g_io_add_watch(client_channel,
G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
p2p_client_event,
client_data);
g_io_channel_unref(client_channel);
server_data->client_list = g_list_append(server_data->client_list,
client_data);
return !driver->single_connection;
}
static int p2p_connect_blocking(uint32_t adapter_idx, uint32_t target_idx,
struct near_ndef_message *ndef,
near_device_io_cb cb,
struct near_p2p_driver *driver)
{
int fd, err = 0;
struct timeval timeout;
struct sockaddr_nfc_llcp addr;
DBG("");
fd = socket(AF_NFC, SOCK_STREAM, NFC_SOCKPROTO_LLCP);
if (fd < 0)
return -errno;
memset(&addr, 0, sizeof(struct sockaddr_nfc_llcp));
addr.sa_family = AF_NFC;
addr.dev_idx = adapter_idx;
addr.target_idx = target_idx;
addr.nfc_protocol = NFC_PROTO_NFC_DEP;
addr.service_name_len = strlen(driver->service_name);
strcpy(addr.service_name, driver->service_name);
timeout.tv_sec = 8;
timeout.tv_usec = 0;
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
sizeof(timeout)) < 0)
near_error("Could not set the receive timeout\n");
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
sizeof(timeout)) < 0)
near_error("Could not set the send timeout\n");
err = connect(fd, (struct sockaddr *) &addr,
sizeof(struct sockaddr_nfc_llcp));
if (err < 0) {
near_error("Connect failed %d", err);
close(fd);
return err;
}
return fd;
}
static gboolean p2p_push_blocking(gpointer user_data)
{
struct p2p_connect *conn = user_data;
int fd, err;
DBG("");
fd = p2p_connect_blocking(conn->adapter_idx, conn->target_idx,
conn->ndef, conn->cb, conn->driver);
if (fd < 0) {
err = fd;
goto out;
}
err = conn->driver->push(fd, conn->adapter_idx, conn->target_idx,
conn->ndef, conn->cb,
conn->driver->user_data);
out:
if (err < 0)
conn->cb(conn->adapter_idx, conn->target_idx, err);
g_free(conn->ndef->data);
g_free(conn->ndef);
g_free(conn);
return FALSE;
}
static bool check_nval(GIOChannel *io)
{
struct pollfd fds;
memset(&fds, 0, sizeof(fds));
fds.fd = g_io_channel_unix_get_fd(io);
fds.events = POLLNVAL;
if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLNVAL))
return true;
return false;
}
static gboolean p2p_connect_event(GIOChannel *channel, GIOCondition condition,
gpointer user_data)
{
struct p2p_connect *conn = user_data;
int err, sk_err, fd;
socklen_t len = sizeof(sk_err);
DBG("condition 0x%x", condition);
if (!conn->driver->push) {
err = -EOPNOTSUPP;
goto out;
}
fd = g_io_channel_unix_get_fd(channel);
if ((condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) ||
check_nval(channel)) {
near_error("%s connect error", conn->driver->name);
if (condition & G_IO_HUP) {
DBG("Trying a blocking connect");
close(fd);
g_timeout_add(300, p2p_push_blocking, conn);
return FALSE;
}
err = -EIO;
goto out;
}
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0)
err = -errno;
else
err = -sk_err;
if (err < 0) {
near_error("%s Connection error %d",
conn->driver->name, -err);
goto out;
}
err = conn->driver->push(fd, conn->adapter_idx, conn->target_idx,
conn->ndef, conn->cb,
conn->driver->user_data);
out:
if (err < 0)
conn->cb(conn->adapter_idx, conn->target_idx, err);
g_free(conn->ndef->data);
g_free(conn->ndef);
g_free(conn);
return FALSE;
}
static int p2p_bind(struct near_p2p_driver *driver, uint32_t adapter_idx,
near_device_io_cb cb)
{
int err, fd;
struct p2p_data *server_data;
GIOFunc g_func;
DBG("");
if (driver->sock_type == SOCK_DGRAM)
g_func = p2p_client_event;
else
g_func = p2p_listener_event;
if (driver->sock_type != SOCK_DGRAM &&
driver->sock_type != SOCK_STREAM) {
near_error("Undefined socket type for %s ", driver->name);
return -EINVAL;
}
fd = socket(AF_NFC, driver->sock_type, NFC_SOCKPROTO_LLCP);
if (fd < 0)
return -errno;
server_data = g_try_malloc0(sizeof(struct p2p_data));
if (!server_data) {
close(fd);
return -ENOMEM;
}
server_data->driver = driver;
server_data->adapter_idx = adapter_idx;
server_data->fd = fd;
server_data->cb = cb;
err = __p2p_bind(server_data, g_func);
if (err < 0) {
g_free(server_data);
return err;
}
server_list = g_list_append(server_list, server_data);
return 0;
}
static int p2p_listen(uint32_t adapter_idx, near_device_io_cb cb)
{
int err = -1, bind_err;
GSList *list;
for (list = driver_list; list; list = list->next) {
struct near_p2p_driver *driver = list->data;
bind_err = p2p_bind(driver, adapter_idx, cb);
if (bind_err == 0) {
err = 0;
} else if (bind_err == -EPROTONOSUPPORT) {
near_error("LLCP is not supported");
return bind_err;
}
}
return err;
}
static int p2p_connect(uint32_t adapter_idx, uint32_t target_idx,
struct near_ndef_message *ndef,
near_device_io_cb cb, struct near_p2p_driver *driver)
{
int fd, err = 0;
struct timeval timeout;
struct sockaddr_nfc_llcp addr;
GIOChannel *channel;
GIOCondition cond;
struct p2p_connect *conn;
DBG("");
fd = socket(AF_NFC, SOCK_STREAM, NFC_SOCKPROTO_LLCP);
if (fd < 0)
return -errno;
conn = g_try_malloc0(sizeof(struct p2p_connect));
if (!conn) {
close(fd);
return -ENOMEM;
}
conn->driver = driver;
conn->ndef = ndef;
conn->cb = cb;
conn->target_idx = target_idx;
conn->adapter_idx = adapter_idx;
channel = g_io_channel_unix_new(fd);
g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL);
memset(&addr, 0, sizeof(struct sockaddr_nfc_llcp));
addr.sa_family = AF_NFC;
addr.dev_idx = adapter_idx;
addr.target_idx = target_idx;
addr.nfc_protocol = NFC_PROTO_NFC_DEP;
addr.service_name_len = strlen(driver->service_name);
strcpy(addr.service_name, driver->service_name);
timeout.tv_sec = 8;
timeout.tv_usec = 0;
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
sizeof(timeout)) < 0)
near_error("Could not set the receive timeout\n");
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
sizeof(timeout)) < 0)
near_error("Could not set the send timeout\n");
err = connect(fd, (struct sockaddr *) &addr,
sizeof(struct sockaddr_nfc_llcp));
if (err < 0 && errno != EINPROGRESS) {
near_error("Connect failed %d", errno);
g_free(conn);
close(fd);
return err;
}
cond = G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
conn->watch = g_io_add_watch(channel, cond, p2p_connect_event, conn);
g_io_channel_unref(channel);
return fd;
}
static int p2p_push(uint32_t adapter_idx, uint32_t target_idx,
struct near_ndef_message *ndef, char *service_name,
near_device_io_cb cb)
{
int fd;
GSList *list;
DBG("");
for (list = driver_list; list; list = list->next) {
struct near_p2p_driver *driver = list->data;
if (strcmp(driver->service_name, service_name) != 0)
continue;
/*
* Because of Android's implementation, we have use SNEP for
* Handover. So, on Handover session, we try to connect to
* the handover service and fallback to SNEP on connect fail.
*/
fd = p2p_connect(adapter_idx, target_idx, ndef, cb, driver);
if (fd > 0)
return fd;
if (driver->fallback_service_name)
return p2p_push(adapter_idx, target_idx, ndef,
(char *) driver->fallback_service_name,
cb);
else
return -1;
}
return -1;
}
static struct near_device_driver p2p_driver = {
.priority = NEAR_DEVICE_PRIORITY_HIGH,
.listen = p2p_listen,
.push = p2p_push,
};
int near_p2p_register(struct near_p2p_driver *driver)
{
struct near_p2p_driver *tmp_driver;
GSList *list = NULL;
DBG("driver %p name %s service %s", driver, driver->name,
driver->service_name);
for (list = driver_list; list; list = list->next) {
tmp_driver = list->data;
if (g_strcmp0(tmp_driver->service_name,
driver->service_name) == 0) {
near_error("%s already registered",
driver->service_name);
return -EALREADY;
}
}
driver_list = g_slist_prepend(driver_list, driver);
return 0;
}
void near_p2p_unregister(struct near_p2p_driver *driver)
{
DBG("driver %p name %s", driver, driver->service_name);
driver_list = g_slist_remove(driver_list, driver);
}
static int p2p_init(void)
{
DBG("");
phdc_init();
npp_init();
snep_init();
snep_validation_init();
llcp_validation_init();
handover_init();
return near_device_driver_register(&p2p_driver);
}
static void p2p_exit(void)
{
DBG("");
g_list_free_full(server_list, free_server_data);
llcp_validation_exit();
snep_exit();
snep_validation_exit();
npp_exit();
phdc_exit();
handover_exit();
near_device_driver_unregister(&p2p_driver);
}
NEAR_PLUGIN_DEFINE(p2p, "NFC Forum peer to peer mode support", VERSION,
NEAR_PLUGIN_PRIORITY_HIGH, p2p_init, p2p_exit)