/* * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }