From 5db56ea0910363185e25a81a3552cdac22b44846 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Thu, 22 Aug 2013 16:51:29 -0300 Subject: [PATCH] bluetooth: Add support for registerRFCOMMServiceByUUID This will create a service handler (in BlueZ terms, a socket listening on an RFCOMM channel and a SDP record identifying this service), which has a callback that will be called when a remote device connects to this service. --- bluetooth/bluetooth_api.js | 110 +++++++++++++++++++++++++++++++++- bluetooth/bluetooth_context.h | 3 + bluetooth/bluetooth_context_bluez4.cc | 86 ++++++++++++++++++++++---- 3 files changed, 183 insertions(+), 16 deletions(-) diff --git a/bluetooth/bluetooth_api.js b/bluetooth/bluetooth_api.js index 22b18ed..004311d 100644 --- a/bluetooth/bluetooth_api.js +++ b/bluetooth/bluetooth_api.js @@ -26,6 +26,8 @@ extension.setMessageListener(function(json) { handleDeviceUpdated(msg); else if (msg.cmd == "AdapterUpdated") handleAdapterUpdated(msg); + else if (msg.cmd == "RFCOMMSocketAccept") + handleRFCOMMSocketAccept(msg); else { // Then we are dealing with postMessage return. var reply_id = msg.reply_id; var callback = _callbacks[reply_id]; @@ -44,6 +46,8 @@ function Adapter() { this.known_devices = []; // Keeps Managed and Found devices. this.discovery_callbacks = {}; this.isReady = false; + this.service_handlers = []; + this.sockets = []; }; Adapter.prototype.serviceNotAvailable = function(errorCallback) { @@ -168,6 +172,26 @@ var handleAdapterUpdated = function(msg) { } }; +var handleRFCOMMSocketAccept = function(msg) { + for (var i in adapter.service_handlers) { + var server = adapter.service_handlers[i]; + if (server.channel === msg.channel) { + var j = adapter.indexOfDevice(adapter.known_devices, msg.peer); + var peer = adapter.known_devices[j]; + + var socket = new BluetoothSocket(server.uuid, peer, msg); + + adapter.sockets.push(socket); + + _addConstProperty(server, "isConnected", true); + + if (server.onconnect && typeof server.onconnect === 'function') + server.onconnect(socket); + return; + } + } +}; + var defaultAdapter = new BluetoothAdapter(); exports.getDefaultAdapter = function() { @@ -578,7 +602,49 @@ BluetoothAdapter.prototype.destroyBonding = function(address, successCallback, e }); }; -BluetoothAdapter.prototype.registerRFCOMMServiceByUUID = function(uuid, name, serviceSuccessCallback, errorCallback) {}; +BluetoothAdapter.prototype.registerRFCOMMServiceByUUID = function(uuid, name, serviceSuccessCallback, errorCallback) { + if (adapter.serviceNotAvailable(errorCallback)) + return; + + if (typeof uuid !== 'string' + || typeof name !== 'string' + || (serviceSuccessCallback && typeof serviceSuccessCallback !== 'function') + || (errorCallback && typeof errorCallback !== 'function')) { + if (errorCallback) { + var error = new tizen.WebAPIError(tizen.WebAPIException.TYPE_MISMATCH_ERR); + errorCallback(error); + } + throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR); + return; + } + + var msg = { + 'cmd': 'RFCOMMListen', + 'uuid': uuid, + 'name': name + }; + + postMessage(msg, function(result) { + if (result.error != 0) { + if (errorCallback) { + var error; + if (result.error == 1) + error = new tizen.WebAPIError(tizen.WebAPIException.NOT_FOUND_ERR); + else + error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR); + errorCallback(error); + } + return; + } + + var service = new BluetoothServiceHandler(uuid, name, result); + adapter.service_handlers.push(service); + + if (serviceSuccessCallback) { + serviceSuccessCallback(service); + } + }); +}; var _deviceClassMask = { "MINOR": 0x3F, @@ -670,7 +736,35 @@ BluetoothDevice.prototype._updateProperties = function(device) { } }; -function BluetoothSocket() {}; +function BluetoothSocket(uuid, peer, msg) { + _addConstProperty(this, "uuid", uuid); + _addConstProperty(this, "peer", peer); + _addConstProperty(this, "state", this.BluetoothSocketState.OPEN); + this.onclose = null; + this.onmessage = null; + this.data = []; + this.channel = 0; + this.socket_fd = 0; + + if (msg) { + this.channel = msg.channel; + this.socket_fd = msg.socket_fd; + } +}; + +BluetoothSocket.prototype.BluetoothSocketState = {}; +var BluetoothSocketState = { + "CLOSE": 1, + "OPEN": 2, +}; +for (var key in BluetoothSocketState) { + Object.defineProperty(BluetoothSocket.prototype.BluetoothSocketState, key, { + configurable: false, + writable: false, + value: BluetoothSocketState[key] + }); +}; + BluetoothSocket.prototype.writeData = function(data) {/*return ulong*/}; BluetoothSocket.prototype.readData = function() {/*return byte[]*/}; BluetoothSocket.prototype.close = function() {/*return byte[]*/}; @@ -684,5 +778,15 @@ BluetoothClass.prototype.hasService = function(service) { return false; }; -function BluetoothServiceHandler() {}; +function BluetoothServiceHandler(name, uuid, msg) { + _addConstProperty(this, "name", name); + _addConstProperty(this, "uuid", uuid); + _addConstProperty(this, "isConnected", false); + + if (msg) { + this.server_fd = msg.server_fd; + this.sdp_handle = msg.sdp_handle; + this.channel = msg.channel; + } +}; BluetoothServiceHandler.prototype.unregister = function(successCallback, errorCallback) {}; diff --git a/bluetooth/bluetooth_context.h b/bluetooth/bluetooth_context.h index 28f6536..f3e762b 100644 --- a/bluetooth/bluetooth_context.h +++ b/bluetooth/bluetooth_context.h @@ -151,6 +151,7 @@ class BluetoothContext { G_CALLBACK_CANCELLABLE_1(OnGotDeviceProperties, GObject*, GAsyncResult*); G_CALLBACK_CANCELLABLE_1(OnServiceProxyCreated, GObject*, GAsyncResult*); G_CALLBACK_CANCELLABLE_1(OnServiceAddRecord, GObject*, GAsyncResult*); + G_CALLBACK_1(OnListenerAccept, GObject*, GAsyncResult*); static void OnSignal(GDBusProxy* proxy, gchar* sender_name, gchar* signal, GVariant* parameters, gpointer user_data); @@ -167,6 +168,8 @@ class BluetoothContext { GDBusProxy* service_proxy_; int pending_listen_socket_; + GSocketListener *rfcomm_listener_; + #endif }; diff --git a/bluetooth/bluetooth_context_bluez4.cc b/bluetooth/bluetooth_context_bluez4.cc index 358739f..36ff31b 100644 --- a/bluetooth/bluetooth_context_bluez4.cc +++ b/bluetooth/bluetooth_context_bluez4.cc @@ -56,6 +56,35 @@ static GCancellable* new_cancellable() { \ " +static uint32_t rfcomm_get_channel(int fd) { + struct sockaddr_rc laddr; + socklen_t alen = sizeof(laddr); + + memset(&laddr, 0, alen); + + if (getsockname(fd, (struct sockaddr *) &laddr, &alen) < 0) + return 0; + + return laddr.rc_channel; +} + +static int rfcomm_get_peer(int fd, char* address) { + if (!address) + return -1; + + struct sockaddr_rc raddr; + socklen_t alen = sizeof(raddr); + + memset(&raddr, 0, alen); + + if (getpeername(fd, (struct sockaddr *) &raddr, &alen) < 0) + return -1; + + ba2str(&raddr.rc_bdaddr, address); + + return 0; +} + // Returns an fd listening on 'channel' RFCOMM Channel static int rfcomm_listen(uint8_t *channel) { int sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); @@ -65,6 +94,7 @@ static int rfcomm_listen(uint8_t *channel) { struct sockaddr_rc laddr; // All zeros means BDADDR_ANY and any channel. memset(&laddr, 0, sizeof(laddr)); + laddr.rc_family = AF_BLUETOOTH; if (bind(sk, (struct sockaddr *) &laddr, sizeof(laddr)) < 0) { close(sk); @@ -73,15 +103,8 @@ static int rfcomm_listen(uint8_t *channel) { listen(sk, 10); - socklen_t alen = sizeof(laddr); - if (getsockname(sk, (struct sockaddr *) &laddr, &alen) < 0) { - close(sk); - return -1; - } - - // Returns the channel as we let the stack allocate it. if (channel) - *channel = laddr.rc_channel; + *channel = rfcomm_get_channel(sk); return sk; } @@ -470,6 +493,8 @@ void BluetoothContext::PlatformInitialize() { pending_listen_socket_ = -1; + rfcomm_listener_ = g_socket_listener_new(); + is_js_context_initialized_ = false; all_pending_ = new_cancellable(); @@ -577,6 +602,32 @@ void BluetoothContext::HandleDestroyBonding(const picojson::value& msg) { CancellableWrap(all_pending_, this)); } +void BluetoothContext::OnListenerAccept(GObject* object, GAsyncResult* res) { + GError* error = 0; + GSocket *socket = g_socket_listener_accept_socket_finish(rfcomm_listener_, res, + NULL, &error); + if (!socket) { + g_printerr("\n\nlistener_accept_socket_finish failed %s\n", error->message); + return; + } + + int fd = g_socket_get_fd(socket); + uint32_t channel = rfcomm_get_channel(fd); + char address[18]; // "XX:XX:XX:XX:XX:XX" + picojson::value::object o; + + rfcomm_get_peer(fd, address); + + o["cmd"] = picojson::value("RFCOMMSocketAccept"); + o["channel"] = picojson::value(static_cast(channel)); + o["socket_fd"] = picojson::value(static_cast(fd)); + o["peer"] = picojson::value(address); + + PostMessage(picojson::value(o)); + + // FIXME(vcgomes): Add a watch for data. +} + void BluetoothContext::OnServiceAddRecord(GObject* object, GAsyncResult* res) { GError* error = 0; picojson::value::object o; @@ -595,15 +646,24 @@ void BluetoothContext::OnServiceAddRecord(GObject* object, GAsyncResult* res) { g_printerr("\n\nError OnServiceAddRecord: %s\n", error->message); g_error_free(error); } else { - uint32_t handle = g_variant_get_uint32(result); + uint32_t handle; int sk = pending_listen_socket_; pending_listen_socket_ = -1; - // FIXME(vcgomes): Add an watch for the socket + GSocket *socket = g_socket_new_from_fd(sk, NULL); + g_socket_set_blocking(socket, false); - o["error"] = picojson::value(static_cast(1)); - o["socket_fd"] = picojson::value(static_cast(sk)); - o["sdp_Handle"] = picojson::value(static_cast(handle)); + g_socket_listener_add_socket(rfcomm_listener_, socket, NULL, NULL); + + g_socket_listener_accept_async(rfcomm_listener_, NULL, + OnListenerAcceptThunk, this); + + g_variant_get(result, "(u)", &handle); + + o["error"] = picojson::value(static_cast(0)); + o["server_fd"] = picojson::value(static_cast(sk)); + o["sdp_handle"] = picojson::value(static_cast(handle)); + o["channel"] = picojson::value(static_cast(rfcomm_get_channel(sk))); } callbacks_map_.erase("RFCOMMListen"); -- 2.7.4