diff --git a/core/Makefile b/core/Makefile index 3d54524..a8340f9 100644 --- a/core/Makefile +++ b/core/Makefile @@ -8,7 +8,7 @@ # Required packages # -PKGS = libglibutil glib-2.0 gobject-2.0 +PKGS = libglibutil glib-2.0 gobject-2.0 gio-2.0 gio-unix-2.0 # # Default target @@ -32,14 +32,28 @@ SRC = \ nfc_adapter.c \ nfc_crc.c \ nfc_core.c \ + nfc_initiator.c \ + nfc_llc.c \ + nfc_llc_io.c \ + nfc_llc_io_initiator.c \ + nfc_llc_io_target.c \ + nfc_llc_param.c \ nfc_locale.c \ nfc_manager.c \ nfc_ndef_rec.c \ nfc_ndef_rec_sp.c \ nfc_ndef_rec_u.c \ nfc_ndef_rec_t.c \ + nfc_peer.c \ + nfc_peer_connection.c \ + nfc_peer_initiator.c \ + nfc_peer_service.c \ + nfc_peer_services.c \ + nfc_peer_socket.c \ + nfc_peer_target.c \ nfc_plugins.c \ nfc_plugin.c \ + nfc_snep_server.c \ nfc_tag.c \ nfc_tag_t2.c \ nfc_tag_t4.c \ diff --git a/core/include/nfc_adapter.h b/core/include/nfc_adapter.h index 06441ea..b218c6d 100644 --- a/core/include/nfc_adapter.h +++ b/core/include/nfc_adapter.h @@ -54,7 +54,7 @@ struct nfc_adapter { NFC_MODE supported_modes; NFC_MODE mode_requested; NFC_MODE mode; - gboolean target_present; + gboolean target_present; /* Presence of anything, actually */ }; GType nfc_adapter_get_type(void) NFCD_EXPORT; @@ -75,6 +75,13 @@ void NfcTag* tag, void* user_data); +typedef +void +(*NfcAdapterPeerFunc)( + NfcAdapter* adapter, + NfcPeer* peer, + void* user_data); /* Since 1.1.0 */ + NfcAdapter* nfc_adapter_ref( NfcAdapter* adapter) @@ -97,6 +104,11 @@ nfc_adapter_request_mode( NFC_MODE mode) NFCD_EXPORT; +NfcPeer** +nfc_adapter_peers( + NfcAdapter* adapter) /* Since 1.1.0 */ + NFCD_EXPORT; + NfcTag* nfc_adapter_add_tag_t2( NfcAdapter* adapter, @@ -140,6 +152,44 @@ nfc_adapter_remove_tag( const char* name) NFCD_EXPORT; +NfcPeer* +nfc_adapter_add_peer_initiator_a( + NfcAdapter* adapter, + NfcTarget* target, + const NfcParamPollA* tech_param, + const NfcParamNfcDepInitiator* nfc_dep_param) /* Since 1.1.0 */ + NFCD_EXPORT; + +NfcPeer* +nfc_adapter_add_peer_initiator_f( + NfcAdapter* adapter, + NfcTarget* target, + const NfcParamPollF* tech_param, + const NfcParamNfcDepInitiator* nfc_dep_param) /* Since 1.1.0 */ + NFCD_EXPORT; + +NfcPeer* +nfc_adapter_add_peer_target_a( + NfcAdapter* adapter, + NfcInitiator* initiator, + const NfcParamListenA* tech_param, + const NfcParamNfcDepTarget* nfc_dep_param) /* Since 1.1.0 */ + NFCD_EXPORT; + +NfcPeer* +nfc_adapter_add_peer_target_f( + NfcAdapter* adapter, + NfcInitiator* initiator, + const NfcParamListenF* tech_param, + const NfcParamNfcDepTarget* nfc_dep_param) /* Since 1.1.0 */ + NFCD_EXPORT; + +void +nfc_adapter_remove_peer( + NfcAdapter* adapter, + const char* name) /* Since 1.1.0 */ + NFCD_EXPORT; + gulong nfc_adapter_add_target_presence_handler( NfcAdapter* adapter, @@ -161,6 +211,20 @@ nfc_adapter_add_tag_removed_handler( void* user_data) NFCD_EXPORT; +gulong +nfc_adapter_add_peer_added_handler( + NfcAdapter* adapter, + NfcAdapterPeerFunc func, + void* user_data) /* Since 1.1.0 */ + NFCD_EXPORT; + +gulong +nfc_adapter_add_peer_removed_handler( + NfcAdapter* adapter, + NfcAdapterPeerFunc func, + void* user_data) /* Since 1.1.0 */ + NFCD_EXPORT; + gulong nfc_adapter_add_powered_changed_handler( NfcAdapter* adapter, diff --git a/core/include/nfc_initiator.h b/core/include/nfc_initiator.h new file mode 100644 index 0000000..3edfaa1 --- /dev/null +++ b/core/include/nfc_initiator.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_INITIATOR_H +#define NFC_INITIATOR_H + +#include "nfc_types.h" + +#include + +G_BEGIN_DECLS + +/* Since 1.1.0 */ + +typedef struct nfc_initiator_priv NfcInitiatorPriv; + +struct nfc_initiator { + GObject object; + NfcInitiatorPriv* priv; + NFC_TECHNOLOGY technology; + NFC_PROTOCOL protocol; + /* This one-way flag is set to FALSE when peer goes away. */ + gboolean present; +}; + +GType nfc_initiator_get_type(void) NFCD_EXPORT; +#define NFC_TYPE_INITIATOR (nfc_initiator_get_type()) +#define NFC_INITIATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + NFC_TYPE_INITIATOR, NfcInitiator)) + +NfcInitiator* +nfc_initiator_ref( + NfcInitiator* initiator) + NFCD_EXPORT; + +void +nfc_initiator_unref( + NfcInitiator* initiator) + NFCD_EXPORT; + +G_END_DECLS + +#endif /* NFC_INITIATOR_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/include/nfc_initiator_impl.h b/core/include/nfc_initiator_impl.h new file mode 100644 index 0000000..212846c --- /dev/null +++ b/core/include/nfc_initiator_impl.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_INITIATOR_IMPL_H +#define NFC_INITIATOR_IMPL_H + +#include "nfc_initiator.h" + +/* Internal API for use by NfcInitiator implemenations */ + +G_BEGIN_DECLS + +/* Since 1.1.0 */ + +typedef struct nfc_initiator_class { + GObjectClass parent; + + /* Base class makes sure that there are no overlapping responses. + * When transmission completes, nfc_initiator_response_sent() is called + * by the derived class. */ + gboolean (*respond)(NfcInitiator* initiator, const void* data, guint len); + + /* This should deactivate the initiator. When the initiator gets + * deactivated, subclass calls nfc_initiator_gone() to update the + * 'present' flag. */ + void (*deactivate)(NfcInitiator* initiator); + + /* These base implementation emits signal, must always be called. */ + void (*gone)(NfcInitiator* initiator); + + /* Padding for future expansion */ + void (*_reserved1)(void); + void (*_reserved2)(void); + void (*_reserved3)(void); + void (*_reserved4)(void); + void (*_reserved5)(void); + void (*_reserved6)(void); + void (*_reserved7)(void); + void (*_reserved8)(void); + void (*_reserved9)(void); + void (*_reserved10)(void); +} NfcInitiatorClass; + +#define NFC_INITIATOR_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), \ + NFC_TYPE_INITIATOR, NfcInitiatorClass) + +/* + * Normally it goes like this: + * + * 1. Data is coming in. Derived class calls nfc_initiator_transmit() + * 2. Base class issues a signal passing in NfcTransmission object + * 3. Transmission handler does whatever and calls nfc_transmission_respond() + * That translates into respond() call to the derived class. + * 4. Derived class calls nfc_initiator_response_sent() when response is sent. + * 5. At this point initiator is ready to receive a new portion of data. + * + * Now, if anything goes wrong... Basically, if anything goes wrong, + * RF interface is deactivated by invoking deactivate() callback of the + * derived class. Here is what can go wrong: + + * a. No one responds to the signal at step 2. + * b. nfc_transmission_unref() is called before nfc_transmission_respond() + * in other words, transmission is received but dropped with no reply + * provided. + * c. nfc_initiator_response_sent() receives an error status at step 4. + * + * It's not quite clear what to do when the next portion of data arrives + * before we have sent a response to the previous one. Even though it + * shouldn't happen in real life, lower level APIs (e.g. NCI) often + * allow it. Currently it's being treated as an error (or was treated + * at the time of this writing). Let's see how it goes. + */ + +void +nfc_initiator_transmit( + NfcInitiator* initiator, + const void* data, + guint len) + NFCD_EXPORT; + +void +nfc_initiator_response_sent( + NfcInitiator* initiator, + NFC_TRANSMIT_STATUS status) + NFCD_EXPORT; + +void +nfc_initiator_gone( + NfcInitiator* initiator) + NFCD_EXPORT; + +G_END_DECLS + +#endif /* NFC_INITIATOR_IMPL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/include/nfc_manager.h b/core/include/nfc_manager.h index aa091b5..1c92fe3 100644 --- a/core/include/nfc_manager.h +++ b/core/include/nfc_manager.h @@ -14,8 +14,8 @@ * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the names of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE @@ -48,6 +48,8 @@ struct nfc_manager { gboolean enabled; gboolean stopped; int error; + /* Since 1.1.0 */ + NFC_MODE mode; }; GType nfc_manager_get_type() NFCD_EXPORT; @@ -122,6 +124,18 @@ nfc_manager_request_power( gboolean on) NFCD_EXPORT; +gboolean +nfc_manager_register_service( + NfcManager* manager, + NfcPeerService* service) /* Since 1.1.0 */ + NFCD_EXPORT; + +void +nfc_manager_unregister_service( + NfcManager* manager, + NfcPeerService* service) /* Since 1.1.0 */ + NFCD_EXPORT; + gulong nfc_manager_add_adapter_added_handler( NfcManager* manager, @@ -143,6 +157,13 @@ nfc_manager_add_enabled_changed_handler( void* user_data) NFCD_EXPORT; +gulong +nfc_manager_add_mode_changed_handler( + NfcManager* manager, + NfcManagerFunc func, + void* user_data) /* Since 1.1.0 */ + NFCD_EXPORT; + gulong nfc_manager_add_stopped_handler( NfcManager* manager, @@ -166,6 +187,35 @@ nfc_manager_remove_handlers( #define nfc_manager_remove_all_handlers(manager,ids) \ nfc_manager_remove_handlers(manager, ids, G_N_ELEMENTS(ids)) +/* + * Plugins can ask NfcManager to enable and/or disable certain NFC modes. + * The last submitted request takes precedence, i.e. if first a request + * is submitted to enable certain mode and then another another request + * to disable the same mode, the mode remains enabled until the first + * request is dropped. + * + * If the same bits are set in both enable and disable masks, the enabling + * bits take precedence. If both are zero, nfc_manager_mode_request_new() + * returns NULL which is tolerated by nfc_manager_mode_request_free() + * + * Note that each NfcModeRequest carries an implicit reference to NfcManager. + */ + +typedef struct nfc_mode_request NfcModeRequest; /* Since 1.1.0 */ + +NfcModeRequest* +nfc_manager_mode_request_new( + NfcManager* manager, + NFC_MODE enable, + NFC_MODE disable) /* Since 1.1.0 */ + G_GNUC_WARN_UNUSED_RESULT + NFCD_EXPORT; + +void +nfc_manager_mode_request_free( + NfcModeRequest* req) /* Since 1.1.0 */ + NFCD_EXPORT; + G_END_DECLS #endif /* NFC_MANAGER_H */ diff --git a/core/include/nfc_peer.h b/core/include/nfc_peer.h new file mode 100644 index 0000000..ff0738c --- /dev/null +++ b/core/include/nfc_peer.h @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_H +#define NFC_PEER_H + +#include "nfc_types.h" + +#include + +G_BEGIN_DECLS + +/* Since 1.1.0 */ + +typedef struct nfc_peer_priv NfcPeerPriv; + +typedef enum nfc_peer_flags { + NFC_PEER_FLAGS_NONE = 0x00, + NFC_PEER_FLAG_INITIALIZED = 0x01, + NFC_PEER_FLAG_INITIATOR = 0x02 +} NFC_PEER_FLAGS; + +struct nfc_peer { + GObject object; + NfcPeerPriv* priv; + const char* name; + gboolean present; + NFC_TECHNOLOGY technology; + NFC_PEER_FLAGS flags; + guint wks; /* Remote Well-Known Services (mask) */ + NfcNdefRec* ndef; /* Received via SNEP */ +}; + +GType nfc_peer_get_type(void) NFCD_EXPORT; +#define NFC_TYPE_PEER (nfc_peer_get_type()) +#define NFC_IS_PEER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, NFC_TYPE_PEER) +#define NFC_PEER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NFC_TYPE_PEER, \ + NfcPeer)) + +/* + * NFC-DEP Initiator and Target parameters. + * Contain relevant parts of ATR_RES/ATR_REQ (General bytes, what else?). + * + * NFCForum-TS-LLCP_1.1 + * 6.2.3.1 Link Activation procedure for the Initiator + * 6.2.3.2 Link Activation procedure for the Target + */ +struct nfc_param_nfc_dep_initator { + GUtilData atr_res_g; /* ATR_RES General Bytes */ +}; + +struct nfc_param_nfc_dep_target { + GUtilData atr_req_g; /* ATR_REQ General Bytes */ +}; + +typedef +void +(*NfcPeerFunc)( + NfcPeer* peer, + void* user_data); + +gulong +nfc_peer_add_initialized_handler( + NfcPeer* peer, + NfcPeerFunc func, + void* user_data) + NFCD_EXPORT; + +NfcPeer* +nfc_peer_ref( + NfcPeer* peer) + NFCD_EXPORT; + +void +nfc_peer_unref( + NfcPeer* peer) + NFCD_EXPORT; + +void +nfc_peer_deactivate( + NfcPeer* peer) + NFCD_EXPORT; + +gulong +nfc_peer_add_wks_changed_handler( + NfcPeer* peer, + NfcPeerFunc func, + void* user_data) + NFCD_EXPORT; + +gulong +nfc_peer_add_ndef_changed_handler( + NfcPeer* peer, + NfcPeerFunc func, + void* user_data) + NFCD_EXPORT; + +gulong +nfc_peer_add_initialized_handler( + NfcPeer* peer, + NfcPeerFunc func, + void* user_data) + NFCD_EXPORT; + +gulong +nfc_peer_add_gone_handler( + NfcPeer* peer, + NfcPeerFunc func, + void* user_data) + NFCD_EXPORT; + +void +nfc_peer_remove_handler( + NfcPeer* peer, + gulong id) + NFCD_EXPORT; + +void +nfc_peer_remove_handlers( + NfcPeer* peer, + gulong* ids, + guint count) + NFCD_EXPORT; + +#define nfc_peer_remove_all_handlers(peer,ids) \ + nfc_peer_remove_handlers(peer, ids, G_N_ELEMENTS(ids)) + +gboolean +nfc_peer_register_service( + NfcPeer* peer, + NfcPeerService* service) + NFCD_EXPORT; + +void +nfc_peer_unregister_service( + NfcPeer* peer, + NfcPeerService* service) + NFCD_EXPORT; + +/* + * Functions below return a NfcPeerConnection pointer, not a reference. + * In other words, if the caller needs to keep this pointer, it needs + * to add its own reference. If is only guaranteed that this pointer + * stays alive until return to the event loop, or until the next call + * to NFC core, whichever happens first. + */ + +typedef +void +(*NfcPeerConnectFunc)( + NfcPeer* peer, + NfcPeerConnection* connection, + NFC_PEER_CONNECT_RESULT result, + void* user_data); + +NfcPeerConnection* +nfc_peer_connect( + NfcPeer* peer, + NfcPeerService* service, + guint rsap, + NfcPeerConnectFunc complete, + GDestroyNotify destroy, + void* user_data) + NFCD_EXPORT; + +NfcPeerConnection* +nfc_peer_connect_sn( + NfcPeer* peer, + NfcPeerService* service, + const char* sn, + NfcPeerConnectFunc complete, + GDestroyNotify destroy, + void* user_data) + NFCD_EXPORT; + +G_END_DECLS + +#endif /* NFC_PEER_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/include/nfc_peer_connection.h b/core/include/nfc_peer_connection.h new file mode 100644 index 0000000..8a04865 --- /dev/null +++ b/core/include/nfc_peer_connection.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_CONNECTION_H +#define NFC_PEER_CONNECTION_H + +#include "nfc_types.h" + +#include + +G_BEGIN_DECLS + +/* Since 1.1.0 */ + +typedef enum nfc_llc_co_state { + NFC_LLC_CO_CONNECTING, /* CONNECT sent, waiting for CC */ + NFC_LLC_CO_ACCEPTING, /* CONNECT received, CC not sent */ + NFC_LLC_CO_ABANDONED, /* CONNECT sent, will disconnect */ + NFC_LLC_CO_ACTIVE, /* Connection established */ + NFC_LLC_CO_DISCONNECTING, /* DISC sent, waiting for DM */ + NFC_LLC_CO_DEAD /* Final state */ +} NFC_LLC_CO_STATE; + +typedef struct nfc_peer_connection_priv NfcPeerConnectionPriv; +struct nfc_peer_connection { + GObject object; + NfcPeerConnectionPriv* priv; + NfcPeerService* service; /* Local service */ + NFC_LLC_CO_STATE state; /* Connection state */ + const char* name; /* Remote service name, if known */ + gsize bytes_queued; /* Bytes currently queued */ + guint64 bytes_sent; /* Bytes sent (passed to LLCP level) */ + guint64 bytes_received; /* Bytes received */ + guint8 rsap; /* Remote SAP */ +}; + +GType nfc_peer_connection_get_type(void) NFCD_EXPORT; +#define NFC_TYPE_PEER_CONNECTION (nfc_peer_connection_get_type()) +#define NFC_PEER_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + NFC_TYPE_PEER_CONNECTION, NfcPeerConnection)) + +typedef +void +(*NfcPeerConnectionFunc)( + NfcPeerConnection* pc, + void* user_data); + +NfcPeerConnection* +nfc_peer_connection_ref( + NfcPeerConnection* pc) + NFCD_EXPORT; + +void +nfc_peer_connection_unref( + NfcPeerConnection* pc) + NFCD_EXPORT; + +guint +nfc_peer_connection_rmiu( + NfcPeerConnection* pc) + NFCD_EXPORT; + +gboolean +nfc_peer_connection_send( + NfcPeerConnection* pc, + GBytes* bytes) + NFCD_EXPORT; + +void +nfc_peer_connection_disconnect( + NfcPeerConnection* pc) + NFCD_EXPORT; + +gboolean +nfc_peer_connection_cancel( + NfcPeerConnection* pc) + NFCD_EXPORT; + +gulong +nfc_peer_connection_add_state_changed_handler( + NfcPeerConnection* pc, + NfcPeerConnectionFunc func, + void* user_data); + NFCD_EXPORT + +void +nfc_peer_connection_remove_handler( + NfcPeerConnection* pc, + gulong id) + NFCD_EXPORT; + +G_END_DECLS + +#endif /* NFC_PEER_CONNECTION_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/include/nfc_peer_connection_impl.h b/core/include/nfc_peer_connection_impl.h new file mode 100644 index 0000000..3e40358 --- /dev/null +++ b/core/include/nfc_peer_connection_impl.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_CONNECTION_IMPL_H +#define NFC_PEER_CONNECTION_IMPL_H + +#include "nfc_peer_connection.h" + +G_BEGIN_DECLS + +/* Since 1.1.0 */ + +/* Internal API for use by classes derived from NfcPeerConnection */ + +typedef struct nfc_peer_connection_class { + GObjectClass parent; + + void (*accept)(NfcPeerConnection* conn); + void (*accept_cancelled)(NfcPeerConnection* conn); + void (*state_changed)(NfcPeerConnection* conn); + void (*data_received)(NfcPeerConnection* conn, const void* data, guint len); + void (*data_dequeued)(NfcPeerConnection* conn); + + /* Padding for future expansion */ + void (*_reserved1)(void); + void (*_reserved2)(void); + void (*_reserved3)(void); + void (*_reserved4)(void); + void (*_reserved5)(void); + void (*_reserved6)(void); + void (*_reserved7)(void); + void (*_reserved8)(void); + void (*_reserved9)(void); + void (*_reserved10)(void); +} NfcPeerConnectionClass; + +#define NFC_PEER_CONNECTION_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), \ + NFC_TYPE_PEER_CONNECTION, NfcPeerConnectionClass) + +void +nfc_peer_connection_init_connect( + NfcPeerConnection* pc, + NfcPeerService* ps, + guint8 rsap, + const char* name) + NFCD_EXPORT; + +void +nfc_peer_connection_init_accept( + NfcPeerConnection* pc, + NfcPeerService* ps, + guint8 rsap) + NFCD_EXPORT; + +/* ACCEPTING => ACTIVE */ +void +nfc_peer_connection_accepted( + NfcPeerConnection* pc) + NFCD_EXPORT; + +/* ACCEPTING => DEAD */ +void +nfc_peer_connection_rejected( + NfcPeerConnection* pc) + NFCD_EXPORT; + +G_END_DECLS + +#endif /* NFC_PEER_CONNECTION_IMPL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/include/nfc_peer_service.h b/core/include/nfc_peer_service.h new file mode 100644 index 0000000..b9ea915 --- /dev/null +++ b/core/include/nfc_peer_service.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_SERVICE_H +#define NFC_PEER_SERVICE_H + +#include "nfc_types.h" + +#include + +G_BEGIN_DECLS + +/* Since 1.1.0 */ + +typedef struct nfc_peer_service_priv NfcPeerServicePriv; +struct nfc_peer_service { + GObject object; + NfcPeerServicePriv* priv; + const char* name; + guint8 sap; +}; + +/* Well-known LLCP SAP values */ +#define NFC_LLC_SAP_SDP (0x01) /* urn:nfc:sn:sdp */ +#define NFC_LLC_SAP_SNEP (0x04) /* urn:nfc:sn:snep */ + +/* Well-known names */ +#define NFC_LLC_NAME_SDP "urn:nfc:sn:sdp" +#define NFC_LLC_NAME_SNEP "urn:nfc:sn:snep" + +GType nfc_peer_service_get_type(void) NFCD_EXPORT; +#define NFC_TYPE_PEER_SERVICE (nfc_peer_service_get_type()) +#define NFC_PEER_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + NFC_TYPE_PEER_SERVICE, NfcPeerService)) + +NfcPeerService* +nfc_peer_service_ref( + NfcPeerService* service) + NFCD_EXPORT; + +void +nfc_peer_service_unref( + NfcPeerService* service) + NFCD_EXPORT; + +void +nfc_peer_service_disconnect_all( + NfcPeerService* service) + NFCD_EXPORT; + +G_END_DECLS + +#endif /* NFC_PEER_SERVICE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/include/nfc_peer_service_impl.h b/core/include/nfc_peer_service_impl.h new file mode 100644 index 0000000..2902158 --- /dev/null +++ b/core/include/nfc_peer_service_impl.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_SERVICE_IMPL_H +#define NFC_PEER_SERVICE_IMPL_H + +#include "nfc_peer_service.h" + +G_BEGIN_DECLS + +/* Since 1.1.0 */ + +/* Internal API for use by NfcPeerService implemenations */ + +typedef struct nfc_peer_service_class { + GObjectClass parent; + + void (*peer_arrived)(NfcPeerService* service, NfcPeer* peer); + void (*peer_left)(NfcPeerService* service, NfcPeer* peer); + NfcPeerConnection* (*new_connect)(NfcPeerService* service, guint8 rsap, + const char* name); + NfcPeerConnection* (*new_accept)(NfcPeerService* service, guint8 rsap); + void (*datagram_received)(NfcPeerService* service, guint8 ssap, + const void* data, guint len); + + /* Padding for future expansion */ + void (*_reserved1)(void); + void (*_reserved2)(void); + void (*_reserved3)(void); + void (*_reserved4)(void); + void (*_reserved5)(void); + void (*_reserved6)(void); + void (*_reserved7)(void); + void (*_reserved8)(void); + void (*_reserved9)(void); + void (*_reserved10)(void); +} NfcPeerServiceClass; + +#define NFC_PEER_SERVICE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), \ + NFC_TYPE_PEER_SERVICE, NfcPeerServiceClass) + +void +nfc_peer_service_init_base( + NfcPeerService* service, + const char* name) + NFCD_EXPORT; + +G_END_DECLS + +#endif /* NFC_PEER_SERVICE_IMPL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/include/nfc_peer_socket.h b/core/include/nfc_peer_socket.h new file mode 100644 index 0000000..69b0829 --- /dev/null +++ b/core/include/nfc_peer_socket.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_SOCKET_H +#define NFC_PEER_SOCKET_H + +#include "nfc_peer_connection.h" + +#include + +G_BEGIN_DECLS + +/* Since 1.1.0 */ + +/* + * NfcPeerSocket essentially provides two ways of writing the data. + * Calling nfc_peer_connection_send() will work, and writing data + * to public file descriptor will also work. File descriptor + * writes will be internally (and asynchronously) translated + * into nfc_peer_connection_send() calls. + * + * The file descritor is exposed as a ref-countable GUnixFDList. + * That list always contains exactly one descriptor. + * + * It doesn't make sense to use both methods though, because chunks + * of data will end up being unpredictably mixed up with each other. + * The preferred way of writing the data and preserving the integrity + * of the stream is to use the file descriptor. + * + * Note that max_send_queue is not a hard limit, the actual amount of + * data buffered at NfcPeerService level may exceed the limit by one MIU. + * That's in addition to buffering happening in other places down the stack. + */ +typedef struct nfc_peer_socket_priv NfcPeerSocketPriv; +struct nfc_peer_socket { + NfcPeerConnection connection; + NfcPeerSocketPriv* priv; + GUnixFDList* fdl; + gsize max_send_queue; +}; + +GType nfc_peer_socket_get_type(void) NFCD_EXPORT; +#define NFC_TYPE_PEER_SOCKET (nfc_peer_socket_get_type()) +#define NFC_PEER_SOCKET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + NFC_TYPE_PEER_SOCKET, NfcPeerSocket)) + +NfcPeerSocket* +nfc_peer_socket_new_connect( + NfcPeerService* service, + guint8 rsap, + const char* name) + G_GNUC_WARN_UNUSED_RESULT + NFCD_EXPORT; + +NfcPeerSocket* +nfc_peer_socket_new_accept( + NfcPeerService* service, + guint8 rsap) + G_GNUC_WARN_UNUSED_RESULT + NFCD_EXPORT; + +int +nfc_peer_socket_fd( + NfcPeerSocket* socket) + NFCD_EXPORT; + +void +nfc_peer_socket_set_max_send_queue( + NfcPeerSocket* socket, + gsize max_send_queue) + NFCD_EXPORT; + +G_END_DECLS + +#endif /* NFC_PEER_SOCKET_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/include/nfc_peer_socket_impl.h b/core/include/nfc_peer_socket_impl.h new file mode 100644 index 0000000..f6f0bb4 --- /dev/null +++ b/core/include/nfc_peer_socket_impl.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_SOCKET_IMPL_H +#define NFC_PEER_SOCKET_IMPL_H + +#include "nfc_peer_connection_impl.h" +#include "nfc_peer_socket.h" + +G_BEGIN_DECLS + +/* Since 1.1.0 */ + +/* Internal API for use by classes derived from NfcPeerSocket */ + +typedef struct nfc_peer_socket_class { + NfcPeerConnectionClass parent; + + /* Padding for future expansion */ + void (*_reserved1)(void); + void (*_reserved2)(void); + void (*_reserved3)(void); + void (*_reserved4)(void); + void (*_reserved5)(void); +} NfcPeerSocketClass; + +#define NFC_PEER_SOCKET_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), \ + NFC_TYPE_PEER_SOCKET, NfcPeerSocketClass) + +gboolean +nfc_peer_socket_init_connect( + NfcPeerSocket* socket, + NfcPeerService* service, + guint8 rsap, + const char* name) + NFCD_EXPORT; + +gboolean +nfc_peer_socket_init_accept( + NfcPeerSocket* socket, + NfcPeerService* service, + guint8 rsap) + NFCD_EXPORT; + +G_END_DECLS + +#endif /* NFC_PEER_SOCKET_IMPL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/include/nfc_types.h b/core/include/nfc_types.h index 39b92a3..3202244 100644 --- a/core/include/nfc_types.h +++ b/core/include/nfc_types.h @@ -41,9 +41,14 @@ G_BEGIN_DECLS /* Types */ typedef struct nfc_adapter NfcAdapter; -typedef struct nfc_language NfcLanguage; /* Since 1.0.15 */ +typedef struct nfc_initiator NfcInitiator; /* Since 1.1.0 */ +typedef struct nfc_language NfcLanguage; /* Since 1.0.15 */ +typedef struct nfc_peer_connection NfcPeerConnection; /* Since 1.1.0 */ +typedef struct nfc_peer_service NfcPeerService; /* Since 1.1.0 */ +typedef struct nfc_peer_socket NfcPeerSocket; /* Since 1.1.0 */ typedef struct nfc_ndef_rec NfcNdefRec; typedef struct nfc_manager NfcManager; +typedef struct nfc_peer NfcPeer; /* Since 1.1.0 */ typedef struct nfc_plugin NfcPlugin; typedef struct nfc_plugin_desc NfcPluginDesc; typedef struct nfc_tag NfcTag; @@ -54,8 +59,11 @@ typedef struct nfc_tag_t4b NfcTagType4b; /* Since 1.0.20 */ typedef struct nfc_target NfcTarget; typedef struct nfc_target_sequence NfcTargetSequence; +typedef struct nfc_param_listen_a NfcParamListenA; /* Since 1.1.0 */ typedef struct nfc_param_iso_dep_poll_a NfcParamIsoDepPollA; /* Since 1.0.20 */ typedef struct nfc_param_iso_dep_poll_b NfcParamIsoDepPollB; /* Since 1.0.20 */ +typedef struct nfc_param_nfc_dep_initator NfcParamNfcDepInitiator; /* 1.1.0 */ +typedef struct nfc_param_nfc_dep_target NfcParamNfcDepTarget; /* 1.1.0 */ /* Constants */ @@ -69,6 +77,11 @@ typedef enum nfc_mode { NFC_MODE_CARD_EMILATION = 0x08 } NFC_MODE; +/* Combined modes (since 1.1.0) */ +#define NFC_MODES_P2P (NFC_MODE_P2P_INITIATOR | NFC_MODE_P2P_TARGET) +#define NFC_MODES_ALL (NFC_MODE_P2P_INITIATOR | NFC_MODE_P2P_TARGET | \ + NFC_MODE_READER_WRITER | NFC_MODE_CARD_EMILATION) + typedef enum nfc_technology { NFC_TECHNOLOGY_UNKNOWN = 0x00, NFC_TECHNOLOGY_A = 0x01, /* NFC-A */ @@ -101,6 +114,15 @@ typedef enum nfc_transmit_status { NFC_TRANSMIT_STATUS_TIMEOUT /* No response from NFCC */ } NFC_TRANSMIT_STATUS; +typedef enum nfc_peer_connect_result { + NFC_PEER_CONNECT_OK, /* Connection was successful */ + NFC_PEER_CONNECT_DUP, /* Duplicate connection */ + NFC_PEER_CONNECT_CANCELLED, /* Connection cancelled */ + NFC_PEER_CONNECT_NO_SERVICE, /* Service not found */ + NFC_PEER_CONNECT_REJECTED, /* Connection rejected */ + NFC_PEER_CONNECT_FAILED /* I/O or protocol error */ +} NFC_PEER_CONNECT_RESULT; + /* RF technology specific parameters */ typedef struct nfc_param_poll_a { @@ -121,9 +143,19 @@ typedef struct nfc_param_poll_b { GUtilData prot_info; } NfcParamPollB; /* Since 1.0.20 */ +typedef struct nfc_param_poll_f { + guint bitrate; /* In kbps, zero if unknown */ + GUtilData nfcid2; /* Bytes 2-9 of SENSF_RES */ +} NfcParamPollF; /* Since 1.1.0 */ + +typedef struct nfc_param_listen_f { + GUtilData nfcid2; /* NFCID2 generated by the Local NFCC */ +} NfcParamListenF; /* Since 1.1.0 */ + typedef union nfc_param_poll { NfcParamPollA a; NfcParamPollB b; + NfcParamPollF f; } NfcParamPoll; /* Since 1.0.33 */ /* Mark functions exported to plugins as weak */ @@ -133,7 +165,13 @@ typedef union nfc_param_poll { /* Logging */ #define NFC_CORE_LOG_MODULE nfc_core_log +#define NFC_LLC_LOG_MODULE nfc_llc_log +#define NFC_PEER_LOG_MODULE nfc_peer_log +#define NFC_SNEP_LOG_MODULE nfc_snep_log extern GLogModule NFC_CORE_LOG_MODULE NFCD_EXPORT; +extern GLogModule NFC_LLC_LOG_MODULE NFCD_EXPORT; /* Since 1.1.0 */ +extern GLogModule NFC_PEER_LOG_MODULE NFCD_EXPORT; /* Since 1.1.0 */ +extern GLogModule NFC_SNEP_LOG_MODULE NFCD_EXPORT; /* Since 1.1.0 */ G_END_DECLS diff --git a/core/src/nfc_adapter.c b/core/src/nfc_adapter.c index e59630e..bcaa6ae 100644 --- a/core/src/nfc_adapter.c +++ b/core/src/nfc_adapter.c @@ -34,8 +34,10 @@ #include "nfc_adapter_p.h" #include "nfc_adapter_impl.h" +#include "nfc_peer_services.h" #include "nfc_tag_p.h" #include "nfc_tag_t4_p.h" +#include "nfc_peer_p.h" #include "nfc_log.h" #include @@ -43,22 +45,31 @@ #include #define NFC_TAG_NAME_FORMAT "tag%u" +#define NFC_PEER_NAME_FORMAT "peer%u" typedef struct nfc_adapter_tag_entry { NfcTag* tag; gulong gone_id; } NfcAdapterTagEntry; +typedef struct nfc_adapter_peer_entry { + NfcPeer* peer; + gulong gone_id; +} NfcAdapterPeerEntry; + struct nfc_adapter_priv { char* name; - GHashTable* tags; + NfcPeerServices* services; + GHashTable* tag_table; + GHashTable* peer_table; + NfcPeer** peers; guint next_tag_index; + guint next_peer_index; guint32 pending_signals; NFC_MODE mode_submitted; gboolean mode_pending; gboolean power_submitted; gboolean power_pending; - gboolean target_presence_notified; }; G_DEFINE_ABSTRACT_TYPE(NfcAdapter, nfc_adapter, G_TYPE_OBJECT) @@ -74,6 +85,8 @@ enum nfc_adapter_signal { SIGNAL_MODE, SIGNAL_MODE_REQUESTED, SIGNAL_TARGET_PRESENCE, + SIGNAL_PEER_ADDED, + SIGNAL_PEER_REMOVED, SIGNAL_COUNT }; @@ -87,12 +100,14 @@ enum nfc_adapter_signal { #define SIGNAL_MODE_NAME "nfc-adapter-mode" #define SIGNAL_MODE_REQUESTED_NAME "nfc-adapter-mode-requested" #define SIGNAL_TARGET_PRESENCE_NAME "nfc-adapter-target-presence" +#define SIGNAL_PEER_ADDED_NAME "nfc-adapter-peer-added" +#define SIGNAL_PEER_REMOVED_NAME "nfc-adapter-peer-removed" static guint nfc_adapter_signals[SIGNAL_COUNT] = { 0 }; -#define NEW_SIGNAL(name) nfc_adapter_signals[SIGNAL_##name] = \ - g_signal_new(SIGNAL_##name##_NAME, G_OBJECT_CLASS_TYPE(klass), \ - G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0) +#define NEW_SIGNAL(name,type) nfc_adapter_signals[SIGNAL_##name] = \ + g_signal_new(SIGNAL_##name##_NAME, type, G_SIGNAL_RUN_FIRST, \ + 0, NULL, NULL, NULL, G_TYPE_NONE, 0) static void @@ -142,17 +157,19 @@ nfc_adapter_compare_tags( } static -NfcTag** -nfc_adapter_tags( - NfcAdapterPriv* priv) +void +nfc_adapter_update_tags( + NfcAdapter* self) { - const guint count = g_hash_table_size(priv->tags); - NfcTag** out = g_new(NfcTag*, count + 1); - NfcTag** ptr = out; + NfcAdapterPriv* priv = self->priv; + const guint count = g_hash_table_size(priv->tag_table); + NfcTag** ptr; GHashTableIter iter; gpointer value; - g_hash_table_iter_init(&iter, priv->tags); + g_free(self->tags); + ptr = self->tags = g_new(NfcTag*, count + 1); + g_hash_table_iter_init(&iter, priv->tag_table); while (g_hash_table_iter_next(&iter, NULL, &value)) { NfcAdapterTagEntry* entry = value; *ptr++ = entry->tag; @@ -160,26 +177,80 @@ nfc_adapter_tags( *ptr = NULL; /* Sort tags by name */ - qsort(out, count, sizeof(NfcTag*), nfc_adapter_compare_tags); - return out; + qsort(self->tags, count, sizeof(NfcTag*), nfc_adapter_compare_tags); +} + +static +int +nfc_adapter_compare_peers( + const void* p1, + const void* p2) +{ + NfcPeer* a1 = *(NfcPeer* const*)p1; + NfcPeer* a2 = *(NfcPeer* const*)p2; + + return strcmp(a1->name, a2->name); } static void -nfc_adapter_update_target_presence( +nfc_adapter_update_peers( + NfcAdapterPriv* priv) +{ + const guint count = g_hash_table_size(priv->peer_table); + NfcPeer** ptr; + GHashTableIter iter; + gpointer value; + + g_free(priv->peers); + ptr = priv->peers = g_new(NfcPeer*, count + 1); + g_hash_table_iter_init(&iter, priv->peer_table); + while (g_hash_table_iter_next(&iter, NULL, &value)) { + NfcAdapterPeerEntry* entry = value; + *ptr++ = entry->peer; + } + *ptr = NULL; + + /* Sort peers by name */ + qsort(priv->peers, count, sizeof(NfcPeer*), nfc_adapter_compare_peers); +} + +static +void +nfc_adapter_set_presence( + NfcAdapter* self, + gboolean present) +{ + if (self->target_present != present) { + self->target_present = present; + GDEBUG("Target %s", present ? "detected" : "disappeared"); + nfc_adapter_queue_signal(self, SIGNAL_TARGET_PRESENCE); + } +} + +static +void +nfc_adapter_update_presence( NfcAdapter* self) { - /* Target detection flag should still work even if implementation - * never calls nfc_adapter_target_notify */ NfcAdapterPriv* priv = self->priv; - const gboolean detected = priv->target_presence_notified || - g_hash_table_size(priv->tags); + gboolean present = FALSE; + GHashTableIter it; + gpointer value; - if (self->target_present != detected) { - self->target_present = detected; - GDEBUG("Target %s", detected ? "detected" : "disappeared"); - nfc_adapter_queue_signal(self, SIGNAL_TARGET_PRESENCE); + g_hash_table_iter_init(&it, priv->tag_table); + while (g_hash_table_iter_next(&it, NULL, &value) && !present) { + present = ((NfcAdapterTagEntry*)value)->tag->present; } + + if (!present) { + g_hash_table_iter_init(&it, priv->peer_table); + while (g_hash_table_iter_next(&it, NULL, &value) && !present) { + present = ((NfcAdapterPeerEntry*)value)->peer->present; + } + } + + nfc_adapter_set_presence(self, present); } static @@ -256,6 +327,25 @@ nfc_adapter_update_mode( } } +static +char* +nfc_adapter_make_name( + GHashTable* table, + const char* format, + guint* next_index) +{ + char* name = g_strdup_printf(format, *next_index); + + (*next_index)++; + while (g_hash_table_contains(table, name)) { + /* This is rather unlikely... */ + g_free(name); + name = g_strdup_printf(format, *next_index); + (*next_index)++; + } + return name; +} + static void nfc_adapter_tag_free( @@ -287,26 +377,17 @@ nfc_adapter_add_tag( if (tag && tag->present) { NfcAdapterPriv* priv = self->priv; NfcAdapterTagEntry* entry = g_slice_new(NfcAdapterTagEntry); - char* name = g_strdup_printf(NFC_TAG_NAME_FORMAT, priv->next_tag_index); - - priv->next_tag_index++; - while (g_hash_table_contains(priv->tags, name)) { - /* This is rather unlikely... */ - g_free(name); - name = g_strdup_printf(NFC_TAG_NAME_FORMAT, priv->next_tag_index); - priv->next_tag_index++; - } + char* name = nfc_adapter_make_name(priv->tag_table, + NFC_TAG_NAME_FORMAT, &priv->next_tag_index); GASSERT(!tag->name); nfc_tag_set_name(tag, name); entry->tag = tag; entry->gone_id = nfc_tag_add_gone_handler(tag, nfc_adapter_tag_gone, self); - g_hash_table_insert(priv->tags, name, entry); - g_free(self->tags); - self->tags = nfc_adapter_tags(priv); - - nfc_adapter_update_target_presence(self); + g_hash_table_insert(priv->tag_table, name, entry); + nfc_adapter_update_tags(self); + nfc_adapter_update_presence(self); nfc_adapter_emit_pending_signals(self); g_signal_emit(self, nfc_adapter_signals[SIGNAL_TAG_ADDED], 0, tag); return tag; @@ -316,6 +397,97 @@ nfc_adapter_add_tag( } } +static +void +nfc_adapter_peer_free( + gpointer data) +{ + NfcAdapterPeerEntry* entry = data; + + nfc_peer_remove_handler(entry->peer, entry->gone_id); + nfc_peer_unref(entry->peer); + g_slice_free(NfcAdapterPeerEntry, entry); +} + +static +void +nfc_adapter_peer_gone( + NfcPeer* peer, + void* adapter) +{ + nfc_adapter_remove_peer(NFC_ADAPTER(adapter), peer->name); +} + +static +NfcPeer* +nfc_adapter_add_peer( + NfcAdapter* self, + NfcPeer* peer) +{ + /* This function takes ownership of the peer */ + if (peer && peer->present) { + NfcAdapterPriv* priv = self->priv; + NfcAdapterPeerEntry* entry = g_slice_new(NfcAdapterPeerEntry); + char* name = nfc_adapter_make_name(priv->peer_table, + NFC_PEER_NAME_FORMAT, &priv->next_peer_index); + + GASSERT(!peer->name); + nfc_peer_set_name(peer, name); + entry->peer = peer; + entry->gone_id = nfc_peer_add_gone_handler(peer, nfc_adapter_peer_gone, + self); + g_hash_table_insert(priv->peer_table, name, entry); + nfc_adapter_update_peers(priv); + nfc_adapter_update_presence(self); + nfc_adapter_emit_pending_signals(self); + g_signal_emit(self, nfc_adapter_signals[SIGNAL_PEER_ADDED], 0, peer); + return peer; + } else { + nfc_peer_unref(peer); + return NULL; + } +} + +static +NfcPeer* +nfc_adapter_add_peer_initiator( + NfcAdapter* self, + NfcTarget* target, + NFC_TECHNOLOGY technology, + const NfcParamNfcDepInitiator* param) +{ + if (G_LIKELY(self) && G_LIKELY(target) && G_LIKELY(param)) { + NfcAdapterPriv* priv = self->priv; + NfcPeer* peer = nfc_peer_new_initiator(target, technology, param, + priv->services); + + if (peer) { + return nfc_adapter_add_peer(self, peer); + } + } + return NULL; +} + +static +NfcPeer* +nfc_adapter_add_peer_target( + NfcAdapter* self, + NfcInitiator* initiator, + NFC_TECHNOLOGY technology, + const NfcParamNfcDepTarget* param) +{ + if (G_LIKELY(self) && G_LIKELY(initiator) && G_LIKELY(param)) { + NfcAdapterPriv* priv = self->priv; + NfcPeer* peer = nfc_peer_new_target(initiator, technology, param, + priv->services); + + if (peer) { + return nfc_adapter_add_peer(self, peer); + } + } + return NULL; +} + /*==========================================================================* * Interface *==========================================================================*/ @@ -391,6 +563,13 @@ nfc_adapter_request_mode( return ok; } +NfcPeer** +nfc_adapter_peers( + NfcAdapter* self) /* Since 1.1.0 */ +{ + return G_LIKELY(self) ? self->priv->peers : NULL; +} + NfcTag* nfc_adapter_add_tag_t2( NfcAdapter* self, @@ -465,6 +644,50 @@ nfc_adapter_add_other_tag2( return NULL; } +NfcPeer* +nfc_adapter_add_peer_initiator_a( + NfcAdapter* self, + NfcTarget* target, + const NfcParamPollA* poll_a, + const NfcParamNfcDepInitiator* param) /* Since 1.1.0 */ +{ + return nfc_adapter_add_peer_initiator(self, target, + NFC_TECHNOLOGY_A, param); +} + +NfcPeer* +nfc_adapter_add_peer_initiator_f( + NfcAdapter* self, + NfcTarget* target, + const NfcParamPollF* poll_f, + const NfcParamNfcDepInitiator* param) /* Since 1.1.0 */ +{ + return nfc_adapter_add_peer_initiator(self, target, + NFC_TECHNOLOGY_F, param); +} + +NfcPeer* +nfc_adapter_add_peer_target_a( + NfcAdapter* self, + NfcInitiator* initiator, + const NfcParamListenA* listen_a, + const NfcParamNfcDepTarget* param) /* Since 1.1.0 */ +{ + return nfc_adapter_add_peer_target(self, initiator, + NFC_TECHNOLOGY_A, param); +} + +NfcPeer* +nfc_adapter_add_peer_target_f( + NfcAdapter* self, + NfcInitiator* initiator, + const NfcParamListenF* listen_f, + const NfcParamNfcDepTarget* param) /* Since 1.1.0 */ +{ + return nfc_adapter_add_peer_target(self, initiator, + NFC_TECHNOLOGY_F, param); +} + void nfc_adapter_remove_tag( NfcAdapter* self, @@ -472,16 +695,14 @@ nfc_adapter_remove_tag( { if (G_LIKELY(self) && G_LIKELY(name)) { NfcAdapterPriv* priv = self->priv; - NfcAdapterTagEntry* entry = g_hash_table_lookup(priv->tags, name); + NfcAdapterTagEntry* entry = g_hash_table_lookup(priv->tag_table, name); if (entry) { NfcTag* tag = nfc_tag_ref(entry->tag); - g_hash_table_remove(priv->tags, name); - g_free(self->tags); - self->tags = nfc_adapter_tags(priv); - - nfc_adapter_update_target_presence(self); + g_hash_table_remove(priv->tag_table, name); + nfc_adapter_update_tags(self); + nfc_adapter_update_presence(self); g_signal_emit(self, nfc_adapter_signals [SIGNAL_TAG_REMOVED], 0, tag); nfc_adapter_emit_pending_signals(self); @@ -490,6 +711,30 @@ nfc_adapter_remove_tag( } } +void +nfc_adapter_remove_peer( + NfcAdapter* self, + const char* name) +{ + if (G_LIKELY(self) && G_LIKELY(name)) { + NfcAdapterPriv* priv = self->priv; + NfcAdapterPeerEntry* entry = + g_hash_table_lookup(priv->peer_table, name); + + if (entry) { + NfcPeer* peer = nfc_peer_ref(entry->peer); + + g_hash_table_remove(priv->peer_table, name); + nfc_adapter_update_peers(priv); + nfc_adapter_update_presence(self); + g_signal_emit(self, nfc_adapter_signals + [SIGNAL_PEER_REMOVED], 0, peer); + nfc_adapter_emit_pending_signals(self); + nfc_peer_unref(peer); + } + } +} + gulong nfc_adapter_add_target_presence_handler( NfcAdapter* self, @@ -520,6 +765,26 @@ nfc_adapter_add_tag_removed_handler( SIGNAL_TAG_REMOVED_NAME, G_CALLBACK(func), user_data) : 0; } +gulong +nfc_adapter_add_peer_added_handler( + NfcAdapter* self, + NfcAdapterPeerFunc func, + void* user_data) /* Since 1.1.0 */ +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_PEER_ADDED_NAME, G_CALLBACK(func), user_data) : 0; +} + +gulong +nfc_adapter_add_peer_removed_handler( + NfcAdapter* self, + NfcAdapterPeerFunc func, + void* user_data) /* Since 1.1.0 */ +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_PEER_REMOVED_NAME, G_CALLBACK(func), user_data) : 0; +} + gulong nfc_adapter_add_powered_changed_handler( NfcAdapter* self, @@ -606,6 +871,19 @@ nfc_adapter_set_name( } } +void +nfc_adapter_set_services( + NfcAdapter* self, + NfcPeerServices* services) +{ + if (G_LIKELY(self)) { + NfcAdapterPriv* priv = self->priv; + + nfc_peer_services_unref(priv->services); + priv->services = nfc_peer_services_ref(services); + } +} + void nfc_adapter_mode_notify( NfcAdapter* self, @@ -666,13 +944,10 @@ nfc_adapter_power_notify( void nfc_adapter_target_notify( NfcAdapter* self, - gboolean present) + gboolean present /* ignored */) { if (G_LIKELY(self)) { - NfcAdapterPriv* priv = self->priv; - - priv->target_presence_notified = present; - nfc_adapter_update_target_presence(self); + nfc_adapter_update_presence(self); nfc_adapter_emit_pending_signals(self); } } @@ -716,8 +991,11 @@ nfc_adapter_init( self->priv = priv; self->tags = g_new0(NfcTag*, 1); - priv->tags = g_hash_table_new_full(g_str_hash, g_str_equal, + priv->peers = g_new0(NfcPeer*, 1); + priv->tag_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nfc_adapter_tag_free); + priv->peer_table = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, nfc_adapter_peer_free); } static @@ -737,7 +1015,7 @@ nfc_adapter_dispose( priv->power_pending = FALSE; c->cancel_power_request(self); } - g_hash_table_remove_all(priv->tags); + g_hash_table_remove_all(priv->tag_table); G_OBJECT_CLASS(nfc_adapter_parent_class)->dispose(object); } @@ -749,7 +1027,10 @@ nfc_adapter_finalize( NfcAdapter* self = NFC_ADAPTER(object); NfcAdapterPriv* priv = self->priv; - g_hash_table_destroy(priv->tags); + nfc_peer_services_unref(priv->services); + g_hash_table_destroy(priv->tag_table); + g_hash_table_destroy(priv->peer_table); + g_free(priv->peers); g_free(self->tags); g_free(priv->name); G_OBJECT_CLASS(nfc_adapter_parent_class)->finalize(object); @@ -760,6 +1041,7 @@ void nfc_adapter_class_init( NfcAdapterClass* klass) { + GType type = G_OBJECT_CLASS_TYPE(klass); GObjectClass* object_class = G_OBJECT_CLASS(klass); g_type_class_add_private(klass, sizeof(NfcAdapterPriv)); @@ -771,19 +1053,27 @@ nfc_adapter_class_init( klass->cancel_mode_request = nfc_adapter_cancel_request; nfc_adapter_signals[SIGNAL_TAG_ADDED] = - g_signal_new(SIGNAL_TAG_ADDED_NAME, G_OBJECT_CLASS_TYPE(klass), + g_signal_new(SIGNAL_TAG_ADDED_NAME, type, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); nfc_adapter_signals[SIGNAL_TAG_REMOVED] = - g_signal_new(SIGNAL_TAG_REMOVED_NAME, G_OBJECT_CLASS_TYPE(klass), + g_signal_new(SIGNAL_TAG_REMOVED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, + G_TYPE_OBJECT); + nfc_adapter_signals[SIGNAL_PEER_ADDED] = + g_signal_new(SIGNAL_PEER_ADDED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, + G_TYPE_OBJECT); + nfc_adapter_signals[SIGNAL_PEER_REMOVED] = + g_signal_new(SIGNAL_PEER_REMOVED_NAME, type, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); - NEW_SIGNAL(ENABLED_CHANGED); - NEW_SIGNAL(POWERED); - NEW_SIGNAL(POWER_REQUESTED); - NEW_SIGNAL(MODE); - NEW_SIGNAL(MODE_REQUESTED); - NEW_SIGNAL(TARGET_PRESENCE); + NEW_SIGNAL(ENABLED_CHANGED, type); + NEW_SIGNAL(POWERED, type); + NEW_SIGNAL(POWER_REQUESTED, type); + NEW_SIGNAL(MODE, type); + NEW_SIGNAL(MODE_REQUESTED, type); + NEW_SIGNAL(TARGET_PRESENCE, type); } /* diff --git a/core/src/nfc_adapter_p.h b/core/src/nfc_adapter_p.h index ad86c14..406a7df 100644 --- a/core/src/nfc_adapter_p.h +++ b/core/src/nfc_adapter_p.h @@ -48,6 +48,12 @@ nfc_adapter_set_name( const char* name) NFCD_INTERNAL; +void +nfc_adapter_set_services( + NfcAdapter* adapter, + NfcPeerServices* services) + NFCD_INTERNAL; + #endif /* NFC_ADAPTER_PRIVATE_H */ /* diff --git a/core/src/nfc_initiator.c b/core/src/nfc_initiator.c new file mode 100644 index 0000000..af1a3dc --- /dev/null +++ b/core/src/nfc_initiator.c @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include "nfc_initiator_p.h" +#include "nfc_initiator_impl.h" +#include "nfc_log.h" + +#include + +struct nfc_initiator_priv { + NfcTransmission* current; /* Pointer */ + NfcTransmission* next; /* Reference */ + GBytes* next_data; + gboolean deactivated; +}; + +#define THIS(obj) NFC_INITIATOR(obj) +#define THIS_TYPE NFC_TYPE_INITIATOR +#define PARENT_CLASS (nfc_initiator_parent_class) +G_DEFINE_ABSTRACT_TYPE(NfcInitiator, nfc_initiator, G_TYPE_OBJECT) +#define NFC_INITIATOR_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), \ + THIS_TYPE, NfcInitiatorClass) + +enum nfc_initiator_signal { + SIGNAL_TRANSMISSION, + SIGNAL_GONE, + SIGNAL_COUNT +}; + +#define SIGNAL_TRANSMISSION_NAME "nfc-initiator-transmission" +#define SIGNAL_GONE_NAME "nfc-initiator-gone" + +static guint nfc_initiator_signals[SIGNAL_COUNT] = { 0 }; + +struct nfc_transmission { + NfcInitiator* owner; + NfcTransmissionDoneFunc done; + void* user_data; + gboolean responded; + gint ref_count; +}; + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +inline +gboolean +nfc_initiator_can_deactivate( + NfcInitiator* self) +{ + return self->present && !self->priv->deactivated; +} + +static +void +nfc_initiator_do_deactivate( + NfcInitiator* self) +{ + /* Caller has checked nfc_initiator_can_deactivate() */ + self->priv->deactivated = TRUE; + NFC_INITIATOR_GET_CLASS(self)->deactivate(self); +} + +/*==========================================================================* + * Transmission API + *==========================================================================*/ + +static +NfcTransmission* +nfc_transmission_new( + NfcInitiator* initiator) +{ + NfcTransmission* self = g_slice_new0(NfcTransmission); + + self->owner = initiator; + g_atomic_int_set(&self->ref_count, 1); + return self; +} + +static +void +nfc_transmission_free( + NfcTransmission* self) +{ + NfcInitiator* owner = self->owner; + + if (owner) { + NfcInitiatorPriv* priv = owner->priv; + + /* + * Clear the pointer. Note that priv->next is an internal reference + * meaning that NfcTransmission pointed to by priv->next can't get + * here before pointer is cleared and reference is released. + * Therefore, there's no need to check priv->next. + */ + if (priv->current == self) { + priv->current = NULL; + if (!self->responded && nfc_initiator_can_deactivate(owner)) { + /* Transmission was dropped without responding */ + GDEBUG("Transmission dropped, deactivating"); + nfc_initiator_do_deactivate(owner); + } + } + } + g_slice_free1(sizeof(*self), self); +} + +NfcTransmission* +nfc_transmission_ref( + NfcTransmission* self) +{ + if (G_LIKELY(self)) { + g_atomic_int_inc(&self->ref_count); + } + return self; +} + +void +nfc_transmission_unref( + NfcTransmission* self) +{ + if (G_LIKELY(self)) { + if (g_atomic_int_dec_and_test(&self->ref_count)) { + nfc_transmission_free(self); + } + } +} + +gboolean +nfc_transmission_respond( + NfcTransmission* self, + const void* data, + guint len, + NfcTransmissionDoneFunc done, + void* user_data) +{ + if (G_LIKELY(self && !self->responded)) { + NfcInitiator* owner = self->owner; + + self->responded = TRUE; + if (owner) { + self->done = done; + self->user_data = user_data; + nfc_transmission_ref(self); + if (NFC_INITIATOR_GET_CLASS(owner)->respond(owner, data, len)) { + nfc_transmission_unref(self); + return TRUE; + } + self->done = NULL; + nfc_transmission_unref(self); + } + } + return FALSE; +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcInitiator* +nfc_initiator_ref( + NfcInitiator* self) +{ + if (G_LIKELY(self)) { + g_object_ref(THIS(self)); + } + return self; +} + +void +nfc_initiator_unref( + NfcInitiator* self) +{ + if (G_LIKELY(self)) { + g_object_unref(THIS(self)); + } +} + +/*==========================================================================* + * Internal interface + *==========================================================================*/ + +void +nfc_initiator_deactivate( + NfcInitiator* self) +{ + if (G_LIKELY(self) && nfc_initiator_can_deactivate(self)) { + nfc_initiator_do_deactivate(self); + } +} + +gulong +nfc_initiator_add_transmission_handler( + NfcInitiator* self, + NfcTransmissionHandlerFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_TRANSMISSION_NAME, G_CALLBACK(func), user_data) : 0; +} + +gulong +nfc_initiator_add_gone_handler( + NfcInitiator* self, + NfcInitiatorFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_GONE_NAME, G_CALLBACK(func), user_data) : 0; +} + +void +nfc_initiator_remove_handler( + NfcInitiator* self, + gulong id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +void +nfc_initiator_remove_handlers( + NfcInitiator* self, + gulong* ids, + guint count) +{ + gutil_disconnect_handlers(self, ids, count); +} + +/*==========================================================================* + * Internal interface + *==========================================================================*/ + +void +nfc_initiator_transmit( + NfcInitiator* self, + const void* bytes, + guint size) +{ + if (G_LIKELY(self)) { + NfcInitiatorPriv* priv = self->priv; + + if (priv->current) { + /* + * This can only legitimately happen if we have already responded + * to the current transmission, but haven't yet got a confirmation + * that our response has been sent. + */ + if (!priv->current->responded || priv->next) { + /* + * Stray transmission, should we ignore it? Let's see if + * that ever happens in real life. For now, treat it as an + * unrecoverable error and deactivate the RF interface. + */ + if (nfc_initiator_can_deactivate(self)) { + GDEBUG("Unexpected transmission, deactivating"); + nfc_initiator_do_deactivate(self); + } + } else { + /* + * Let's queue it until the current response has been sent. + * There may be some room for optimization here (we could + * start processing the next transmission before the previous + * one has fully completed). + */ + priv->next = nfc_transmission_new(self); + priv->next_data = g_bytes_new(bytes, size); /* Copy the data */ + } + } else { + gboolean handled = FALSE; + GUtilData data; + + /* Fresh new transmission coming in, notify the handler */ + priv->current = nfc_transmission_new(self); + data.bytes = bytes; + data.size = size; + g_signal_emit(self, nfc_initiator_signals[SIGNAL_TRANSMISSION], 0, + priv->current, &data, &handled); + if (!handled && nfc_initiator_can_deactivate(self)) { + /* Signal wasn't handled, drop the link */ + GDEBUG("Incoming transmission not handled, deactivating"); + nfc_initiator_do_deactivate(self); + } + + /* + * If the handler doesn't reference it, this will deallocate the + * transmission and wipe the pointer. + */ + nfc_transmission_unref(priv->current); + } + } +} + +void +nfc_initiator_response_sent( + NfcInitiator* self, + NFC_TRANSMIT_STATUS status) +{ + if (G_LIKELY(self)) { + NfcInitiatorPriv* priv = self->priv; + NfcTransmission* t = priv->current; + + GASSERT(t); + if (t) { + NfcTransmissionDoneFunc done = t->done; + NfcTransmission* next = priv->next; /* This was a reference */ + GBytes* bytes = priv->next_data; + + /* Make sure completion callback is not invoked twice */ + t->done = NULL; + + /* The next transmission (if any) becomes the current one */ + priv->current = next; + priv->next_data= NULL; + priv->next = NULL; + if (done) { + /* Let the handler know that response has been sent */ + done(t, status == NFC_TRANSMIT_STATUS_OK, t->user_data); + } + if (next) { + gboolean handled = FALSE; + GUtilData data; + + data.bytes = g_bytes_get_data(bytes, &data.size); + g_signal_emit(self, nfc_initiator_signals + [SIGNAL_TRANSMISSION], 0, next, &data, &handled); + if (!handled && nfc_initiator_can_deactivate(self)) { + /* Signal wasn't handled, drop the link */ + GDEBUG("Incoming transmission not handled, deactivating"); + nfc_initiator_do_deactivate(self); + } + /* Release our reference */ + nfc_transmission_unref(next); + } + if (bytes) { + g_bytes_unref(bytes); + } + } + } +} + +void +nfc_initiator_gone( + NfcInitiator* self) +{ + if (G_LIKELY(self) && self->present) { + self->present = FALSE; + NFC_INITIATOR_GET_CLASS(self)->gone(self); + } +} + +/*==========================================================================* + * Methods + *==========================================================================*/ + +static +gboolean +nfc_initiator_default_respond( + NfcInitiator* self, + const void* data, + guint len) +{ + return FALSE; +} + +static +void +nfc_initiator_nop( + NfcInitiator* self) +{ +} + +static +void +nfc_initiator_default_gone( + NfcInitiator* self) +{ + g_signal_emit(self, nfc_initiator_signals[SIGNAL_GONE], 0); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_initiator_init( + NfcInitiator* self) +{ + NfcInitiatorPriv* priv = G_TYPE_INSTANCE_GET_PRIVATE(self, THIS_TYPE, + NfcInitiatorPriv); + + /* When initiator is created, it must be present, right? */ + self->present = TRUE; + self->priv = priv; +} + +static +void +nfc_initiator_finalize( + GObject* object) +{ + NfcInitiator* self = THIS(object); + NfcInitiatorPriv* priv = self->priv; + + if (priv->next) { + priv->next->owner = NULL; + /* This is a reference */ + nfc_transmission_unref(priv->next); + } + if (priv->current) { + priv->current->owner = NULL; + /* And this is just a pointer */ + } + if (priv->next_data) { + g_bytes_unref(priv->next_data); + } + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +nfc_initiator_class_init( + NfcInitiatorClass* klass) +{ + GType type = G_OBJECT_CLASS_TYPE(klass); + + g_type_class_add_private(klass, sizeof(NfcInitiatorPriv)); + klass->respond = nfc_initiator_default_respond; + klass->deactivate = nfc_initiator_nop; + klass->gone = nfc_initiator_default_gone; + G_OBJECT_CLASS(klass)->finalize = nfc_initiator_finalize; + nfc_initiator_signals[SIGNAL_TRANSMISSION] = + g_signal_new(SIGNAL_TRANSMISSION_NAME, type, G_SIGNAL_RUN_LAST, 0, + g_signal_accumulator_true_handled, NULL, NULL, + G_TYPE_BOOLEAN, 2, G_TYPE_POINTER, G_TYPE_POINTER); + nfc_initiator_signals[SIGNAL_GONE] = + g_signal_new(SIGNAL_GONE_NAME, type, G_SIGNAL_RUN_FIRST, 0, + NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_initiator_p.h b/core/src/nfc_initiator_p.h new file mode 100644 index 0000000..ccb2199 --- /dev/null +++ b/core/src/nfc_initiator_p.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_INITIATOR_PRIVATE_H +#define NFC_INITIATOR_PRIVATE_H + +#include "nfc_types_p.h" + +#include + +typedef struct nfc_transmission NfcTransmission; + +typedef +void +(*NfcInitiatorFunc)( + NfcInitiator* initiator, + void* user_data); + +typedef +gboolean +(*NfcTransmissionHandlerFunc)( + NfcInitiator* initiator, + NfcTransmission* transmission, + const GUtilData* data, + void* user_data); + +typedef +void +(*NfcTransmissionDoneFunc)( + NfcTransmission* transmission, + gboolean ok, + void* user_data); + +void +nfc_initiator_deactivate( + NfcInitiator* initiator) + NFCD_INTERNAL; + +gulong +nfc_initiator_add_transmission_handler( + NfcInitiator* initiator, + NfcTransmissionHandlerFunc func, + void* user_data) + NFCD_INTERNAL; + +gulong +nfc_initiator_add_gone_handler( + NfcInitiator* initiator, + NfcInitiatorFunc func, + void* user_data) + NFCD_INTERNAL; + +void +nfc_initiator_remove_handler( + NfcInitiator* initiator, + gulong id) + NFCD_INTERNAL; + +void +nfc_initiator_remove_handlers( + NfcInitiator* initiator, + gulong* ids, + guint count) + NFCD_INTERNAL; + +#define nfc_initiator_remove_all_handlers(initiator,ids) \ + nfc_initiator_remove_handlers(initiator, ids, G_N_ELEMENTS(ids)) + +/* + * Incoming transmission API + */ + +NfcTransmission* +nfc_transmission_ref( + NfcTransmission* transmission) + NFCD_INTERNAL; + +void +nfc_transmission_unref( + NfcTransmission* transmission) + NFCD_INTERNAL; + +gboolean +nfc_transmission_respond( + NfcTransmission* transmission, + const void* data, + guint len, + NfcTransmissionDoneFunc done, + void* user_data) + NFCD_INTERNAL; + +#endif /* NFC_INITIATOR_PRIVATE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_llc.c b/core/src/nfc_llc.c new file mode 100644 index 0000000..f66a447 --- /dev/null +++ b/core/src/nfc_llc.c @@ -0,0 +1,1854 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_llc.h" +#include "nfc_llc_io.h" +#include "nfc_llc_param.h" +#include "nfc_peer_connection_p.h" +#include "nfc_peer_service_p.h" +#include "nfc_peer_services.h" + +#define GLOG_MODULE_NAME NFC_LLC_LOG_MODULE +#include +#include +#include +#include + +#include +#include + +GLOG_MODULE_DEFINE2("llc", NFC_CORE_LOG_MODULE); + +typedef enum llcp_ptype { + LLCP_PTYPE_SYMM = 0x00, + LLCP_PTYPE_PAX = 0x01, + LLCP_PTYPE_AGF = 0x02, + LLCP_PTYPE_UI = 0x03, + LLCP_PTYPE_CONNECT = 0x04, + LLCP_PTYPE_DISC = 0x05, + LLCP_PTYPE_CC = 0x06, + LLCP_PTYPE_DM = 0x07, + LLCP_PTYPE_FRMR = 0x08, + LLCP_PTYPE_SNL = 0x09, /* LLCP 1.1 */ + /* Reserved 0x0a*/ + /* Reserved 0x0b*/ + LLCP_PTYPE_I = 0x0c, + LLCP_PTYPE_RR = 0x0d, + LLCP_PTYPE_RNR = 0x0e +} LLCP_PTYPE; + +typedef enum llc_frmr_flags { + NFC_LLC_FRMR_S = 0x01, + NFC_LLC_FRMR_R = 0x02, + NFC_LLC_FRMR_I = 0x04, + NFC_LLC_FRMR_W = 0x08 +} NFC_LLC_FRMR_FLAGS; + +#define LLCP_MAKE_HDR(dsap,ptype,ssap) \ + (((((guint16)(dsap)) & 0x3f) << 10) | \ + (((guint16)(ptype)) << 6) /* Assuming PTYPE is within range */| \ + (((guint16)(ssap)) & 0x3f)) +#define LLCP_GET_DSAP(hdr) ((guint8)((hdr) >> 10)) +#define LLCP_GET_PTYPE(hrd) ((LLCP_PTYPE)(((hdr) >> 6) & 0x0f)) +#define LLCP_GET_SSAP(hdr) ((guint8)((hdr) & 0x3f)) + +enum nfc_llc_io_events { + LLC_IO_EVENT_CAN_SEND, + LLC_IO_EVENT_RECEIVE, + LLC_IO_EVENT_ERROR, + LLC_IO_EVENT_COUNT +}; + +typedef struct nfc_llc_connect_req { + NfcPeerConnection* connection; + NfcLlcConnectFunc complete; + GDestroyNotify destroy; + void* user_data; +} NfcLlcConnectReq; + +typedef struct nfc_llc_object { + GObject object; + NfcLlc pub; + NfcLlcIo* io; + gulong io_event[LLC_IO_EVENT_COUNT]; + GUtilIdlePool* pool; + NfcPeerServices* services; + guint8 version; + guint miu; + guint lto; + guint packets_handled; + GList* pdu_queue; + GSList* connect_queue; + GHashTable* conn_table; +} NfcLlcObject; + +typedef GObjectClass NfcLlcObjectClass; +G_DEFINE_TYPE(NfcLlcObject, nfc_llc_object, G_TYPE_OBJECT) +#define NFC_TYPE_LLC (nfc_llc_object_get_type()) +#define NFC_LLC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + NFC_TYPE_LLC, NfcLlcObject)) + +typedef enum nfc_llc_signal { + SIGNAL_STATE_CHANGED, + SIGNAL_IDLE_CHANGED, + SIGNAL_WKS_CHANGED, + SIGNAL_COUNT +} NFC_LLC_SIGNAL; + +typedef struct nfc_llc_closure { + GCClosure cclosure; + NfcLlcFunc func; + void* user_data; +} NfcLlcClosure; + +#define nfc_llc_closure_new() ((NfcLlcClosure*) \ + g_closure_new_simple(sizeof(NfcLlcClosure), NULL)) + +#define SIGNAL_STATE_CHANGED_NAME "nfc-llc-state-changed" +#define SIGNAL_IDLE_CHANGED_NAME "nfc-llc-idle-changed" +#define SIGNAL_WKS_CHANGED_NAME "nfc-llc-wks-changed" + +static guint nfc_llc_signals[SIGNAL_COUNT] = { 0 }; + +static +void +nfc_llc_send_next_pdu( + NfcLlcObject* self); + +static +gboolean +nfc_llc_handle_pdu( + NfcLlcObject* self, + const void* data, + gsize len); + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +inline +NfcLlcObject* +nfc_llc_object_cast( + NfcLlc* llc) +{ + return llc ? NFC_LLC(G_CAST(llc,NfcLlcObject,pub)) : NULL; +} + +static +void +nfc_llc_closure_callback( + NfcLlcObject* self, + NfcLlcClosure* closure) +{ + closure->func(&self->pub, closure->user_data); +} + +static +gulong +nfc_llc_add_handler( + NfcLlc* llc, + NFC_LLC_SIGNAL signal, + NfcLlcFunc func, + void* user_data) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + if (G_LIKELY(self) && G_LIKELY(func)) { + NfcLlcClosure* closure = nfc_llc_closure_new(); + GCClosure* cc = &closure->cclosure; + + cc->closure.data = closure; + cc->callback = G_CALLBACK(nfc_llc_closure_callback); + closure->func = func; + closure->user_data = user_data; + return g_signal_connect_closure_by_id(self, nfc_llc_signals + [signal], 0, &cc->closure, FALSE); + } + return 0; +} + +static +void +nfc_llc_connection_destroy( + gpointer user_data) +{ + NfcPeerConnection* conn = NFC_PEER_CONNECTION(user_data); + + nfc_peer_connection_set_llc(conn, NULL); + nfc_peer_connection_unref(conn); +} + +static +void +nfc_llc_abort_all_connections( + NfcLlcObject* self) +{ + GHashTableIter it; + GSList* conns = NULL; + GSList* l; + gpointer value; + + g_hash_table_iter_init(&it, self->conn_table); + while (g_hash_table_iter_next(&it, NULL, &value)) { + conns = g_slist_append(conns, nfc_peer_connection_ref(value)); + } + for (l = conns; l; l = l->next) { + nfc_peer_connection_set_state(NFC_PEER_CONNECTION(l->data), + NFC_LLC_CO_DEAD); + } + g_slist_free_full(conns, g_object_unref); +} + +#if GUTIL_LOG_DEBUG +static +const char* +nfc_llc_state_name( + NfcLlcObject* self, + NFC_LLC_STATE state) +{ + char* tmp; + + switch (state) { + case NFC_LLC_STATE_START: return "START"; + case NFC_LLC_STATE_ACTIVE: return "ACTIVE"; + case NFC_LLC_STATE_ERROR: return "ERROR"; + case NFC_LLC_STATE_PEER_LOST: return "PEER_LOST"; + } + tmp = g_strdup_printf("%d (?)", state); + gutil_idle_pool_add(self->pool, tmp, g_free); + return tmp; +} +#endif /* GUTIL_LOG_DEBUG */ + +static +NfcLlcConnectReq* +nfc_llc_connect_req_new( + NfcLlcObject* self, + NfcPeerService* service, + guint8 rsap, + const char* rname, + NfcLlcConnectFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + NfcPeerConnection* pc = nfc_peer_service_new_connect(service, rsap, rname); + + if (pc) { + NfcLlcConnectReq* req = g_slice_new(NfcLlcConnectReq); + + nfc_peer_connection_set_llc(pc, &self->pub); + req->connection = pc; + req->complete = complete; + req->destroy = destroy; + req->user_data = user_data; + return req; + } + return NULL; +} + +static +void +nfc_llc_connect_req_free( + NfcLlcConnectReq* req) +{ + if (req->destroy) { + req->destroy(req->user_data); + } + nfc_peer_connection_unref(req->connection); + g_slice_free1(sizeof(*req), req); +} + +static +GBytes* +nfc_llc_dequeue_pdu( + NfcLlcObject* self) +{ + GList* first = g_list_first(self->pdu_queue); + + if (first) { + GBytes* bytes = first->data; + + self->pdu_queue = g_list_delete_link(self->pdu_queue, first); + return bytes; + } + return NULL; +} + +static +guint +nfc_llc_apply_params( + NfcLlcObject* self, + const NfcLlcParam* const* params) +{ + guint mask = 0; + + if (params) { + NfcLlc* llc = &self->pub; + const NfcLlcParam* const* ptr = params; + + while (*ptr) { + const NfcLlcParam* param = *ptr++; + const NfcLlcParamValue* value = ¶m->value; + + switch (param->type) { + case NFC_LLC_PARAM_VERSION: + if (self->version != value->version) { + self->version = value->version; + mask |= (1 << param->type); + } + GDEBUG(" Version: %u.%u", self->version >> 4, + self->version & 0x0f); + break; + case NFC_LLC_PARAM_MIUX: + if (self->miu != value->miu) { + self->miu = value->miu; + mask |= (1 << param->type); + } + GDEBUG(" MIU: %u bytes", self->miu); + break; + case NFC_LLC_PARAM_WKS: + if (llc->wks != value->wks) { + llc->wks = value->wks; + mask |= (1 << param->type); + } + GDEBUG(" WKS: 0x%04x", llc->wks); + break; + case NFC_LLC_PARAM_LTO: + if (self->lto != value->lto) { + self->lto = value->lto; + mask |= (1 << param->type); + } + GDEBUG(" Link Timeout: %u ms", self->lto); + break; + default: + break; + } + } + } + return mask; +} + +static +GByteArray* +nfc_llc_pdu_new( + guint8 dsap, + LLCP_PTYPE ptype, + guint8 ssap) +{ + const guint hdr = LLCP_MAKE_HDR(dsap, ptype, ssap); + GByteArray* pdu = g_byte_array_new(); + + g_byte_array_set_size(pdu, 2); + pdu->data[0] = (guint8)(hdr >> 8); + pdu->data[1] = (guint8)hdr; + return pdu; +} + +static +void +nfc_llc_submit( + NfcLlcObject* self, + GBytes* pdu) +{ + self->pdu_queue = g_list_append(self->pdu_queue, g_bytes_ref(pdu)); + if (self->io->can_send) { + nfc_llc_send_next_pdu(self); + } +} + +static +void +nfc_llc_submit_frmr( + NfcLlcObject* self, + const guint8 dsap, + const guint8 ssap, + NFC_LLC_FRMR_FLAGS flags, + LLCP_PTYPE ptype, + guint8 seq, + NfcPeerConnection* conn) +{ + const guint hdr = LLCP_MAKE_HDR(dsap, LLCP_PTYPE_FRMR, ssap); + const guint size = 6; + guint8* pkt = g_malloc(size); + GBytes* pdu = g_bytes_new_take(pkt, size); + + pkt[0] = (guint8)(hdr >> 8); + pkt[1] = (guint8)hdr; + pkt[2] = (guint8)((flags << 4) | ptype); + pkt[3] = seq; + if (conn) { + const NfcPeerConnectionLlcpState* ps = nfc_peer_connection_ps(conn); + + pkt[4] = (guint8)((ps->vs << 4) | ps->vr); + pkt[5] = (guint8)((ps->vsa << 4) | ps->vra); + } else { + pkt[4] = pkt[5] = 0; + } + nfc_llc_submit(self, pdu); + g_bytes_unref(pdu); +} + +static +inline +void +nfc_llc_submit_frmr_i( + NfcLlcObject* self, + const guint8 dsap, + const guint8 ssap, + guint8 ptype) +{ + nfc_llc_submit_frmr(self, dsap, ssap, NFC_LLC_FRMR_I, ptype, 0, NULL); +} + +static +void +nfc_llc_submit_connect( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap, + const NfcLlcParam* const* params) +{ + GByteArray* bytes = nfc_llc_pdu_new(dsap, LLCP_PTYPE_CONNECT, ssap); + GBytes* pdu; + + nfc_llc_param_encode(params, bytes, self->miu); + pdu = g_byte_array_free_to_bytes(bytes); + nfc_llc_submit(self, pdu); + g_bytes_unref(pdu); +} + +static +void +nfc_llc_submit_next_connect( + NfcLlcObject* self) +{ + if (self->connect_queue) { + NfcLlcConnectReq* req = self->connect_queue->data; + NfcPeerConnection* conn = req->connection; + NfcPeerService* service = conn->service; + const NfcLlcParam* const* lp = nfc_peer_connection_lp(conn); + const guint8 lsap = service->sap; + + if (conn->name) { + NfcLlcParam sn; + const guint n = nfc_llc_param_count(lp); + const NfcLlcParam** params = g_new(const NfcLlcParam*, n + 2); + guint i; + + /* Add SN parameter */ + memset(&sn, 0, sizeof(sn)); + sn.type = NFC_LLC_PARAM_SN; + sn.value.sn = conn->name; + for (i = 0; i < n; i++) { + params[i] = lp[i]; + } + params[i++] = &sn; + params[i] = NULL; + + /* Submit CONNECT */ + nfc_llc_submit_connect(self, NFC_LLC_SAP_SDP, lsap, params); + g_free(params); + } else { + /* CONNECT to the particular SAP */ + nfc_llc_submit_connect(self, conn->rsap, lsap, lp); + } + } +} + +static +void +nfc_llc_submit_disc( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap) +{ + const guint hdr = LLCP_MAKE_HDR(dsap, LLCP_PTYPE_DISC, ssap); + const guint size = 2; + guint8* pkt = g_malloc(size); + GBytes* pdu = g_bytes_new_take(pkt, size); + + pkt[0] = (guint8)(hdr >> 8); + pkt[1] = (guint8)hdr; + nfc_llc_submit(self, pdu); + g_bytes_unref(pdu); +} + +static +void +nfc_llc_submit_dm( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap, + NFC_LLC_DM_REASON reason) +{ + const guint hdr = LLCP_MAKE_HDR(dsap, LLCP_PTYPE_DM, ssap); + const guint size = 3; + guint8* pkt = g_malloc(size); + GBytes* pdu = g_bytes_new_take(pkt, size); + + pkt[0] = (guint8)(hdr >> 8); + pkt[1] = (guint8)hdr; + pkt[2] = reason; + nfc_llc_submit(self, pdu); + g_bytes_unref(pdu); +} + +static +void +nfc_llc_ack_internal( + NfcLlcObject* self, + NfcPeerConnection* conn, + gboolean last) +{ + NfcPeerConnectionLlcpState* ps = nfc_peer_connection_ps(conn); + + /* + * 5.6.1.4 Receive Acknowledgement State Variable V(RA) + * + * The receive acknowledgement state variable V(RA) SHALL + * denote the most recently sent N(R) value for a specific + * data link connection. + */ + if (conn->state == NFC_LLC_CO_ACTIVE && ps->vra != ps->vr) { + NfcPeerService* service = conn->service; + const LLCP_PTYPE ptype = last ? LLCP_PTYPE_RNR : LLCP_PTYPE_RR; + guint8 dsap = conn->rsap; + guint8 ssap = service->sap; + const guint hdr = LLCP_MAKE_HDR(dsap, ptype, ssap); + const guint size = 3; + guint8* pkt = g_malloc(size); + GBytes* pdu = g_bytes_new_take(pkt, size); + + /* Ack the last PDU */ + ps->vra = ps->vr; + pkt[0] = (guint8)(hdr >> 8); + pkt[1] = (guint8)hdr; + pkt[2] = ps->vra; + nfc_llc_submit(self, pdu); + g_bytes_unref(pdu); + } +} + +static +void +nfc_llc_handle_connect( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap, + const void* plist, + guint plen) +{ + NfcPeerService* service = NULL; + NfcLlcParam** params = nfc_llc_param_decode_bytes(plist, plen); + + if (dsap == NFC_LLC_SAP_SDP) { + /* + * NFCForum-TS-LLCP_1.1 + * 4.5.6 Service Name, SN + * + * The service name (SN) parameter MAY be transmitted with a + * CONNECT PDU to the well-known destination service access + * point address 01h and SHALL then indicate that the sending + * LLC intends to establish a data link connection with the + * named service registered in the remote service environment. + * ... + * If the service name parameter is transmitted with a CONNECT + * PDU to a destination service access point other than 01h, it + * SHALL be ignored. + */ + const NfcLlcParam* sn_param = nfc_llc_param_find + (nfc_llc_param_constify(params), NFC_LLC_PARAM_SN); + + /* Why would we access connection to SDP SAP (without a name) ? */ + if (sn_param) { + const char* sn = sn_param->value.sn; + + GDEBUG(" SN: \"%s\"", sn); + service = nfc_peer_services_find_sn(self->services, sn); + if (service) { + /* Resolved SAP */ + GDEBUG(" SAP: %u", service->sap); + dsap = service->sap; + } else { + GDEBUG("Service \"%s\" NOT FOUND", sn); + } + } else { + GDEBUG("Rejecting connection to SDP SAP"); + } + } else { + service = nfc_peer_services_find_sap(self->services, dsap); + if (!service) { + GDEBUG("No service at SAP %u", dsap); + } + } + if (service) { + /* Check for existing connection */ + const gpointer key = LLCP_CONN_KEY(dsap, ssap); + NfcPeerConnection* conn = g_hash_table_lookup(self->conn_table, key); + + if (conn) { + /* + * NFCForum-TS-LLCP_1.1 + * 5.6.3 Connection Establishment + * + * If the local LLC receives a CONNECT PDU and is unable to + * process the connection request, it SHALL return a DM PDU + * with the appropriate reason code (cf. Table 4 in Section + * 4.3.8) to the remote LLC at the earliest opportunity. + * + * But what is the appropriate reason code here (CONNECT for + * already connected SAP)? Table 4 in Section 4.3.8 doesn't + * have a code which would exactly match this situation. + */ + GWARN("Duplicate connection %u:%u", ssap, dsap); + nfc_llc_submit_dm(self, ssap, dsap, NFC_LLC_DM_REJECT); + } else { + /* Set up new connection */ + conn = nfc_peer_service_new_accept(service, ssap); + if (conn) { + if (conn->state != NFC_LLC_CO_DEAD) { + g_hash_table_insert(self->conn_table, key, conn); + nfc_peer_connection_set_llc(conn, &self->pub); + nfc_peer_connection_apply_remote_params(conn, + nfc_llc_param_constify(params)); + nfc_peer_connection_accept(conn); + } else { + /* Stillborn connection */ + nfc_llc_submit_dm(self, ssap, dsap, NFC_LLC_DM_REJECT); + nfc_peer_connection_unref(conn); + } + } else { + nfc_llc_submit_dm(self, ssap, dsap, NFC_LLC_DM_REJECT); + } + } + } else { + nfc_llc_submit_dm(self, ssap, dsap, NFC_LLC_DM_NO_SERVICE); + } + nfc_llc_param_free(params); +} + +static +void +nfc_llc_handle_cc( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap, + const void* plist, + guint plen) +{ + NfcLlcConnectReq* req = self->connect_queue ? + self->connect_queue->data : NULL; + + if (req) { + NfcPeerConnection* conn = req->connection; + NfcPeerService* service = conn->service; + + if (service->sap == dsap && (!conn->rsap || conn->rsap == ssap)) { + gpointer key; + + /* Done with this request */ + self->connect_queue = g_slist_delete_link + (self->connect_queue, self->connect_queue); + + /* Update remote SAP (key depends on it) */ + conn->rsap = ssap; + key = nfc_peer_connection_key(conn); + + if (plen > 0) { + NfcLlcParam** cp = nfc_llc_param_decode_bytes(plist, plen); + + /* Apply connection parameters */ + if (cp) { + nfc_peer_connection_apply_remote_params(conn, + nfc_llc_param_constify(cp)); + nfc_llc_param_free(cp); + } + } + + /* Complete the request */ + if (req->complete) { + NfcLlcConnectFunc complete = req->complete; + + req->complete = NULL; + complete(conn, (conn->state == NFC_LLC_CO_CONNECTING) ? + NFC_PEER_CONNECT_OK : NFC_PEER_CONNECT_CANCELLED, + req->user_data); + } + + switch (conn->state) { + case NFC_LLC_CO_CONNECTING: + /* Upgrade connection's state to ACTIVE */ + g_hash_table_insert(self->conn_table, key, + nfc_peer_connection_ref(conn)); + nfc_peer_connection_set_state(conn, NFC_LLC_CO_ACTIVE); + break; + case NFC_LLC_CO_ABANDONED: + /* We changed our mind (still need to keep connection + * in the table for the time being) */ + g_hash_table_insert(self->conn_table, key, + nfc_peer_connection_ref(conn)); + GDEBUG("Abandoned %u:%u", service->sap, conn->rsap); + nfc_llc_submit_disc(self, conn->rsap, service->sap); + break; + case NFC_LLC_CO_DISCONNECTING: + case NFC_LLC_CO_DEAD: + case NFC_LLC_CO_ACCEPTING: + case NFC_LLC_CO_ACTIVE: + break; + } + nfc_llc_connect_req_free(req); + nfc_llc_submit_next_connect(self); + return; + } + } + GWARN("Unexpected CC"); + nfc_llc_submit_frmr_i(self, ssap, 0, LLCP_PTYPE_CC); +} + +static +void +nfc_llc_handle_disc( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap) +{ + const gpointer key = LLCP_CONN_KEY(dsap, ssap); + NfcPeerConnection* conn = g_hash_table_lookup(self->conn_table, key); + + if (conn) { + /* + * NFCForum-TS-LLCP_1.1 + * 5.6.6 Connection Termination + * + * ... + * When receiving a DISC PDU, the LLC SHALL return a DM PDU + * and pass a disconnect indication to the service access point + * for that data link connection. The data link connection SHALL + * then be closed. + */ + nfc_peer_connection_set_state(conn, NFC_LLC_CO_DEAD); + GASSERT(!g_hash_table_contains(self->conn_table, key)); + nfc_llc_submit_dm(self, ssap, dsap, NFC_LLC_DM_DISC_RECEIVED); + } else { + GWARN("Non-existent connection %u:%u", dsap, ssap); + nfc_llc_submit_frmr_i(self, ssap, dsap, LLCP_PTYPE_DISC); + } +} + +static +void +nfc_llc_handle_dm( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap, + guint reason) +{ + const gpointer key = LLCP_CONN_KEY(dsap, ssap); + NfcPeerConnection* conn = g_hash_table_lookup(self->conn_table, key); + + if (conn) { + nfc_peer_connection_set_state(conn, NFC_LLC_CO_DEAD); + GASSERT(!g_hash_table_contains(self->conn_table, key)); + return; + } else if (self->connect_queue) { + NfcLlcConnectReq* req = self->connect_queue->data; + NfcPeerConnection* conn = req->connection; + NfcPeerService* service = conn->service; + + if (dsap == service->sap) { + /* + * NFCForum-TS-LLCP_1.1 + * 5.6 Connection-oriented Transport Mode Procedures + * 5.6.3 Connection Establishment + * ... + * If the LLC receives a DM PDU with a DSAP value equal + * to the SSAP value of a sent but not yet acknowledged + * CONNECT PDU, it SHALL abandon connection establishment + * and report the reason to the service layer. + */ + self->connect_queue = g_slist_delete_link + (self->connect_queue, self->connect_queue); + + /* Complete the request */ + if (req->complete) { + NfcLlcConnectFunc complete = req->complete; + NFC_PEER_CONNECT_RESULT result; + + if (conn->state == NFC_LLC_CO_ABANDONED) { + result = NFC_PEER_CONNECT_CANCELLED; + } else { + result = NFC_PEER_CONNECT_FAILED; + switch (reason) { + case NFC_LLC_DM_NO_SERVICE: + result = NFC_PEER_CONNECT_NO_SERVICE; + break; + case NFC_LLC_DM_REJECT: + result = NFC_PEER_CONNECT_REJECTED; + break; + case NFC_LLC_DM_DISC_RECEIVED: + case NFC_LLC_DM_NOT_CONNECTED: + /* NFC_PEER_CONNECT_FAILED */ + break; + } + } + req->complete = NULL; + complete(conn, result, req->user_data); + } + nfc_peer_connection_set_state(conn, NFC_LLC_CO_DEAD); + nfc_llc_connect_req_free(req); + nfc_llc_submit_next_connect(self); + return; + } + } + GWARN("Non-existent connection %u:%u", dsap, ssap); + nfc_llc_submit_frmr_i(self, ssap, dsap, LLCP_PTYPE_DM); +} + +static +void +nfc_llc_handle_frmr( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap, + LLCP_PTYPE ptype) +{ + const gpointer key = LLCP_CONN_KEY(dsap, ssap); + NfcPeerConnection* conn = g_hash_table_lookup(self->conn_table, key); + + /* Do we need anything more sophisticated than that? */ + if (conn) { + nfc_peer_connection_set_state(conn, NFC_LLC_CO_DEAD); + GASSERT(!g_hash_table_contains(self->conn_table, key)); + return; + } else if (self->connect_queue && ptype == LLCP_PTYPE_CONNECT) { + NfcLlcConnectReq* req = self->connect_queue->data; + NfcPeerConnection* conn = req->connection; + NfcPeerService* service = conn->service; + + if (service->sap == dsap && (!conn->rsap || conn->rsap == ssap)) { + /* Abort pending connection */ + self->connect_queue = g_slist_delete_link + (self->connect_queue, self->connect_queue); + + /* Complete the request */ + if (req->complete) { + NfcLlcConnectFunc complete = req->complete; + + req->complete = NULL; + complete(conn, NFC_PEER_CONNECT_REJECTED, req->user_data); + } + nfc_peer_connection_set_state(conn, NFC_LLC_CO_DEAD); + nfc_llc_connect_req_free(req); + nfc_llc_submit_next_connect(self); + } + } +} + +static +void +nfc_llc_handle_snl( + NfcLlcObject* self, + const void* plist, + guint plen) +{ + GByteArray* pdu_bytes = nfc_llc_pdu_new + (NFC_LLC_SAP_SDP, LLCP_PTYPE_SNL, NFC_LLC_SAP_SDP); + GPtrArray* resp_list = g_ptr_array_new_with_free_func(g_free); + NfcLlcParam** params = nfc_llc_param_decode_bytes(plist, plen); + NfcLlcParam** ptr = params; + GBytes* pdu; + + /* Resolve names */ + while (*ptr) { + const NfcLlcParam* param = *ptr++; + + if (param->type == NFC_LLC_PARAM_SDREQ) { + const NfcLlcParamSdReq* sdreq = ¶m->value.sdreq; + NfcLlcParam* resp_param = g_new(NfcLlcParam, 1); + NfcLlcParamSdRes* sdres = &resp_param->value.sdres; + NfcPeerService* svc = nfc_peer_services_find_sn + (self->services, sdreq->uri); + + resp_param->type = NFC_LLC_PARAM_SDRES; + sdres->tid = sdreq->tid; + if (svc) { + sdres->sap = svc->sap; + GDEBUG(" \"%s\" => %u", sdreq->uri, sdres->sap); + } else if (!g_strcmp0(sdreq->uri, NFC_LLC_NAME_SDP)) { + sdres->sap = NFC_LLC_SAP_SDP; + GDEBUG(" \"%s\" => %u (built-in)", sdreq->uri, sdres->sap); + } else { + sdres->sap = 0; + GDEBUG(" \"%s\" (unknown)", sdreq->uri); + } + g_ptr_array_add(resp_list, resp_param); + } + } + nfc_llc_param_free(params); + + /* Encode response parameters */ + g_ptr_array_add(resp_list, NULL); + nfc_llc_param_encode((const NfcLlcParam**)resp_list->pdata, + pdu_bytes, self->miu); + g_ptr_array_free(resp_list, TRUE); + + /* Submit the packet */ + pdu = g_byte_array_free_to_bytes(pdu_bytes); + nfc_llc_submit(self, pdu); + g_bytes_unref(pdu); +} + +static +void +nfc_llc_handle_pax( + NfcLlcObject* self, + const void* plist, + guint plen) +{ + NfcLlcParam** params = nfc_llc_param_decode_bytes(plist, plen); + guint change = nfc_llc_apply_params(self, nfc_llc_param_constify(params)); + + nfc_llc_param_free(params); + /* Signal the change */ + if (change & (1 << NFC_LLC_PARAM_WKS)) { + g_signal_emit(self, nfc_llc_signals[SIGNAL_WKS_CHANGED], 0); + } +} + +static +gboolean +nfc_llc_handle_agf( + NfcLlcObject* self, + const void* data, + guint size) +{ + const guint8* pkt = data; + const guint8* end = pkt + size; + + while ((pkt + 1) < end) { + const guint len = ((guint)pkt[0]) + pkt[1]; + + /* Eat the length */ + pkt += 2; + + /* Ignore empty PDUs */ + if (len) { + /* Make sure we are within the bounds */ + if ((pkt + len) > end) { + GWARN("Broken AFG frame"); + return FALSE; + } else { + /* Handle encapsulated PDU */ + GDEBUG("Handling encapsulated PDU (%u bytes)", len); + if (!nfc_llc_handle_pdu(self, pkt, len)) { + return FALSE; + } + } + pkt += len; + } else { + GDEBUG("Skipping empty encapsulated PDU"); + } + } + return (pkt == end); +} + +static +void +nfc_llc_handle_ui( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap, + const void* data, + guint len) +{ + NfcPeerService* svc = nfc_peer_services_find_sap(self->services, dsap); + + if (svc) { + nfc_peer_service_datagram_received(svc, ssap, data, len); + } else { + GDEBUG("No service at SAP %u", dsap); + nfc_llc_submit_frmr_i(self, ssap, dsap, LLCP_PTYPE_UI); + } +} + +static +void +nfc_llc_handle_i( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap, + guint8 seq, + const void* data, + guint len) +{ + const gpointer key = LLCP_CONN_KEY(dsap, ssap); + NfcPeerConnection* conn = g_hash_table_lookup(self->conn_table, key); + + if (conn) { + NfcPeerConnectionLlcpState* ps = nfc_peer_connection_ps(conn); + const guint8 ns = seq >> 4; + const guint8 nr = seq & 0x0f; + + /* + * NFCForum-TS-LLCP_1.1 + * 5.6 Connection-oriented Transport Mode Procedures + * + * 5.6.1.2 Send Acknowledgement State Variable V(SA) + * + * The send acknowledgement state variable V(SA) SHALL denote + * the most recently received N(R) value for a specific data + * link connection. + */ + ps->vsa = nr; + + /* + * 5.6.4.2 Receiving I PDUs + * + * When an I PDU is received with the send sequence number N(S) + * equal to the receive state variable V(R), the LLC SHALL pass + * the service data unit, contained in the information field, + * to the service access point and increment by one its receive + * state variable, V(R). + * + */ + if (ps->vr == ns) { + ps->vr = ((ps->vr + 1) & 0x0f); + nfc_peer_connection_ref(conn); + nfc_peer_connection_data_received(conn, data, len); + nfc_llc_ack_internal(self, conn, FALSE); + nfc_peer_connection_unref(conn); + } else { + nfc_llc_submit_frmr(self, ssap, dsap, NFC_LLC_FRMR_S, + LLCP_PTYPE_I, seq, conn); + } + nfc_peer_connection_flush(conn); + } else { + nfc_llc_submit_frmr_i(self, ssap, dsap, LLCP_PTYPE_I); + } +} + +static +void +nfc_llc_handle_rr( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap, + guint8 nr) +{ + const gpointer key = LLCP_CONN_KEY(dsap, ssap); + NfcPeerConnection* conn = g_hash_table_lookup(self->conn_table, key); + + if (conn) { + /* + * NFCForum-TS-LLCP_1.1 + * 5.6 Connection-oriented Transport Mode Procedures + * + * 5.6.1.2 Send Acknowledgement State Variable V(SA) + * + * The send acknowledgement state variable V(SA) SHALL denote + * the most recently received N(R) value for a specific data + * link connection. + */ + nfc_peer_connection_ps(conn)->vsa = nr; + nfc_peer_connection_flush(conn); + } else { + nfc_llc_submit_frmr_i(self, ssap, dsap, LLCP_PTYPE_RR); + } +} + +static +void +nfc_llc_handle_rnr( + NfcLlcObject* self, + guint8 dsap, + guint8 ssap, + guint8 nr) +{ + const gpointer key = LLCP_CONN_KEY(dsap, ssap); + NfcPeerConnection* conn = g_hash_table_lookup(self->conn_table, key); + + if (conn) { + /* + * NFCForum-TS-LLCP_1.1 + * 5.6 Connection-oriented Transport Mode Procedures + * + * 5.6.1.2 Send Acknowledgement State Variable V(SA) + * + * The send acknowledgement state variable V(SA) SHALL denote + * the most recently received N(R) value for a specific data + * link connection. + */ + nfc_peer_connection_ps(conn)->vsa = nr; + nfc_peer_connection_flush(conn); +#pragma message("TODO: Suspend sending?") + } else { + nfc_llc_submit_frmr_i(self, ssap, dsap, LLCP_PTYPE_RNR); + } +} + +static +gboolean +nfc_llc_handle_pdu( + NfcLlcObject* self, + const void* data, + gsize len) +{ + if (len >= 2) { + const guint8* pkt = data; + const guint hdr = (((guint)(pkt[0])) << 8) | pkt[1]; + const guint8 dsap = LLCP_GET_DSAP(hdr); + const LLCP_PTYPE ptype = LLCP_GET_PTYPE(hdr); + const guint8 ssap = LLCP_GET_SSAP(hdr); + + switch (ptype) { + case LLCP_PTYPE_SYMM: + if (len == 2 && dsap == 0 && ssap == 0) { + GDEBUG("> SYMM"); + return TRUE; + } + GDEBUG("> SYMM (malformed?)"); + return FALSE; + case LLCP_PTYPE_PAX: + self->packets_handled++; + if (!dsap && !ssap) { + GDEBUG("> PAX"); + nfc_llc_handle_pax(self, pkt + 2, len - 2); + } else { + GDEBUG("> PAX %u:%u (malformed?)", ssap, dsap); + nfc_llc_submit_frmr_i(self, ssap, dsap, LLCP_PTYPE_PAX); + } + return TRUE; + case LLCP_PTYPE_AGF: + if (!dsap && !ssap) { + self->packets_handled++; + GDEBUG("> AGF"); + return nfc_llc_handle_agf(self, pkt + 2, len - 2); + } + GDEBUG("> AGF (malformed?)"); + return FALSE; + case LLCP_PTYPE_UI: + self->packets_handled++; + GDEBUG("> UI %u:%u (%u bytes)", ssap, dsap, (guint) len - 2); + nfc_llc_handle_ui(self, dsap, ssap, pkt + 2, len - 2); + return TRUE; + break; + case LLCP_PTYPE_CONNECT: + self->packets_handled++; + GDEBUG("> CONNECT %u:%u", ssap, dsap); + nfc_llc_handle_connect(self, dsap, ssap, pkt + 2, len - 2); + return TRUE; + case LLCP_PTYPE_DISC: + if (len == 2) { + self->packets_handled++; + GDEBUG("> DISC %u:%u", ssap, dsap); + nfc_llc_handle_disc(self, dsap, ssap); + return TRUE; + } + GDEBUG("> DISC (malformed?)"); + return FALSE; + case LLCP_PTYPE_CC: + self->packets_handled++; + GDEBUG("> CC %u:%u", ssap, dsap); + nfc_llc_handle_cc(self, dsap, ssap, pkt + 2, len - 2); + return TRUE; + case LLCP_PTYPE_DM: + if (len == 3) { + const guint8 reason = pkt[2]; + + self->packets_handled++; + GDEBUG("> DM %u:%u (0x%02x)", ssap, dsap, reason); + nfc_llc_handle_dm(self, dsap, ssap, reason); + return TRUE; + } + GDEBUG("> DM %u:%u (malformed?)", ssap, dsap); + return FALSE; + case LLCP_PTYPE_FRMR: + if (len == 6) { + self->packets_handled++; + GDEBUG("> FRMR %u:%u (0x%02x)", ssap, dsap, pkt[2] & 0x0f); + nfc_llc_handle_frmr(self, dsap, ssap, pkt[2] & 0x0f); + return TRUE; + } + GDEBUG("> FRMR %u:%u (malformed?)", ssap, dsap); + return FALSE; + case LLCP_PTYPE_SNL: + self->packets_handled++; + if (dsap == NFC_LLC_SAP_SDP && ssap == NFC_LLC_SAP_SDP) { + GDEBUG("> SNL"); + nfc_llc_handle_snl(self, pkt + 2, len - 2); + } else { + GDEBUG("> SNL %u:%u (malformed?)", ssap, dsap); + nfc_llc_submit_frmr_i(self, ssap, dsap, LLCP_PTYPE_SNL); + } + return TRUE; + case LLCP_PTYPE_I: + if (len >= 3) { + const guint8 seq = pkt[2]; + + self->packets_handled++; + GDEBUG("> I %u:%u (0x%02x, %u bytes)", ssap, dsap, seq, (guint) + len - 3); + nfc_llc_handle_i(self, dsap, ssap, pkt[2], pkt + 3, len - 3); + return TRUE; + } + GDEBUG("> I %u:%u (malformed?)", ssap, dsap); + return FALSE; + case LLCP_PTYPE_RR: + if (len == 3) { + const guint8 nr = pkt[2]; + + self->packets_handled++; + GDEBUG("> RR %u:%u (0x%02x)", ssap, dsap, nr); + nfc_llc_handle_rr(self, dsap, ssap, nr & 0xff); + return TRUE; + } + GDEBUG("> RR %u:%u (malformed?)", ssap, dsap); + return FALSE; + case LLCP_PTYPE_RNR: + if (len == 3) { + const guint8 nr = pkt[2]; + + self->packets_handled++; + GDEBUG("> RNR %u:%u (0x%02x)", ssap, dsap, nr); + nfc_llc_handle_rnr(self, dsap, ssap, nr & 0xff); + return TRUE; + } + GDEBUG("> RNR %u:%u (malformed?)", ssap, dsap); + return FALSE; + } + GWARN("Packet 0x%x not handled", ptype); + return FALSE; + } else { + GWARN("Single byte LLCP packet received, bailing out"); + return FALSE; + } +} + +static +NfcPeerConnection* +nfc_llc_connect_internal( + NfcLlcObject* self, + NfcPeerService* service, + guint rsap, + const char* rname, + NfcLlcConnectFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + if (G_LIKELY(service)) { + NfcLlcConnectReq* req = nfc_llc_connect_req_new + (self, service, rsap, rname, complete, user_data, destroy); + + if (req) { + const gboolean do_connect = !self->connect_queue; + + self->connect_queue = g_slist_append(self->connect_queue, req); + if (do_connect) { + nfc_llc_submit_next_connect(self); + } + return req->connection; + } + } + return NULL; +} + +static +void +nfc_llc_set_state( + NfcLlcObject* self, + NFC_LLC_STATE state) +{ + NfcLlc* llc = &self->pub; + + if (llc->state != state) { + GDEBUG("LLCP state %s -> %s", + nfc_llc_state_name(self, llc->state), + nfc_llc_state_name(self, state)); + llc->state = state; + g_signal_emit(self, nfc_llc_signals[SIGNAL_STATE_CHANGED], 0); + } +} + +static +void +nfc_llc_set_idle( + NfcLlcObject* self, + gboolean idle) +{ + NfcLlc* llc = &self->pub; + + if (llc->idle != idle) { + GDEBUG("LLCP %s", idle ? "idle" : "busy"); + llc->idle = idle; + g_signal_emit(self, nfc_llc_signals[SIGNAL_IDLE_CHANGED], 0); + } +} + +static +void +nfc_llc_can_send( + NfcLlcIo* io, + gpointer user_data) +{ + nfc_llc_send_next_pdu(NFC_LLC(user_data)); +} + +static +gboolean +nfc_llc_receive( + NfcLlcIo* io, + const GUtilData* data, + gpointer user_data) +{ + NfcLlcObject* self = NFC_LLC(user_data); + const guint packets_handled = self->packets_handled; + + GASSERT(self->pub.state < NFC_LLC_STATE_ERROR); + if (data->size > 0) { + if (nfc_llc_handle_pdu(self, data->bytes, data->size)) { + if (self->pub.state == NFC_LLC_STATE_START) { + /* Peer is talking to us! */ + nfc_llc_set_state(self, NFC_LLC_STATE_ACTIVE); + } + } else { + /* Protocol error */ + GWARN("LLC protocol error"); + nfc_llc_set_state(self, NFC_LLC_STATE_ERROR); + return LLC_IO_IGNORE; + } + } + if (self->io->can_send) { + nfc_llc_send_next_pdu(self); + } + if (self->packets_handled == packets_handled && io->can_send) { + nfc_llc_set_idle(self, !self->pdu_queue && !self->connect_queue); + return LLC_IO_IGNORE; + } else { + nfc_llc_set_idle(self, FALSE); + return LLC_IO_EXPECT_MORE; + } +} + +static +void +nfc_llc_error( + NfcLlcIo* io, + gpointer user_data) +{ + GDEBUG("LLC transmit failed"); + nfc_llc_set_state(NFC_LLC(user_data), NFC_LLC_STATE_PEER_LOST); +} + +static +void +nfc_llc_send_next_pdu( + NfcLlcObject* self) +{ + GBytes* packet = nfc_llc_dequeue_pdu(self); + + if (packet) { + gsize pktsize; + const guint8* pkt = g_bytes_get_data(packet, &pktsize); + const guint hdr = (((guint)(pkt[0])) << 8) | pkt[1]; + +#if GUTIL_LOG_DEBUG + if (GLOG_ENABLED(GLOG_LEVEL_DEBUG)) { + const guint8 dsap = LLCP_GET_DSAP(hdr); + const guint8 ssap = LLCP_GET_SSAP(hdr); + + switch (LLCP_GET_PTYPE(hdr)) { + case LLCP_PTYPE_SYMM: + /* These are actually sent (and logged) by NfcLlcIo */ + GDEBUG("< SYMM"); + break; + case LLCP_PTYPE_PAX: + GDEBUG("< PAX"); + break; + case LLCP_PTYPE_AGF: + GDEBUG("< AGF"); + break; + case LLCP_PTYPE_UI: + GDEBUG("< UI %u:%u", ssap, dsap); + break; + case LLCP_PTYPE_CONNECT: + GDEBUG("< CONNECT %u:%u", ssap, dsap); + break; + case LLCP_PTYPE_DISC: + GDEBUG("< DISC %u:%u", ssap, dsap); + break; + case LLCP_PTYPE_CC: + GDEBUG("< CC %u:%u", ssap, dsap); + break; + case LLCP_PTYPE_DM: + GDEBUG("< DM %u:%u (0x%02x)", ssap, dsap, pkt[2]); + break; + case LLCP_PTYPE_FRMR: + GDEBUG("< FRMR %u:%u (0x%02x)", ssap, dsap, (pkt[2] & 0x0f)); + break; + case LLCP_PTYPE_SNL: + GDEBUG("< SNL"); + break; + case LLCP_PTYPE_I: + GDEBUG("< I %u:%u (%u bytes)", ssap, dsap, (guint)pktsize - 3); + break; + case LLCP_PTYPE_RR: + GDEBUG("< RR %u:%u (0x%02x)", ssap, dsap, pkt[2]); + break; + case LLCP_PTYPE_RNR: + GDEBUG("< RNR %u:%u", ssap, dsap); + break; + } + } +#endif /* GUTIL_LOG_DEBUG */ + + if (nfc_llc_io_send(self->io, packet)) { + if (LLCP_GET_PTYPE(hdr) == LLCP_PTYPE_I) { + const guint8 dsap = LLCP_GET_DSAP(hdr); + const guint8 ssap = LLCP_GET_SSAP(hdr); + NfcPeerConnection* conn = g_hash_table_lookup(self->conn_table, + LLCP_CONN_KEY(ssap, dsap) /* SSAP and DSAP reversed */); + + if (conn) { + nfc_peer_connection_flush(conn); + } + } + } else { + GDEBUG("LLC transmit failed"); + nfc_llc_set_state(self, NFC_LLC_STATE_PEER_LOST); + } + g_bytes_unref(packet); + } +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcLlc* +nfc_llc_new( + NfcLlcIo* io, + NfcPeerServices* services, + const NfcLlcParam* const* params) +{ + NfcLlc* llc = NULL; + + if (G_LIKELY(io)) { + NfcLlcObject* self = g_object_new(NFC_TYPE_LLC, NULL); + + GDEBUG("Initializing"); + llc = &self->pub; + self->io = io; + self->services = nfc_peer_services_ref(services); + + /* Apply parameters provided by the MAC layer */ + nfc_llc_apply_params(self, params); + + /* + * PAX PDU exchange is defined in LLCP spec but SHALL NOT be used. + * OK :) + * + * NFCForum-TS-LLCP_1.1 + * + * 5.2 Link Activation Procedure + * 5.2.1 Exchange of PAX PDU + * + * Operating in the Initiator role: + * + * The local LLC SHALL send a PAX PDU to the remote LLC that + * includes all required LLC parameters not exchanged during + * the MAC link activation. The local LLC SHALL then await + * receipt of a PAX PDU from the remote LLC. Upon receipt of + * the PAX PDU, the local LLC SHALL perform the version number + * agreement procedure defined in Section 5.2.2. + * + * 6.2.3.1 Link Activation procedure for the Initiator + * + * All LLC parameters defined in Section 4.5 Table 6 for use + * in PAX PDUs that are to be exchanged SHALL be included as + * TLVs beginning at the fourth octet of the ATR_REQ General + * Bytes field. The PAX PDU exchange described in the LLC link + * activation procedure (cf. Section 5.2) SHALL NOT be used. + */ + + /* Start the conversation */ + self->io = nfc_llc_io_ref(io); + if (nfc_llc_io_start(io)) { + self->io_event[LLC_IO_EVENT_CAN_SEND] = + nfc_llc_io_add_can_send_handler(io, nfc_llc_can_send, self); + self->io_event[LLC_IO_EVENT_RECEIVE] = + nfc_llc_io_add_receive_handler(io, nfc_llc_receive, self); + self->io_event[LLC_IO_EVENT_ERROR] = + nfc_llc_io_add_error_handler(io, nfc_llc_error, self); + } else { + llc->idle = TRUE; + llc->state = NFC_LLC_STATE_PEER_LOST; + } + } + return llc; +} + +void +nfc_llc_free( + NfcLlc* llc) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + if (G_LIKELY(self)) { + nfc_llc_abort_all_connections(self); + nfc_llc_io_remove_all_handlers(self->io, self->io_event); + g_object_unref(self); + } +} + +NfcPeerConnection* +nfc_llc_connect( + NfcLlc* llc, + NfcPeerService* service, + guint rsap, + NfcLlcConnectFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + return G_LIKELY(self) ? nfc_llc_connect_internal + (self, service, rsap, NULL, complete, user_data, destroy) : NULL; +} + +NfcPeerConnection* +nfc_llc_connect_sn( + NfcLlc* llc, + NfcPeerService* service, + const char* sn, + NfcLlcConnectFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + return G_LIKELY(self) ? nfc_llc_connect_internal + (self, service, 0, sn, complete, user_data, destroy) : NULL; +} + +gulong +nfc_llc_add_state_changed_handler( + NfcLlc* llc, + NfcLlcFunc func, + void* user_data) +{ + return nfc_llc_add_handler(llc, SIGNAL_STATE_CHANGED, func, user_data); +} + +gulong +nfc_llc_add_idle_changed_handler( + NfcLlc* llc, + NfcLlcFunc func, + void* user_data) +{ + return nfc_llc_add_handler(llc, SIGNAL_IDLE_CHANGED, func, user_data); +} + +gulong +nfc_llc_add_wks_changed_handler( + NfcLlc* llc, + NfcLlcFunc func, + void* user_data) +{ + return nfc_llc_add_handler(llc, SIGNAL_WKS_CHANGED, func, user_data); +} + +void +nfc_llc_remove_handler( + NfcLlc* llc, + gulong id) +{ + if (G_LIKELY(id)) { + NfcLlcObject* self = nfc_llc_object_cast(llc); + + if (G_LIKELY(self)) { + g_signal_handler_disconnect(self, id); + } + } +} + +void +nfc_llc_remove_handlers( + NfcLlc* llc, + gulong* ids, + guint count) +{ + gutil_disconnect_handlers(nfc_llc_object_cast(llc), ids, count); +} + +void +nfc_llc_connection_dead( + NfcLlc* llc, + NfcPeerConnection* conn) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + if (G_LIKELY(self) && G_LIKELY(conn)) { + g_hash_table_remove(self->conn_table, nfc_peer_connection_key(conn)); + } +} + +gboolean +nfc_llc_cancel_connect_request( + NfcLlc* llc, + NfcPeerConnection* conn) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + gboolean cancelled = FALSE; + + if (G_LIKELY(self) && G_LIKELY(conn)) { + GSList* l; + + for (l = self->connect_queue; l; l = g_slist_next(l)) { + NfcLlcConnectReq* req = l->data; + + if (req->connection == conn) { + req->complete = NULL; + cancelled = TRUE; + break; + } + } + } + return cancelled; +} + +void +nfc_llc_ack( + NfcLlc* llc, + NfcPeerConnection* conn, + gboolean last) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + if (G_LIKELY(self) && G_LIKELY(conn)) { + nfc_llc_ack_internal(self, conn, last); + } +} + +gboolean +nfc_llc_i_pdu_queued( + NfcLlc* llc, + NfcPeerConnection* conn) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + if (G_LIKELY(self) && G_LIKELY(conn)) { + GList* l = g_list_first(self->pdu_queue); + + if (l) { + const guint8 dsap = conn->rsap; + const guint8 ssap = conn->service->sap; + const guint hdr = LLCP_MAKE_HDR(dsap, LLCP_PTYPE_I, ssap); + + do { + const guint8* pkt = g_bytes_get_data((GBytes*)l->data, NULL); + + if (((((guint)(pkt[0])) << 8) | pkt[1]) == hdr) { + return TRUE; + } + l = l->next; + } while (l); + } + } + return FALSE; +} + +void +nfc_llc_submit_dm_pdu( + NfcLlc* llc, + guint8 dsap, + guint8 ssap, + NFC_LLC_DM_REASON reason) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + if (G_LIKELY(self)) { + nfc_llc_submit_dm(self, dsap, ssap, reason); + } +} + +void +nfc_llc_submit_cc_pdu( + NfcLlc* llc, + NfcPeerConnection* conn) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + if (G_LIKELY(self) && G_LIKELY(conn)) { + NfcPeerService* service = conn->service; + const NfcLlcParam* const* lp = nfc_peer_connection_lp(conn); + const guint8 dsap = conn->rsap; + const guint8 ssap = service->sap; + GByteArray* bytes = nfc_llc_pdu_new(dsap, LLCP_PTYPE_CC, ssap); + GBytes* pdu; + + nfc_llc_param_encode(lp, bytes, nfc_peer_connection_rmiu(conn)); + pdu = g_byte_array_free_to_bytes(bytes); + nfc_llc_submit(self, pdu); + g_bytes_unref(pdu); + } +} + +void +nfc_llc_submit_i_pdu( + NfcLlc* llc, + NfcPeerConnection* conn, + const void* data, + guint len) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + if (G_LIKELY(self) && G_LIKELY(conn)) { + NfcPeerConnectionLlcpState* ps = nfc_peer_connection_ps(conn); + NfcPeerService* service = conn->service; + const guint8 dsap = conn->rsap; + const guint8 ssap = service->sap; + const guint hdr = LLCP_MAKE_HDR(dsap, LLCP_PTYPE_I, ssap); + const guint size = 3 + len; + guint8* pkt = g_malloc(size); + GBytes* pdu = g_bytes_new_take(pkt, size); + + pkt[0] = (guint8)(hdr >> 8); + pkt[1] = (guint8)hdr; + pkt[2] = (guint8)((ps->vs << 4) /* N(S) */ | ps->vr /* N(R) */); + memcpy(pkt + 3, data, len); + + /* + * NFCForum-TS-LLCP_1.1 + * 5.6 Connection-oriented Transport Mode Procedures + * + * 5.6.1.1 Send State Variable V(S) + * + * The send state variable V(S) SHALL denote the sequence number, + * modulo-16, of the next in-sequence I PDU to be sent on a specific + * data link connection. The value of the send state variable V(S) + * SHALL be incremented by one following each successive I PDU + * transmission on the associated data link connection. + */ + ps->vs = ((ps->vs + 1) & 0x0f); + + /* + * 5.6.1.4 Receive Acknowledgement State Variable V(RA) + * + * The receive acknowledgement state variable V(RA) SHALL denote + * the most recently sent N(R) value for a specific data link + * connection. + */ + ps->vra = ps->vr; + + nfc_llc_submit(self, pdu); + g_bytes_unref(pdu); + } +} + +void +nfc_llc_submit_disc_pdu( + NfcLlc* llc, + guint8 dsap, + guint8 ssap) +{ + NfcLlcObject* self = nfc_llc_object_cast(llc); + + if (G_LIKELY(self)) { + nfc_llc_submit_disc(self, dsap, ssap); + } +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_llc_object_init( + NfcLlcObject* self) +{ + NfcLlc* llc = &self->pub; + + llc->state = NFC_LLC_STATE_START; + self->miu = NFC_LLC_MIU_DEFAULT; + self->lto = NFC_LLC_LTO_DEFAULT; + self->pool = gutil_idle_pool_new(); + self->conn_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, nfc_llc_connection_destroy); +} + +static +void +nfc_llc_object_finalize( + GObject* object) +{ + NfcLlcObject* self = NFC_LLC(object); + + nfc_llc_abort_all_connections(self); + nfc_peer_services_unref(self->services); + nfc_llc_io_remove_all_handlers(self->io, self->io_event); + nfc_llc_io_unref(self->io); + g_hash_table_unref(self->conn_table); + g_list_free_full(self->pdu_queue, (GDestroyNotify) g_bytes_unref); + g_slist_free_full(self->connect_queue, (GDestroyNotify) + nfc_llc_connect_req_free); + gutil_idle_pool_destroy(self->pool); + G_OBJECT_CLASS(nfc_llc_object_parent_class)->finalize(object); +} + +static +void +nfc_llc_object_class_init( + NfcLlcObjectClass* klass) +{ + GType type = G_OBJECT_CLASS_TYPE(klass); + + G_OBJECT_CLASS(klass)->finalize = nfc_llc_object_finalize; + nfc_llc_signals[SIGNAL_STATE_CHANGED] = + g_signal_new(SIGNAL_STATE_CHANGED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + nfc_llc_signals[SIGNAL_IDLE_CHANGED] = + g_signal_new(SIGNAL_IDLE_CHANGED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + nfc_llc_signals[SIGNAL_WKS_CHANGED] = + g_signal_new(SIGNAL_WKS_CHANGED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_llc.h b/core/src/nfc_llc.h new file mode 100644 index 0000000..f123832 --- /dev/null +++ b/core/src/nfc_llc.h @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_LLC_H +#define NFC_LLC_H + +#include "nfc_types_p.h" + +typedef enum nfc_llc_flags { + NFC_LLC_FLAGS_NONE = 0x00, + NFC_LLC_FLAG_INITIATOR = 0x01 /* Otherwise Target */ +} NFC_LLC_FLAGS; + +/* + * NFCForum-TS-LLCP_1.1 + * 4.3.8 Disconnected Mode (DM) + * Table 4: Disconnected Mode Reasons + */ +typedef enum nfc_llc_dm_reason { + /* The LLC has received a DISC PDU and is now logically disconnected + * from the data link connection. */ + NFC_LLC_DM_DISC_RECEIVED = 0x00, + /* The LLC has received a connection-oriented PDU but the target + * service access point has no active connection. */ + NFC_LLC_DM_NOT_CONNECTED = 0x01, + /* The remote LLC has received a CONNECT PDU and there is no service + * bound to the specified target service access point. */ + NFC_LLC_DM_NO_SERVICE = 0x02, + /* The remote LLC has processed a CONNECT PDU and the request to + * connect was rejected by the service layer. */ + NFC_LLC_DM_REJECT = 0x03 +} NFC_LLC_DM_REASON; + +/* + * LLC Link Management state machine: + * + * +=======+ + * +---> | ERROR | <---+ + * | +=======+ | + * protocol protocol + * error error + * | | + * +-------+ +--------+ + * | START | -- ok --> | ACTIVE | + * +-------+ +--------+ + * | | + * transmit transmit + * error error + * | +===========+ | + * +-> | PEER_LOST | <-+ + * +===========+ + */ +typedef enum nfc_llc_state { + NFC_LLC_STATE_START, /* Initial state */ + NFC_LLC_STATE_ACTIVE, /* Functional state */ + NFC_LLC_STATE_ERROR, /* Terminal state */ + NFC_LLC_STATE_PEER_LOST /* Terminal state */ +} NFC_LLC_STATE; + +struct nfc_llc { + NFC_LLC_STATE state; + gboolean idle; + guint wks; /* Remote well-known services (mask) */ +}; + +typedef +void +(*NfcLlcFunc)( + NfcLlc* llc, + void* user_data); + +typedef +void +(*NfcLlcConnectFunc)( + NfcPeerConnection* conn, + NFC_PEER_CONNECT_RESULT result, + void* user_data); + +NfcLlc* +nfc_llc_new( + NfcLlcIo* io, + NfcPeerServices* services, + const NfcLlcParam* const* params) + NFCD_INTERNAL; + +void +nfc_llc_free( + NfcLlc* llc) + NFCD_INTERNAL; + +NfcPeerConnection* +nfc_llc_connect( + NfcLlc* llc, + NfcPeerService* service, + guint rsap, + NfcLlcConnectFunc complete, + GDestroyNotify destroy, + void* user_data) + NFCD_INTERNAL; + +NfcPeerConnection* +nfc_llc_connect_sn( + NfcLlc* llc, + NfcPeerService* service, + const char* sn, + NfcLlcConnectFunc complete, + GDestroyNotify destroy, + void* user_data) + NFCD_INTERNAL; + +gulong +nfc_llc_add_state_changed_handler( + NfcLlc* llc, + NfcLlcFunc func, + void* user_data) + NFCD_INTERNAL; + +gulong +nfc_llc_add_idle_changed_handler( + NfcLlc* llc, + NfcLlcFunc func, + void* user_data) + NFCD_INTERNAL; + +gulong +nfc_llc_add_wks_changed_handler( + NfcLlc* llc, + NfcLlcFunc func, + void* user_data) + NFCD_INTERNAL; + +void +nfc_llc_remove_handler( + NfcLlc* llc, + gulong id) + NFCD_INTERNAL; + +void +nfc_llc_remove_handlers( + NfcLlc* llc, + gulong* ids, + guint count) + NFCD_INTERNAL; + +void +nfc_llc_ack( + NfcLlc* llc, + NfcPeerConnection* conn, + gboolean last) + NFCD_INTERNAL; + +gboolean +nfc_llc_i_pdu_queued( + NfcLlc* llc, + NfcPeerConnection* conn) + NFCD_INTERNAL; + +void +nfc_llc_submit_dm_pdu( + NfcLlc* llc, + guint8 dsap, + guint8 ssap, + NFC_LLC_DM_REASON reason) + NFCD_INTERNAL; + +void +nfc_llc_submit_disc_pdu( + NfcLlc* llc, + guint8 dsap, + guint8 ssap) + NFCD_INTERNAL; + +void +nfc_llc_submit_cc_pdu( + NfcLlc* llc, + NfcPeerConnection* conn) + NFCD_INTERNAL; + +void +nfc_llc_submit_i_pdu( + NfcLlc* llc, + NfcPeerConnection* conn, + const void* data, + guint len) + NFCD_INTERNAL; + +void +nfc_llc_connection_dead( + NfcLlc* llc, + NfcPeerConnection* conn) + NFCD_INTERNAL; + +gboolean +nfc_llc_cancel_connect_request( + NfcLlc* llc, + NfcPeerConnection* conn) + NFCD_INTERNAL; + +#define nfc_llc_remove_all_handlers(llc,ids) \ + nfc_llc_remove_handlers(llc, ids, G_N_ELEMENTS(ids)) + +#endif /* NFC_LLC_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_llc_io.c b/core/src/nfc_llc_io.c new file mode 100644 index 0000000..8feaec7 --- /dev/null +++ b/core/src/nfc_llc_io.c @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_llc_io_impl.h" + +#define GLOG_MODULE_NAME NFC_LLC_LOG_MODULE +#include +#include + +#define THIS(obj) NFC_LLC_IO(obj) +G_DEFINE_ABSTRACT_TYPE(NfcLlcIo, nfc_llc_io, G_TYPE_OBJECT) +#define NFC_LLC_IO_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), \ + NFC_TYPE_LLC_IO, NfcLlcIoClass) + +enum nfc_llc_io_signal { + SIGNAL_CAN_SEND, + SIGNAL_RECEIVE, + SIGNAL_ERROR, + SIGNAL_COUNT +}; + +#define SIGNAL_ERROR_NAME "nfc-llc-io-error" +#define SIGNAL_CAN_SEND_NAME "nfc-llc-io-can-send" +#define SIGNAL_RECEIVE_NAME "nfc-llc-io-receive" + +static guint nfc_llc_io_signals[SIGNAL_COUNT] = { 0 }; + +/*==========================================================================* + * Internal interface + *==========================================================================*/ + +void +nfc_llc_io_error( + NfcLlcIo* self) +{ + if (!self->error) { + self->error = TRUE; + self->can_send = FALSE; + g_signal_emit(self, nfc_llc_io_signals[SIGNAL_ERROR], 0); + } +} + +void +nfc_llc_io_can_send( + NfcLlcIo* self) +{ + if (!self->can_send && !self->error) { + self->can_send = TRUE; + g_signal_emit(self, nfc_llc_io_signals[SIGNAL_CAN_SEND], 0); + } +} + +gboolean +nfc_llc_io_receive( + NfcLlcIo* self, + const GUtilData* data) +{ + gboolean ret = FALSE; + + g_signal_emit(self, nfc_llc_io_signals[SIGNAL_RECEIVE], 0, data, &ret); + return ret; +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcLlcIo* +nfc_llc_io_ref( + NfcLlcIo* self) +{ + if (G_LIKELY(self)) { + g_object_ref(THIS(self)); + } + return self; +} + +void +nfc_llc_io_unref( + NfcLlcIo* self) +{ + if (G_LIKELY(self)) { + g_object_unref(THIS(self)); + } +} + +gboolean +nfc_llc_io_start( + NfcLlcIo* self) +{ + return G_LIKELY(self) && NFC_LLC_IO_GET_CLASS(self)->start(self); +} + +gboolean +nfc_llc_io_send( + NfcLlcIo* self, + GBytes* data) +{ + if (G_LIKELY(self)) { + GASSERT(self->can_send); + if (self->can_send) { + return NFC_LLC_IO_GET_CLASS(self)->send(self, data); + } + } + return FALSE; +} + +gulong +nfc_llc_io_add_can_send_handler( + NfcLlcIo* self, + NfcLlcIoFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_CAN_SEND_NAME, G_CALLBACK(func), user_data) : 0; +} + +gulong +nfc_llc_io_add_receive_handler( + NfcLlcIo* self, + NfcLlcIoReceiveFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_RECEIVE_NAME, G_CALLBACK(func), user_data) : 0; +} + +gulong +nfc_llc_io_add_error_handler( + NfcLlcIo* self, + NfcLlcIoFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_ERROR_NAME, G_CALLBACK(func), user_data) : 0; +} + +void +nfc_llc_io_remove_handlers( + NfcLlcIo* self, + gulong* ids, + guint count) +{ + gutil_disconnect_handlers(self, ids, count); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_llc_io_init( + NfcLlcIo* self) +{ +} + +static +void +nfc_llc_io_class_init( + NfcLlcIoClass* klass) +{ + GType type = G_OBJECT_CLASS_TYPE(klass); + + nfc_llc_io_signals[SIGNAL_CAN_SEND] = + g_signal_new(SIGNAL_CAN_SEND_NAME, type, G_SIGNAL_RUN_FIRST, 0, + NULL, NULL, NULL, G_TYPE_NONE, 0); + nfc_llc_io_signals[SIGNAL_RECEIVE] = + g_signal_new(SIGNAL_RECEIVE_NAME, type, G_SIGNAL_RUN_LAST, 0, + g_signal_accumulator_true_handled, NULL, NULL, + G_TYPE_BOOLEAN, 1, G_TYPE_POINTER); + nfc_llc_io_signals[SIGNAL_ERROR] = + g_signal_new(SIGNAL_ERROR_NAME, type, G_SIGNAL_RUN_FIRST, 0, + NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_llc_io.h b/core/src/nfc_llc_io.h new file mode 100644 index 0000000..ee301fe --- /dev/null +++ b/core/src/nfc_llc_io.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_LLC_IO_H +#define NFC_LLC_IO_H + +#include "nfc_types_p.h" + +#include + +/* + * LLC I/O API + * + * I/O modules reponsible for the symmetry procedure, i.e. sending + * SYMM packets in the Initiator mode to request data from the peer. + * It also does the polling when necessary. + * + * Basically, it hides the difference between Target and Initiator roles. + * + * If can_transmit is FALSE, the client needs to ways + * to be invoked. can_transmit field is updated before invoking the callback. + */ + +struct nfc_llc_io { + GObject object; + gboolean error; + gboolean can_send; +}; + +GType nfc_llc_io_get_type(void) NFCD_INTERNAL; +#define NFC_TYPE_LLC_IO (nfc_llc_io_get_type()) +#define NFC_LLC_IO(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + NFC_TYPE_LLC_IO, NfcLlcIo)) + +#define LLC_IO_EXPECT_MORE (TRUE) +#define LLC_IO_IGNORE (FALSE) + +typedef +gboolean +(*NfcLlcIoReceiveFunc)( + NfcLlcIo* io, + const GUtilData* data, + gpointer user_data); + +typedef +void +(*NfcLlcIoFunc)( + NfcLlcIo* io, + gpointer user_data); + +NfcLlcIo* +nfc_llc_io_ref( + NfcLlcIo* io) + NFCD_INTERNAL; + +void +nfc_llc_io_unref( + NfcLlcIo* io) + NFCD_INTERNAL; + +gboolean +nfc_llc_io_start( + NfcLlcIo* io) + NFCD_INTERNAL; + +gboolean +nfc_llc_io_send( + NfcLlcIo* io, + GBytes* data) + NFCD_INTERNAL; + +gulong +nfc_llc_io_add_can_send_handler( + NfcLlcIo* io, + NfcLlcIoFunc func, + void* user_data) + NFCD_INTERNAL; + +gulong +nfc_llc_io_add_receive_handler( + NfcLlcIo* io, + NfcLlcIoReceiveFunc func, + void* user_data) + NFCD_INTERNAL; + +gulong +nfc_llc_io_add_error_handler( + NfcLlcIo* io, + NfcLlcIoFunc func, + void* user_data) + NFCD_INTERNAL; + +void +nfc_llc_io_remove_handlers( + NfcLlcIo* io, + gulong* ids, + guint count) + NFCD_INTERNAL; + +#define nfc_llc_io_remove_all_handlers(io,ids) \ + nfc_llc_io_remove_handlers(io, ids, G_N_ELEMENTS(ids)) + +/* Initiator-side I/O */ + +NfcLlcIo* +nfc_llc_io_initiator_new( + NfcTarget* target) + NFCD_INTERNAL; + +/* Target-side I/O */ + +NfcLlcIo* +nfc_llc_io_target_new( + NfcInitiator* initiator) + NFCD_INTERNAL; + +#endif /* NFC_LLC_IO_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_llc_io_impl.h b/core/src/nfc_llc_io_impl.h new file mode 100644 index 0000000..7e93d8c --- /dev/null +++ b/core/src/nfc_llc_io_impl.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_LLC_IO_IMPL_H +#define NFC_LLC_IO_IMPL_H + +#include "nfc_types_p.h" +#include "nfc_llc_io.h" + +/* Internal API for use by NfcLlcIo implemenations */ + +typedef struct nfc_llc_io_class { + GObjectClass parent; + gboolean (*start)(NfcLlcIo* io); + gboolean (*send)(NfcLlcIo* io, GBytes* data); +} NfcLlcIoClass; + +#define NFC_LLC_IO_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), \ + NFC_TYPE_LLC_IO, NfcLlcIoClass) + +void +nfc_llc_io_error( + NfcLlcIo* io) + NFCD_INTERNAL; + +void +nfc_llc_io_can_send( + NfcLlcIo* io) + NFCD_INTERNAL; + +gboolean +nfc_llc_io_receive( + NfcLlcIo* io, + const GUtilData* data) + NFCD_INTERNAL; + +#endif /* NFC_LLC_IO_IMPL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_llc_io_initiator.c b/core/src/nfc_llc_io_initiator.c new file mode 100644 index 0000000..5a052ee --- /dev/null +++ b/core/src/nfc_llc_io_initiator.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_llc_io_impl.h" +#include "nfc_target_p.h" + +#define GLOG_MODULE_NAME NFC_LLC_LOG_MODULE +#include + +#define DEFAULT_POLL_PERIOD (100) /* ms */ + +typedef struct nfc_llc_io_initiator { + NfcLlcIo io; + NfcTarget* target; + guint poll_period; + guint poll_id; + guint tx_id; +} NfcLlcIoInitiator; + +#define THIS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + THIS_TYPE, NfcLlcIoInitiator)) +#define THIS_TYPE (nfc_llc_io_initiator_get_type()) +#define PARENT_TYPE NFC_TYPE_LLC_IO +#define PARENT_CLASS (nfc_llc_io_initiator_parent_class) + +GType nfc_llc_io_initiator_get_type(void) NFCD_INTERNAL; +typedef NfcLlcIoClass NfcLlcIoInitiatorClass; +G_DEFINE_TYPE(NfcLlcIoInitiator, nfc_llc_io_initiator, PARENT_TYPE) + +static +gboolean +nfc_llc_io_initiator_send_symm( + NfcLlcIoInitiator* self); + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +gboolean +nfc_llc_io_initiator_poll( + gpointer user_data) +{ + NfcLlcIoInitiator* self = THIS(user_data); + + /* Polling is happening with can_send being TRUE */ + GASSERT(self->io.can_send); + GASSERT(self->poll_id); + self->poll_id = 0; + GDEBUG("< SYMM (poll)"); + nfc_llc_io_initiator_send_symm(self); + return G_SOURCE_REMOVE; +} + +static +void +nfc_llc_io_initiator_symm_transmit_done( + NfcTarget* target, + NFC_TRANSMIT_STATUS status, + const void* data, + guint len, + void* user_data) +{ + NfcLlcIoInitiator* self = THIS(user_data); + NfcLlcIo* io = &self->io; + + GASSERT(!io->can_send); + GASSERT(self->tx_id); + self->tx_id = 0; + io->can_send = TRUE; /* Don't issue a signal just yet */ + nfc_llc_io_ref(io); + if (status == NFC_TRANSMIT_STATUS_OK) { + GUtilData received; + + received.bytes = data; + received.size = len; + if (nfc_llc_io_receive(io, &received)) { + if (!self->tx_id) { + /* Something else might be coming, don't wait */ + GDEBUG("< SYMM"); + nfc_llc_io_initiator_send_symm(self); + } + } else if (!self->tx_id) { + /* Nothing is expected to arrive urgently, start polling. */ + self->poll_id = g_timeout_add(self->poll_period, + nfc_llc_io_initiator_poll, self); + nfc_llc_io_can_send(io); + } + } else { + nfc_llc_io_error(io); + } + nfc_llc_io_unref(io); +} + +static +void +nfc_llc_io_initiator_pdu_transmit_done( + NfcTarget* target, + NFC_TRANSMIT_STATUS status, + const void* data, + guint len, + void* user_data) +{ + NfcLlcIoInitiator* self = THIS(user_data); + NfcLlcIo* io = &self->io; + + GASSERT(!io->can_send); + GASSERT(self->tx_id); + self->tx_id = 0; + io->can_send = TRUE; /* Don't issue a signal just yet */ + nfc_llc_io_ref(io); + if (status == NFC_TRANSMIT_STATUS_OK) { + GUtilData received; + + received.bytes = data; + received.size = len; + nfc_llc_io_receive(io, &received); + if (!self->tx_id) { + GDEBUG("< SYMM"); + nfc_llc_io_initiator_send_symm(self); + } + } else { + nfc_llc_io_error(io); + } + nfc_llc_io_unref(io); +} + +static +gboolean +nfc_llc_io_initiator_send_symm( + NfcLlcIoInitiator* self) +{ + static const guint8 SYMM[] = { 0x00, 0x00 }; + NfcLlcIo* io = &self->io; + + GASSERT(!self->tx_id); + io->can_send = FALSE; + self->tx_id = nfc_target_transmit(self->target, SYMM, sizeof(SYMM), NULL, + nfc_llc_io_initiator_symm_transmit_done, NULL, self); + if (self->tx_id) { + return TRUE; + } else { + nfc_llc_io_error(io); + return FALSE; + } +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcLlcIo* +nfc_llc_io_initiator_new( + NfcTarget* target) +{ + if (G_LIKELY(target)) { + NfcLlcIoInitiator* self = g_object_new(THIS_TYPE, NULL); + + self->target = nfc_target_ref(target); + self->poll_period = DEFAULT_POLL_PERIOD; + return &self->io; + } + return NULL; +} + +/*==========================================================================* + * Methods + *==========================================================================*/ + +static +gboolean +nfc_llc_io_initiator_start( + NfcLlcIo* io) +{ + return nfc_llc_io_initiator_send_symm(THIS(io)); +} + +static +gboolean +nfc_llc_io_initiator_send( + NfcLlcIo* io, + GBytes* send) +{ + gsize size; + const guint8* data = g_bytes_get_data(send, &size); + NfcLlcIoInitiator* self = THIS(io); + + GASSERT(io->can_send); + if (self->poll_id) { + /* Cancel scheduled polling */ + g_source_remove(self->poll_id); + self->poll_id = 0; + } + + io->can_send = FALSE; + self->tx_id = nfc_target_transmit(self->target, data, size, NULL, + nfc_llc_io_initiator_pdu_transmit_done, NULL, self); + if (self->tx_id) { + return TRUE; + } else { + nfc_llc_io_error(io); + return FALSE; + } +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_llc_io_initiator_init( + NfcLlcIoInitiator* self) +{ + self->io.can_send = TRUE; +} + +static +void +nfc_llc_io_initiator_finalize( + GObject* object) +{ + NfcLlcIoInitiator* self = THIS(object); + + if (self->poll_id) { + g_source_remove(self->poll_id); + } + nfc_target_cancel_transmit(self->target, self->tx_id); + nfc_target_unref(self->target); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +nfc_llc_io_initiator_class_init( + NfcLlcIoInitiatorClass* klass) +{ + klass->start = nfc_llc_io_initiator_start; + klass->send = nfc_llc_io_initiator_send; + G_OBJECT_CLASS(klass)->finalize = nfc_llc_io_initiator_finalize; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_llc_io_target.c b/core/src/nfc_llc_io_target.c new file mode 100644 index 0000000..29b6d1e --- /dev/null +++ b/core/src/nfc_llc_io_target.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_llc_io_impl.h" +#include "nfc_initiator_p.h" + +#define GLOG_MODULE_NAME NFC_LLC_LOG_MODULE +#include + +typedef struct nfc_llc_io_target { + NfcLlcIo io; + NfcInitiator* initiator; + NfcTransmission* transmission; + gulong tx_handler_id; +} NfcLlcIoTarget; + +#define THIS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + THIS_TYPE, NfcLlcIoTarget)) +#define THIS_TYPE (nfc_llc_io_target_get_type()) +#define PARENT_TYPE NFC_TYPE_LLC_IO +#define PARENT_CLASS (nfc_llc_io_target_parent_class) + +GType nfc_llc_io_target_get_type(void) NFCD_INTERNAL; +typedef NfcLlcIoClass NfcLlcIoTargetClass; +G_DEFINE_TYPE(NfcLlcIoTarget, nfc_llc_io_target, PARENT_TYPE) + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +void +nfc_llc_io_target_response_sent( + NfcTransmission* transmission, + gboolean ok, + void* user_data) +{ + NfcLlcIoTarget* self = THIS(user_data); + NfcLlcIo* io = &self->io; + + GASSERT(transmission == self->transmission); + nfc_transmission_unref(self->transmission); + self->transmission = NULL; + if (!ok) { + nfc_llc_io_error(io); + } +} + +static +gboolean +nfc_llc_io_target_transmission_handler( + NfcInitiator* initiator, + NfcTransmission* transmission, + const GUtilData* data, + void* user_data) +{ + NfcLlcIoTarget* self = THIS(user_data); + + GASSERT(!self->transmission); + if (!self->transmission) { + NfcLlcIo* io = &self->io; + + self->transmission = nfc_transmission_ref(transmission); + if (data) { + io->can_send = TRUE; + nfc_llc_io_receive(io, data); + } else { + nfc_llc_io_can_send(io); + } + /* nfc_llc_io_target_send() sets can_send to FALSE */ + if (self->transmission && io->can_send) { + static const guint8 SYMM[] = { 0x00, 0x00 }; + + /* LLC isn't sending anything, respond with a SYMM */ + GDEBUG("< SYMM"); + io->can_send = FALSE; + if (nfc_transmission_respond(transmission, SYMM, sizeof(SYMM), + nfc_llc_io_target_response_sent, self)) { + } else { + nfc_llc_io_error(io); + } + } + return TRUE; + } + return FALSE; +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcLlcIo* +nfc_llc_io_target_new( + NfcInitiator* initiator) +{ + if (G_LIKELY(initiator)) { + NfcLlcIoTarget* self = g_object_new(THIS_TYPE, NULL); + + self->initiator = nfc_initiator_ref(initiator); + self->tx_handler_id = nfc_initiator_add_transmission_handler(initiator, + nfc_llc_io_target_transmission_handler, self); + return &self->io; + } + return NULL; +} + +/*==========================================================================* + * Methods + *==========================================================================*/ + +static +gboolean +nfc_llc_io_target_start( + NfcLlcIo* io) +{ + return TRUE; +} + +static +gboolean +nfc_llc_io_target_send( + NfcLlcIo* io, + GBytes* send) +{ + NfcLlcIoTarget* self = THIS(io); + gsize size; + gconstpointer data = g_bytes_get_data(send, &size); + + io->can_send = FALSE; + if (nfc_transmission_respond(self->transmission, data, (guint)size, + nfc_llc_io_target_response_sent, self)) { + return TRUE; + } else { + nfc_llc_io_error(io); + return FALSE; + } +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_llc_io_target_init( + NfcLlcIoTarget* self) +{ +} + +static +void +nfc_llc_io_target_finalize( + GObject* object) +{ + NfcLlcIoTarget* self = THIS(object); + + nfc_transmission_unref(self->transmission); + nfc_initiator_remove_handler(self->initiator, self->tx_handler_id); + nfc_initiator_unref(self->initiator); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +nfc_llc_io_target_class_init( + NfcLlcIoTargetClass* klass) +{ + klass->start = nfc_llc_io_target_start; + klass->send = nfc_llc_io_target_send; + G_OBJECT_CLASS(klass)->finalize = nfc_llc_io_target_finalize; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_llc_param.c b/core/src/nfc_llc_param.c new file mode 100644 index 0000000..9481de7 --- /dev/null +++ b/core/src/nfc_llc_param.c @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_llc_param.h" + +#include + +/* + * NFCForum-TS-LLCP_1.1 + * Section 4.4 LLC Parameter Format + */ + +GByteArray* +nfc_llc_param_encode( + const NfcLlcParam* const* params, + GByteArray* dest, + guint maxlen) +{ + if (params) { + const NfcLlcParam* const* ptr = params; + + if (!dest) dest = g_byte_array_new(); + while (*ptr) { + const NfcLlcParam* param = *ptr++; + const NfcLlcParamValue* value = ¶m->value; + const guint len = dest->len; + guint8* tlv; + guint l, v; + + switch (param->type) { + /* 4.5.1 Version Number, VERSION */ + case NFC_LLC_PARAM_VERSION: + g_byte_array_set_size(dest, len + 3); + tlv = dest->data + len; + tlv[0] = (guint8)param->type; + tlv[1] = 0x01; + tlv[2] = value->version; + break; + /* 4.5.2 Maximum Information Unit Extension, MIUX */ + case NFC_LLC_PARAM_MIUX: + g_byte_array_set_size(dest, len + 4); + tlv = dest->data + len; + v = MAX(value->miu, NFC_LLC_MIU_MIN) - NFC_LLC_MIU_MIN; + tlv[0] = (guint8)param->type; + tlv[1] = 0x02; + tlv[2] = (v >> 8) & 0x07; + tlv[3] = (guint8)v; + break; + /* 4.5.3 Well-Known Service List, WKS */ + case NFC_LLC_PARAM_WKS: + g_byte_array_set_size(dest, len + 4); + tlv = dest->data + len; + tlv[0] = (guint8)param->type; + tlv[1] = 0x02; + tlv[2] = (value->wks >> 8) & 0x07; + tlv[3] = (guint8)value->wks; + break; + /* 4.5.4 Link Timeout, LTO */ + case NFC_LLC_PARAM_LTO: + g_byte_array_set_size(dest, len + 3); + tlv = dest->data + len; + v = value->lto / 10; + tlv[0] = (guint8)param->type; + tlv[1] = 0x01; + tlv[2] = (guint8)MIN(v, 0xff); + break; + /* 4.5.5 Receive Window Size, RW */ + case NFC_LLC_PARAM_RW: + g_byte_array_set_size(dest, len + 3); + tlv = dest->data + len; + tlv[0] = (guint8)param->type; + tlv[1] = 0x01; + tlv[2] = (guint8)MIN(value->rw, 0x0f); + break; + /* 4.5.6 Service Name, SN */ + case NFC_LLC_PARAM_SN: + l = (guint)(value->sn ? strlen(value->sn) : 0); + l = MIN(l, 0xff); + g_byte_array_set_size(dest, len + 2 + l); + tlv = dest->data + len; + tlv[0] = (guint8)param->type; + tlv[1] = (guint8)l; + if (l) memcpy(tlv + 2, value->sn, l); + break; + /* 4.5.7 Option, OPT */ + case NFC_LLC_PARAM_OPT: + g_byte_array_set_size(dest, len + 3); + tlv = dest->data + len; + tlv[0] = (guint8)param->type; + tlv[1] = 0x01; + tlv[2] = (guint8)value->opt; + break; + /* 4.5.8 Service Discovery Request, SDREQ */ + case NFC_LLC_PARAM_SDREQ: + l = (guint)(value->sdreq.uri ? strlen(value->sdreq.uri) : 0); + l = MIN(l, 0xfe); + g_byte_array_set_size(dest, len + 3 + l); + tlv = dest->data + len; + tlv[0] = (guint8)param->type; + tlv[1] = (guint8)(l + 1); + tlv[2] = value->sdreq.tid; + if (l) memcpy(tlv + 3, value->sdreq.uri, l); + break; + /* 4.5.9 Service Discovery Response, SDRES */ + case NFC_LLC_PARAM_SDRES: + g_byte_array_set_size(dest, len + 4); + tlv = dest->data + len; + tlv[0] = (guint8)param->type; + tlv[1] = 0x02; + tlv[2] = value->sdres.tid; + tlv[3] = value->sdres.sap; + break; + } + if (maxlen && dest->len >= maxlen) { + if (dest->len > maxlen) { + g_byte_array_set_size(dest, len); + } + break; + } + } + } + return dest; +} + +NfcLlcParam** +nfc_llc_param_decode( + const GUtilData* tlvs) +{ + NfcLlcParam** params = NULL; + + if (tlvs) { + const guint8* ptr = tlvs->bytes; + const guint8* end = ptr + tlvs->size; + GPtrArray* list = g_ptr_array_new(); + + while (ptr + 1 < end && ptr + (ptr[1] + 1) < end) { + const guint t = ptr[0]; + const guint l = ptr[1]; + const guint8* v = ptr + 2; + NfcLlcParam* param = NULL; + char* buf; + + switch (t) { + /* 4.5.1 Version Number, VERSION */ + case NFC_LLC_PARAM_VERSION: + if (l == 1) { + param = g_new0(NfcLlcParam, 1); + param->value.version = v[0]; + } + break; + /* 4.5.2 Maximum Information Unit Extension, MIUX */ + case NFC_LLC_PARAM_MIUX: + if (l == 2) { + const guint miux = (((((guint)v[0]) << 8) | v[1]) & 0x7ff); + + param = g_new0(NfcLlcParam, 1); + param->value.miu = miux + NFC_LLC_MIU_MIN; + } + break; + /* 4.5.3 Well-Known Service List, WKS */ + case NFC_LLC_PARAM_WKS: + if (l == 2) { + param = g_new0(NfcLlcParam, 1); + param->value.wks = ((((guint)v[0]) << 8) | v[1]); + } + break; + /* 4.5.4 Link Timeout, LTO */ + case NFC_LLC_PARAM_LTO: + /* + * The LTO parameter value SHALL be an 8-bit unsigned + * integer that specifies the link timeout value in + * multiples of 10 milliseconds. + * + * If no LTO parameter is transmitted or if the LTO + * parameter value is zero, the default link timeout + * value of 100 milliseconds SHALL be used. + */ + if (l == 1) { + param = g_new0(NfcLlcParam, 1); + param->value.lto = v[0] ? (10 * (guint)v[0]) : + NFC_LLC_LTO_DEFAULT; + } + break; + /* 4.5.5 Receive Window Size, RW */ + case NFC_LLC_PARAM_RW: + if (l == 1) { + param = g_new0(NfcLlcParam, 1); + param->value.rw = (v[0] & 0x0f); + } + break; + /* 4.5.6 Service Name, SN */ + case NFC_LLC_PARAM_SN: + param = g_malloc0(G_ALIGN8(sizeof(NfcLlcParam)) + l + 1); + buf = ((char*)param) + G_ALIGN8(sizeof(NfcLlcParam)); + param->value.sn = buf; + memcpy(buf, v, l); + break; + /* 4.5.7 Option, OPT */ + case NFC_LLC_PARAM_OPT: + if (l == 1) { + param = g_new0(NfcLlcParam, 1); + param->value.opt = v[0]; + } + break; + /* 4.5.8 Service Discovery Request, SDREQ */ + case NFC_LLC_PARAM_SDREQ: + if (l >= 1) { + param = g_malloc0(G_ALIGN8(sizeof(NfcLlcParam)) + l); + buf = ((char*)param) + G_ALIGN8(sizeof(NfcLlcParam)); + param->value.sdreq.tid = v[0]; + param->value.sdreq.uri = buf; + memcpy(buf, v + 1, l - 1); + } + break; + /* 4.5.9 Service Discovery Response, SDRES */ + case NFC_LLC_PARAM_SDRES: + if (l == 2) { + param = g_new0(NfcLlcParam, 1); + param->value.sdres.tid = v[0]; + param->value.sdres.sap = (v[1] & 0x3f); + } + break; + } + if (param) { + param->type = t; + g_ptr_array_add(list, param); + } + /* Advance to the next block */ + ptr += l + 2; + } + g_ptr_array_add(list, NULL); + params = (NfcLlcParam**)g_ptr_array_free(list, FALSE); + } + return params; +} + +NfcLlcParam** +nfc_llc_param_decode_bytes( + const void* data, + guint size) +{ + if (data) { + GUtilData tlvs; + + tlvs.bytes = data; + tlvs.size = size; + return nfc_llc_param_decode(&tlvs); + } else { + return NULL; + } +} + +guint +nfc_llc_param_count( + const NfcLlcParam* const* params) +{ + guint count = 0; + + if (params) { + const NfcLlcParam* const* ptr = params; + + while (*ptr) { + ptr++; + count++; + } + } + return count; +} + +const NfcLlcParam* +nfc_llc_param_find( + const NfcLlcParam* const* params, + NFC_LLC_PARAM_TYPE type) +{ + if (params) { + const NfcLlcParam* const* ptr = params; + + while (*ptr) { + const NfcLlcParam* param = *ptr++; + + if (param->type == type) { + return param; + } + } + } + return NULL; +} + +void +nfc_llc_param_free( + NfcLlcParam** params) +{ + if (params) { + NfcLlcParam** ptr = params; + + while (*ptr) { + g_free(*ptr++); + } + g_free(params); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_llc_param.h b/core/src/nfc_llc_param.h new file mode 100644 index 0000000..9fbaaa9 --- /dev/null +++ b/core/src/nfc_llc_param.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_LLC_PARAM_H +#define NFC_LLC_PARAM_H + +#include "nfc_types_p.h" + +#define NFC_LLC_MIU_MIN (128) +#define NFC_LLC_MIU_MAX (0x7ff + NFC_LLC_MIU_MIN) +#define NFC_LLC_MIU_DEFAULT NFC_LLC_MIU_MIN +#define NFC_LLC_LTO_DEFAULT (100) /* milliseconds */ +#define NFC_LLC_RW_DEFAULT (1) +#define NFC_LLC_RW_MAX (0xf) + +#define NFC_LLCP_VERSION_1_0 (0x10) /* LLCP 1.0 */ +#define NFC_LLCP_VERSION_1_1 (0x11) /* LLCP 1.1 */ + +typedef enum nfc_llc_param_type { + NFC_LLC_PARAM_VERSION = 1, + NFC_LLC_PARAM_MIUX = 2, + NFC_LLC_PARAM_WKS = 3, + NFC_LLC_PARAM_LTO = 4, + NFC_LLC_PARAM_RW = 5, + NFC_LLC_PARAM_SN = 6, + NFC_LLC_PARAM_OPT = 7, + NFC_LLC_PARAM_SDREQ = 8, /* LLCP 1.1 */ + NFC_LLC_PARAM_SDRES = 9 /* LLCP 1.1 */ +} NFC_LLC_PARAM_TYPE; + +typedef enum nfc_llc_opt { + NFC_LLC_OPT_NONE = 0x00, + NFC_LLC_OPT_CL = 0x01, /* Connectionless link service */ + NFC_LLC_OPT_CO = 0x02 /* Connection-oriented link service */ +} NFC_LLC_OPT; + +typedef struct nfc_llc_param_sdreq { + guint8 tid; + const char* uri; +} NfcLlcParamSdReq; + +typedef struct nfc_llc_param_sdres { + guint8 tid; + guint8 sap; +} NfcLlcParamSdRes; + +typedef union nfc_llc_param_value { + guint8 version; + guint miu; /* MIUX + NFC_LLC_MIU_MIN */ + guint wks; + guint lto; /* milliseconds */ + guint8 rw; + NFC_LLC_OPT opt; + const char* sn; + NfcLlcParamSdReq sdreq; + NfcLlcParamSdRes sdres; +} NfcLlcParamValue; + +struct nfc_llc_param { + NFC_LLC_PARAM_TYPE type; + NfcLlcParamValue value; +}; + +GByteArray* +nfc_llc_param_encode( + const NfcLlcParam* const* params, + GByteArray* dest, + guint maxlen) + NFCD_INTERNAL; + +NfcLlcParam** +nfc_llc_param_decode( + const GUtilData* tlvs) + NFCD_INTERNAL; + +NfcLlcParam** +nfc_llc_param_decode_bytes( + const void* data, + guint size) + NFCD_INTERNAL; + +guint +nfc_llc_param_count( + const NfcLlcParam* const* params) + NFCD_INTERNAL; + +const NfcLlcParam* +nfc_llc_param_find( + const NfcLlcParam* const* params, + NFC_LLC_PARAM_TYPE type) + NFCD_INTERNAL; + +void +nfc_llc_param_free( + NfcLlcParam** list) + NFCD_INTERNAL; + +static inline const NfcLlcParam** nfc_llc_param_constify(NfcLlcParam** params) + { return (const NfcLlcParam**)params; } + +#endif /* NFC_LLC_PARAM_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_manager.c b/core/src/nfc_manager.c index 24203fd..7061ab0 100644 --- a/core/src/nfc_manager.c +++ b/core/src/nfc_manager.c @@ -35,10 +35,12 @@ #include "nfc_manager_p.h" #include "internal/nfc_manager_i.h" #include "nfc_adapter_p.h" +#include "nfc_peer_services.h" #include "nfc_plugins.h" #include "nfc_log.h" #include +#include #include @@ -46,12 +48,22 @@ GLOG_MODULE_DEFINE("nfc-core"); #define NFC_ADAPTER_NAME_FORMAT "nfc%u" +struct nfc_mode_request { + NfcModeRequest* next; + NfcManager* manager; + NFC_MODE enable; + NFC_MODE disable; +}; + struct nfc_manager_priv { NfcPlugins* plugins; + NfcPeerServices* services; + NfcModeRequest* p2p_request; GHashTable* adapters; guint next_adapter_index; gboolean requested_power; - NFC_MODE requested_mode; + NFC_MODE default_mode; + NfcModeRequest* mode_requests; }; typedef GObjectClass NfcManagerClass; @@ -61,6 +73,7 @@ enum nfc_manager_signal { SIGNAL_ADAPTER_ADDED, SIGNAL_ADAPTER_REMOVED, SIGNAL_ENABLED_CHANGED, + SIGNAL_MODE_CHANGED, SIGNAL_STOPPED, SIGNAL_COUNT }; @@ -68,6 +81,7 @@ enum nfc_manager_signal { #define SIGNAL_ADAPTER_ADDED_NAME "nfc-manager-adapter-added" #define SIGNAL_ADAPTER_REMOVED_NAME "nfc-manager-adapter-removed" #define SIGNAL_ENABLED_CHANGED_NAME "nfc-manager-enabled-changed" +#define SIGNAL_MODE_CHANGED_NAME "nfc-manager-mode-changed" #define SIGNAL_STOPPED_NAME "nfc-manager-stopped" static guint nfc_manager_signals[SIGNAL_COUNT] = { 0 }; @@ -143,6 +157,121 @@ nfc_manager_unref_adapters( g_free(adapters); } +static +void +nfc_manager_update_adapter_modes( + NfcManager* self) +{ + NfcAdapter** adapters = nfc_manager_ref_adapters(self->priv); + + if (adapters) { + const NFC_MODE mode = self->mode; + NfcAdapter** ptr = adapters; + + while (*ptr) { + nfc_adapter_request_mode(*ptr++, mode); + } + nfc_manager_unref_adapters(adapters); + } +} + +static +gboolean +nfc_manager_update_mode( + NfcManager* self) +{ + NfcManagerPriv* priv = self->priv; + const NFC_MODE prev_mode = self->mode; + const NfcModeRequest* req = priv->mode_requests; + + self->mode = priv->default_mode; + for (req = priv->mode_requests; req; req = req->next) { + self->mode = (self->mode & ~req->disable) | req->enable; + } + if (self->mode != prev_mode) { + GDEBUG("NFC mode 0x%02x", self->mode); + g_signal_emit(self, nfc_manager_signals[SIGNAL_MODE_CHANGED], 0); + return TRUE; + } else { + return FALSE; + } +} + +static +NfcModeRequest* +nfc_manager_mode_request_new_internal( + NfcManager* self, + gboolean internal, + NFC_MODE enable, + NFC_MODE disable) +{ + NfcManagerPriv* priv = self->priv; + NfcModeRequest* req = g_slice_new0(NfcModeRequest); + + if (!internal) { + req->manager = nfc_manager_ref(self); + } + req->enable = enable; + req->disable = disable; + req->next = priv->mode_requests; + priv->mode_requests = req; + if (nfc_manager_update_mode(self)) { + nfc_manager_update_adapter_modes(self); + } + return req; +} + +static +void +nfc_manager_mode_request_free_internal( + NfcManager* self, + NfcModeRequest* req) +{ + NfcManagerPriv* priv = self->priv; + + /* Remove it from the list */ + if (priv->mode_requests == req) { + priv->mode_requests = req->next; + } else { + NfcModeRequest* prev = priv->mode_requests; + + while (prev) { + if (prev->next == req) { + prev->next = req->next; + break; + } + prev = prev->next; + } + } + + /* Update the effective mode */ + if (nfc_manager_update_mode(self)) { + nfc_manager_update_adapter_modes(self); + } + + nfc_manager_unref(req->manager); /* Can be NULL */ + req->next = NULL; + gutil_slice_free(req); +} + +static +void +nfc_manager_release_p2p_mode_request( + NfcManager* self) +{ + NfcManagerPriv* priv = self->priv; + NfcModeRequest* req = priv->p2p_request; + + if (req) { + /* + * Since nfc_manager_mode_request_free_internal() may emit signals, + * we need to NULLify the pointer beforehand. + */ + priv->p2p_request = NULL; + nfc_manager_mode_request_free_internal(self, req); + } +} + /*==========================================================================* * Interface *==========================================================================*/ @@ -213,8 +342,9 @@ nfc_manager_add_adapter( } nfc_adapter_set_name(adapter, name); + nfc_adapter_set_services(adapter, priv->services); nfc_adapter_set_enabled(adapter, self->enabled); - nfc_adapter_request_mode(adapter, priv->requested_mode); + nfc_adapter_request_mode(adapter, self->mode); nfc_adapter_request_power(adapter, priv->requested_power); g_hash_table_insert(priv->adapters, name, nfc_adapter_ref(adapter)); g_free(self->adapters); @@ -298,16 +428,12 @@ nfc_manager_request_mode( { if (G_LIKELY(self)) { NfcManagerPriv* priv = self->priv; - NfcAdapter** adapters = nfc_manager_ref_adapters(self->priv); - priv->requested_mode = mode; - if (adapters) { - NfcAdapter** ptr = adapters; - - while (*ptr) { - nfc_adapter_request_mode(*ptr++, mode); - } - nfc_manager_unref_adapters(adapters); + if (priv->default_mode != mode) { + GDEBUG("Default mode 0x%02x", mode); + priv->default_mode = mode; + nfc_manager_update_mode(self); + nfc_manager_update_adapter_modes(self); } } } @@ -333,6 +459,40 @@ nfc_manager_stop( } } +gboolean +nfc_manager_register_service( + NfcManager* self, + NfcPeerService* service) /* Since 1.1.0 */ +{ + if (G_LIKELY(self)) { + NfcManagerPriv* priv = self->priv; + + if (nfc_peer_services_add(priv->services, service)) { + if (!priv->p2p_request) { + priv->p2p_request = nfc_manager_mode_request_new_internal(self, + TRUE, NFC_MODES_P2P, NFC_MODE_NONE); + } + return TRUE; + } + } + return FALSE; +} + +void +nfc_manager_unregister_service( + NfcManager* self, + NfcPeerService* service) /* Since 1.1.0 */ +{ + if (G_LIKELY(self)) { + NfcManagerPriv* priv = self->priv; + + nfc_peer_services_remove(priv->services, service); + if (!priv->services->list[0]) { + nfc_manager_release_p2p_mode_request(self); + } + } +} + gulong nfc_manager_add_adapter_added_handler( NfcManager* self, @@ -363,6 +523,16 @@ nfc_manager_add_enabled_changed_handler( SIGNAL_ENABLED_CHANGED_NAME, G_CALLBACK(func), user_data) : 0; } +gulong +nfc_manager_add_mode_changed_handler( + NfcManager* self, + NfcManagerFunc func, + void* user_data) /* Since 1.1.0 */ +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_MODE_CHANGED_NAME, G_CALLBACK(func), user_data) : 0; +} + gulong nfc_manager_add_stopped_handler( NfcManager* self, @@ -392,6 +562,26 @@ nfc_manager_remove_handlers( gutil_disconnect_handlers(self, ids, count); } +NfcModeRequest* +nfc_manager_mode_request_new( + NfcManager* self, + NFC_MODE enable, + NFC_MODE disable) /* Since 1.1.0 */ +{ + return (G_LIKELY(self) && G_LIKELY(enable || disable)) ? + nfc_manager_mode_request_new_internal(self, FALSE, enable, disable) : + NULL; +} + +void +nfc_manager_mode_request_free( + NfcModeRequest* req) /* Since 1.1.0 */ +{ + if (G_LIKELY(req)) { + nfc_manager_mode_request_free_internal(req->manager, req); + } +} + /*==========================================================================* * Internal interface *==========================================================================*/ @@ -434,7 +624,8 @@ nfc_manager_init( self->priv = priv; self->adapters = g_new0(NfcAdapter*, 1); self->enabled = TRUE; - priv->requested_mode = NFC_MODE_READER_WRITER; + self->mode = priv->default_mode = NFC_MODE_READER_WRITER; + priv->services = nfc_peer_services_new(); priv->adapters = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref); } @@ -447,7 +638,9 @@ nfc_manager_finalize( NfcManager* self = NFC_MANAGER(object); NfcManagerPriv* priv = self->priv; + nfc_manager_release_p2p_mode_request(self); nfc_plugins_free(priv->plugins); + nfc_peer_services_unref(priv->services); g_hash_table_destroy(priv->adapters); g_free(self->adapters); G_OBJECT_CLASS(nfc_manager_parent_class)->finalize(object); @@ -458,25 +651,27 @@ void nfc_manager_class_init( NfcManagerClass* klass) { + GType type = G_OBJECT_CLASS_TYPE(klass); GObjectClass* object_class = G_OBJECT_CLASS(klass); g_type_class_add_private(klass, sizeof(NfcManagerPriv)); object_class->finalize = nfc_manager_finalize; nfc_manager_signals[SIGNAL_ADAPTER_ADDED] = - g_signal_new(SIGNAL_ADAPTER_ADDED_NAME, G_OBJECT_CLASS_TYPE(klass), - G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, - G_TYPE_OBJECT); + g_signal_new(SIGNAL_ADAPTER_ADDED_NAME, type, G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); nfc_manager_signals[SIGNAL_ADAPTER_REMOVED] = - g_signal_new(SIGNAL_ADAPTER_REMOVED_NAME, G_OBJECT_CLASS_TYPE(klass), - G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, - G_TYPE_OBJECT); + g_signal_new(SIGNAL_ADAPTER_REMOVED_NAME, type, G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); nfc_manager_signals[SIGNAL_ENABLED_CHANGED] = - g_signal_new(SIGNAL_ENABLED_CHANGED_NAME, G_OBJECT_CLASS_TYPE(klass), - G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + g_signal_new(SIGNAL_ENABLED_CHANGED_NAME, type, G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + nfc_manager_signals[SIGNAL_MODE_CHANGED] = + g_signal_new(SIGNAL_MODE_CHANGED_NAME, type, G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 0); nfc_manager_signals[SIGNAL_STOPPED] = - g_signal_new(SIGNAL_STOPPED_NAME, G_OBJECT_CLASS_TYPE(klass), - G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + g_signal_new(SIGNAL_STOPPED_NAME, type, G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } /* diff --git a/core/src/nfc_peer.c b/core/src/nfc_peer.c new file mode 100644 index 0000000..d75dea7 --- /dev/null +++ b/core/src/nfc_peer.c @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include "nfc_peer_p.h" +#include "nfc_llc.h" +#include "nfc_llc_param.h" +#include "nfc_peer_services.h" +#include "nfc_snep_server.h" +#include "nfc_ndef.h" + +#define GLOG_MODULE_NAME NFC_PEER_LOG_MODULE +#include +#include + +GLOG_MODULE_DEFINE2("nfc-peer", NFC_CORE_LOG_MODULE); + +enum { + LLC_EVENT_STATE, + LLC_EVENT_IDLE, + LLC_EVENT_WKS, + LLC_EVENT_COUNT +}; + +enum { + SNEP_EVENT_NDEF, + SNEP_EVENT_STATE, + SNEP_EVENT_COUNT +}; + +typedef struct nfc_peer_connect { + NfcPeer* peer; + NfcPeerConnectFunc complete; + void* user_data; + GDestroyNotify destroy; +} NfcPeerConnect; + +struct nfc_peer_priv { + NfcLlc* llc; + NfcPeerServices* services; + NfcSnepServer* snep; + char* name; + gulong llc_event_id[LLC_EVENT_COUNT]; + gulong snep_event_id[SNEP_EVENT_COUNT]; + gboolean ndef_reception_started; +}; + +#define THIS(obj) NFC_PEER(obj) +#define THIS_TYPE NFC_TYPE_PEER +#define PARENT_TYPE G_TYPE_OBJECT +#define PARENT_CLASS (nfc_peer_parent_class) +G_DEFINE_ABSTRACT_TYPE(NfcPeer, nfc_peer, PARENT_TYPE) + +#define NFC_PEER_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), \ + NFC_TYPE_PEER, NfcPeerClass) + +enum nfc_peer_signal { + SIGNAL_WKS_CHANGED, + SIGNAL_NDEF_CHANGED, + SIGNAL_INITIALIZED, + SIGNAL_GONE, + SIGNAL_COUNT +}; + +#define SIGNAL_WKS_CHANGED_NAME "nfc-peer-wks-changed" +#define SIGNAL_NDEF_CHANGED_NAME "nfc-peer-ndef-changed" +#define SIGNAL_INITIALIZED_NAME "nfc-peer-initialized" +#define SIGNAL_GONE_NAME "nfc-peer-gone" + +static guint nfc_peer_signals[SIGNAL_COUNT] = { 0 }; +static const guint8 LLCP_MAGIC[] = { 0x46, 0x66, 0x6d }; + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +NfcPeerConnect* +nfc_peer_connect_new( + NfcPeer* peer, + NfcPeerConnectFunc complete, + void* user_data, + GDestroyNotify destroy) +{ + NfcPeerConnect* connect = g_slice_new(NfcPeerConnect); + + g_object_ref(connect->peer = peer); + connect->complete = complete; + connect->user_data = user_data; + connect->destroy = destroy; + return connect; +} + +static +void +nfc_peer_connect_free( + NfcPeerConnect* connect) +{ + if (connect->destroy) { + connect->destroy(connect->user_data); + } + g_object_unref(connect->peer); + g_slice_free1(sizeof(*connect), connect); +} + +static +void +nfc_peer_connect_complete( + NfcPeerConnection* pc, + NFC_PEER_CONNECT_RESULT result, + void* user_data) +{ + NfcPeerConnect* connect = user_data; + + if (connect->complete) { + connect->complete(connect->peer, pc, result, connect->user_data); + } +} + +static +void +nfc_peer_disconnect_handlers( + NfcPeer* self) +{ + NfcPeerPriv* priv = self->priv; + + nfc_snep_server_remove_all_handlers(priv->snep, priv->snep_event_id); + nfc_llc_remove_all_handlers(priv->llc, priv->llc_event_id); +} + +static +void +nfc_peer_initialized( + NfcPeer* self) +{ + NfcPeerPriv* priv = self->priv; + + /* + * Once we are initialized, we are no longer interested in LLC_EVENT_IDLE + * but we need to keep the the remaining ones (WKS and STATE) registered. + */ + if (priv->llc_event_id[LLC_EVENT_IDLE]) { + nfc_llc_remove_handler(priv->llc, priv->llc_event_id[LLC_EVENT_IDLE]); + priv->llc_event_id[LLC_EVENT_IDLE] = 0; + } + nfc_snep_server_remove_all_handlers(priv->snep, priv->snep_event_id); + if (!(self->flags & NFC_PEER_FLAG_INITIALIZED)) { + GDEBUG("Peer initialized"); + self->flags |= NFC_PEER_FLAG_INITIALIZED; + g_signal_emit(self, nfc_peer_signals[SIGNAL_INITIALIZED], 0); + + /* Notify the services */ + nfc_peer_services_peer_arrived(priv->services, self); + } +} + +static +void +nfc_peer_check_ndef_reception_state( + NfcPeer* self) +{ + NfcPeerPriv* priv = self->priv; + NfcSnepServer* snep = priv->snep; + NfcLlc* llc = priv->llc; + + if (snep->ndef || llc->idle) { + /* Either NDEF has been received or nothing seems to be coming */ + nfc_ndef_rec_unref(self->ndef); + self->ndef = nfc_ndef_rec_ref(snep->ndef); + nfc_peer_initialized(self); + } +} + +static +void +nfc_peer_snep_event( + NfcSnepServer* snep, + void* user_data) +{ + nfc_peer_check_ndef_reception_state(NFC_PEER(user_data)); +} + +static +void +nfc_peer_llc_idle_changed( + NfcLlc* llc, + void* user_data) +{ + nfc_peer_check_ndef_reception_state(NFC_PEER(user_data)); +} + +static +void +nfc_peer_maybe_start_ndef_reception( + NfcPeer* self) +{ + NfcPeerPriv* priv = self->priv; + + if (!priv->ndef_reception_started) { + NfcSnepServer* snep = priv->snep; + NfcLlc* llc = priv->llc; + + priv->ndef_reception_started = TRUE; + if (snep->ndef || llc->idle) { + /* Either we already have an NDEF (unlikely) or nothing + * is happening (more likely). In either case, there seems + * to be nothing to wait for. */ + nfc_ndef_rec_unref(self->ndef); + self->ndef = nfc_ndef_rec_ref(snep->ndef); + nfc_peer_initialized(self); + } else { + /* Wait until NDEF arrives */ + GDEBUG("Waiting for NDEF..."); + priv->snep_event_id[SNEP_EVENT_NDEF] = + nfc_snep_server_add_ndef_changed_handler(snep, + nfc_peer_snep_event, self); + priv->snep_event_id[SNEP_EVENT_STATE] = + nfc_snep_server_add_ndef_changed_handler(snep, + nfc_peer_snep_event, self); + priv->llc_event_id[LLC_EVENT_IDLE] = + nfc_llc_add_idle_changed_handler(priv->llc, + nfc_peer_llc_idle_changed, self); + } + } +} + +static +void +nfc_peer_llc_state_changed( + NfcLlc* llc, + void* user_data) +{ + NfcPeer* self = NFC_PEER(user_data); + + nfc_peer_ref(self); + if (llc->state == NFC_LLC_STATE_ACTIVE) { + nfc_peer_maybe_start_ndef_reception(self); + } else if (llc->state != NFC_LLC_STATE_START) { + /* Some kind of LLCP error has occurred, deactivate the interface */ + nfc_peer_disconnect_handlers(self); + nfc_peer_deactivate(self); + } + nfc_peer_unref(self); +} + +static +void +nfc_peer_wks_changed( + NfcLlc* llc, + void* user_data) +{ + NfcPeer* self = NFC_PEER(user_data); + + self->wks = llc->wks; + g_signal_emit(self, nfc_peer_signals[SIGNAL_WKS_CHANGED], 0); +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcPeer* +nfc_peer_ref( + NfcPeer* self) +{ + if (G_LIKELY(self)) { + g_object_ref(THIS(self)); + } + return self; +} + +void +nfc_peer_unref( + NfcPeer* self) +{ + if (G_LIKELY(self)) { + g_object_unref(THIS(self)); + } +} + +void +nfc_peer_deactivate( + NfcPeer* self) +{ + if (G_LIKELY(self)) { + NFC_PEER_GET_CLASS(self)->deactivate(self); + } +} + +gulong +nfc_peer_add_wks_changed_handler( + NfcPeer* self, + NfcPeerFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_WKS_CHANGED_NAME, G_CALLBACK(func), user_data) : 0; +} + +gulong +nfc_peer_add_ndef_changed_handler( + NfcPeer* self, + NfcPeerFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_NDEF_CHANGED_NAME, G_CALLBACK(func), user_data) : 0; +} + +gulong +nfc_peer_add_initialized_handler( + NfcPeer* self, + NfcPeerFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_INITIALIZED_NAME, G_CALLBACK(func), user_data) : 0; +} + +gulong +nfc_peer_add_gone_handler( + NfcPeer* self, + NfcPeerFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_GONE_NAME, G_CALLBACK(func), user_data) : 0; +} + +void +nfc_peer_remove_handler( + NfcPeer* self, + gulong id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +void +nfc_peer_remove_handlers( + NfcPeer* self, + gulong* ids, + guint count) +{ + gutil_disconnect_handlers(self, ids, count); +} + +gboolean +nfc_peer_register_service( + NfcPeer* self, + NfcPeerService* service) +{ + if (G_LIKELY(self)) { + NfcPeerPriv* priv = self->priv; + + return nfc_peer_services_add(priv->services, service); + } + return FALSE; +} + +void +nfc_peer_unregister_service( + NfcPeer* self, + NfcPeerService* service) +{ + if (G_LIKELY(self)) { + NfcPeerPriv* priv = self->priv; + + nfc_peer_services_remove(priv->services, service); + } +} + +NfcPeerConnection* +nfc_peer_connect( + NfcPeer* self, + NfcPeerService* service, + guint rsap, + NfcPeerConnectFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + if (G_LIKELY(self)) { + NfcPeerPriv* priv = self->priv; + NfcPeerConnect* connect = nfc_peer_connect_new(self, complete, + user_data, destroy); + NfcPeerConnection* pc = nfc_llc_connect(priv->llc, service, rsap, + nfc_peer_connect_complete, (GDestroyNotify) nfc_peer_connect_free, + connect); + + if (pc) { + return pc; + } + connect->destroy = NULL; + nfc_peer_connect_free(connect); + } + return NULL; +} + +NfcPeerConnection* +nfc_peer_connect_sn( + NfcPeer* self, + NfcPeerService* service, + const char* sn, + NfcPeerConnectFunc complete, + GDestroyNotify destroy, + void* user_data) +{ + if (G_LIKELY(self)) { + NfcPeerPriv* priv = self->priv; + NfcPeerConnect* connect = nfc_peer_connect_new(self, complete, + user_data, destroy); + NfcPeerConnection* pc = nfc_llc_connect_sn(priv->llc, service, sn, + nfc_peer_connect_complete, (GDestroyNotify) nfc_peer_connect_free, + connect); + + if (pc) { + return pc; + } + connect->destroy = NULL; + nfc_peer_connect_free(connect); + } + return NULL; +} + +/*==========================================================================* + * Internal interface + *==========================================================================*/ + +gboolean +nfc_peer_init_base( + NfcPeer* self, + NfcLlcIo* llc_io, + const GUtilData* gb, /* ATR_RES/ATR_REQ General Bytes */ + NfcPeerServices* services, + NFC_TECHNOLOGY technology, + NFC_PEER_FLAGS flags) +{ + /* + * Check LLCP Magic Number + * + * NFCForum-TS-LLCP_1.1 + * 6.2.3.1 Link Activation procedure for the Initiator + */ + if (gb->size >= sizeof(LLCP_MAGIC) && + !memcmp(gb->bytes, LLCP_MAGIC, sizeof(LLCP_MAGIC))) { + NfcPeerPriv* priv = self->priv; + GUtilData tlvs; + NfcLlcParam** params; + NfcLlc* llc; + + GDEBUG("NFC-DEP %s", (flags & NFC_PEER_FLAG_INITIATOR) ? + "Initiator" : "Target"); + + priv->services = services ? + nfc_peer_services_copy(services) : + nfc_peer_services_new(); + + tlvs.bytes = gb->bytes + sizeof(LLCP_MAGIC); + tlvs.size = gb->size - sizeof(LLCP_MAGIC); + params = nfc_llc_param_decode(&tlvs); + nfc_peer_services_add(priv->services, NFC_PEER_SERVICE(priv->snep)); + priv->llc = llc = nfc_llc_new(llc_io, priv->services, + nfc_llc_param_constify(params)); + priv->llc_event_id[LLC_EVENT_STATE] = + nfc_llc_add_state_changed_handler(llc, + nfc_peer_llc_state_changed, self); + priv->llc_event_id[LLC_EVENT_WKS] = + nfc_llc_add_wks_changed_handler(llc, + nfc_peer_wks_changed, self); + nfc_llc_param_free(params); + self->wks = llc->wks; + self->technology = technology; + self->flags = flags; + switch (llc->state) { + case NFC_LLC_STATE_START: + case NFC_LLC_STATE_ACTIVE: + return TRUE; + case NFC_LLC_STATE_ERROR: + case NFC_LLC_STATE_PEER_LOST: + break; + } + } else { + GDEBUG("No LLCP magic found"); + } + return FALSE; +} + +void +nfc_peer_set_name( + NfcPeer* self, + const char* name) +{ + NfcPeerPriv* priv = self->priv; + + g_free(priv->name); + self->name = priv->name = g_strdup(name); +} + +void +nfc_peer_gone( + NfcPeer* self) +{ + nfc_peer_ref(self); + NFC_PEER_GET_CLASS(self)->gone(self); + nfc_peer_unref(self); +} + +/*==========================================================================* + * Methods + *==========================================================================*/ + +static +void +nfc_peer_nop( + NfcPeer* self) +{ +} + +static +void +nfc_peer_default_gone( + NfcPeer* self) +{ + /* Must only be invoked once per lifetime */ + GASSERT(self->present); + self->present = FALSE; + if (self->flags & NFC_PEER_FLAG_INITIALIZED) { + NfcPeerPriv* priv = self->priv; + + /* Notify the services */ + nfc_peer_services_peer_left(priv->services, self); + } + g_signal_emit(self, nfc_peer_signals[SIGNAL_GONE], 0); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_peer_init( + NfcPeer* self) +{ + NfcPeerPriv* priv = G_TYPE_INSTANCE_GET_PRIVATE(self, THIS_TYPE, + NfcPeerPriv); + + self->priv = priv; + priv->snep = nfc_snep_server_new(); +} + +static +void +nfc_peer_finalize( + GObject* object) +{ + NfcPeer* self = THIS(object); + NfcPeerPriv* priv = self->priv; + NfcSnepServer* snep = priv->snep; + + nfc_ndef_rec_unref(self->ndef); + nfc_peer_disconnect_handlers(self); + nfc_peer_service_unref(&snep->service); + nfc_peer_services_unref(priv->services); + nfc_llc_free(priv->llc); + g_free(priv->name); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +nfc_peer_class_init( + NfcPeerClass* klass) +{ + GType type = G_OBJECT_CLASS_TYPE(klass); + + g_type_class_add_private(klass, sizeof(NfcPeerPriv)); + klass->deactivate = nfc_peer_nop; + klass->gone = nfc_peer_default_gone; + G_OBJECT_CLASS(klass)->finalize = nfc_peer_finalize; + nfc_peer_signals[SIGNAL_WKS_CHANGED] = + g_signal_new(SIGNAL_WKS_CHANGED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + nfc_peer_signals[SIGNAL_NDEF_CHANGED] = + g_signal_new(SIGNAL_NDEF_CHANGED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + nfc_peer_signals[SIGNAL_INITIALIZED] = + g_signal_new(SIGNAL_INITIALIZED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + nfc_peer_signals[SIGNAL_GONE] = + g_signal_new(SIGNAL_GONE_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_peer_connection.c b/core/src/nfc_peer_connection.c new file mode 100644 index 0000000..1847f07 --- /dev/null +++ b/core/src/nfc_peer_connection.c @@ -0,0 +1,698 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include "nfc_llc.h" +#include "nfc_llc_param.h" +#include "nfc_peer_connection_impl.h" +#include "nfc_peer_connection_p.h" +#include "nfc_peer_service_p.h" + +#define GLOG_MODULE_NAME NFC_PEER_LOG_MODULE +#include +#include +#include + +#define NFC_LLC_LOCAL_RW NFC_LLC_RW_MAX +#define NFC_LLC_LOCAL_MIU NFC_LLC_MIU_MAX + +struct nfc_peer_connection_priv { + char* name; + NfcLlc* llc; + GUtilIdlePool* pool; + NfcPeerConnectionLlcpState ps; + NfcLlcParam miu_param; + NfcLlcParam rw_param; + const NfcLlcParam* lp[3]; + guint send_off; + GList* send_queue; + GByteArray* send_buf; + gboolean disc_sent; +}; + +#define THIS(obj) NFC_PEER_CONNECTION(obj) +#define THIS_TYPE NFC_TYPE_PEER_CONNECTION +#define PARENT_CLASS (nfc_peer_connection_parent_class) + +G_DEFINE_ABSTRACT_TYPE(NfcPeerConnection, nfc_peer_connection, G_TYPE_OBJECT) +#define NFC_PEER_CONNECTION_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), \ + THIS_TYPE, NfcPeerConnectionClass) + +enum nfc_peer_connection_signal { + SIGNAL_STATE_CHANGED, + SIGNAL_COUNT +}; + +#define SIGNAL_STATE_CHANGED_NAME "nfc-llc-connection-state-changed" + +static guint nfc_peer_connection_signals[SIGNAL_COUNT] = { 0 }; + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +#if GUTIL_LOG_DEBUG +static +const char* +nfc_peer_connection_state_name( + NfcPeerConnectionPriv* priv, + NFC_LLC_CO_STATE state) +{ + char* tmp; + + switch (state) { + case NFC_LLC_CO_CONNECTING: return "CONNECTING"; + case NFC_LLC_CO_ACCEPTING: return "ACCEPTING"; + case NFC_LLC_CO_ABANDONED: return "ABANDONED"; + case NFC_LLC_CO_ACTIVE: return "ACTIVE"; + case NFC_LLC_CO_DISCONNECTING: return "DISCONNECTING"; + case NFC_LLC_CO_DEAD: return "DEAD"; + } + tmp = g_strdup_printf("%d (?)", state); + gutil_idle_pool_add(priv->pool, tmp, g_free); + return tmp; +} +#endif /* GUTIL_LOG_DEBUG */ + +static +gboolean +nfc_peer_connection_can_send( + NfcPeerConnection* self) +{ + NfcPeerConnectionPriv* priv = self->priv; + const NfcPeerConnectionLlcpState* ps = &priv->ps; + + /* + * NFCForum-TS-LLCP_1.1 + * 5.6 Connection-oriented Transport Mode Procedures + * 5.6.4.1 Sending I PDUs + * + * While the send state variable V(S) is equal to the send + * acknowledge state variable V(SA) plus the remote receive + * window size RW(R), the LLC SHALL NOT send an I PDU on that + * data link connection. + */ + return ps->vs != ((ps->vsa + ps->rwr) & 0x0f); +} + +static +void +nfc_peer_connection_submit_i_pdu( + NfcPeerConnection* self, + const void* data, + guint len) +{ + NfcPeerConnectionPriv* priv = self->priv; + + nfc_llc_submit_i_pdu(priv->llc, self, data, len); + GASSERT(self->bytes_queued >= len); + self->bytes_queued -= len; + self->bytes_sent += len; +} + +static +gboolean +nfc_peer_connection_drop_queued_data( + NfcPeerConnection* self) +{ + NfcPeerConnectionPriv* priv = self->priv; + + if (priv->send_queue) { + GASSERT(self->bytes_queued); + self->bytes_queued = 0; + g_list_free_full(priv->send_queue, (GDestroyNotify) g_bytes_unref); + priv->send_queue = NULL; + return TRUE; + } + return FALSE; +} + +static +inline +void +nfc_peer_connection_data_dequeued( + NfcPeerConnection* self) +{ + NFC_PEER_CONNECTION_GET_CLASS(self)->data_dequeued(self); +} + +static +void +nfc_peer_connection_disconnect_internal( + NfcPeerConnection* self, + gboolean flush) +{ + NfcPeerConnectionPriv* priv = self->priv; + NfcPeerService* service = self->service; + gboolean data_dropped = FALSE; + + switch (self->state) { + case NFC_LLC_CO_CONNECTING: + GDEBUG("Abandoning %u:%u", service->sap, self->rsap); + data_dropped = nfc_peer_connection_drop_queued_data(self); + nfc_peer_connection_set_state(self, NFC_LLC_CO_ABANDONED); + break; + case NFC_LLC_CO_ACCEPTING: + GDEBUG("Connection %u:%u cancelled", service->sap, self->rsap); + data_dropped = nfc_peer_connection_drop_queued_data(self); + NFC_PEER_CONNECTION_GET_CLASS(self)->accept_cancelled(self); + nfc_peer_connection_set_state(self, NFC_LLC_CO_DEAD); + break; + case NFC_LLC_CO_ACTIVE: + nfc_llc_ack(priv->llc, self, TRUE); + GDEBUG("Disconnecting %u:%u", service->sap, self->rsap); + if (!flush) { + data_dropped = nfc_peer_connection_drop_queued_data(self); + } + if (!priv->send_queue) { + nfc_llc_submit_disc_pdu(priv->llc, self->rsap, service->sap); + priv->disc_sent = TRUE; + } + nfc_peer_connection_set_state(self, NFC_LLC_CO_DISCONNECTING); + break; + case NFC_LLC_CO_ABANDONED: + case NFC_LLC_CO_DISCONNECTING: + case NFC_LLC_CO_DEAD: + /* Nothing to do */ + break; + } + + if (data_dropped) { + nfc_peer_connection_data_dequeued(self); + } +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcPeerConnection* +nfc_peer_connection_ref( + NfcPeerConnection* self) +{ + if (G_LIKELY(self)) { + g_object_ref(THIS(self)); + } + return self; +} + +void +nfc_peer_connection_unref( + NfcPeerConnection* self) +{ + if (G_LIKELY(self)) { + g_object_unref(THIS(self)); + } +} + +gboolean +nfc_peer_connection_send( + NfcPeerConnection* self, + GBytes* bytes) +{ + if (G_LIKELY(self)) { + NfcPeerConnectionPriv* priv = self->priv; + const gsize size = bytes ? g_bytes_get_size(bytes) : 0; + + switch (self->state) { + case NFC_LLC_CO_ACCEPTING: + case NFC_LLC_CO_CONNECTING: + case NFC_LLC_CO_ACTIVE: + if (size > 0) { + self->bytes_queued += size; + priv->send_queue = g_list_append(priv->send_queue, + g_bytes_ref(bytes)); + if (self->state == NFC_LLC_CO_ACTIVE) { + nfc_peer_connection_flush(self); + } + } + return TRUE; + case NFC_LLC_CO_DISCONNECTING: + case NFC_LLC_CO_ABANDONED: + case NFC_LLC_CO_DEAD: + break; + } + } + return FALSE; +} + +void +nfc_peer_connection_disconnect( + NfcPeerConnection* self) +{ + if (G_LIKELY(self)) { + nfc_peer_connection_disconnect_internal(self, TRUE); + } +} + +gboolean +nfc_peer_connection_cancel( + NfcPeerConnection* self) +{ + gboolean cancelled = FALSE; + + if (G_LIKELY(self)) { + NfcPeerConnectionPriv* priv = self->priv; + + cancelled = nfc_llc_cancel_connect_request(priv->llc, self); + nfc_peer_connection_disconnect_internal(self, FALSE); + } + return cancelled; +} + +gulong +nfc_peer_connection_add_state_changed_handler( + NfcPeerConnection* self, + NfcPeerConnectionFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_STATE_CHANGED_NAME, G_CALLBACK(func), user_data) : 0; +} + +void +nfc_peer_connection_remove_handler( + NfcPeerConnection* self, + gulong id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +/*==========================================================================* + * Internal interface + * + * These functions may assume that NfcPeerConnection pointer is not NULL. + * The caller checks that. + *==========================================================================*/ + +void +nfc_peer_connection_init_connect( + NfcPeerConnection* self, + NfcPeerService* service, + guint8 rsap, + const char* name) +{ + NfcPeerConnectionPriv* priv = self->priv; + + self->state = NFC_LLC_CO_CONNECTING; + self->name = priv->name = g_strdup(name); + self->service = nfc_peer_service_ref(service); + self->rsap = rsap; + nfc_peer_service_connection_created(service, self); + GDEBUG("Connection %u:%u %s", service->sap, self->rsap, + nfc_peer_connection_state_name(priv, self->state)); +} + +void +nfc_peer_connection_init_accept( + NfcPeerConnection* self, + NfcPeerService* service, + guint8 rsap) +{ + NfcPeerConnectionPriv* priv = self->priv; + + self->state = NFC_LLC_CO_ACCEPTING; + self->service = nfc_peer_service_ref(service); + self->rsap = rsap; + nfc_peer_service_connection_created(service, self); + GDEBUG("Connection %u:%u %s", service->sap, self->rsap, + nfc_peer_connection_state_name(priv, self->state)); +} + +guint +nfc_peer_connection_rmiu( + NfcPeerConnection* self) +{ + return G_LIKELY(self) ? self->priv->ps.rmiu : 0; +} + +gpointer +nfc_peer_connection_key( + NfcPeerConnection* self) +{ + return self ? LLCP_CONN_KEY(self->service->sap, self->rsap) : NULL; +} + +void +nfc_peer_connection_set_llc( + NfcPeerConnection* self, + NfcLlc* llc) +{ + self->priv->llc = llc; +} + +const NfcLlcParam* const* +nfc_peer_connection_lp( + NfcPeerConnection* self) +{ + return self->priv->lp; +} + +NfcPeerConnectionLlcpState* +nfc_peer_connection_ps( + NfcPeerConnection* self) +{ + return &self->priv->ps; +} + +void +nfc_peer_connection_apply_remote_params( + NfcPeerConnection* self, + const NfcLlcParam* const* params) +{ + if (G_LIKELY(params)) { + NfcPeerConnectionPriv* priv = self->priv; + NfcPeerConnectionLlcpState* ps = &priv->ps; + const NfcLlcParam* const* ptr = params; + + while (*ptr) { + const NfcLlcParam* param = *ptr++; + + switch (param->type) { + case NFC_LLC_PARAM_MIUX: + ps->rmiu = param->value.miu; + GDEBUG(" MIU(R): %u bytes", ps->rmiu); + break; + case NFC_LLC_PARAM_RW: + ps->rwr = param->value.rw; + GDEBUG(" RW(R): %u", ps->rwr); + /* The rest is irrelevant */ + /* fallthrough */ + case NFC_LLC_PARAM_VERSION: + case NFC_LLC_PARAM_WKS: + case NFC_LLC_PARAM_LTO: + case NFC_LLC_PARAM_SN: + case NFC_LLC_PARAM_OPT: + case NFC_LLC_PARAM_SDREQ: + case NFC_LLC_PARAM_SDRES: + break; + } + } + } +} + +void +nfc_peer_connection_accept( + NfcPeerConnection* self) +{ + NFC_PEER_CONNECTION_GET_CLASS(self)->accept(self); +} + +void +nfc_peer_connection_data_received( + NfcPeerConnection* self, + const void* data, + guint len) +{ + self->bytes_received += len; + NFC_PEER_CONNECTION_GET_CLASS(self)->data_received(self, data, len); +} + +void +nfc_peer_connection_set_state( + NfcPeerConnection* self, + NFC_LLC_CO_STATE state) +{ + if (G_LIKELY(self) && self->state != state && + self->state != NFC_LLC_CO_DEAD /* Terminal state */) { + nfc_peer_connection_ref(self); + GDEBUG("Connection %u:%u %s -> %s", self->service->sap, self->rsap, + nfc_peer_connection_state_name(self->priv, self->state), + nfc_peer_connection_state_name(self->priv, state)); + self->state = state; + NFC_PEER_CONNECTION_GET_CLASS(self)->state_changed(self); + nfc_peer_connection_unref(self); + } +} + +void +nfc_peer_connection_accepted( + NfcPeerConnection* self) +{ + GASSERT(self->state == NFC_LLC_CO_ACCEPTING); + if (G_LIKELY(self->state == NFC_LLC_CO_ACCEPTING)) { + NfcPeerConnectionPriv* priv = self->priv; + + GDEBUG("Connection %u:%u accepted", self->service->sap, self->rsap); + nfc_llc_submit_cc_pdu(priv->llc, self); + nfc_peer_connection_set_state(self, NFC_LLC_CO_ACTIVE); + } +} + +void +nfc_peer_connection_rejected( + NfcPeerConnection* self) +{ + GASSERT(self->state == NFC_LLC_CO_ACCEPTING); + if (G_LIKELY(self->state == NFC_LLC_CO_ACCEPTING)) { + NfcPeerConnectionPriv* priv = self->priv; + NfcPeerService* service = self->service; + + GDEBUG("Connection %u:%u rejected", service->sap, self->rsap); + nfc_llc_submit_dm_pdu(priv->llc, self->rsap, service->sap, + NFC_LLC_DM_REJECT); + nfc_peer_connection_set_state(self, NFC_LLC_CO_DEAD); + } +} + +void +nfc_peer_connection_flush( + NfcPeerConnection* self) +{ + NfcPeerConnectionPriv* priv = self->priv; + gboolean submitted = FALSE; + + /* + * If nfc_llc_i_pdu_queued return TRUE (i.e. an I-frame is already + * queued for this connection), just continue to accumulate the data. + * This function will be called again when I-frame is dequeued. + */ + while (priv->send_queue && + nfc_peer_connection_can_send(self) && + !nfc_llc_i_pdu_queued(priv->llc, self)) { + const NfcPeerConnectionLlcpState* ps = &priv->ps; + GList* first = g_list_first(priv->send_queue); + GBytes* block = first->data; + gsize block_size; + const guint8* block_data = g_bytes_get_data(block, &block_size); + const guint8* ptr = block_data + priv->send_off; + guint remaining = block_size - priv->send_off; + + if (remaining > ps->rmiu) { + /* Send a full I frame and still have some data left in this + * buffer, shift the offset by MIU */ + priv->send_off += ps->rmiu; + nfc_peer_connection_submit_i_pdu(self, ptr, ps->rmiu); + submitted = TRUE; + } else if (remaining == ps->rmiu || !priv->send_queue->next) { + /* Send the remaining data in this block as a single I PDU */ + priv->send_off = 0; + priv->send_queue = g_list_delete_link(priv->send_queue, + priv->send_queue); + nfc_peer_connection_submit_i_pdu(self, ptr, remaining); + submitted = TRUE; + g_bytes_unref(block); + } else { + GByteArray* buf = priv->send_buf; + + /* Have to concatenate blocks */ + g_byte_array_set_size(buf, 0); + g_byte_array_append(buf, ptr, remaining); + g_bytes_unref(block); + priv->send_off = 0; + priv->send_queue = g_list_delete_link(priv->send_queue, + priv->send_queue); + + while (buf->len < ps->rmiu && priv->send_queue) { + remaining = ps->rmiu - buf->len; + block = priv->send_queue->data; + block_data = g_bytes_get_data(block, &block_size); + if (block_size <= remaining) { + g_byte_array_append(buf, block_data, block_size); + g_bytes_unref(block); + priv->send_queue = g_list_delete_link(priv->send_queue, + priv->send_queue); + } else { + priv->send_off = remaining; + g_byte_array_append(buf, block_data, remaining); + } + } + nfc_peer_connection_submit_i_pdu(self, buf->data, buf->len); + submitted = TRUE; + } + } + + if (!priv->send_queue && + self->state == NFC_LLC_CO_DISCONNECTING && + !priv->disc_sent) { + NfcPeerService* service = self->service; + + /* Done flushing */ + nfc_llc_submit_disc_pdu(priv->llc, self->rsap, service->sap); + priv->disc_sent = TRUE; + } + + if (submitted) { + nfc_peer_connection_data_dequeued(self); + } +} + +/*==========================================================================* + * Methods + *==========================================================================*/ + +static +void +nfc_peer_connection_default_state_changed( + NfcPeerConnection* self) +{ + NfcPeerConnectionPriv* priv = self->priv; + const gboolean data_dropped = (self->state == NFC_LLC_CO_DEAD) && + nfc_peer_connection_drop_queued_data(self); + + g_signal_emit(self, nfc_peer_connection_signals[SIGNAL_STATE_CHANGED], 0); + switch (self->state) { + case NFC_LLC_CO_DEAD: + /* Notify LLC that we have died */ + nfc_llc_connection_dead(priv->llc, self); + nfc_peer_service_connection_dead(self->service, self); + if (data_dropped) { + nfc_peer_connection_data_dequeued(self); + } + break; + case NFC_LLC_CO_ACTIVE: + /* Send queued data */ + nfc_peer_connection_flush(self); + break; + case NFC_LLC_CO_CONNECTING: + case NFC_LLC_CO_ACCEPTING: + case NFC_LLC_CO_ABANDONED: + case NFC_LLC_CO_DISCONNECTING: + break; + } +} + +static +void +nfc_peer_connection_nop( + NfcPeerConnection* self) +{ +} + +static +void +nfc_peer_connection_default_data_received( + NfcPeerConnection* self, + const void* data, + guint len) +{ +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_peer_connection_init( + NfcPeerConnection* self) +{ + NfcPeerConnectionPriv* priv = G_TYPE_INSTANCE_GET_PRIVATE(self, THIS_TYPE, + NfcPeerConnectionPriv); + NfcPeerConnectionLlcpState* ps = &priv->ps; + NfcLlcParam* miu = &priv->miu_param; + NfcLlcParam* rw = &priv->rw_param; + + self->priv = priv; + priv->send_buf = g_byte_array_new(); + + /* Set up local parameters */ + miu->type = NFC_LLC_PARAM_MIUX; + rw->type = NFC_LLC_PARAM_RW; + priv->lp[0] = miu; + priv->lp[1] = rw; + + /* Default (maximum) values for local parameters */ + miu->value.miu = NFC_LLC_LOCAL_MIU; + rw->value.rw = NFC_LLC_LOCAL_RW; + + /* Default values for remote parameters */ + ps->rmiu = NFC_LLC_MIU_DEFAULT; + ps->rwr = NFC_LLC_RW_DEFAULT; +} + +static +void +nfc_peer_connection_finalize( + GObject* object) +{ + NfcPeerConnection* self = THIS(object); + NfcPeerConnectionPriv* priv = self->priv; + + if (self->service && self->state != NFC_LLC_CO_DEAD) { + nfc_peer_service_connection_dead(self->service, self); + } + nfc_peer_connection_drop_queued_data(self); + nfc_peer_service_unref(self->service); + gutil_idle_pool_destroy(priv->pool); + g_byte_array_free(priv->send_buf, TRUE); + g_free(priv->name); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +nfc_peer_connection_class_init( + NfcPeerConnectionClass* klass) +{ + g_type_class_add_private(klass, sizeof(NfcPeerConnectionPriv)); + klass->accept = nfc_peer_connection_accepted; + klass->accept_cancelled = nfc_peer_connection_nop; + klass->state_changed = nfc_peer_connection_default_state_changed; + klass->data_received = nfc_peer_connection_default_data_received; + klass->data_dequeued = nfc_peer_connection_nop; + G_OBJECT_CLASS(klass)->finalize = nfc_peer_connection_finalize; + nfc_peer_connection_signals[SIGNAL_STATE_CHANGED] = + g_signal_new(SIGNAL_STATE_CHANGED_NAME, G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_peer_connection_p.h b/core/src/nfc_peer_connection_p.h new file mode 100644 index 0000000..d882de2 --- /dev/null +++ b/core/src/nfc_peer_connection_p.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_CONNECTION_PRIVATE_H +#define NFC_PEER_CONNECTION_PRIVATE_H + +#include "nfc_types_p.h" + +#include + +typedef struct nfc_peer_connection_llcp_state { + /* + * NFCForum-TS-LLCP_1.1 + * 5.6 Connection-oriented Transport Mode Procedures + * 5.6.1 Data Link Connection State Variables + */ + guint8 vs; /* Send State Variable V(S) */ + guint8 vsa; /* Send Acknowledgement State Variable V(SA) */ + guint8 vr; /* Receive State Variable V(R) */ + guint8 vra; /* Receive Acknowledgement State Variable V(RA) */ + + /* + * 5.6.2 Data Link Connection Parameters + */ + guint8 rwr; /* Remote Receive Window Size, RW(R) */ + guint16 rmiu; /* Remote Maximum Information Unit size for I PDUs */ +} NfcPeerConnectionLlcpState; + +#define LLCP_CONN_KEY(lsap,rsap) GINT_TO_POINTER(\ + ((((guint16)(lsap)) & 0x3f) << 10) | \ + (((guint16)(rsap)) & 0x3f)) + +gpointer +nfc_peer_connection_key( + NfcPeerConnection* pc) + NFCD_INTERNAL; + +void +nfc_peer_connection_set_llc( + NfcPeerConnection* pc, + NfcLlc* llc) + NFCD_INTERNAL; + +const NfcLlcParam* const* +nfc_peer_connection_lp( + NfcPeerConnection* pc) + NFCD_INTERNAL; + +NfcPeerConnectionLlcpState* +nfc_peer_connection_ps( + NfcPeerConnection* pc) + NFCD_INTERNAL; + +void +nfc_peer_connection_apply_remote_params( + NfcPeerConnection* pc, + const NfcLlcParam* const* params) + NFCD_INTERNAL; + +void +nfc_peer_connection_set_state( + NfcPeerConnection* pc, + NFC_LLC_CO_STATE state) + NFCD_INTERNAL; + +void +nfc_peer_connection_accept( + NfcPeerConnection* pc) + NFCD_INTERNAL; + +void +nfc_peer_connection_data_received( + NfcPeerConnection* pc, + const void* data, + guint len) + NFCD_INTERNAL; + +void +nfc_peer_connection_flush( + NfcPeerConnection* pc) + NFCD_INTERNAL; + +#endif /* NFC_PEER_CONNECTION_PRIVATE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_peer_initiator.c b/core/src/nfc_peer_initiator.c new file mode 100644 index 0000000..58e68a2 --- /dev/null +++ b/core/src/nfc_peer_initiator.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_peer_p.h" +#include "nfc_target_p.h" +#include "nfc_llc.h" +#include "nfc_llc_io.h" + +#define GLOG_MODULE_NAME NFC_PEER_LOG_MODULE +#include + +#define DEFAULT_POLL_PERIOD (100) /* ms */ + +typedef struct nfc_peer_initiator { + NfcPeer peer; + NfcLlcIo* llc_io; + NfcTarget* target; + gulong gone_id; +} NfcPeerInitiator; + +#define THIS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + THIS_TYPE, NfcPeerInitiator)) +#define THIS_TYPE (nfc_peer_initiator_get_type()) +#define PARENT_TYPE NFC_TYPE_PEER +#define PARENT_CLASS (nfc_peer_initiator_parent_class) + +typedef NfcPeerClass NfcPeerInitiatorClass; +G_DEFINE_TYPE(NfcPeerInitiator, nfc_peer_initiator, PARENT_TYPE) + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +void +nfc_peer_initiator_gone( + NfcTarget* target, + void* user_data) +{ + /* NfcTarget makes sure that this signal is only issued once */ + nfc_peer_gone(NFC_PEER(user_data)); +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcPeer* +nfc_peer_new_initiator( + NfcTarget* target, + NFC_TECHNOLOGY technology, + const NfcParamNfcDepInitiator* nfc_dep, + NfcPeerServices* services) +{ + if (G_LIKELY(target) && G_LIKELY(nfc_dep)) { + NfcPeerInitiator* self = g_object_new(THIS_TYPE, NULL); + NfcPeer* peer = &self->peer; + + self->target = nfc_target_ref(target); + self->llc_io = nfc_llc_io_initiator_new(target); + if (nfc_peer_init_base(peer, self->llc_io, &nfc_dep->atr_res_g, + services, technology, NFC_PEER_FLAG_INITIATOR)) { + peer->present = target->present; + self->gone_id = nfc_target_add_gone_handler(target, + nfc_peer_initiator_gone, self); + return peer; + } + g_object_unref(self); + } + return NULL; +} + +/*==========================================================================* + * Methods + *==========================================================================*/ + +static +void +nfc_peer_initiator_deactivate( + NfcPeer* peer) +{ + NfcPeerInitiator* self = THIS(peer); + + nfc_target_deactivate(self->target); + NFC_PEER_CLASS(PARENT_CLASS)->deactivate(peer); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_peer_initiator_init( + NfcPeerInitiator* self) +{ +} + +static +void +nfc_peer_initiator_finalize( + GObject* object) +{ + NfcPeerInitiator* self = THIS(object); + + nfc_llc_io_unref(self->llc_io); + nfc_target_remove_handler(self->target, self->gone_id); + nfc_target_unref(self->target); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +nfc_peer_initiator_class_init( + NfcPeerInitiatorClass* klass) +{ + klass->deactivate = nfc_peer_initiator_deactivate; + G_OBJECT_CLASS(klass)->finalize = nfc_peer_initiator_finalize; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_peer_p.h b/core/src/nfc_peer_p.h new file mode 100644 index 0000000..41afddb --- /dev/null +++ b/core/src/nfc_peer_p.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_PRIVATE_H +#define NFC_PEER_PRIVATE_H + +#include "nfc_types_p.h" +#include "nfc_peer.h" + +typedef struct nfc_peer_class { + GObjectClass object; + void (*deactivate)(NfcPeer* peer); + void (*gone)(NfcPeer* peer); +} NfcPeerClass; + +#define NFC_PEER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), \ + NFC_TYPE_PEER, NfcPeerClass) + +NfcPeer* +nfc_peer_new_initiator( + NfcTarget* target, + NFC_TECHNOLOGY technology, + const NfcParamNfcDepInitiator* nfc_dep, + NfcPeerServices* services) + NFCD_INTERNAL; + +NfcPeer* +nfc_peer_new_target( + NfcInitiator* initiator, + NFC_TECHNOLOGY technology, + const NfcParamNfcDepTarget* nfc_dep, + NfcPeerServices* services) + NFCD_INTERNAL; + +void +nfc_peer_set_name( + NfcPeer* peer, + const char* name) + NFCD_INTERNAL; + +/* For use by derived classes */ + +gboolean +nfc_peer_init_base( + NfcPeer* peer, + NfcLlcIo* llc_io, + const GUtilData* gb, /* ATR_RES/ATR_REQ General Bytes */ + NfcPeerServices* services, + NFC_TECHNOLOGY technology, + NFC_PEER_FLAGS flags) + NFCD_INTERNAL; + +void +nfc_peer_gone( + NfcPeer* peer) + NFCD_INTERNAL; + +#endif /* NFC_PEER_PRIVATE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_peer_service.c b/core/src/nfc_peer_service.c new file mode 100644 index 0000000..e9cc706 --- /dev/null +++ b/core/src/nfc_peer_service.c @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include "nfc_peer_connection_p.h" +#include "nfc_peer_service_impl.h" +#include "nfc_peer_service_p.h" +#include "nfc_llc.h" + +#define GLOG_MODULE_NAME NFC_PEER_LOG_MODULE +#include + +struct nfc_peer_service_priv { + char* name; + NfcPeerConnection** conns; +}; + +#define THIS(obj) NFC_PEER_SERVICE(obj) +#define THIS_TYPE NFC_TYPE_PEER_SERVICE +#define PARENT_CLASS (nfc_peer_service_parent_class) + +G_DEFINE_ABSTRACT_TYPE(NfcPeerService, nfc_peer_service, G_TYPE_OBJECT) +#define NFC_PEER_SERVICE_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), \ + THIS_TYPE, NfcPeerServiceClass) + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcPeerService* +nfc_peer_service_ref( + NfcPeerService* self) +{ + if (G_LIKELY(self)) { + g_object_ref(THIS(self)); + } + return self; +} + +void +nfc_peer_service_unref( + NfcPeerService* self) +{ + if (G_LIKELY(self)) { + g_object_unref(THIS(self)); + } +} + +void +nfc_peer_service_disconnect_all( + NfcPeerService* self) +{ + if (G_LIKELY(self)) { + NfcPeerServicePriv* priv = self->priv; + + if (priv->conns) { + NfcPeerConnection** ptr = priv->conns; + NfcPeerConnection** tmp; + guint n = 0; + + /* Temporarily bump references */ + for (ptr = priv->conns; *ptr++; n++); + tmp = g_new(NfcPeerConnection*, n + 1); + for (n = 0, ptr = priv->conns; *ptr; n++, ptr++) { + tmp[n] = nfc_peer_connection_ref(*ptr); + } + tmp[n] = NULL; + + /* Disconnect all connections and release temporary refs */ + for (ptr = tmp; *ptr; ptr++) { + nfc_peer_connection_disconnect(*ptr); + nfc_peer_connection_unref(*ptr); + } + + g_free(tmp); + } + } +} + +/*==========================================================================* + * Internal interface + *==========================================================================*/ + +void +nfc_peer_service_init_base( + NfcPeerService* self, + const char* name) +{ + NfcPeerServicePriv* priv = self->priv; + + GASSERT(!self->name); + if (name) { + if (!strcmp(name, NFC_LLC_NAME_SNEP)) { + self->name = NFC_LLC_NAME_SNEP; + self->sap = NFC_LLC_SAP_SNEP; + } else { + self->name = priv->name = g_strdup(name); + } + } +} + +NfcPeerConnection* +nfc_peer_service_new_connect( + NfcPeerService* self, + guint8 rsap, + const char* name) +{ + NfcPeerConnection* pc = NFC_PEER_SERVICE_GET_CLASS(self)-> + new_connect(self, rsap, name); + + /* Make sure the state is right */ + nfc_peer_connection_set_state(pc, NFC_LLC_CO_CONNECTING); + return pc; +} + +NfcPeerConnection* +nfc_peer_service_new_accept( + NfcPeerService* self, + guint8 rsap) +{ + NfcPeerConnection* pc = NFC_PEER_SERVICE_GET_CLASS(self)-> + new_accept(self, rsap); + + /* Make sure the state is right */ + nfc_peer_connection_set_state(pc, NFC_LLC_CO_ACCEPTING); + return pc; +} + +void +nfc_peer_service_connection_created( + NfcPeerService* self, + NfcPeerConnection* connection) +{ + NfcPeerServicePriv* priv = self->priv; + guint n = 0; + + if (priv->conns) { + NfcPeerConnection** ptr = priv->conns; + + while (*ptr++) n++; + } + priv->conns = g_renew(NfcPeerConnection*, priv->conns, n + 2); + priv->conns[n] = connection; + priv->conns[n + 1] = NULL; +} + +void +nfc_peer_service_connection_dead( + NfcPeerService* self, + NfcPeerConnection* pc) +{ + NfcPeerServicePriv* priv = self->priv; + int pos = -1; + uint n = 0; + + if (priv->conns) { + NfcPeerConnection** ptr = priv->conns; + + while (*ptr) { + if (*ptr++ == pc) { + pos = n; + } + n++; + } + } + if (pos == 0 && n == 1) { + g_free(priv->conns); + priv->conns = NULL; + } else if (pos >= 0) { + memmove(priv->conns + pos, priv->conns + pos + 1, + sizeof(NfcPeerConnection*) * (n - pos) /* Copy NULL too */); + priv->conns = g_renew(NfcPeerConnection*, priv->conns, n); + } +} + +void +nfc_peer_service_peer_arrived( + NfcPeerService* self, + NfcPeer* peer) +{ + NFC_PEER_SERVICE_GET_CLASS(self)->peer_arrived(self, peer); +} + +void +nfc_peer_service_peer_left( + NfcPeerService* self, + NfcPeer* peer) +{ + NFC_PEER_SERVICE_GET_CLASS(self)->peer_left(self, peer); +} + +void +nfc_peer_service_datagram_received( + NfcPeerService* self, + guint8 ssap, + const void* data, + guint len) +{ + NFC_PEER_SERVICE_GET_CLASS(self)->datagram_received(self, ssap, data, len); +} + +/*==========================================================================* + * Default methods + *==========================================================================*/ + +static +void +nfc_peer_service_default_peer_callback( + NfcPeerService* self, + NfcPeer* peer) +{ +} + +static +NfcPeerConnection* +nfc_peer_service_default_new_connect( + NfcPeerService* self, + guint8 rsap, + const char* name) +{ + return NULL; +} + +static +NfcPeerConnection* +nfc_peer_service_default_new_accept( + NfcPeerService* self, + guint8 rsap) +{ + return NULL; +} + +static +void +nfc_peer_service_default_datagram_received( + NfcPeerService* service, + guint8 ssap, + const void* data, + guint len) +{ +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_peer_service_init( + NfcPeerService* self) +{ + NfcPeerServicePriv* priv = G_TYPE_INSTANCE_GET_PRIVATE(self, THIS_TYPE, + NfcPeerServicePriv); + + self->priv = priv; +} + +static +void +nfc_peer_service_finalize( + GObject* object) +{ + NfcPeerService* self = NFC_PEER_SERVICE(object); + NfcPeerServicePriv* priv = self->priv; + + g_free(priv->name); + g_free(priv->conns); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +nfc_peer_service_class_init( + NfcPeerServiceClass* klass) +{ + g_type_class_add_private(klass, sizeof(NfcPeerServicePriv)); + klass->peer_arrived = nfc_peer_service_default_peer_callback; + klass->peer_left = nfc_peer_service_default_peer_callback; + klass->new_connect = nfc_peer_service_default_new_connect; + klass->new_accept = nfc_peer_service_default_new_accept; + klass->datagram_received = nfc_peer_service_default_datagram_received; + G_OBJECT_CLASS(klass)->finalize = nfc_peer_service_finalize; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_peer_service_p.h b/core/src/nfc_peer_service_p.h new file mode 100644 index 0000000..7608088 --- /dev/null +++ b/core/src/nfc_peer_service_p.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_SERVICE_PRIVATE_H +#define NFC_PEER_SERVICE_PRIVATE_H + +#include "nfc_types_p.h" + +#include + +NfcPeerConnection* +nfc_peer_service_new_connect( + NfcPeerService* service, + guint8 rsap, + const char* rname) + NFCD_INTERNAL; + +NfcPeerConnection* +nfc_peer_service_new_accept( + NfcPeerService* service, + guint8 rsap) + NFCD_INTERNAL; + +void +nfc_peer_service_connection_created( + NfcPeerService* service, + NfcPeerConnection* connection) + NFCD_INTERNAL; + +void +nfc_peer_service_connection_dead( + NfcPeerService* service, + NfcPeerConnection* connection) + NFCD_INTERNAL; + +void +nfc_peer_service_peer_arrived( + NfcPeerService* service, + NfcPeer* peer) + NFCD_INTERNAL; + +void +nfc_peer_service_peer_left( + NfcPeerService* service, + NfcPeer* peer) + NFCD_INTERNAL; + +void +nfc_peer_service_datagram_received( + NfcPeerService* service, + guint8 ssap, + const void* data, + guint len) + NFCD_INTERNAL; + +#endif /* NFC_PEER_SERVICE_PRIVATE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_peer_services.c b/core/src/nfc_peer_services.c new file mode 100644 index 0000000..8c9308a --- /dev/null +++ b/core/src/nfc_peer_services.c @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_peer_services.h" +#include "nfc_peer_service_p.h" + +#define GLOG_MODULE_NAME NFC_PEER_LOG_MODULE +#include +#include + +#include + +#define SAP_BIT(sap) (((guint64)1) << (sap)) + +typedef struct nfc_peer_services_object { + NfcPeerServices pub; + NfcPeerService** list; + gint64 sap_mask; + gint refcount; +} NfcPeerServicesObject; + +static NfcPeerService* const nfc_peer_services_empty_list[] = { NULL }; + +static inline +NfcPeerServicesObject* nfc_peer_services_cast(NfcPeerServices* pub) + { return G_LIKELY(pub) ? G_CAST(pub,NfcPeerServicesObject,pub) : NULL; } + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +void +nfc_peer_services_free( + NfcPeerServicesObject* self) +{ + NfcPeerService* const* ptr = self->pub.list; + + while (*ptr) nfc_peer_service_unref(*ptr++); + g_free(self->list); + g_slice_free1(sizeof(*self), self); +} + +static +gboolean +nfc_peer_services_contains( + NfcPeerServices* services, + NfcPeerService* service) +{ + NfcPeerService* const* ptr; + + for (ptr = services->list; *ptr; ptr++) { + if (*ptr == service) { + return TRUE; + } + } + return FALSE; +} + +static +int +nfc_peer_services_compare( + const void* p1, + const void* p2) +{ + NfcPeerService* ps1 = *(NfcPeerService**)p1; + NfcPeerService* ps2 = *(NfcPeerService**)p2; + + return (int)ps1->sap - (int)ps2->sap; +} + +static +void +nfc_peer_services_peer_notify( + NfcPeerServices* services, + NfcPeer* peer, + void (*notify)(NfcPeerService* ps, NfcPeer* peer)) +{ + if (G_LIKELY(services)) { + NfcPeerService* const* ptr; + guint n = 0; + + /* Count the services and bump references at the same time */ + for (ptr = services->list; *ptr; ptr++) { + nfc_peer_service_ref(*ptr); + n++; + } + + if (n) { + NfcPeerService** tmp = g_new(NfcPeerService*, n + 1); + + /* In case if callbacks modify the list, make a copy */ + memcpy(tmp, services->list, sizeof(NfcPeerService*) * (n + 1)); + for (ptr = tmp; *ptr; ptr++) { + NfcPeerService* ps = *ptr; + + /* Paranoid check if the service is still there */ + if (nfc_peer_services_contains(services, ps)) { + notify(ps, peer); + } + } + + /* Release temporary references */ + for (ptr = tmp; *ptr; ptr++) { + nfc_peer_service_unref(*ptr); + } + g_free(tmp); + } + } +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcPeerServices* +nfc_peer_services_new( + void) +{ + NfcPeerServicesObject* self = g_slice_new0(NfcPeerServicesObject); + NfcPeerServices* services = &self->pub; + + services->list = nfc_peer_services_empty_list; + self->sap_mask = 1; /* Reserved for LLC Link Management Service */ + g_atomic_int_set(&self->refcount, 1); + return services; +} + +NfcPeerServices* +nfc_peer_services_ref( + NfcPeerServices* services) +{ + NfcPeerServicesObject* self = nfc_peer_services_cast(services); + + if (G_LIKELY(self)) { + GASSERT(self->refcount > 0); + g_atomic_int_inc(&self->refcount); + } + return services; +} + +void +nfc_peer_services_unref( + NfcPeerServices* services) +{ + NfcPeerServicesObject* self = nfc_peer_services_cast(services); + + if (G_LIKELY(self)) { + GASSERT(self->refcount > 0); + if (g_atomic_int_dec_and_test(&self->refcount)) { + nfc_peer_services_free(self); + } + } +} + +NfcPeerServices* +nfc_peer_services_copy( + NfcPeerServices* services) +{ + NfcPeerServicesObject* self = nfc_peer_services_cast(services); + + if (G_LIKELY(self)) { + NfcPeerServices* copy = nfc_peer_services_new(); + + if (self->list) { + NfcPeerServicesObject* priv = nfc_peer_services_cast(copy); + NfcPeerService* const* ptr; + guint n = 0; + + /* Count the services and bump references at the same time */ + for (ptr = services->list; *ptr; ptr++) { + nfc_peer_service_ref(*ptr); + n++; + } + + priv->sap_mask = self->sap_mask; + copy->list = priv->list = g_memdup(self->list, + sizeof(NfcPeerService*) * (n + 1)); + } + return copy; + } + return NULL; +} + +NfcPeerService* +nfc_peer_services_find_sn( + NfcPeerServices* services, + const char* name) +{ + NfcPeerServicesObject* self = nfc_peer_services_cast(services); + + if (G_LIKELY(self) && name && name[0]) { + NfcPeerService* const* ptr = services->list; + + while (*ptr) { + NfcPeerService* ps = *ptr++; + + if (!g_strcmp0(ps->name, name)) { + return ps; + } + } + } + return NULL; +} + +NfcPeerService* +nfc_peer_services_find_sap( + NfcPeerServices* services, + guint8 sap) +{ + NfcPeerServicesObject* self = nfc_peer_services_cast(services); + + if (G_LIKELY(self) && sap > NFC_LLC_SAP_SDP) { + NfcPeerService* const* ptr = services->list; + + while (*ptr) { + NfcPeerService* ps = *ptr++; + + if (ps->sap == sap) { + return ps; + } else if (ps->sap > sap) { + /* The list is sorted */ + break; + } + } + } + return NULL; +} + +gboolean +nfc_peer_services_add( + NfcPeerServices* services, + NfcPeerService* ps) +{ + NfcPeerServicesObject* self = nfc_peer_services_cast(services); + + if (G_LIKELY(self) && G_LIKELY(ps)) { + NfcPeerService* const* ptr; + guint8 sap_min, sap_max, sap; + guint n; + const char* name = (ps->name && ps->name[0]) ? ps->name : NULL; + + /* + * Count the services and at the same time check if it's already + * there or if the name has already been taken. + */ + for (ptr = services->list, n = 0; *ptr; ptr++, n++) { + if (*ptr == ps || (name && !g_strcmp0(name, (*ptr)->name))) { + return FALSE; + } + } + + /* Pick SAP from the right range */ + if (name) { + /* Check reserved named */ + if (!g_strcmp0(name, NFC_LLC_NAME_SDP)) { + /* Can't register this one */ + return FALSE; + } else if (!g_strcmp0(name, NFC_LLC_NAME_SNEP)) { + /* This is a well-known service */ + sap_min = sap_max = NFC_LLC_SAP_SNEP; + } else { + /* Dynamically pick the number */ + sap_min = NFC_LLC_SAP_NAMED; + sap_max = NFC_LLC_SAP_UNNAMED - 1; + } + } else { + sap_min = NFC_LLC_SAP_UNNAMED; + sap_max = NFC_LLC_SAP_MAX; + } + + for (sap = sap_min; + sap <= sap_max && (self->sap_mask & SAP_BIT(sap)); + sap++); + + if (sap <= sap_max) { + ps->sap = sap; + /* Reallocate the memory and append the reference */ + self->list = g_renew(NfcPeerService*, self->list, n + 2); + self->list[n] = nfc_peer_service_ref(ps); + self->list[n + 1] = NULL; + qsort(self->list, n + 1, sizeof(NfcPeerService*), + nfc_peer_services_compare); + services->list = self->list; + self->sap_mask |= SAP_BIT(sap); + return TRUE; + } + } + return FALSE; +} + +gboolean +nfc_peer_services_remove( + NfcPeerServices* services, + NfcPeerService* ps) +{ + NfcPeerServicesObject* self = nfc_peer_services_cast(services); + + if (G_LIKELY(self) && G_LIKELY(ps)) { + NfcPeerService* const* ptr; + int pos = -1; + guint n; + + /* + * Count the services and at the same time find the position of + * the service that we are about to remove. + */ + for (ptr = services->list, n = 0; *ptr; ptr++, n++) { + if (*ptr == ps) { + pos = n; + } + } + + if (pos >= 0) { + GASSERT(self->sap_mask & SAP_BIT(ps->sap)); + self->sap_mask &= ~SAP_BIT(ps->sap); + if (n == 1) { + /* The last service is gone */ + g_free(self->list); + self->list = NULL; + services->list = nfc_peer_services_empty_list; + } else { + memmove(self->list + pos, self->list + pos + 1, + sizeof(NfcPeerService*) * (n - pos)); + self->list = g_renew(NfcPeerService*, self->list, n); + services->list = self->list; + } + nfc_peer_service_unref(ps); + return TRUE; + } + } + return FALSE; +} + +void +nfc_peer_services_peer_arrived( + NfcPeerServices* svcs, + NfcPeer* peer) +{ + nfc_peer_services_peer_notify(svcs, peer, nfc_peer_service_peer_arrived); +} + +void +nfc_peer_services_peer_left( + NfcPeerServices* svcs, + NfcPeer* peer) +{ + nfc_peer_services_peer_notify(svcs, peer, nfc_peer_service_peer_left); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_peer_services.h b/core/src/nfc_peer_services.h new file mode 100644 index 0000000..f3c4c77 --- /dev/null +++ b/core/src/nfc_peer_services.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_PEER_SERVICES_H +#define NFC_PEER_SERVICES_H + +#include "nfc_types_p.h" + +struct nfc_peer_services { + NfcPeerService* const* list; /* NULL-terminated */ +}; + +NfcPeerServices* +nfc_peer_services_new( + void) + NFCD_INTERNAL; + +NfcPeerServices* +nfc_peer_services_ref( + NfcPeerServices* services) + NFCD_INTERNAL; + +void +nfc_peer_services_unref( + NfcPeerServices* services) + NFCD_INTERNAL; + +NfcPeerServices* +nfc_peer_services_copy( + NfcPeerServices* services) + NFCD_INTERNAL; + +NfcPeerService* +nfc_peer_services_find_sn( + NfcPeerServices* services, + const char* name) + NFCD_INTERNAL; + +NfcPeerService* +nfc_peer_services_find_sap( + NfcPeerServices* services, + guint8 sap) + NFCD_INTERNAL; + +gboolean +nfc_peer_services_add( + NfcPeerServices* services, + NfcPeerService* service) + NFCD_INTERNAL; + +gboolean +nfc_peer_services_remove( + NfcPeerServices* services, + NfcPeerService* service) + NFCD_INTERNAL; + +void +nfc_peer_services_peer_arrived( + NfcPeerServices* services, + NfcPeer* peer) + NFCD_INTERNAL; + +void +nfc_peer_services_peer_left( + NfcPeerServices* services, + NfcPeer* peer) + NFCD_INTERNAL; + +#endif /* NFC_PEER_SERVICES_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_peer_socket.c b/core/src/nfc_peer_socket.c new file mode 100644 index 0000000..6a79c29 --- /dev/null +++ b/core/src/nfc_peer_socket.c @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include "nfc_llc.h" +#include "nfc_peer_service.h" +#include "nfc_peer_socket.h" +#include "nfc_peer_socket_impl.h" + +#define GLOG_MODULE_NAME NFC_PEER_LOG_MODULE +#include +#include + +#include + +#include +#include +#include +#include + +struct nfc_peer_socket_priv { + GIOChannel* io_channel; + GList* write_queue; + guint read_watch_id; + guint write_watch_id; + guint write_pos; + int fd; +}; + +/* NOTE: we can exceed this limit, but by no more than MIU. There's no + * need to be overly strict about it. */ +#define DEFAULT_MAX_SEND_QUEUE (128*1024) + +#define THIS(obj) NFC_PEER_SOCKET(obj) +#define THIS_TYPE NFC_TYPE_PEER_SOCKET +#define PARENT_TYPE NFC_TYPE_PEER_CONNECTION +#define PARENT_CLASS (nfc_peer_socket_parent_class) + +G_DEFINE_TYPE(NfcPeerSocket, nfc_peer_socket, PARENT_TYPE) + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +void +nfc_peer_socket_shutdown( + NfcPeerSocket* self) +{ + NfcPeerSocketPriv* priv = self->priv; + + if (priv->read_watch_id) { + g_source_remove(priv->read_watch_id); + priv->read_watch_id = 0; + } + if (priv->write_watch_id) { + g_source_remove(priv->write_watch_id); + priv->write_watch_id = 0; + } + if (priv->io_channel) { + shutdown(priv->fd, SHUT_RDWR); + g_io_channel_shutdown(priv->io_channel, FALSE, NULL); + g_io_channel_unref(priv->io_channel); + priv->io_channel = NULL; + priv->fd = -1; + } +} + +static +gboolean +nfc_peer_socket_read_bytes( + NfcPeerSocket* self, + gchar* buf, + gsize count, + gsize* bytes_read) +{ + GError* error = NULL; + NfcPeerSocketPriv* priv = self->priv; + NfcPeerConnection* conn = &self->connection; + GIOStatus status = g_io_channel_read_chars(priv->io_channel, buf, + count, bytes_read, &error); + + if (error) { + GDEBUG("Connection %u:%u read failed: %s", conn->service->sap, + conn->rsap, GERRMSG(error)); + priv->read_watch_id = 0; + nfc_peer_socket_shutdown(self); + nfc_peer_connection_disconnect(conn); + g_error_free(error); + return FALSE; + } else if (status == G_IO_STATUS_EOF) { + GDEBUG("Connection %u:%u hung up", conn->service->sap, conn->rsap); + priv->read_watch_id = 0; + nfc_peer_socket_shutdown(self); + nfc_peer_connection_disconnect(conn); + return FALSE; + } else { + GVERBOSE("Connection %u:%u read %u bytes", conn->service->sap, + conn->rsap, (guint)(*bytes_read)); + return TRUE; + } +} + +static +gboolean +nfc_peer_socket_read( + NfcPeerSocket* self) +{ + NfcPeerConnection* conn = &self->connection; + const guint rmiu = nfc_peer_connection_rmiu(conn); + void* buf = g_malloc(rmiu); + gsize bytes_read; + + if (nfc_peer_socket_read_bytes(self, buf, rmiu, &bytes_read)) { + GBytes* bytes = g_bytes_new_take(buf, bytes_read); + const gboolean sent = nfc_peer_connection_send(conn, bytes); + + g_bytes_unref(bytes); + /* Stop reading when we hit the queue size limit */ + return sent && (self->connection.bytes_queued <= self->max_send_queue); + } else { + g_free(buf); + } + return FALSE; +} + +static +gboolean +nfc_peer_socket_read_callback( + GIOChannel* source, + GIOCondition condition, + gpointer user_data) +{ + NfcPeerSocket* self = THIS(user_data); + gboolean result; + + g_object_ref(self); + if ((condition & G_IO_IN) && nfc_peer_socket_read(self)) { + result = G_SOURCE_CONTINUE; + } else { + NfcPeerSocketPriv* priv = self->priv; + + priv->read_watch_id = 0; + result = G_SOURCE_REMOVE; + } + g_object_unref(self); + return result; +} + +static +void +nfc_peer_socket_read_check( + NfcPeerSocket* self) +{ + NfcPeerConnection* conn = &self->connection; + NfcPeerSocketPriv* priv = self->priv; + + if (priv->io_channel && !priv->read_watch_id && + conn->bytes_queued <= self->max_send_queue && + conn->state <= NFC_LLC_CO_ACTIVE) { + priv->read_watch_id = g_io_add_watch(priv->io_channel, + G_IO_IN | G_IO_ERR | G_IO_HUP, nfc_peer_socket_read_callback, + self); + } +} + +static +gboolean +nfc_peer_socket_write_bytes( + NfcPeerSocket* self, + const void* buf, + gssize count, + gsize* bytes_written, + GError** error) +{ + NfcPeerSocketPriv* priv = self->priv; + NfcPeerConnection* conn = &self->connection; + const GIOStatus status = g_io_channel_write_chars(priv->io_channel, + buf, count, bytes_written, error); + + if (status == G_IO_STATUS_NORMAL || status == G_IO_STATUS_AGAIN) { + GVERBOSE("Connection %u:%u wrote %u bytes", conn->service->sap, + conn->rsap, (guint)(*bytes_written)); + return TRUE; + } else { + GASSERT(*error); + GDEBUG("Connection %u:%u write failed: %s", conn->service->sap, + conn->rsap, GERRMSG(*error)); + nfc_peer_connection_disconnect(conn); + return FALSE; + } +} + +static +gboolean +nfc_peer_socket_write( + NfcPeerSocket* self, + GError** error) +{ + NfcPeerSocketPriv* priv = self->priv; + + if (priv->write_queue) { + GList* first = g_list_first(priv->write_queue); + GBytes* bytes = first->data; + gsize len, bytes_written = 0; + const guint8* data = g_bytes_get_data(bytes, &len); + + GASSERT(priv->write_pos < len); + if (!nfc_peer_socket_write_bytes(self, data + priv->write_pos, + len - priv->write_pos, &bytes_written, error)) { + return FALSE; + } + priv->write_pos += bytes_written; + GASSERT(priv->write_pos <= len); + if (priv->write_pos < len) { + /* Will have to wait */ + return TRUE; + } + + /* Done with this one */ + priv->write_pos = 0; + priv->write_queue = g_list_delete_link(priv->write_queue, first); + g_bytes_unref(bytes); + if (priv->write_queue) { + /* Have more */ + return TRUE; + } + } + + GVERBOSE("Connection %u:%u has no more data to write", + self->connection.service->sap, self->connection.rsap); + return TRUE; +} + +static +gboolean +nfc_peer_socket_write_callback( + GIOChannel* source, + GIOCondition condition, + gpointer user_data) +{ + NfcPeerSocket* self = THIS(user_data); + NfcPeerSocketPriv* priv = self->priv; + gboolean result = G_SOURCE_REMOVE; + GError* error = NULL; + + g_object_ref(self); + if ((condition & G_IO_OUT) && + nfc_peer_socket_write(self, &error) && + priv->write_queue) { + result = G_SOURCE_CONTINUE; + } else { + priv->write_watch_id = 0; + result = G_SOURCE_REMOVE; + } + if (error) { + GERR("Connection %u:%u write failed: %s", + self->connection.service->sap, self->connection.rsap, + GERRMSG(error)); + priv->write_watch_id = 0; + nfc_peer_socket_shutdown(self); + g_error_free(error); + } + g_object_unref(self); + return result; +} + +/*==========================================================================* + * Internal interface + *==========================================================================*/ + +gboolean +nfc_peer_socket_init_connect( + NfcPeerSocket* self, + NfcPeerService* service, + guint8 rsap, + const char* name) +{ + if (G_LIKELY(service)) { + int fd[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == 0) { + NfcPeerConnection* conn = &self->connection; + NfcPeerSocketPriv* priv = self->priv; + + nfc_peer_connection_init_connect(conn, service, rsap, name); + self->fdl = g_unix_fd_list_new_from_array(fd, 1); + priv->fd = fd[1]; + return TRUE; + } + GERR("Connection %u:%u failed to create socket pair: %s", + service->sap, rsap, strerror(errno)); + } + return FALSE; +} + +gboolean +nfc_peer_socket_init_accept( + NfcPeerSocket* self, + NfcPeerService* service, + guint8 rsap) +{ + if (G_LIKELY(service)) { + int fd[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == 0) { + NfcPeerConnection* conn = &self->connection; + NfcPeerSocketPriv* priv = self->priv; + + nfc_peer_connection_init_accept(conn, service, rsap); + self->fdl = g_unix_fd_list_new_from_array(fd, 1); + priv->fd = fd[1]; + return TRUE; + } + GERR("Connection %u:%u failed to create socket pair: %s", + service->sap, rsap, strerror(errno)); + } + return FALSE; +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcPeerSocket* +nfc_peer_socket_new_connect( + NfcPeerService* service, + guint8 rsap, + const char* name) +{ + NfcPeerSocket* self = g_object_new(THIS_TYPE, NULL); + + if (nfc_peer_socket_init_connect(self, service, rsap, name)) { + return self; + } else { + g_object_unref(THIS(self)); + return NULL; + } +} + +NfcPeerSocket* +nfc_peer_socket_new_accept( + NfcPeerService* service, + guint8 rsap) +{ + NfcPeerSocket* self = g_object_new(THIS_TYPE, NULL); + + if (nfc_peer_socket_init_accept(self, service, rsap)) { + return self; + } else { + g_object_unref(THIS(self)); + return NULL; + } +} + +int +nfc_peer_socket_fd( + NfcPeerSocket* self) +{ + return (G_LIKELY(self) && self->fdl) ? + g_unix_fd_list_peek_fds(self->fdl, NULL)[0] : -1; +} + +void +nfc_peer_socket_set_max_send_queue( + NfcPeerSocket* self, + gsize max_send_queue) +{ + if (G_LIKELY(self) && (self->max_send_queue != max_send_queue)) { + self->max_send_queue = max_send_queue; + nfc_peer_socket_read_check(self); + } +} + +/*==========================================================================* + * Methods + *==========================================================================*/ + +static +void +nfc_peer_socket_state_changed( + NfcPeerConnection* conn) +{ + if (conn->state == NFC_LLC_CO_ACTIVE) { + NfcPeerSocket* self = THIS(conn); + NfcPeerSocketPriv* priv = self->priv; + + GASSERT(!priv->io_channel); /* We can become active only once */ + priv->io_channel = g_io_channel_unix_new(priv->fd); + if (priv->io_channel) { + g_io_channel_set_flags(priv->io_channel, G_IO_FLAG_NONBLOCK, NULL); + g_io_channel_set_encoding(priv->io_channel, NULL, NULL); + g_io_channel_set_buffered(priv->io_channel, FALSE); + g_io_channel_set_close_on_unref(priv->io_channel, TRUE); + nfc_peer_socket_read_check(self); + } + } + NFC_PEER_CONNECTION_CLASS(PARENT_CLASS)->state_changed(conn); +} + +static +void +nfc_peer_socket_data_received( + NfcPeerConnection* conn, + const void* data, + guint len) +{ + NfcPeerSocket* self = THIS(conn); + NfcPeerSocketPriv* priv = self->priv; + + if (len > 0 && priv->io_channel) { + priv->write_queue = g_list_append(priv->write_queue, + g_bytes_new(data, len)); + if (!priv->write_watch_id) { + GVERBOSE("Connection %u:%u scheduling write", + conn->service->sap, conn->rsap); + priv->write_watch_id = g_io_add_watch(priv->io_channel, + G_IO_OUT | G_IO_ERR | G_IO_HUP, nfc_peer_socket_write_callback, + self); + } + } + NFC_PEER_CONNECTION_CLASS(PARENT_CLASS)->data_received(conn, data, len); +} + +static +void +nfc_peer_socket_data_dequeued( + NfcPeerConnection* conn) +{ + nfc_peer_socket_read_check(THIS(conn)); + NFC_PEER_CONNECTION_CLASS(PARENT_CLASS)->data_dequeued(conn); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_peer_socket_init( + NfcPeerSocket* self) +{ + NfcPeerSocketPriv* priv = G_TYPE_INSTANCE_GET_PRIVATE(self, THIS_TYPE, + NfcPeerSocketPriv); + + self->max_send_queue = DEFAULT_MAX_SEND_QUEUE; + self->priv = priv; + priv->fd = -1; +} + +static +void +nfc_peer_socket_finalize( + GObject* object) +{ + NfcPeerSocket* self = THIS(object); + NfcPeerSocketPriv* priv = self->priv; + + nfc_peer_socket_shutdown(self); + g_list_free_full(priv->write_queue, (GDestroyNotify) g_bytes_unref); + if (self->fdl) { + g_object_unref(self->fdl); + } + if (priv->fd >= 0) { + close(priv->fd); + } + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +nfc_peer_socket_class_init( + NfcPeerSocketClass* klass) +{ + NfcPeerConnectionClass* connection = NFC_PEER_CONNECTION_CLASS(klass); + + g_type_class_add_private(klass, sizeof(NfcPeerSocketPriv)); + connection->state_changed = nfc_peer_socket_state_changed; + connection->data_received = nfc_peer_socket_data_received; + connection->data_dequeued = nfc_peer_socket_data_dequeued; + G_OBJECT_CLASS(klass)->finalize = nfc_peer_socket_finalize; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_peer_target.c b/core/src/nfc_peer_target.c new file mode 100644 index 0000000..7e05a17 --- /dev/null +++ b/core/src/nfc_peer_target.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_peer_p.h" +#include "nfc_initiator_p.h" +#include "nfc_llc.h" +#include "nfc_llc_io.h" + +#define GLOG_MODULE_NAME NFC_PEER_LOG_MODULE +#include + +typedef struct nfc_peer_target { + NfcPeer peer; + NfcLlcIo* llc_io; + NfcInitiator* initiator; + gulong gone_id; +} NfcPeerTarget; + +#define THIS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), THIS_TYPE, NfcPeerTarget)) +#define THIS_TYPE (nfc_peer_target_get_type()) +#define PARENT_TYPE NFC_TYPE_PEER +#define PARENT_CLASS (nfc_peer_target_parent_class) + +typedef NfcPeerClass NfcPeerTargetClass; +G_DEFINE_TYPE(NfcPeerTarget, nfc_peer_target, PARENT_TYPE) + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +void +nfc_peer_target_gone( + NfcInitiator* initiator, + void* user_data) +{ + /* NfcInitiator makes sure that this signal is only issued once */ + nfc_peer_gone(NFC_PEER(user_data)); +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcPeer* +nfc_peer_new_target( + NfcInitiator* initiator, + NFC_TECHNOLOGY technology, + const NfcParamNfcDepTarget* nfc_dep, + NfcPeerServices* services) +{ + if (G_LIKELY(initiator) && G_LIKELY(nfc_dep)) { + NfcPeerTarget* self = g_object_new(THIS_TYPE, NULL); + NfcPeer* peer = &self->peer; + + self->initiator = nfc_initiator_ref(initiator); + self->llc_io = nfc_llc_io_target_new(initiator); + if (nfc_peer_init_base(peer, self->llc_io, &nfc_dep->atr_req_g, + services, technology, NFC_PEER_FLAGS_NONE)) { + peer->present = initiator->present; + self->gone_id = nfc_initiator_add_gone_handler(initiator, + nfc_peer_target_gone, self); + return peer; + } + g_object_unref(self); + } + return NULL; +} + +/*==========================================================================* + * Methods + *==========================================================================*/ + +static +void +nfc_peer_target_deactivate( + NfcPeer* peer) +{ + NfcPeerTarget* self = THIS(peer); + + nfc_initiator_deactivate(self->initiator); + NFC_PEER_CLASS(PARENT_CLASS)->deactivate(peer); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_peer_target_init( + NfcPeerTarget* self) +{ +} + +static +void +nfc_peer_target_finalize( + GObject* object) +{ + NfcPeerTarget* self = THIS(object); + + nfc_llc_io_unref(self->llc_io); + nfc_initiator_remove_handler(self->initiator, self->gone_id); + nfc_initiator_unref(self->initiator); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +nfc_peer_target_class_init( + NfcPeerTargetClass* klass) +{ + klass->deactivate = nfc_peer_target_deactivate; + G_OBJECT_CLASS(klass)->finalize = nfc_peer_target_finalize; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_snep_server.c b/core/src/nfc_snep_server.c new file mode 100644 index 0000000..205de63 --- /dev/null +++ b/core/src/nfc_snep_server.c @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include "nfc_snep_server.h" +#include "nfc_peer_connection_impl.h" +#include "nfc_peer_connection_p.h" +#include "nfc_peer_service_impl.h" +#include "nfc_peer_service_p.h" +#include "nfc_ndef.h" +#include "nfc_llc.h" + +#define GLOG_MODULE_NAME NFC_SNEP_LOG_MODULE +#include +#include + +GLOG_MODULE_DEFINE2("snep", NFC_CORE_LOG_MODULE); + +/* + * NFCForum-TS-SNEP_1.0 + * + * Table 2: Request Field Values + */ +typedef enum snep_request_code { + SNEP_REQUEST_CONTINUE = 0x00, + SNEP_REQUEST_GET = 0x01, + SNEP_REQUEST_PUT = 0x02, + SNEP_REQUEST_REJECT = 0x7f +} SNEP_REQUEST_CODE; + +/* + * Table 3: Response Field Values + */ +typedef enum snep_response_code { + SNEP_RESPONSE_CONTINUE = 0x80, + SNEP_RESPONSE_SUCCESS = 0x81, + SNEP_RESPONSE_NOT_FOUND = 0xc0, + SNEP_RESPONSE_EXCESS_DATA = 0xc1, + SNEP_RESPONSE_BAD_REQUEST = 0xc2, + SNEP_RESPONSE_NOT_IMPLEMENTED = 0xe0, + SNEP_RESPONSE_UNSUPPORTED_VERSION = 0xe1, + SNEP_RESPONSE_REJECT = 0xff +} SNEP_RESPONSE_CODE; + +#define SNEP_MAJOR_VERSION (1) +#define SNEP_VERSION (0x10) /* (MAJOR << 4) | MINOR */ + +typedef struct nfc_snep_server_connection { + NfcPeerConnection connection; + GByteArray* buf; + guint ndef_length; +} NfcSnepServerConnection; + +typedef NfcPeerConnectionClass NfcSnepServerConnectionClass; +GType nfc_snep_server_connection_get_type(void) NFCD_INTERNAL; +G_DEFINE_TYPE(NfcSnepServerConnection, nfc_snep_server_connection, \ + NFC_TYPE_PEER_CONNECTION) +#define NFC_TYPE_SNEP_SERVER_CONNECTION (nfc_snep_server_connection_get_type()) +#define NFC_SNEP_SERVER_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + NFC_TYPE_SNEP_SERVER_CONNECTION, NfcSnepServerConnection)) + +struct nfc_snep_server_priv { + int connection_count; +}; + +typedef NfcPeerServiceClass NfcSnepServerClass; +GType nfc_snep_server_get_type(void) NFCD_INTERNAL; +G_DEFINE_TYPE(NfcSnepServer, nfc_snep_server, NFC_TYPE_PEER_SERVICE) +#define NFC_TYPE_SNEP_SERVER (nfc_snep_server_get_type()) +#define NFC_SNEP_SERVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + NFC_TYPE_SNEP_SERVER, NfcSnepServer)) + +enum nfc_snep_server_signal { + SIGNAL_STATE_CHANGED, + SIGNAL_NDEF_CHANGED, + SIGNAL_COUNT +}; + +#define SIGNAL_STATE_CHANGED_NAME "nfc-snep-server-state-changed" +#define SIGNAL_NDEF_CHANGED_NAME "nfc-snep-server-ndef-changed" + +static guint nfc_snep_server_signals[SIGNAL_COUNT] = { 0 }; + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +void +nfc_snep_server_response( + NfcPeerConnection* conn, + SNEP_RESPONSE_CODE code) +{ + const guint size = 6; + guint8* data = g_malloc(size); + GBytes* pkt = g_bytes_new_take(data, size); + + memset(data, 0, size); + data[0] = SNEP_VERSION; /* Version */ + data[1] = code; /* Response */ + nfc_peer_connection_send(conn, pkt); + g_bytes_unref(pkt); +} + +static +void +nfc_snep_server_set_state( + NfcSnepServer* self, + NFC_SNEP_SERVER_STATE state) +{ + if (self->state != state) { + self->state = state; + g_signal_emit(self, nfc_snep_server_signals[SIGNAL_STATE_CHANGED], 0); + } +} + +static +void +nfc_snep_server_update_connection_count( + NfcSnepServer* self, + int change) +{ + NfcSnepServerPriv* priv = self->priv; + const int prev_count = priv->connection_count; + + priv->connection_count += change; + if (change > 0) { + if (!prev_count) { + nfc_snep_server_set_state(self, NFC_SNEP_SERVER_RECEIVING); + } + } else { + if (!priv->connection_count) { + nfc_snep_server_set_state(self, NFC_SNEP_SERVER_LISTENING); + } + } +} + +/*==========================================================================* + * Connection + *==========================================================================*/ + +static +void +nfc_snep_server_connection_receive_ndef( + NfcSnepServerConnection* self, + const void* data, + guint len) +{ + NfcPeerConnection* conn = &self->connection; + NfcSnepServer* snep = NFC_SNEP_SERVER(conn->service); + GByteArray* buf = self->buf; + + if ((buf->len + len) > self->ndef_length) { + GWARN("Broken SNEP Response (%u > %u)", buf->len + len, + self->ndef_length); + nfc_peer_connection_disconnect(conn); + } else { + g_byte_array_append(buf, data, len); + GDEBUG("Received %u bytes", buf->len); + if (buf->len == self->ndef_length) { + GUtilData ndef_data; + NfcNdefRec* prev_ndef; + + /* Done with receiving NDEF. Parse it. */ + ndef_data.bytes = buf->data; + ndef_data.size = buf->len; + prev_ndef = snep->ndef; + snep->ndef = nfc_ndef_rec_new(&ndef_data); + + /* Need to actually compare NDEFs? */ + if (prev_ndef != snep->ndef) { + g_signal_emit(snep, nfc_snep_server_signals + [SIGNAL_NDEF_CHANGED], 0); + } + nfc_ndef_rec_unref(prev_ndef); + + /* Done, terminate the connection */ + nfc_peer_connection_disconnect(conn); + } + } +} + +static +void +nfc_snep_server_connection_data_received( + NfcPeerConnection* conn, + const void* data, + guint len) +{ + NfcSnepServerConnection* self = NFC_SNEP_SERVER_CONNECTION(conn); + + if (self->buf) { + /* Receiving fragmented message */ + nfc_snep_server_connection_receive_ndef(self, data, len); + } else if (len >= 6) { + /* + * NFCForum-TS-SNEP_1.0 + * 2.1. SNEP Communication Protocol + * + * In order for the receiver of a fragmented SNEP message to + * determine the number of octets that are to be received with + * subsequent fragments, the first fragment SHALL include at + * least the entire SNEP message header. + */ + const guint8* pkt = data; + const guint version = pkt[0]; + const SNEP_REQUEST_CODE op = pkt[1]; + const guint v1 = (version >> 4); + + GDEBUG("SNEP Version %u.%u", v1, version & 0x0f); + if (v1 != SNEP_MAJOR_VERSION) { + GDEBUG("Unsupported SNEP Version %u", v1); + nfc_snep_server_response(conn, SNEP_RESPONSE_UNSUPPORTED_VERSION); + nfc_peer_connection_disconnect(conn); + } else if (op == SNEP_REQUEST_GET) { + /* + * 6.1. Functional Description + * + * The default server SHALL NOT accept Get requests. + * The appropriate response for a Get request message + * is Not Implemented. + */ + GDEBUG("NDEF Get not accepted"); + nfc_snep_server_response(conn, SNEP_RESPONSE_NOT_IMPLEMENTED); + nfc_peer_connection_disconnect(conn); + } else if (op != SNEP_REQUEST_PUT) { + GDEBUG("Unsupported SNEP Request 0x%02x", op); + nfc_snep_server_response(conn, SNEP_RESPONSE_BAD_REQUEST); + nfc_peer_connection_disconnect(conn); + } else { + /* + * 3.1.3. Length Field + * + * The Length field specifies the total length in octets of + * the Information field. The Length field is four octets + * representing a 32-bit unsigned integer. Transmission + * order SHALL be most significant octet first. + */ + self->ndef_length = + (((guint32)pkt[2]) << 24) | + (((guint32)pkt[3]) << 16) | + (((guint32)pkt[4]) << 8) | + ((guint32)pkt[5]); + GDEBUG("NDEF Put %u bytes", self->ndef_length); + self->buf = g_byte_array_sized_new(self->ndef_length); + nfc_snep_server_connection_receive_ndef(self, pkt + 6, len - 6); + if (self->buf->len < self->ndef_length) { + /* + * 5.1. Continue + * + * The server received the first fragment of a fragmented + * SNEP request message and is able to receive the remaining + * fragments. The server indicates its ability to receive + * the remaining fragments and successfully reassemble the + * complete SNEP request message. This response code SHALL + * only be sent after receipt of the first fragment of a + * fragmented SNEP request message. An information field + * SHALL NOT be transmitted with this response. + */ + nfc_snep_server_response(conn, SNEP_RESPONSE_CONTINUE); + } + } + } else { + GWARN("Not enough bytes for SNEP header (%u)", len); + nfc_peer_connection_disconnect(conn); + } +} + +static +void +nfc_snep_server_connection_init( + NfcSnepServerConnection* self) +{ +} + +static +void +nfc_snep_server_connection_finalize( + GObject* object) +{ + NfcSnepServerConnection* self = NFC_SNEP_SERVER_CONNECTION(object); + NfcSnepServer* snep = NFC_SNEP_SERVER(self->connection.service); + + nfc_snep_server_update_connection_count(snep, -1); + if (self->buf) { + g_byte_array_free(self->buf, TRUE); + } + G_OBJECT_CLASS(nfc_snep_server_connection_parent_class)->finalize(object); +} + +static +void +nfc_snep_server_connection_class_init( + NfcSnepServerConnectionClass* klass) +{ + klass->data_received = nfc_snep_server_connection_data_received; + G_OBJECT_CLASS(klass)->finalize = nfc_snep_server_connection_finalize; +} + +static +NfcPeerConnection* +nfc_snep_server_connection_new( + NfcSnepServer* snep, + guint8 rsap) +{ + NfcSnepServerConnection* self = g_object_new + (NFC_TYPE_SNEP_SERVER_CONNECTION, NULL); + NfcPeerConnection* conn = &self->connection; + + GDEBUG("Accepting incoming SNEP connection"); + nfc_peer_connection_init_accept(conn, &snep->service, rsap); + nfc_snep_server_update_connection_count(snep, 1); + return conn; +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +NfcSnepServer* +nfc_snep_server_new( + void) +{ + NfcSnepServer* self = g_object_new(NFC_TYPE_SNEP_SERVER, NULL); + + nfc_peer_service_init_base(&self->service, NFC_LLC_NAME_SNEP); + return self; +} + +gulong +nfc_snep_server_add_state_changed_handler( + NfcSnepServer* self, + NfcSnepServerFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_STATE_CHANGED_NAME, G_CALLBACK(func), user_data) : 0; +} + +gulong +nfc_snep_server_add_ndef_changed_handler( + NfcSnepServer* self, + NfcSnepServerFunc func, + void* user_data) +{ + return (G_LIKELY(self) && G_LIKELY(func)) ? g_signal_connect(self, + SIGNAL_NDEF_CHANGED_NAME, G_CALLBACK(func), user_data) : 0; +} + +void +nfc_snep_server_remove_handler( + NfcSnepServer* self, + gulong id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +void +nfc_snep_server_remove_handlers( + NfcSnepServer* self, + gulong* ids, + guint count) +{ + gutil_disconnect_handlers(self, ids, count); +} + +/*==========================================================================* + * Methods + *==========================================================================*/ + +static +NfcPeerConnection* +nfc_snep_server_new_accept( + NfcPeerService* service, + guint8 rsap) +{ + return nfc_snep_server_connection_new(NFC_SNEP_SERVER(service), rsap); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +static +void +nfc_snep_server_init( + NfcSnepServer* self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NFC_TYPE_SNEP_SERVER, + NfcSnepServerPriv); + self->state = NFC_SNEP_SERVER_LISTENING; +} + +static +void +nfc_snep_server_finalize( + GObject* object) +{ + NfcSnepServer* self = NFC_SNEP_SERVER(object); + + nfc_ndef_rec_unref(self->ndef); + G_OBJECT_CLASS(nfc_snep_server_parent_class)->finalize(object); +} + +static +void +nfc_snep_server_class_init( + NfcSnepServerClass* klass) +{ + GType type = G_OBJECT_CLASS_TYPE(klass); + + g_type_class_add_private(klass, sizeof(NfcSnepServerPriv)); + klass->new_accept = nfc_snep_server_new_accept; + G_OBJECT_CLASS(klass)->finalize = nfc_snep_server_finalize; + nfc_snep_server_signals[SIGNAL_STATE_CHANGED] = + g_signal_new(SIGNAL_STATE_CHANGED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + nfc_snep_server_signals[SIGNAL_NDEF_CHANGED] = + g_signal_new(SIGNAL_NDEF_CHANGED_NAME, type, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_snep_server.h b/core/src/nfc_snep_server.h new file mode 100644 index 0000000..7b398f6 --- /dev/null +++ b/core/src/nfc_snep_server.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NFC_SNEP_SERVER_H +#define NFC_SNEP_SERVER_H + +#include "nfc_types_p.h" +#include "nfc_peer_service.h" + +/* + * SNEP server accespts NDEF message from SNEP client. + */ +typedef enum nfc_snep_server_state { + NFC_SNEP_SERVER_LISTENING, + NFC_SNEP_SERVER_RECEIVING +} NFC_SNEP_SERVER_STATE; + +typedef struct nfc_snep_server_priv NfcSnepServerPriv; +typedef struct nfc_snep_server { + NfcPeerService service; + NfcSnepServerPriv* priv; + NFC_SNEP_SERVER_STATE state; + NfcNdefRec* ndef; +} NfcSnepServer; + +typedef +void +(*NfcSnepServerFunc)( + NfcSnepServer* snep, + void* user_data); + +NfcSnepServer* +nfc_snep_server_new( + void) + NFCD_INTERNAL; + +gulong +nfc_snep_server_add_state_changed_handler( + NfcSnepServer* snep, + NfcSnepServerFunc func, + void* user_data) + NFCD_INTERNAL; + +gulong +nfc_snep_server_add_ndef_changed_handler( + NfcSnepServer* snep, + NfcSnepServerFunc func, + void* user_data) + NFCD_INTERNAL; + +void +nfc_snep_server_remove_handler( + NfcSnepServer* snep, + gulong id) + NFCD_INTERNAL; + +void +nfc_snep_server_remove_handlers( + NfcSnepServer* snep, + gulong* ids, + guint count) + NFCD_INTERNAL; + +#define nfc_snep_server_remove_all_handlers(snep,ids) \ + nfc_snep_server_remove_handlers(snep, ids, G_N_ELEMENTS(ids)) + +#endif /* NFC_SNEP_SERVER_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/core/src/nfc_tag.c b/core/src/nfc_tag.c index 0a1ef50..595230b 100644 --- a/core/src/nfc_tag.c +++ b/core/src/nfc_tag.c @@ -48,6 +48,8 @@ struct nfc_tag_priv { }; G_DEFINE_TYPE(NfcTag, nfc_tag, G_TYPE_OBJECT) +#define NFC_TAG_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), \ + NFC_TYPE_TAG, NfcTagClass) enum nfc_tag_signal { SIGNAL_INITIALIZED, @@ -69,9 +71,7 @@ nfc_tag_gone( NfcTag* self = NFC_TAG(user_data); /* NfcTarget makes sure that this signal is only issued once */ - GASSERT(self->present); - self->present = FALSE; - g_signal_emit(self, nfc_tag_signals[SIGNAL_GONE], 0); + NFC_TAG_GET_CLASS(self)->gone(self); } /*==========================================================================* @@ -257,6 +257,21 @@ nfc_tag_set_initialized( } } +/*==========================================================================* + * Methods + *==========================================================================*/ + +static +void +nfc_tag_default_gone( + NfcTag* self) +{ + /* Must only be invoked once per lifetime */ + GASSERT(self->present); + self->present = FALSE; + g_signal_emit(self, nfc_tag_signals[SIGNAL_GONE], 0); +} + /*==========================================================================* * Internals *==========================================================================*/ @@ -291,6 +306,7 @@ nfc_tag_class_init( NfcTagClass* klass) { g_type_class_add_private(klass, sizeof(NfcTagPriv)); + klass->gone = nfc_tag_default_gone; G_OBJECT_CLASS(klass)->finalize = nfc_tag_finalize; nfc_tag_signals[SIGNAL_INITIALIZED] = g_signal_new(SIGNAL_INITIALIZED_NAME, G_OBJECT_CLASS_TYPE(klass), diff --git a/core/src/nfc_tag_p.h b/core/src/nfc_tag_p.h index 629730c..b55927f 100644 --- a/core/src/nfc_tag_p.h +++ b/core/src/nfc_tag_p.h @@ -39,8 +39,12 @@ typedef struct nfc_tag_class { GObjectClass parent; + void (*gone)(NfcTag* tag); } NfcTagClass; +#define NFC_TAG_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), \ + NFC_TYPE_TAG, NfcTagClass) + NfcTag* nfc_tag_new( NfcTarget* target, diff --git a/core/src/nfc_types_p.h b/core/src/nfc_types_p.h index 0446f67..f4819c7 100644 --- a/core/src/nfc_types_p.h +++ b/core/src/nfc_types_p.h @@ -39,6 +39,27 @@ /* Now pull in the public types */ #include +/* Types */ +typedef struct nfc_llc NfcLlc; +typedef struct nfc_llc_io NfcLlcIo; +typedef struct nfc_llc_param NfcLlcParam; +typedef struct nfc_peer_services NfcPeerServices; + +/* + * SAP: + * + * 00h..0Fh Well-Known Service access points + * 10h..1Fh Named services advertised by SDP + * 20h..3Fh Unnamed services that are NOT advertised by SDP + */ + +#define NFC_LLC_SAP_MASK (0x3f) /* 6 bit */ +#define NFC_LLC_SAP_COUNT (NFC_LLC_SAP_MASK + 1) +#define NFC_LLC_SAP_WKS_MASK (0x0f) /* Well-Known Services */ +#define NFC_LLC_SAP_NAMED (0x10) /* First named service */ +#define NFC_LLC_SAP_UNNAMED (0x20) /* First unnamed service */ +#define NFC_LLC_SAP_MAX NFC_LLC_SAP_MASK /* Maximum SAP value */ + /* Macros */ #define NFCD_INTERNAL G_GNUC_INTERNAL diff --git a/plugins/Makefile b/plugins/Makefile index eb04791..20ae6f0 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -41,8 +41,10 @@ DBUS_SERVICE_PLUGIN_SRC = \ dbus_service_adapter.c \ dbus_service_error.c \ dbus_service_isodep.c \ + dbus_service_local.c \ dbus_service_name.c \ dbus_service_ndef.c \ + dbus_service_peer.c \ dbus_service_plugin.c \ dbus_service_util.c \ dbus_service_tag.c \ @@ -52,7 +54,9 @@ DBUS_SERVICE_GEN_SRC = \ org.sailfishos.nfc.Adapter.c \ org.sailfishos.nfc.Daemon.c \ org.sailfishos.nfc.IsoDep.c \ + org.sailfishos.nfc.LocalService.c \ org.sailfishos.nfc.NDEF.c \ + org.sailfishos.nfc.Peer.c \ org.sailfishos.nfc.Tag.c \ org.sailfishos.nfc.TagType2.c diff --git a/plugins/dbus_service/dbus_service.h b/plugins/dbus_service/dbus_service.h index 455b131..47435b4 100644 --- a/plugins/dbus_service/dbus_service.h +++ b/plugins/dbus_service/dbus_service.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 2018-2020 Jolla Ltd. - * Copyright (C) 2018-2020 Slava Monich + * Copyright (C) 2018-2021 Jolla Ltd. + * Copyright (C) 2018-2021 Slava Monich * * You may use this file under the terms of BSD license as follows: * @@ -38,7 +38,7 @@ #define GLOG_MODULE_NAME dbus_service_log #include -#include +#include #include @@ -48,6 +48,7 @@ typedef struct dbus_service_plugin DBusServicePlugin; typedef struct dbus_service_tag DBusServiceTag; typedef struct dbus_service_tag_t2 DBusServiceTagType2; typedef struct dbus_service_isodep DBusServiceIsoDep; +typedef struct dbus_service_peer DBusServicePeer; #define DBUS_SERVICE_ERROR (dbus_service_error_quark()) GQuark dbus_service_error_quark(void); @@ -60,6 +61,10 @@ typedef enum dbus_service_error { DBUS_SERVICE_ERROR_NOT_SUPPORTED, /* NotSupported */ DBUS_SERVICE_ERROR_ABORTED, /* Aborted */ DBUS_SERVICE_ERROR_NACK, /* NACK */ + DBUS_SERVICE_ERROR_CANCELLED, /* Cancelled */ + DBUS_SERVICE_ERROR_NO_SERVICE, /* NoService */ + DBUS_SERVICE_ERROR_REJECTED, /* Rejected */ + DBUS_SERVICE_ERROR_ALREADY_EXISTS, /* AlreadyExists */ DBUS_SERVICE_NUM_ERRORS } DBusServiceError; @@ -78,6 +83,27 @@ void dbus_service_name_unown( guint id); +DBusServicePeer* +dbus_service_plugin_find_peer( + DBusServicePlugin* plugin, + NfcPeer* peer); + +/* org.sailfishos.nfc.LocalService */ + +typedef struct dbus_service_local { + NfcPeerService service; + DBusServicePlugin* plugin; + const char* dbus_name; + const char* obj_path; +} DBusServiceLocal; + +DBusServiceLocal* +dbus_service_local_new( + GDBusConnection* connection, + const char* obj_path, + const char* llc_name, + const char* dbus_name); + /* org.sailfishos.nfc.Adapter */ DBusServiceAdapter* @@ -89,26 +115,29 @@ const char* dbus_service_adapter_path( DBusServiceAdapter* adapter); +DBusServicePeer* +dbus_service_adapter_find_peer( + DBusServiceAdapter* self, + NfcPeer* peer); + void dbus_service_adapter_free( DBusServiceAdapter* adapter); /* org.sailfishos.nfc.Tag */ +struct dbus_service_tag { + GDBusConnection* connection; + const char* path; + NfcTag* tag; +}; + DBusServiceTag* dbus_service_tag_new( NfcTag* tag, const char* parent_path, GDBusConnection* connection); -GDBusConnection* -dbus_service_tag_connection( - DBusServiceTag* tag); - -const char* -dbus_service_tag_path( - DBusServiceTag* tag); - NfcTargetSequence* dbus_service_tag_sequence( DBusServiceTag* tag, @@ -154,7 +183,25 @@ dbus_service_isodep_new( void dbus_service_isodep_free( - DBusServiceIsoDep* t2); + DBusServiceIsoDep* isodep); + +/* org.sailfishos.nfc.Peer */ + +struct dbus_service_peer { + GDBusConnection* connection; + const char* path; + NfcPeer* peer; +}; + +DBusServicePeer* +dbus_service_peer_new( + NfcPeer* peer, + const char* parent_path, + GDBusConnection* connection); + +void +dbus_service_peer_free( + DBusServicePeer* peer); #endif /* DBUS_SERVICE_H */ diff --git a/plugins/dbus_service/dbus_service_adapter.c b/plugins/dbus_service/dbus_service_adapter.c index be86de8..4029e53 100644 --- a/plugins/dbus_service/dbus_service_adapter.c +++ b/plugins/dbus_service/dbus_service_adapter.c @@ -1,6 +1,6 @@ /* - * Copyright (C) 2018-2019 Jolla Ltd. - * Copyright (C) 2018-2019 Slava Monich + * Copyright (C) 2018-2020 Jolla Ltd. + * Copyright (C) 2018-2020 Slava Monich * * You may use this file under the terms of BSD license as follows: * @@ -34,6 +34,7 @@ #include "dbus_service/org.sailfishos.nfc.Adapter.h" #include +#include #include #include @@ -48,11 +49,14 @@ enum { EVENT_TARGET_PRESENCE, EVENT_TAG_ADDED, EVENT_TAG_REMOVED, + EVENT_PEER_ADDED, + EVENT_PEER_REMOVED, EVENT_COUNT }; enum { CALL_GET_ALL, + CALL_GET_ALL2, CALL_GET_INTERFACE_VERSION, CALL_GET_ENABLED, CALL_GET_POWERED, @@ -60,6 +64,7 @@ enum { CALL_GET_MODE, CALL_GET_TARGET_PRESENT, CALL_GET_TAGS, + CALL_GET_PEERS, CALL_COUNT }; @@ -69,12 +74,22 @@ struct dbus_service_adapter { OrgSailfishosNfcAdapter* iface; GUtilIdlePool* pool; GHashTable* tags; + GHashTable* peers; NfcAdapter* adapter; gulong event_id[EVENT_COUNT]; gulong call_id[CALL_COUNT]; }; -#define NFC_DBUS_ADAPTER_INTERFACE_VERSION (1) +#define NFC_DBUS_ADAPTER_INTERFACE_VERSION (2) + +static +int +dbus_service_adapter_compare_strings( + const void* p1, + const void* p2) +{ + return strcmp(*(char* const*)p1, *(char* const*)p2); +} static gboolean @@ -93,6 +108,23 @@ dbus_service_adapter_create_tag( } } +static +gboolean +dbus_service_adapter_create_peer( + DBusServiceAdapter* self, + NfcPeer* peer) +{ + DBusServicePeer* dbus = + dbus_service_peer_new(peer, self->path, self->connection); + + if (dbus) { + g_hash_table_replace(self->peers, g_strdup(peer->name), dbus); + return TRUE; + } else { + return FALSE; + } +} + static void dbus_service_adapter_free_tag( @@ -102,12 +134,11 @@ dbus_service_adapter_free_tag( } static -int -dbus_service_adapter_compare_strings( - const void* p1, - const void* p2) +void +dbus_service_adapter_free_peer( + void* peer) { - return strcmp(*(char* const*)p1, *(char* const*)p2); + dbus_service_peer_free((DBusServicePeer*)peer); } static @@ -122,7 +153,7 @@ dbus_service_adapter_get_tag_paths( g_hash_table_iter_init(&it, self->tags); while (g_hash_table_iter_next(&it, NULL, &value)) { - out[n++] = dbus_service_tag_path((DBusServiceTag*)value); + out[n++] = ((DBusServiceTag*)value)->path; } out[n] = NULL; qsort(out, n, sizeof(char*), dbus_service_adapter_compare_strings); @@ -132,6 +163,28 @@ dbus_service_adapter_get_tag_paths( return out; } +static +const char* const* +dbus_service_adapter_get_peer_paths( + DBusServiceAdapter* self) +{ + const char** out = g_new(const char*, g_hash_table_size(self->peers) + 1); + GHashTableIter it; + gpointer value; + int n = 0; + + g_hash_table_iter_init(&it, self->peers); + while (g_hash_table_iter_next(&it, NULL, &value)) { + out[n++] = ((DBusServicePeer*)value)->path; + } + out[n] = NULL; + qsort(out, n, sizeof(char*), dbus_service_adapter_compare_strings); + + /* Deallocated by the idle pool (actual strings are owned by peers) */ + gutil_idle_pool_add(self->pool, out, g_free); + return out; +} + static void dbus_service_adapter_tags_changed( @@ -141,6 +194,15 @@ dbus_service_adapter_tags_changed( dbus_service_adapter_get_tag_paths(self)); } +static +void +dbus_service_adapter_peers_changed( + DBusServiceAdapter* self) +{ + org_sailfishos_nfc_adapter_emit_peers_changed(self->iface, + dbus_service_adapter_get_peer_paths(self)); +} + /*==========================================================================* * NfcAdapter events *==========================================================================*/ @@ -221,10 +283,61 @@ dbus_service_adapter_tag_removed( } } +static +void +dbus_service_adapter_peer_added( + NfcAdapter* adapter, + NfcPeer* peer, + void* user_data) +{ + DBusServiceAdapter* self = user_data; + + if (dbus_service_adapter_create_peer(self, peer)) { + dbus_service_adapter_peers_changed(self); + } +} + +static +void +dbus_service_adapter_peer_removed( + NfcAdapter* adapter, + NfcPeer* peer, + void* user_data) +{ + DBusServiceAdapter* self = user_data; + + if (g_hash_table_remove(self->peers, (void*)peer->name)) { + dbus_service_adapter_peers_changed(self); + } +} + +DBusServicePeer* +dbus_service_adapter_find_peer( + DBusServiceAdapter* self, + NfcPeer* peer) +{ + if (G_LIKELY(self)) { + GHashTableIter it; + gpointer value; + + g_hash_table_iter_init(&it, self->peers); + while (g_hash_table_iter_next(&it, NULL, &value)) { + DBusServicePeer* dbus_peer = value; + + if (dbus_peer->peer == peer) { + return dbus_peer; + } + } + } + return NULL; +} + /*==========================================================================* * D-Bus calls *==========================================================================*/ +/* GetAll */ + static gboolean dbus_service_adapter_handle_get_all( @@ -241,6 +354,8 @@ dbus_service_adapter_handle_get_all( return TRUE; } +/* GetInterfaceVersion */ + static gboolean dbus_service_adapter_handle_get_interface_version( @@ -253,6 +368,8 @@ dbus_service_adapter_handle_get_interface_version( return TRUE; } +/* GetEnabled */ + static gboolean dbus_service_adapter_handle_get_enabled( @@ -265,6 +382,8 @@ dbus_service_adapter_handle_get_enabled( return TRUE; } +/* GetPowered */ + static gboolean dbus_service_adapter_handle_get_powered( @@ -277,6 +396,8 @@ dbus_service_adapter_handle_get_powered( return TRUE; } +/* GetSupportedModes */ + static gboolean dbus_service_adapter_handle_get_supported_modes( @@ -289,6 +410,8 @@ dbus_service_adapter_handle_get_supported_modes( return TRUE; } +/* GetMode */ + static gboolean dbus_service_adapter_handle_get_mode( @@ -301,6 +424,8 @@ dbus_service_adapter_handle_get_mode( return TRUE; } +/* GetTargetPresent */ + static gboolean dbus_service_adapter_handle_get_target_present( @@ -313,6 +438,8 @@ dbus_service_adapter_handle_get_target_present( return TRUE; } +/* GetTags */ + static gboolean dbus_service_adapter_handle_get_tags( @@ -325,6 +452,41 @@ dbus_service_adapter_handle_get_tags( return TRUE; } +/* Interface verson 2 */ + +/* GetAll2 */ + +static +gboolean +dbus_service_adapter_handle_get_all2( + OrgSailfishosNfcAdapter* iface, + GDBusMethodInvocation* call, + DBusServiceAdapter* self) +{ + NfcAdapter* adapter = self->adapter; + + org_sailfishos_nfc_adapter_complete_get_all2(iface, call, + NFC_DBUS_ADAPTER_INTERFACE_VERSION, adapter->enabled, adapter->powered, + adapter->supported_modes, adapter->mode, adapter->target_present, + dbus_service_adapter_get_tag_paths(self), + dbus_service_adapter_get_peer_paths(self)); + return TRUE; +} + +/* GetPeers */ + +static +gboolean +dbus_service_adapter_handle_get_peers( + OrgSailfishosNfcAdapter* iface, + GDBusMethodInvocation* call, + DBusServiceAdapter* self) +{ + org_sailfishos_nfc_adapter_complete_get_peers(iface, call, + dbus_service_adapter_get_peer_paths(self)); + return TRUE; +} + /*==========================================================================* * Interface *==========================================================================*/ @@ -335,6 +497,7 @@ dbus_service_adapter_free_unexported( DBusServiceAdapter* self) { g_hash_table_destroy(self->tags); + g_hash_table_destroy(self->peers); nfc_adapter_remove_all_handlers(self->adapter, self->event_id); nfc_adapter_unref(self->adapter); @@ -343,8 +506,7 @@ dbus_service_adapter_free_unexported( g_object_unref(self->iface); g_object_unref(self->connection); - gutil_idle_pool_drain(self->pool); - gutil_idle_pool_unref(self->pool); + gutil_idle_pool_destroy(self->pool); g_free(self->path); g_free(self); @@ -364,6 +526,7 @@ dbus_service_adapter_new( { DBusServiceAdapter* self = g_new0(DBusServiceAdapter, 1); NfcTag** tags; + NfcPeer** peers; GError* error = NULL; g_object_ref(self->connection = connection); @@ -373,6 +536,8 @@ dbus_service_adapter_new( self->iface = org_sailfishos_nfc_adapter_skeleton_new(); self->tags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, dbus_service_adapter_free_tag); + self->peers = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, dbus_service_adapter_free_peer); /* NfcAdapter events */ self->event_id[EVENT_ENABLED_CHANGED] = @@ -393,11 +558,20 @@ dbus_service_adapter_new( self->event_id[EVENT_TAG_REMOVED] = nfc_adapter_add_tag_removed_handler(adapter, dbus_service_adapter_tag_removed, self); + self->event_id[EVENT_PEER_ADDED] = + nfc_adapter_add_peer_added_handler(adapter, + dbus_service_adapter_peer_added, self); + self->event_id[EVENT_PEER_REMOVED] = + nfc_adapter_add_peer_removed_handler(adapter, + dbus_service_adapter_peer_removed, self); /* D-Bus calls */ self->call_id[CALL_GET_ALL] = g_signal_connect(self->iface, "handle-get-all", G_CALLBACK(dbus_service_adapter_handle_get_all), self); + self->call_id[CALL_GET_ALL] = + g_signal_connect(self->iface, "handle-get-all2", + G_CALLBACK(dbus_service_adapter_handle_get_all2), self); self->call_id[CALL_GET_INTERFACE_VERSION] = g_signal_connect(self->iface, "handle-get-interface-version", G_CALLBACK(dbus_service_adapter_handle_get_interface_version), self); @@ -419,11 +593,17 @@ dbus_service_adapter_new( self->call_id[CALL_GET_TAGS] = g_signal_connect(self->iface, "handle-get-tags", G_CALLBACK(dbus_service_adapter_handle_get_tags), self); + self->call_id[CALL_GET_PEERS] = + g_signal_connect(self->iface, "handle-get-peers", + G_CALLBACK(dbus_service_adapter_handle_get_peers), self); - /* Initialize D-Bus context for existing tags (usually none) */ + /* Initialize D-Bus context for existing tags and peers (usually none) */ for (tags = adapter->tags; *tags; tags++) { dbus_service_adapter_create_tag(self, *tags); } + for (peers = nfc_adapter_peers(adapter); *peers; peers++) { + dbus_service_adapter_create_peer(self, *peers); + } /* Export the interface */ if (g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON diff --git a/plugins/dbus_service/dbus_service_error.c b/plugins/dbus_service/dbus_service_error.c index c6c6c6d..258d591 100644 --- a/plugins/dbus_service/dbus_service_error.c +++ b/plugins/dbus_service/dbus_service_error.c @@ -1,6 +1,6 @@ /* - * Copyright (C) 2018-2019 Jolla Ltd. - * Copyright (C) 2018-2019 Slava Monich + * Copyright (C) 2018-2020 Jolla Ltd. + * Copyright (C) 2018-2020 Slava Monich * * You may use this file under the terms of BSD license as follows: * @@ -35,13 +35,17 @@ #define DBUS_SERVICE_ERROR_(error) "org.sailfishos.nfc.Error." error static const GDBusErrorEntry dbus_service_errors[] = { - { DBUS_SERVICE_ERROR_FAILED, DBUS_SERVICE_ERROR_("Failed") }, - { DBUS_SERVICE_ERROR_ACCESS_DENIED, DBUS_SERVICE_ERROR_("AccessDenied") }, - { DBUS_SERVICE_ERROR_INVALID_ARGS, DBUS_SERVICE_ERROR_("InvalidArgs") }, - { DBUS_SERVICE_ERROR_NOT_FOUND, DBUS_SERVICE_ERROR_("NotFound") }, - { DBUS_SERVICE_ERROR_NOT_SUPPORTED, DBUS_SERVICE_ERROR_("NotSupported") }, - { DBUS_SERVICE_ERROR_ABORTED, DBUS_SERVICE_ERROR_("Aborted") }, - { DBUS_SERVICE_ERROR_NACK, DBUS_SERVICE_ERROR_("NACK") } + { DBUS_SERVICE_ERROR_FAILED, DBUS_SERVICE_ERROR_("Failed") }, + { DBUS_SERVICE_ERROR_ACCESS_DENIED, DBUS_SERVICE_ERROR_("AccessDenied") }, + { DBUS_SERVICE_ERROR_INVALID_ARGS, DBUS_SERVICE_ERROR_("InvalidArgs") }, + { DBUS_SERVICE_ERROR_NOT_FOUND, DBUS_SERVICE_ERROR_("NotFound") }, + { DBUS_SERVICE_ERROR_NOT_SUPPORTED, DBUS_SERVICE_ERROR_("NotSupported") }, + { DBUS_SERVICE_ERROR_ABORTED, DBUS_SERVICE_ERROR_("Aborted") }, + { DBUS_SERVICE_ERROR_NACK, DBUS_SERVICE_ERROR_("NACK") }, + { DBUS_SERVICE_ERROR_CANCELLED, DBUS_SERVICE_ERROR_("Cancelled") }, + { DBUS_SERVICE_ERROR_NO_SERVICE, DBUS_SERVICE_ERROR_("NoService") }, + { DBUS_SERVICE_ERROR_REJECTED, DBUS_SERVICE_ERROR_("Rejected") }, + { DBUS_SERVICE_ERROR_ALREADY_EXISTS, DBUS_SERVICE_ERROR_("AlreadyExists") } }; G_STATIC_ASSERT(G_N_ELEMENTS(dbus_service_errors) == DBUS_SERVICE_NUM_ERRORS); diff --git a/plugins/dbus_service/dbus_service_isodep.c b/plugins/dbus_service/dbus_service_isodep.c index 9d93c06..d86de22 100644 --- a/plugins/dbus_service/dbus_service_isodep.c +++ b/plugins/dbus_service/dbus_service_isodep.c @@ -341,8 +341,6 @@ dbus_service_isodep_new( NfcTagType4* t4, DBusServiceTag* owner) { - GDBusConnection* connection = dbus_service_tag_connection(owner); - const char* path = dbus_service_tag_path(owner); DBusServiceIsoDep* self = g_new0(DBusServiceIsoDep, 1); GError* error = NULL; @@ -371,11 +369,11 @@ dbus_service_isodep_new( G_CALLBACK(dbus_service_isodep_handle_reset), self); if (g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON - (self->iface), connection, path, &error)) { - GDEBUG("Created D-Bus object %s (ISO-DEP)", path); + (self->iface), owner->connection, owner->path, &error)) { + GDEBUG("Created D-Bus object %s (ISO-DEP)", owner->path); return self; } else { - GERR("%s: %s", path, GERRMSG(error)); + GERR("%s: %s", owner->path, GERRMSG(error)); g_error_free(error); dbus_service_isodep_free_unexported(self); return NULL; @@ -387,8 +385,7 @@ dbus_service_isodep_free( DBusServiceIsoDep* self) { if (self) { - GDEBUG("Removing D-Bus object %s (ISO-DEP)", - dbus_service_tag_path(self->owner)); + GDEBUG("Removing D-Bus object %s (ISO-DEP)", self->owner->path); g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON (self->iface)); dbus_service_isodep_free_unexported(self); diff --git a/plugins/dbus_service/dbus_service_local.c b/plugins/dbus_service/dbus_service_local.c new file mode 100644 index 0000000..ec8b4b0 --- /dev/null +++ b/plugins/dbus_service/dbus_service_local.c @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dbus_service.h" +#include "dbus_service_util.h" +#include "dbus_service/org.sailfishos.nfc.LocalService.h" + +#include +#include +#include +#include + +typedef NfcPeerServiceClass DBusServiceLocalObjectClass; +typedef struct dbus_service_local_object { + DBusServiceLocal pub; + OrgSailfishosNfcLocalService* proxy; + char* peer_path; + char* dbus_name; + char* obj_path; + guint watch_id; +} DBusServiceLocalObject; + +#define DBUS_SERVICE_TYPE_LOCAL_OBJECT (dbus_service_local_object_get_type()) +G_DEFINE_TYPE(DBusServiceLocalObject, dbus_service_local_object, \ + NFC_TYPE_PEER_SERVICE) +#define DBUS_SERVICE_LOCAL_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj,\ + DBUS_SERVICE_TYPE_LOCAL_OBJECT, DBusServiceLocalObject)) + +typedef NfcPeerSocketClass DBusServiceConnectionClass; +typedef struct dbus_service_connection { + NfcPeerSocket socket; + GCancellable* cancel_accept; + OrgSailfishosNfcLocalService* proxy; +} DBusServiceConnection; + +#define DBUS_SERVICE_TYPE_CONNECTION (dbus_service_connection_get_type()) +G_DEFINE_TYPE(DBusServiceConnection, dbus_service_connection, \ + NFC_TYPE_PEER_SOCKET) +#define DBUS_SERVICE_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj,\ + DBUS_SERVICE_TYPE_CONNECTION, DBusServiceConnection)) + +#define LOCAL_SERVICE_INTERFACE "org.sailfishos.nfc.LocalService" +#define PEER_ARRIVED "PeerArrived" +#define PEER_LEFT "PeerLeft" +#define DATAGRAM_RECEIVED "DatagramReceived" + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +GDBusConnection* +dbus_service_local_connection( + DBusServiceLocalObject* self) +{ + return g_dbus_proxy_get_connection(G_DBUS_PROXY(self->proxy)); +} + +static +void +dbus_service_local_peer_notify( + DBusServiceLocalObject* self, + const char* method, + const char* peer_path) +{ + GDBusConnection* connection = dbus_service_local_connection(self); + GDBusMessage* message = g_dbus_message_new_method_call(self->dbus_name, + self->obj_path, LOCAL_SERVICE_INTERFACE, method); + + /* + * Generated stub doesn't allow setting "no-reply-expected" flag, + * we have to build and send D-Bus message manually. + */ + g_dbus_message_set_flags(message, g_dbus_message_get_flags(message) | + G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED); + g_dbus_message_set_body(message, g_variant_new("(o)", peer_path)); + g_dbus_connection_send_message(connection, message, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); + g_object_unref(message); +} + +static +void +dbus_service_local_peer_left_notify( + DBusServiceLocalObject* self) +{ + if (self->peer_path) { + dbus_service_local_peer_notify(self, PEER_LEFT, self->peer_path); + g_free(self->peer_path); + self->peer_path = NULL; + } +} + +/*==========================================================================* + * Connection + *==========================================================================*/ + +static +void +dbus_service_connection_state_changed( + NfcPeerConnection* connection) +{ + DBusServiceConnection* self = DBUS_SERVICE_CONNECTION(connection); + + if (self->cancel_accept && connection->state != NFC_LLC_CO_ACCEPTING) { + GDEBUG("Cancelling Accept D-Bus call"); + g_cancellable_cancel(self->cancel_accept); + g_object_unref(self->cancel_accept); + self->cancel_accept = NULL; + } + NFC_PEER_CONNECTION_CLASS(dbus_service_connection_parent_class)-> + state_changed(connection); +} + +static +void +dbus_service_connection_accept_done( + GObject* proxy, + GAsyncResult* result, + gpointer user_data) +{ + DBusServiceConnection* self = DBUS_SERVICE_CONNECTION(user_data); + NfcPeerConnection* connection = NFC_PEER_CONNECTION(self); + gboolean accepted = FALSE; + GError* error = NULL; + + if (self->cancel_accept) { + g_object_unref(self->cancel_accept); + self->cancel_accept = NULL; + } + if (org_sailfishos_nfc_local_service_call_accept_finish(self->proxy, + &accepted, NULL, result, &error)) { + nfc_peer_connection_accepted(connection); + } else { + GDEBUG("%s", GERRMSG(error)); + g_error_free(error); + nfc_peer_connection_rejected(connection); + } + nfc_peer_connection_unref(connection); +} + +static +void +dbus_service_connection_accept( + NfcPeerConnection* conn) +{ + DBusServiceConnection* self = DBUS_SERVICE_CONNECTION(conn); + NfcPeerSocket* socket = &self->socket; + + /* Ask D-Bus client to accept the connection */ + nfc_peer_connection_ref(conn); + self->cancel_accept = g_cancellable_new(); + org_sailfishos_nfc_local_service_call_accept(self->proxy, conn->rsap, + g_variant_new_handle(0), socket->fdl, self->cancel_accept, + dbus_service_connection_accept_done, self); +} + +static +void +dbus_service_connection_finalize( + GObject* object) +{ + DBusServiceConnection* self = DBUS_SERVICE_CONNECTION(object); + + /* + * self->cancel_accept must be NULL because this object remains + * referenced for the entire duration of the Accept call. + */ + GASSERT(!self->cancel_accept); + if (self->proxy) { + g_object_unref(self->proxy); + } + G_OBJECT_CLASS(dbus_service_connection_parent_class)->finalize(object); +} + +static +void +dbus_service_connection_init( + DBusServiceConnection* self) +{ +} + +static +void +dbus_service_connection_class_init( + DBusServiceConnectionClass* klass) +{ + NfcPeerConnectionClass* connection = NFC_PEER_CONNECTION_CLASS(klass); + + connection->accept = dbus_service_connection_accept; + connection->state_changed = dbus_service_connection_state_changed; + G_OBJECT_CLASS(klass)->finalize = dbus_service_connection_finalize; +} + +static +NfcPeerConnection* +dbus_service_connection_new( + OrgSailfishosNfcLocalService* proxy, + NfcPeerService* service, + guint8 rsap) +{ + DBusServiceConnection* self = g_object_new + (DBUS_SERVICE_TYPE_CONNECTION, NULL); + + if (nfc_peer_socket_init_accept(&self->socket, service, rsap)) { + g_object_ref(self->proxy = proxy); + return NFC_PEER_CONNECTION(self); + } else { + g_object_unref(self); + return NULL; + } +} + +/*==========================================================================* + * Service + *==========================================================================*/ + +static +void +dbus_service_local_peer_arrived( + NfcPeerService* service, + NfcPeer* peer) +{ + DBusServiceLocalObject* self = DBUS_SERVICE_LOCAL_OBJECT(service); + DBusServicePlugin* plugin = self->pub.plugin; + DBusServicePeer* dbus_peer = dbus_service_plugin_find_peer(plugin, peer); + const char* path = dbus_peer ? dbus_peer->path : NULL; + + dbus_service_local_peer_left_notify(self); + if (path) { + dbus_service_local_peer_notify(self, PEER_ARRIVED, path); + self->peer_path = g_strdup(path); + } + NFC_PEER_SERVICE_CLASS(dbus_service_local_object_parent_class)-> + peer_arrived(service, peer); +} + +static +void +dbus_service_local_peer_left( + NfcPeerService* service, + NfcPeer* peer) +{ + dbus_service_local_peer_left_notify(DBUS_SERVICE_LOCAL_OBJECT(service)); + NFC_PEER_SERVICE_CLASS(dbus_service_local_object_parent_class)-> + peer_left(service, peer); +} + +static +NfcPeerConnection* +dbus_service_local_new_accept( + NfcPeerService* service, + guint8 rsap) +{ + DBusServiceLocalObject* self = DBUS_SERVICE_LOCAL_OBJECT(service); + + return dbus_service_connection_new(self->proxy, service, rsap); +} + +static +void +dbus_service_local_datagram_received( + NfcPeerService* service, + guint8 rsap, + const void* data, + guint len) +{ + DBusServiceLocalObject* self = DBUS_SERVICE_LOCAL_OBJECT(service); + GDBusConnection* connection = dbus_service_local_connection(self); + GDBusMessage* message = g_dbus_message_new_method_call(self->dbus_name, + self->obj_path, LOCAL_SERVICE_INTERFACE, DATAGRAM_RECEIVED); + + GDEBUG("Datagram %u byte(s) for %s%s", len, + self->dbus_name, self->obj_path); + + /* + * Generated stub doesn't allow setting "no-reply-expected" flag, + * we have to build and send D-Bus message manually. + */ + g_dbus_message_set_flags(message, g_dbus_message_get_flags(message) | + G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED); + g_dbus_message_set_body(message, g_variant_new("(u@ay)", + rsap, dbus_service_dup_byte_array_as_variant(data, len))); + g_dbus_connection_send_message(connection, message, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); + g_object_unref(message); +} + +static +void +dbus_service_local_finalize( + GObject* object) +{ + DBusServiceLocalObject* self = DBUS_SERVICE_LOCAL_OBJECT(object); + + if (self->watch_id) { + g_bus_unwatch_name(self->watch_id); + } + g_free(self->peer_path); + g_free(self->obj_path); + g_free(self->dbus_name); + g_object_unref(self->proxy); + G_OBJECT_CLASS(dbus_service_local_object_parent_class)->finalize(object); +} + +static +void +dbus_service_local_object_init( + DBusServiceLocalObject* self) +{ +} + +static +void +dbus_service_local_object_class_init( + DBusServiceLocalObjectClass* klass) +{ + klass->peer_arrived = dbus_service_local_peer_arrived; + klass->peer_left = dbus_service_local_peer_left; + klass->new_accept = dbus_service_local_new_accept; + klass->datagram_received = dbus_service_local_datagram_received; + G_OBJECT_CLASS(klass)->finalize = dbus_service_local_finalize; +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +DBusServiceLocal* +dbus_service_local_new( + GDBusConnection* connection, + const char* obj_path, + const char* peer_name, + const char* dbus_name) +{ + GError* error = NULL; + OrgSailfishosNfcLocalService* proxy = /* This won't actually block */ + org_sailfishos_nfc_local_service_proxy_new_sync(connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + dbus_name, obj_path, NULL, &error); + + if (proxy) { + DBusServiceLocalObject* self = g_object_new + (DBUS_SERVICE_TYPE_LOCAL_OBJECT, NULL); + DBusServiceLocal* local = &self->pub; + NfcPeerService* service = &local->service; + + nfc_peer_service_init_base(service, peer_name); + local->obj_path = self->obj_path = g_strdup(obj_path); + local->dbus_name = self->dbus_name = g_strdup(dbus_name); + self->proxy = proxy; + return local; + } + return NULL; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/plugins/dbus_service/dbus_service_peer.c b/plugins/dbus_service/dbus_service_peer.c new file mode 100644 index 0000000..a3d3600 --- /dev/null +++ b/plugins/dbus_service/dbus_service_peer.c @@ -0,0 +1,689 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dbus_service.h" +#include "dbus_service/org.sailfishos.nfc.Peer.h" + +#include +#include +#include + +#include +#include + +#include + +enum { + PEER_INITIALIZED, + PEER_WELL_KNOWN_SERVICES_CHANGED, + PEER_EVENT_COUNT +}; + +enum { + CALL_GET_ALL, + CALL_GET_INTERFACE_VERSION, + CALL_GET_PRESENT, + CALL_GET_TECHNOLOGY, + CALL_GET_INTERFACES, + CALL_GET_WELL_KNOWN_SERVICES, + CALL_DEACTIVATE, + CALL_CONNECT_ACCESS_POINT, + CALL_CONNECT_SERVICE_NAME, + CALL_COUNT +}; + +typedef struct dbus_service_peer_call DBusServicePeerCall; +typedef struct dbus_service_peer_priv DBusServicePeerPriv; + +typedef +void +(*DBusServicePeerCallFunc)( + GDBusMethodInvocation* call, + DBusServicePeerPriv* self); + +struct dbus_service_peer_call { + DBusServicePeerCall* next; + GDBusMethodInvocation* invocation; + DBusServicePeerCallFunc func; +}; + +typedef struct dbus_service_peer_call_queue { + DBusServicePeerCall* first; + DBusServicePeerCall* last; +} DBusServicePeerCallQueue; + +typedef +void +(*DBusServicePeerAsyncConnectCompleteFunc)( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + GUnixFDList* fdl, + GVariant* fd); + +typedef struct dbus_service_peer_async_connect { + OrgSailfishosNfcPeer* iface; + GDBusMethodInvocation* call; + NfcPeerConnection* connection; + DBusServicePeerAsyncConnectCompleteFunc complete; +} DBusServicePeerAsyncConnect; + +struct dbus_service_peer_priv { + DBusServicePeer pub; + char* path; + DBusServicePeerCallQueue queue; + OrgSailfishosNfcPeer* iface; + gulong call_id[CALL_COUNT]; + gulong peer_event_id[PEER_EVENT_COUNT]; + NfcPeerService* peer_client; +}; + +#define NFC_DBUS_PEER_INTERFACE "org.sailfishos.nfc.Peer" +#define NFC_DBUS_PEER_INTERFACE_VERSION (1) + +static const char* const dbus_service_peer_default_interfaces[] = { + NFC_DBUS_PEER_INTERFACE, NULL +}; + +static inline DBusServicePeerPriv* dbus_service_peer_cast(DBusServicePeer* pub) + { return G_LIKELY(pub) ? G_CAST(pub, DBusServicePeerPriv, pub) : NULL; } + +/*==========================================================================* + * Peer client + *==========================================================================*/ + +typedef NfcPeerServiceClass DBusServicePeerClientClass; +typedef struct dbus_service_peer_client { + NfcPeerService service; +} DBusServicePeerClient; + +G_DEFINE_TYPE(DBusServicePeerClient, dbus_service_peer_client, \ + NFC_TYPE_PEER_SERVICE) +#define DBUS_SERVICE_TYPE_PEER_CLIENT dbus_service_peer_client_get_type() +#define DBUS_SERVICE_PEER_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + DBUS_SERVICE_TYPE_PEER_CLIENT, DBusServicePeerClient)) + +static +NfcPeerConnection* +dbus_service_peer_client_new_connect( + NfcPeerService* self, + guint8 rsap, + const char* name) +{ + return (NfcPeerConnection*)nfc_peer_socket_new_connect(self, rsap, name); +} + +static +void +dbus_service_peer_client_init( + DBusServicePeerClient* self) +{ +} + +static +void +dbus_service_peer_client_class_init( + DBusServicePeerClientClass* klass) +{ + klass->new_connect = dbus_service_peer_client_new_connect; +} + +static +NfcPeerService* +dbus_service_peer_client_get( + DBusServicePeerPriv* self) +{ + if (!self->peer_client) { + DBusServicePeerClient* client = g_object_new + (DBUS_SERVICE_TYPE_PEER_CLIENT, NULL); + + self->peer_client = NFC_PEER_SERVICE(client); + nfc_peer_service_init_base(self->peer_client, NULL); + if (!nfc_peer_register_service(self->pub.peer, self->peer_client)) { + nfc_peer_service_unref(self->peer_client); + self->peer_client = NULL; + } + } + return self->peer_client; +} + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +void +dbus_service_peer_free_call( + DBusServicePeerCall* call) +{ + g_object_unref(call->invocation); + g_slice_free(DBusServicePeerCall, call); +} + +static +void +dbus_service_peer_queue_call( + DBusServicePeerCallQueue* queue, + GDBusMethodInvocation* invocation, + DBusServicePeerCallFunc func) +{ + DBusServicePeerCall* call = g_slice_new0(DBusServicePeerCall); + + g_object_ref(call->invocation = invocation); + call->func = func; + if (queue->last) { + queue->last->next = call; + } else { + queue->first = call; + } + queue->last = call; +} + +static +DBusServicePeerCall* +dbus_service_peer_dequeue_call( + DBusServicePeerCallQueue* queue) +{ + DBusServicePeerCall* call = queue->first; + + if (call) { + queue->first = call->next; + if (!queue->first) { + queue->last = NULL; + } + } + return call; +} + +static +gboolean +dbus_service_peer_handle_call( + DBusServicePeerPriv* self, + GDBusMethodInvocation* call, + DBusServicePeerCallFunc func) +{ + NfcPeer* peer = self->pub.peer; + + if (peer->flags & NFC_PEER_FLAG_INITIALIZED) { + func(call, self); + } else { + dbus_service_peer_queue_call(&self->queue, call, func); + } + return TRUE; +} + +static +void +dbus_service_peer_complete_pending_calls( + DBusServicePeerPriv* self) +{ + DBusServicePeerCall* call; + + while ((call = dbus_service_peer_dequeue_call(&self->queue)) != NULL) { + call->func(call->invocation, self); + dbus_service_peer_free_call(call); + } +} + +/*==========================================================================* + * NfcPeer events + *==========================================================================*/ + +static +void +dbus_service_peer_well_known_services_changed( + NfcPeer* peer, + void* user_data) +{ + DBusServicePeerPriv* self = user_data; + + org_sailfishos_nfc_peer_emit_well_known_services_changed + (self->iface, peer->wks); +} + +static +void +dbus_service_peer_initialized( + NfcPeer* peer, + void* user_data) +{ + DBusServicePeerPriv* self = user_data; + + nfc_peer_remove_handlers(peer, self->peer_event_id + PEER_INITIALIZED, 1); + dbus_service_peer_complete_pending_calls(self); + self->peer_event_id[PEER_WELL_KNOWN_SERVICES_CHANGED] = + nfc_peer_add_wks_changed_handler(peer, + dbus_service_peer_well_known_services_changed, self); +} + +/*==========================================================================* + * Async call context + *==========================================================================*/ + +static +DBusServicePeerAsyncConnect* +dbus_service_peer_async_connect_new( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + DBusServicePeerAsyncConnectCompleteFunc complete) +{ + DBusServicePeerAsyncConnect* connect = + g_slice_new0(DBusServicePeerAsyncConnect); + + g_object_ref(connect->iface = iface); + g_object_ref(connect->call = call); + connect->complete = complete; + return connect; +} + +static +void +dbus_service_peer_async_connect_error( + DBusServicePeerAsyncConnect* connect, + DBusServiceError error, + const char* message) +{ + g_dbus_method_invocation_return_error_literal(connect->call, + DBUS_SERVICE_ERROR, error, message ? message : + "Data link connection failed"); + g_object_unref(connect->call); + connect->call = NULL; +} + +static +void +dbus_service_peer_async_connect_failed( + DBusServicePeerAsyncConnect* connect, + const char* message) +{ + dbus_service_peer_async_connect_error(connect, + DBUS_SERVICE_ERROR_FAILED, NULL); +} + +static +void +dbus_service_peer_async_connect_free( + DBusServicePeerAsyncConnect* connect) +{ + nfc_peer_connection_unref(connect->connection); + if (connect->call) dbus_service_peer_async_connect_failed(connect, NULL); + g_object_unref(connect->iface); + g_slice_free1(sizeof(*connect), connect); +} + +static +void +dbus_service_peer_async_connect_free1( + void* async) +{ + dbus_service_peer_async_connect_free(async); +} + +/*==========================================================================* + * D-Bus calls + *==========================================================================*/ + +/* GetAll */ + +static +void +dbus_service_peer_complete_get_all( + GDBusMethodInvocation* call, + DBusServicePeerPriv* self) +{ + NfcPeer* peer = self->pub.peer; + + org_sailfishos_nfc_peer_complete_get_all(self->iface, call, + NFC_DBUS_PEER_INTERFACE_VERSION, peer->present, peer->technology, + dbus_service_peer_default_interfaces, peer->wks); +} + +static +gboolean +dbus_service_peer_handle_get_all( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + DBusServicePeerPriv* self) +{ + /* Queue the call if the peer is not initialized yet */ + return dbus_service_peer_handle_call(self, call, + dbus_service_peer_complete_get_all); +} + +/* GetInterfaceVersion */ + +static +gboolean +dbus_service_peer_handle_get_interface_version( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + DBusServicePeerPriv* self) +{ + org_sailfishos_nfc_peer_complete_get_interface_version(iface, call, + NFC_DBUS_PEER_INTERFACE_VERSION); + return TRUE; +} + +/* GetPresent */ + +static +gboolean +dbus_service_peer_handle_get_present( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + DBusServicePeerPriv* self) +{ + org_sailfishos_nfc_peer_complete_get_present(iface, call, + self->pub.peer->present); + return TRUE; +} + +/* GetTechnology */ + +static +gboolean +dbus_service_peer_handle_get_technology( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + DBusServicePeerPriv* self) +{ + org_sailfishos_nfc_peer_complete_get_technology(iface, call, + self->pub.peer->technology); + return TRUE; +} + +/* GetInterfaces */ + +static +gboolean +dbus_service_peer_handle_get_interfaces( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + DBusServicePeerPriv* self) +{ + org_sailfishos_nfc_peer_complete_get_interfaces(self->iface, call, + dbus_service_peer_default_interfaces); + return TRUE; +} + +/* GetWellKnownServices */ + +static +void +dbus_service_peer_complete_get_well_known_services( + GDBusMethodInvocation* call, + DBusServicePeerPriv* self) +{ + org_sailfishos_nfc_peer_complete_get_well_known_services(self->iface, + call, self->pub.peer->wks); +} + +static +gboolean +dbus_service_peer_handle_get_well_known_services( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + DBusServicePeerPriv* self) +{ + /* Queue the call if the peer is not initialized yet */ + return dbus_service_peer_handle_call(self, call, + dbus_service_peer_complete_get_well_known_services); +} + +/* Deactivate */ + +static +gboolean +dbus_service_peer_handle_deactivate( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + DBusServicePeerPriv* self) +{ + nfc_peer_deactivate(self->pub.peer); + org_sailfishos_nfc_peer_complete_deactivate(iface, call); + return TRUE; +} + +/* ConnectAccessPoint */ + +static +void +dbus_service_peer_async_connect_complete( + NfcPeer* peer, + NfcPeerConnection* connection, + NFC_PEER_CONNECT_RESULT result, + void* user_data) +{ + DBusServicePeerAsyncConnect* connect = user_data; + NfcPeerSocket* socket = NFC_PEER_SOCKET(connection); + + /* + * Note: dbus_service_peer_async_connect_free() will still + * complete the call with a default error, even if we miss all + * the cases in this switch. + */ + switch (result) { + case NFC_PEER_CONNECT_OK: + GDEBUG("Data link connection established"); + connect->complete(connect->iface, connect->call, socket->fdl, + g_variant_new_handle(0)); + g_object_unref(connect->call); + connect->call = NULL; + break; + case NFC_PEER_CONNECT_CANCELLED: + case NFC_PEER_CONNECT_NO_SERVICE: + GDEBUG("Data link connection refused (no service)"); + dbus_service_peer_async_connect_error(connect, + DBUS_SERVICE_ERROR_NO_SERVICE, "No such service"); + break; + case NFC_PEER_CONNECT_REJECTED: + GDEBUG("Data link connection rejected"); + dbus_service_peer_async_connect_error(connect, + DBUS_SERVICE_ERROR_NO_SERVICE, "Connection rejected"); + break; + case NFC_PEER_CONNECT_DUP: + case NFC_PEER_CONNECT_FAILED: + GDEBUG("Data link connection failed"); + dbus_service_peer_async_connect_failed(connect, NULL); + break; + } +} + +static +gboolean +dbus_service_peer_handle_connect_access_point( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + GUnixFDList* fdlist, + guint rsap, + DBusServicePeerPriv* self) +{ + DBusServicePeerAsyncConnect* connect = + dbus_service_peer_async_connect_new(iface, call, + org_sailfishos_nfc_peer_complete_connect_access_point); + + GDEBUG("Connecting to SAP %u", rsap); + connect->connection = + nfc_peer_connection_ref(nfc_peer_connect(self->pub.peer, + dbus_service_peer_client_get(self), rsap, + dbus_service_peer_async_connect_complete, + dbus_service_peer_async_connect_free1, connect)); + if (!connect->connection) { + dbus_service_peer_async_connect_failed(connect, + "Failed to set up data link connection"); + dbus_service_peer_async_connect_free(connect); + } + return TRUE; +} + +/* ConnectServiceName */ + +static +gboolean +dbus_service_peer_handle_connect_service_name( + OrgSailfishosNfcPeer* iface, + GDBusMethodInvocation* call, + GUnixFDList* fdlist, + const char* sn, + DBusServicePeerPriv* self) +{ + DBusServicePeerAsyncConnect* connect = + dbus_service_peer_async_connect_new(iface, call, + org_sailfishos_nfc_peer_complete_connect_service_name); + + GDEBUG("Connecting to \"%s\"", sn); + connect->connection = + nfc_peer_connection_ref(nfc_peer_connect_sn(self->pub.peer, + dbus_service_peer_client_get(self), sn, + dbus_service_peer_async_connect_complete, + dbus_service_peer_async_connect_free1, connect)); + if (!connect->connection) { + dbus_service_peer_async_connect_failed(connect, + "Failed to set up data link connection"); + dbus_service_peer_async_connect_free(connect); + } + return TRUE; +} + +/*==========================================================================* + * Interface + *==========================================================================*/ + +static +void +dbus_service_peer_free_unexported( + DBusServicePeerPriv* self) +{ + DBusServicePeerCall* call; + DBusServicePeer* pub = &self->pub; + + nfc_peer_remove_all_handlers(pub->peer, self->peer_event_id); + gutil_disconnect_handlers(self->iface, self->call_id, CALL_COUNT); + + /* Cancel pending calls if there are any */ + while ((call = dbus_service_peer_dequeue_call(&self->queue)) != NULL) { + g_dbus_method_invocation_return_error_literal(call->invocation, + DBUS_SERVICE_ERROR, DBUS_SERVICE_ERROR_ABORTED, "Object is gone"); + dbus_service_peer_free_call(call); + } + + nfc_peer_service_unref(self->peer_client); + nfc_peer_unref(pub->peer); + g_object_unref(pub->connection); + g_object_unref(self->iface); + g_free(self->path); + g_free(self); +} + +DBusServicePeer* +dbus_service_peer_new( + NfcPeer* peer, + const char* parent_path, + GDBusConnection* connection) +{ + DBusServicePeerPriv* self = g_new0(DBusServicePeerPriv, 1); + DBusServicePeer* pub = &self->pub; + GError* error = NULL; + + g_object_ref(pub->connection = connection); + pub->path = self->path = g_strconcat(parent_path, "/", peer->name, NULL); + pub->peer = nfc_peer_ref(peer); + self->iface = org_sailfishos_nfc_peer_skeleton_new(); + + /* D-Bus calls */ + self->call_id[CALL_GET_ALL] = + g_signal_connect(self->iface, "handle-get-all", + G_CALLBACK(dbus_service_peer_handle_get_all), self); + self->call_id[CALL_GET_INTERFACE_VERSION] = + g_signal_connect(self->iface, "handle-get-interface-version", + G_CALLBACK(dbus_service_peer_handle_get_interface_version), self); + self->call_id[CALL_GET_PRESENT] = + g_signal_connect(self->iface, "handle-get-present", + G_CALLBACK(dbus_service_peer_handle_get_present), self); + self->call_id[CALL_GET_TECHNOLOGY] = + g_signal_connect(self->iface, "handle-get-technology", + G_CALLBACK(dbus_service_peer_handle_get_technology), self); + self->call_id[CALL_GET_INTERFACES] = + g_signal_connect(self->iface, "handle-get-interfaces", + G_CALLBACK(dbus_service_peer_handle_get_interfaces), self); + self->call_id[CALL_GET_WELL_KNOWN_SERVICES] = + g_signal_connect(self->iface, "handle-get-well-known-services", + G_CALLBACK(dbus_service_peer_handle_get_well_known_services), self); + self->call_id[CALL_DEACTIVATE] = + g_signal_connect(self->iface, "handle-deactivate", + G_CALLBACK(dbus_service_peer_handle_deactivate), self); + self->call_id[CALL_CONNECT_ACCESS_POINT] = + g_signal_connect(self->iface, "handle-connect-access-point", + G_CALLBACK(dbus_service_peer_handle_connect_access_point), self); + self->call_id[CALL_CONNECT_SERVICE_NAME] = + g_signal_connect(self->iface, "handle-connect-service-name", + G_CALLBACK(dbus_service_peer_handle_connect_service_name), self); + + if (peer->present && !(peer->flags & NFC_PEER_FLAG_INITIALIZED)) { + /* Have to wait until the peer is initialized */ + self->peer_event_id[PEER_INITIALIZED] = + nfc_peer_add_initialized_handler(peer, + dbus_service_peer_initialized, self); + } + if (g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON + (self->iface), connection, self->path, &error)) { + GDEBUG("Created D-Bus object %s (NFC-DEP)", self->path); + return pub; + } else { + GERR("%s: %s", self->path, GERRMSG(error)); + g_error_free(error); + dbus_service_peer_free_unexported(self); + return NULL; + } +} + +void +dbus_service_peer_free( + DBusServicePeer* pub) +{ + if (pub) { + DBusServicePeerPriv* self = dbus_service_peer_cast(pub); + + GDEBUG("Removing D-Bus object %s (NFC-DEP)", self->path); + org_sailfishos_nfc_peer_emit_removed(self->iface); + g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON + (self->iface)); + dbus_service_peer_free_unexported(self); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/plugins/dbus_service/dbus_service_plugin.c b/plugins/dbus_service/dbus_service_plugin.c index b49972d..eebc796 100644 --- a/plugins/dbus_service/dbus_service_plugin.c +++ b/plugins/dbus_service/dbus_service_plugin.c @@ -1,6 +1,6 @@ /* - * Copyright (C) 2018-2019 Jolla Ltd. - * Copyright (C) 2018-2019 Slava Monich + * Copyright (C) 2018-2021 Jolla Ltd. + * Copyright (C) 2018-2021 Slava Monich * * You may use this file under the terms of BSD license as follows: * @@ -14,8 +14,8 @@ * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the names of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE @@ -37,11 +37,12 @@ #include #include #include +#include #include #include +#include #include -#include #include @@ -50,6 +51,7 @@ GLOG_MODULE_DEFINE("dbus-service"); enum { EVENT_ADAPTER_ADDED, EVENT_ADAPTER_REMOVED, + EVENT_MODE_CHANGED, EVENT_COUNT }; @@ -59,16 +61,32 @@ enum { CALL_GET_ADAPTERS, CALL_GET_ALL2, CALL_GET_DAEMON_VERSION, + CALL_GET_ALL3, + CALL_GET_MODE, + CALL_REQUEST_MODE, + CALL_RELEASE_MODE, + CALL_REGISTER_LOCAL_SERVICE, + CALL_UNREGISTER_LOCAL_SERVICE, CALL_COUNT }; +typedef struct dbus_service_client { + char* dbus_name; + guint watch_id; + DBusServicePlugin* plugin; + GHashTable* peer_services; /* objpath => DBusServiceLocal */ + GHashTable* mode_requests; /* id => NfcModeRequest */ +} DBusServiceClient; + typedef NfcPluginClass DBusServicePluginClass; struct dbus_service_plugin { NfcPlugin parent; guint own_name_id; + guint last_mode_request_id; GUtilIdlePool* pool; GDBusConnection* connection; GHashTable* adapters; + GHashTable* clients; NfcManager* manager; OrgSailfishosNfcDaemon* iface; gulong event_id[EVENT_COUNT]; @@ -83,7 +101,7 @@ G_DEFINE_TYPE(DBusServicePlugin, dbus_service_plugin, NFC_TYPE_PLUGIN) #define NFC_SERVICE "org.sailfishos.nfc.daemon" #define NFC_DAEMON_PATH "/" -#define NFC_DBUS_PLUGIN_INTERFACE_VERSION (2) +#define NFC_DBUS_PLUGIN_INTERFACE_VERSION (3) static gboolean @@ -110,6 +128,39 @@ dbus_service_plugin_free_adapter( dbus_service_adapter_free((DBusServiceAdapter*)adapter); } +static +void +dbus_service_plugin_peer_service_destroy( + gpointer user_data) +{ + DBusServiceLocal* local = user_data; + DBusServicePlugin* plugin = local->plugin; + NfcPeerService* service = &local->service; + + local->plugin = NULL; + nfc_manager_unregister_service(plugin->manager, service); + nfc_peer_service_disconnect_all(service); + nfc_peer_service_unref(service); +} + +static +void +dbus_service_plugin_client_destroy( + void* value) +{ + DBusServiceClient* client = value; + + if (client->peer_services) { + g_hash_table_destroy(client->peer_services); + } + if (client->mode_requests) { + g_hash_table_destroy(client->mode_requests); + } + g_bus_unwatch_name(client->watch_id); + g_free(client->dbus_name); + gutil_slice_free(client); +} + static int dbus_service_plugin_compare_strings( @@ -150,13 +201,95 @@ dbus_service_plugin_adapters_changed( dbus_service_plugin_get_adapter_paths(self)); } +static +void +dbus_service_plugin_client_gone( + GDBusConnection* bus, + const char* name, + gpointer plugin) +{ + DBusServicePlugin* self = DBUS_SERVICE_PLUGIN(plugin); + + GDEBUG("Name '%s' has disappeared", name); + g_hash_table_remove(self->clients, name); +} + +static +DBusServiceClient* +dbus_service_plugin_client_new( + DBusServicePlugin* self, + const char* dbus_name) +{ + DBusServiceClient* client = g_slice_new0(DBusServiceClient); + + client->dbus_name = g_strdup(dbus_name); + client->watch_id = g_bus_watch_name_on_connection(self->connection, + client->dbus_name, G_BUS_NAME_WATCHER_FLAGS_NONE, NULL, + dbus_service_plugin_client_gone, self, NULL); + return client; +} + +static +DBusServiceClient* +dbus_service_plugin_client_get( + DBusServicePlugin* self, + const char* dbus_name) +{ + DBusServiceClient* client = NULL; + + if (self->clients) { + client = g_hash_table_lookup(self->clients, dbus_name); + } else { + self->clients = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + dbus_service_plugin_client_destroy); + } + if (!client) { + client = dbus_service_plugin_client_new(self, dbus_name); + g_hash_table_insert(self->clients, client->dbus_name, client); + } + return client; +} + +static +DBusServiceLocal* +dbus_service_plugin_register_local_service( + DBusServicePlugin* self, + const char* peer_name, + const char* obj_path, + const char* dbus_name) +{ + DBusServiceLocal* local = dbus_service_local_new(self->connection, + obj_path, peer_name, dbus_name); + + if (local) { + NfcPeerService* service = &local->service; + + if (nfc_manager_register_service(self->manager, service)) { + DBusServiceClient* client = dbus_service_plugin_client_get + (self, dbus_name); + + if (!client->peer_services) { + client->peer_services = + g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + dbus_service_plugin_peer_service_destroy); + } + g_hash_table_insert(client->peer_services, (gpointer) + local->obj_path, local); + local->plugin = self; + return local; + } + nfc_peer_service_unref(service); + } + return NULL; +} + /*==========================================================================* * NfcManager events *==========================================================================*/ static void -dbus_service_adapter_added( +dbus_service_plugin_event_adapter_added( NfcManager* manager, NfcAdapter* adapter, void* plugin) @@ -172,7 +305,7 @@ dbus_service_adapter_added( static void -dbus_service_adapter_removed( +dbus_service_plugin_event_adapter_removed( NfcManager* manager, NfcAdapter* adapter, void* plugin) @@ -184,6 +317,18 @@ dbus_service_adapter_removed( } } +static +void +dbus_service_plugin_event_mode_changed( + NfcManager* manager, + void* plugin) +{ + DBusServicePlugin* self = DBUS_SERVICE_PLUGIN(plugin); + + org_sailfishos_nfc_daemon_emit_mode_changed(self->iface, + self->manager->mode); +} + /*==========================================================================* * D-Bus calls *==========================================================================*/ @@ -193,10 +338,8 @@ gboolean dbus_service_plugin_handle_get_all( OrgSailfishosNfcDaemon* iface, GDBusMethodInvocation* call, - gpointer user_data) + DBusServicePlugin* self) { - DBusServicePlugin* self = DBUS_SERVICE_PLUGIN(user_data); - org_sailfishos_nfc_daemon_complete_get_all(iface, call, NFC_DBUS_PLUGIN_INTERFACE_VERSION, dbus_service_plugin_get_adapter_paths(self)); @@ -208,7 +351,7 @@ gboolean dbus_service_plugin_handle_get_interface_version( OrgSailfishosNfcDaemon* iface, GDBusMethodInvocation* call, - gpointer user_data) + DBusServicePlugin* self) { org_sailfishos_nfc_daemon_complete_get_interface_version(iface, call, NFC_DBUS_PLUGIN_INTERFACE_VERSION); @@ -220,10 +363,8 @@ gboolean dbus_service_plugin_handle_get_adapters( OrgSailfishosNfcDaemon* iface, GDBusMethodInvocation* call, - gpointer user_data) + DBusServicePlugin* self) { - DBusServicePlugin* self = DBUS_SERVICE_PLUGIN(user_data); - org_sailfishos_nfc_daemon_complete_get_adapters(iface, call, dbus_service_plugin_get_adapter_paths(self)); return TRUE; @@ -236,10 +377,8 @@ gboolean dbus_service_plugin_handle_get_all2( OrgSailfishosNfcDaemon* iface, GDBusMethodInvocation* call, - gpointer user_data) + DBusServicePlugin* self) { - DBusServicePlugin* self = DBUS_SERVICE_PLUGIN(user_data); - org_sailfishos_nfc_daemon_complete_get_all2(iface, call, NFC_DBUS_PLUGIN_INTERFACE_VERSION, dbus_service_plugin_get_adapter_paths(self), @@ -252,13 +391,176 @@ gboolean dbus_service_plugin_handle_get_daemon_version( OrgSailfishosNfcDaemon* iface, GDBusMethodInvocation* call, - gpointer user_data) + DBusServicePlugin* self) { org_sailfishos_nfc_daemon_complete_get_daemon_version(iface, call, nfc_core_version()); return TRUE; } +/* Interface version 3 */ + +static +gboolean +dbus_service_plugin_handle_get_all3( + OrgSailfishosNfcDaemon* iface, + GDBusMethodInvocation* call, + DBusServicePlugin* self) +{ + org_sailfishos_nfc_daemon_complete_get_all3(iface, call, + NFC_DBUS_PLUGIN_INTERFACE_VERSION, + dbus_service_plugin_get_adapter_paths(self), + nfc_core_version(), + self->manager->mode); + return TRUE; +} + +static +gboolean +dbus_service_plugin_handle_get_mode( + OrgSailfishosNfcDaemon* iface, + GDBusMethodInvocation* call, + DBusServicePlugin* self) +{ + org_sailfishos_nfc_daemon_complete_get_mode(iface, call, + self->manager->mode); + return TRUE; +} + +static +gboolean +dbus_service_plugin_handle_request_mode( + OrgSailfishosNfcDaemon* iface, + GDBusMethodInvocation* call, + guint enable, + guint disable, + DBusServicePlugin* self) +{ + const char* sender = g_dbus_method_invocation_get_sender(call); + DBusServiceClient* client = dbus_service_plugin_client_get(self, sender); + NfcModeRequest* req = nfc_manager_mode_request_new(self->manager, + enable, disable); + + if (!client->mode_requests) { + client->mode_requests = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, (GDestroyNotify) + nfc_manager_mode_request_free); + } + self->last_mode_request_id++; + while (g_hash_table_contains(client->mode_requests, GUINT_TO_POINTER + (self->last_mode_request_id)) || !self->last_mode_request_id) { + self->last_mode_request_id++; + } + g_hash_table_insert(client->mode_requests, + GUINT_TO_POINTER(self->last_mode_request_id), req); + GDEBUG("Mode request 0x%02x/0x%02x => %s/%u", enable, disable, + sender, self->last_mode_request_id); + + org_sailfishos_nfc_daemon_complete_request_mode(iface, call, + self->last_mode_request_id); + return TRUE; +} + +static +gboolean +dbus_service_plugin_handle_release_mode( + OrgSailfishosNfcDaemon* iface, + GDBusMethodInvocation* call, + guint id, + DBusServicePlugin* self) +{ + const char* sender = g_dbus_method_invocation_get_sender(call); + gboolean released = FALSE; + + if (self->clients) { + DBusServiceClient* client = g_hash_table_lookup(self->clients, sender); + + released = (client && client->mode_requests && + g_hash_table_remove(client->mode_requests, GUINT_TO_POINTER(id))); + } + if (released) { + GDEBUG("Mode request %s/%u released", sender, id); + org_sailfishos_nfc_daemon_complete_release_mode(iface, call); + } else { + GDEBUG("Mode request %s/%u not found", sender, id); + g_dbus_method_invocation_return_error(call, + DBUS_SERVICE_ERROR, DBUS_SERVICE_ERROR_NOT_FOUND, + "Invalid mode request %s/%u", sender, id); + } + return TRUE; +} + +static +gboolean +dbus_service_plugin_handle_register_local_service( + OrgSailfishosNfcDaemon* iface, + GDBusMethodInvocation* call, + const char* obj_path, + const char* sn, + DBusServicePlugin* self) +{ + DBusServiceLocal* local = NULL; + const char* sender = g_dbus_method_invocation_get_sender(call); + + if (self->clients) { + DBusServiceClient* client = g_hash_table_lookup(self->clients, sender); + + if (client && client->peer_services) { + local = g_hash_table_lookup(client->peer_services, obj_path); + } + } + if (local) { + GWARN("Duplicate service %s%s", sender, obj_path); + g_dbus_method_invocation_return_error(call, + DBUS_SERVICE_ERROR, DBUS_SERVICE_ERROR_ALREADY_EXISTS, + "Service '%s' already registered", obj_path); + } else { + local = dbus_service_plugin_register_local_service(self, sn, + obj_path, sender); + if (local) { + GDEBUG("Registered service %s%s (SAP %u)", sender, obj_path, + local->service.sap); + org_sailfishos_nfc_daemon_complete_register_local_service(iface, + call, local->service.sap); + } else { + g_dbus_method_invocation_return_error(call, + DBUS_SERVICE_ERROR, DBUS_SERVICE_ERROR_FAILED, + "Failed to register service %s%s", sender, obj_path); + } + } + return TRUE; +} + +static +gboolean +dbus_service_plugin_handle_unregister_local_service( + OrgSailfishosNfcDaemon* iface, + GDBusMethodInvocation* call, + const char* obj_path, + DBusServicePlugin* self) +{ + const char* sender = g_dbus_method_invocation_get_sender(call); + gboolean removed = FALSE; + + if (self->clients) { + DBusServiceClient* client = g_hash_table_lookup(self->clients, sender); + + removed = (client && client->peer_services && + g_hash_table_remove(client->peer_services, obj_path)); + } + if (removed) { + GDEBUG("Unregistered service %s%s", sender, obj_path); + org_sailfishos_nfc_daemon_complete_unregister_local_service + (iface, call); + } else { + GDEBUG("Service %s%s is not registered", sender, obj_path); + g_dbus_method_invocation_return_error(call, + DBUS_SERVICE_ERROR, DBUS_SERVICE_ERROR_NOT_FOUND, + "Service %s%s is not registered", sender, obj_path); + } + return TRUE; +} + /*==========================================================================* * Name watching *==========================================================================*/ @@ -336,10 +638,13 @@ dbus_service_plugin_start( /* NfcManager events */ self->event_id[EVENT_ADAPTER_ADDED] = nfc_manager_add_adapter_added_handler(manager, - dbus_service_adapter_added, self); + dbus_service_plugin_event_adapter_added, self); self->event_id[EVENT_ADAPTER_REMOVED] = nfc_manager_add_adapter_removed_handler(manager, - dbus_service_adapter_removed, self); + dbus_service_plugin_event_adapter_removed, self); + self->event_id[EVENT_MODE_CHANGED] = + nfc_manager_add_mode_changed_handler(manager, + dbus_service_plugin_event_mode_changed, self); /* D-Bus calls */ self->call_id[CALL_GET_ALL] = @@ -357,6 +662,24 @@ dbus_service_plugin_start( self->call_id[CALL_GET_DAEMON_VERSION] = g_signal_connect(self->iface, "handle-get-daemon-version", G_CALLBACK(dbus_service_plugin_handle_get_daemon_version), self); + self->call_id[CALL_GET_ALL3] = + g_signal_connect(self->iface, "handle-get-all3", + G_CALLBACK(dbus_service_plugin_handle_get_all3), self); + self->call_id[CALL_GET_MODE] = + g_signal_connect(self->iface, "handle-get-mode", + G_CALLBACK(dbus_service_plugin_handle_get_mode), self); + self->call_id[CALL_REQUEST_MODE] = + g_signal_connect(self->iface, "handle-request-mode", + G_CALLBACK(dbus_service_plugin_handle_request_mode), self); + self->call_id[CALL_RELEASE_MODE] = + g_signal_connect(self->iface, "handle-release-mode", + G_CALLBACK(dbus_service_plugin_handle_release_mode), self); + self->call_id[CALL_REGISTER_LOCAL_SERVICE] = + g_signal_connect(self->iface, "handle-register-local-service", + G_CALLBACK(dbus_service_plugin_handle_register_local_service), self); + self->call_id[CALL_UNREGISTER_LOCAL_SERVICE] = + g_signal_connect(self->iface, "handle-unregister-local-service", + G_CALLBACK(dbus_service_plugin_handle_unregister_local_service), self); return TRUE; } @@ -382,6 +705,26 @@ dbus_service_plugin_stop( } } +DBusServicePeer* +dbus_service_plugin_find_peer( + DBusServicePlugin* self, + NfcPeer* peer) +{ + GHashTableIter it; + gpointer value; + + g_hash_table_iter_init(&it, self->adapters); + while (g_hash_table_iter_next(&it, NULL, &value)) { + DBusServicePeer* dbus_peer = dbus_service_adapter_find_peer + ((DBusServiceAdapter*)value, peer); + + if (dbus_peer) { + return dbus_peer; + } + } + return NULL; +} + /*==========================================================================* * Internals *==========================================================================*/ @@ -403,8 +746,11 @@ dbus_service_plugin_finalize( { DBusServicePlugin* self = DBUS_SERVICE_PLUGIN(plugin); - gutil_idle_pool_destroy(self->pool); + if (self->clients) { + g_hash_table_destroy(self->clients); + } g_hash_table_destroy(self->adapters); + gutil_idle_pool_destroy(self->pool); G_OBJECT_CLASS(dbus_service_plugin_parent_class)->finalize(plugin); } diff --git a/plugins/dbus_service/dbus_service_tag.c b/plugins/dbus_service/dbus_service_tag.c index 76bd909..1749ba7 100644 --- a/plugins/dbus_service/dbus_service_tag.c +++ b/plugins/dbus_service/dbus_service_tag.c @@ -1,6 +1,6 @@ /* - * Copyright (C) 2018-2020 Jolla Ltd. - * Copyright (C) 2018-2020 Slava Monich + * Copyright (C) 2018-2021 Jolla Ltd. + * Copyright (C) 2018-2021 Slava Monich * Copyright (C) 2020 Open Mobile Platform LLC. * * You may use this file under the terms of BSD license as follows: @@ -42,6 +42,7 @@ #include #include +#include #include enum { @@ -72,13 +73,15 @@ enum { CALL_COUNT }; +typedef struct dbus_service_tag_priv DBusServiceTagPriv; +typedef struct dbus_service_tag_call DBusServiceTagCall; + typedef void (*DBusServiceTagCallFunc)( GDBusMethodInvocation* call, - DBusServiceTag* self); + DBusServiceTagPriv* self); -typedef struct dbus_service_tag_call DBusServiceTagCall; struct dbus_service_tag_call { DBusServiceTagCall* next; GDBusMethodInvocation* invocation; @@ -95,7 +98,7 @@ typedef struct dbus_service_tag_lock { guint watch_id; guint count; NfcTargetSequence* seq; - DBusServiceTag* tag; + DBusServiceTagPriv* tag; } DBusServiceTagLock; typedef struct dbus_service_tag_lock_waiter { @@ -108,16 +111,15 @@ typedef struct dbus_service_tag_async_call { GDBusMethodInvocation* call; } DBusServiceTagAsyncCall; -struct dbus_service_tag { +struct dbus_service_tag_priv { + DBusServiceTag pub; char* path; - GDBusConnection* connection; OrgSailfishosNfcTag* iface; GUtilIdlePool* pool; - GSList* lock_waters; + GSList* lock_waiters; DBusServiceTagLock* lock; DBusServiceTagCallQueue queue; GSList* ndefs; - NfcTag* tag; gulong target_event_id[TARGET_EVENT_COUNT]; gulong tag_event_id[TAG_EVENT_COUNT]; gulong call_id[CALL_COUNT]; @@ -133,15 +135,18 @@ static const char* const dbus_service_tag_default_interfaces[] = { NFC_DBUS_TAG_INTERFACE, NULL }; +static inline DBusServiceTagPriv* dbus_service_tag_cast(DBusServiceTag* pub) + { return G_LIKELY(pub) ? G_CAST(pub, DBusServiceTagPriv, pub) : NULL; } + static DBusServiceTagLockWaiter* dbus_service_tag_find_waiter( - DBusServiceTag* self, + DBusServiceTagPriv* self, const char* name) { GSList* l; - for (l = self->lock_waters; l; l = l->next) { + for (l = self->lock_waiters; l; l = l->next) { DBusServiceTagLockWaiter* waiter = l->data; if (!g_strcmp0(waiter->lock->name, name)) { @@ -153,12 +158,14 @@ dbus_service_tag_find_waiter( NfcTargetSequence* dbus_service_tag_sequence( - DBusServiceTag* self, + DBusServiceTag* pub, GDBusMethodInvocation* call) { const char* sender = g_dbus_method_invocation_get_sender(call); - if (G_LIKELY(sender)) { + if (G_LIKELY(pub) && G_LIKELY(sender)) { + DBusServiceTagPriv* self = dbus_service_tag_cast(pub); + if (self->lock && !g_strcmp0(self->lock->name, sender)) { return self->lock->seq; } else { @@ -173,6 +180,53 @@ dbus_service_tag_sequence( return NULL; } +static +GVariant* +dbus_service_tag_get_poll_parameters( + NfcTag* tag, + const NfcParamPoll* poll) +{ + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + if (G_LIKELY(tag) && G_UNLIKELY(poll)) { + const NfcTarget* target = tag->target; + const NfcParamPollA* poll_a = NULL; + const NfcParamPollB* poll_b = NULL; + + if (G_LIKELY(target)) { + switch(tag->target->technology) { + case NFC_TECHNOLOGY_B: + poll_b = &poll->b; + dbus_service_dict_add_byte_array(&builder, "APPDATA", + poll_b->app_data, sizeof(poll_b->app_data)); + if (poll_b->prot_info.bytes) { + dbus_service_dict_add_byte_array_data(&builder, "PROTINFO", + &poll_b->prot_info); + } + if (poll_b->nfcid0.bytes) { + dbus_service_dict_add_byte_array_data(&builder, "NFCID0", + &poll_b->nfcid0); + } + break; + case NFC_TECHNOLOGY_A: + poll_a = &poll->a; + dbus_service_dict_add_byte(&builder, "SEL_RES", + poll_a->sel_res); + if (poll_a->nfcid1.bytes) { + dbus_service_dict_add_byte_array_data(&builder, "NFCID1", + &poll_a->nfcid1); + } + break; + case NFC_TECHNOLOGY_F: + case NFC_TECHNOLOGY_UNKNOWN: + break; + } + } + } + return g_variant_builder_end(&builder); +} + static void dbus_service_tag_lock_cancel_acquire( @@ -192,7 +246,7 @@ dbus_service_tag_lock_complete_acquire( gpointer user_data) { GDBusMethodInvocation* acquire = data; - DBusServiceTag* dbus = user_data; + DBusServiceTagPriv* dbus = user_data; org_sailfishos_nfc_tag_complete_acquire(dbus->iface, acquire); g_object_unref(acquire); @@ -239,7 +293,7 @@ dbus_service_tag_target_sequence_changed( NfcTarget* target, void* user_data) { - DBusServiceTag* self = user_data; + DBusServiceTagPriv* self = user_data; /* * Once the lock has been acquired, it remains acquired until we @@ -251,12 +305,12 @@ dbus_service_tag_target_sequence_changed( if (target->sequence) { GSList* l; - for (l = self->lock_waters; l; l = l->next) { + for (l = self->lock_waiters; l; l = l->next) { DBusServiceTagLockWaiter* waiter = l->data; DBusServiceTagLock* lock = waiter->lock; if (lock->seq == target->sequence) { - self->lock_waters = g_slist_delete_link(self->lock_waters, l); + self->lock_waiters = g_slist_delete_link(self->lock_waiters, l); GDEBUG("%s owns %s", lock->name, self->path); /* @@ -287,7 +341,7 @@ dbus_service_tag_lock_peer_vanished( gpointer data) { DBusServiceTagLock* lock = data; - DBusServiceTag* self = lock->tag; + DBusServiceTagPriv* self = lock->tag; if (self->lock == lock) { /* This owner of the current lock is gone */ @@ -297,12 +351,12 @@ dbus_service_tag_lock_peer_vanished( GSList* l; /* Dispose of the dead waiter */ - for (l = self->lock_waters; l; l = l->next) { + for (l = self->lock_waiters; l; l = l->next) { DBusServiceTagLockWaiter* waiter = l->data; if (waiter->lock == lock) { GDEBUG("Name '%s' has disappeared, dropping the waiter", name); - self->lock_waters = g_slist_delete_link(self->lock_waters, l); + self->lock_waiters = g_slist_delete_link(self->lock_waiters, l); dbus_service_tag_lock_waiter_free(waiter); break; } @@ -322,9 +376,10 @@ dbus_service_tag_lock_peer_vanished( static void dbus_service_tag_export_all( - DBusServiceTag* self) + DBusServiceTagPriv* self) { - NfcTag* tag = self->tag; + DBusServiceTag* pub = &self->pub; + NfcTag* tag = pub->tag; NfcNdefRec* rec = tag->ndef; GPtrArray* interfaces = g_ptr_array_new(); @@ -341,7 +396,7 @@ dbus_service_tag_export_all( g_string_set_size(buf, base_len); g_string_append_printf(buf, "%u", i); - ndef = dbus_service_ndef_new(rec, buf->str, self->connection); + ndef = dbus_service_ndef_new(rec, buf->str, pub->connection); if (ndef) { self->ndefs = g_slist_append(self->ndefs, ndef); i++; @@ -353,7 +408,7 @@ dbus_service_tag_export_all( /* Export sub-interfaces */ g_ptr_array_add(interfaces, (gpointer)NFC_DBUS_TAG_INTERFACE); if (NFC_IS_TAG_T2(tag)) { - self->t2 = dbus_service_tag_t2_new(NFC_TAG_T2(tag), self); + self->t2 = dbus_service_tag_t2_new(NFC_TAG_T2(tag), pub); if (self->t2) { const char* iface = NFC_DBUS_TAG_T2_INTERFACE; GDEBUG("Adding %s", iface); @@ -362,7 +417,7 @@ dbus_service_tag_export_all( } if (NFC_IS_TAG_T4(tag)) { - self->isodep = dbus_service_isodep_new(NFC_TAG_T4(tag), self); + self->isodep = dbus_service_isodep_new(NFC_TAG_T4(tag), pub); if (self->isodep) { const char* iface = NFC_DBUS_ISODEP_INTERFACE; GDEBUG("Adding %s", iface); @@ -386,7 +441,7 @@ dbus_service_tag_free_ndef_rec( static const char** dbus_service_tag_get_ndef_rec_paths( - DBusServiceTag* self) + DBusServiceTagPriv* self) { const char** paths = g_new(const char*, g_slist_length(self->ndefs) + 1); GSList* l; @@ -448,11 +503,11 @@ dbus_service_tag_dequeue_call( static gboolean dbus_service_tag_handle_call( - DBusServiceTag* self, + DBusServiceTagPriv* self, GDBusMethodInvocation* call, DBusServiceTagCallFunc func) { - if (self->tag->flags & NFC_TAG_FLAG_INITIALIZED) { + if (self->pub.tag->flags & NFC_TAG_FLAG_INITIALIZED) { func(call, self); } else { dbus_service_tag_queue_call(&self->queue, call, func); @@ -463,7 +518,7 @@ dbus_service_tag_handle_call( static void dbus_service_tag_complete_pending_calls( - DBusServiceTag* self) + DBusServiceTagPriv* self) { DBusServiceTagCall* call; @@ -483,7 +538,7 @@ dbus_service_tag_initialized( NfcTag* tag, void* user_data) { - DBusServiceTag* self = user_data; + DBusServiceTagPriv* self = user_data; dbus_service_tag_export_all(self); dbus_service_tag_complete_pending_calls(self); @@ -499,9 +554,9 @@ static void dbus_service_tag_complete_get_all( GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { - NfcTag* tag = self->tag; + NfcTag* tag = self->pub.tag; NfcTarget* target = tag->target; org_sailfishos_nfc_tag_complete_get_all(self->iface, call, @@ -516,7 +571,7 @@ gboolean dbus_service_tag_handle_get_all( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { /* Queue the call if the tag is not initialized yet */ return dbus_service_tag_handle_call(self, call, @@ -530,7 +585,7 @@ gboolean dbus_service_tag_handle_get_interface_version( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { org_sailfishos_nfc_tag_complete_get_interface_version(iface, call, NFC_DBUS_TAG_INTERFACE_VERSION); @@ -544,10 +599,10 @@ gboolean dbus_service_tag_handle_get_present( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { org_sailfishos_nfc_tag_complete_get_present(iface, call, - self->tag->present); + self->pub.tag->present); return TRUE; } @@ -558,10 +613,10 @@ gboolean dbus_service_tag_handle_get_technology( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { org_sailfishos_nfc_tag_complete_get_technology(iface, call, - self->tag->target->technology); + self->pub.tag->target->technology); return TRUE; } @@ -572,10 +627,10 @@ gboolean dbus_service_tag_handle_get_protocol( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { org_sailfishos_nfc_tag_complete_get_protocol(iface, call, - self->tag->target->protocol); + self->pub.tag->target->protocol); return TRUE; } @@ -586,10 +641,10 @@ gboolean dbus_service_tag_handle_get_type( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { org_sailfishos_nfc_tag_complete_get_type(iface, call, - self->tag->type); + self->pub.tag->type); return TRUE; } @@ -599,7 +654,7 @@ static void dbus_service_tag_complete_get_interfaces( GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { org_sailfishos_nfc_tag_complete_get_interfaces(self->iface, call, self->interfaces ? self->interfaces : @@ -611,7 +666,7 @@ gboolean dbus_service_tag_handle_get_interfaces( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { return dbus_service_tag_handle_call(self, call, dbus_service_tag_complete_get_interfaces); @@ -623,7 +678,7 @@ static void dbus_service_tag_complete_get_ndef_records( GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { org_sailfishos_nfc_tag_complete_get_ndef_records(self->iface, call, dbus_service_tag_get_ndef_rec_paths(self)); @@ -634,7 +689,7 @@ gboolean dbus_service_tag_handle_get_ndef_records( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { /* Queue the call if the tag is not initialized yet */ dbus_service_tag_handle_call(self, call, @@ -649,9 +704,9 @@ gboolean dbus_service_tag_handle_deactivate( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { - nfc_tag_deactivate(self->tag); + nfc_tag_deactivate(self->pub.tag); org_sailfishos_nfc_tag_complete_deactivate(iface, call); return TRUE; } @@ -664,9 +719,10 @@ dbus_service_tag_handle_acquire( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, gboolean wait, - DBusServiceTag* self) + DBusServiceTagPriv* self) { - NfcTarget* target = self->tag->target; + DBusServiceTag* pub = &self->pub; + NfcTarget* target = pub->tag->target; DBusServiceTagLock* current_lock = self->lock; const char* name = g_dbus_method_invocation_get_sender(call); @@ -695,7 +751,7 @@ dbus_service_tag_handle_acquire( lock->name = g_strdup(name); lock->seq = nfc_target_sequence_new(target); lock->tag = self; - lock->watch_id = g_bus_watch_name_on_connection(self->connection, + lock->watch_id = g_bus_watch_name_on_connection(pub->connection, name, G_BUS_NAME_WATCHER_FLAGS_NONE, NULL, dbus_service_tag_lock_peer_vanished, lock, NULL); @@ -714,7 +770,7 @@ dbus_service_tag_handle_acquire( waiter->lock = lock; waiter->pending_calls = g_slist_append(waiter->pending_calls, g_object_ref(call)); - self->lock_waters = g_slist_append(self->lock_waters, waiter); + self->lock_waiters = g_slist_append(self->lock_waiters, waiter); } } } @@ -728,7 +784,7 @@ gboolean dbus_service_tag_handle_release( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { DBusServiceTagLock* current_lock = self->lock; const char* name = g_dbus_method_invocation_get_sender(call); @@ -760,7 +816,7 @@ dbus_service_tag_handle_release( /* If no more requests is pending, delete the waiter */ if (!waiter->pending_calls) { - self->lock_waters = g_slist_remove(self->lock_waters, waiter); + self->lock_waiters = g_slist_remove(self->lock_waiters, waiter); dbus_service_tag_lock_free(waiter->lock); dbus_service_tag_lock_waiter_free(waiter); } @@ -775,62 +831,15 @@ dbus_service_tag_handle_release( /* Interface Version 3 */ -static -GVariant* -dbus_service_tag_get_poll_parameters( - NfcTag* tag, - const NfcParamPoll* poll) -{ - GVariantBuilder builder; - g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); - - if (G_LIKELY(tag) && G_UNLIKELY(poll)) { - const NfcTarget* target = tag->target; - const NfcParamPollA* poll_a = NULL; - const NfcParamPollB* poll_b = NULL; - - if (G_LIKELY(target)) { - switch(tag->target->technology) { - case NFC_TECHNOLOGY_B: - poll_b = &poll->b; - dbus_service_dict_add_byte_array(&builder, "APPDATA", - poll_b->app_data, sizeof(poll_b->app_data)); - if (poll_b->prot_info.bytes) { - dbus_service_dict_add_byte_array_data(&builder, "PROTINFO", - &poll_b->prot_info); - } - if (poll_b->nfcid0.bytes) { - dbus_service_dict_add_byte_array_data(&builder, "NFCID0", - &poll_b->nfcid0); - } - break; - case NFC_TECHNOLOGY_A: - poll_a = &poll->a; - dbus_service_dict_add_byte(&builder, "SEL_RES", - poll_a->sel_res); - if (poll_a->nfcid1.bytes) { - dbus_service_dict_add_byte_array_data(&builder, "NFCID1", - &poll_a->nfcid1); - } - break; - case NFC_TECHNOLOGY_F: - case NFC_TECHNOLOGY_UNKNOWN: - break; - } - } - } - return g_variant_builder_end(&builder); -} - /* GetAll3 */ static void dbus_service_tag_complete_get_all3( GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { - NfcTag* tag = self->tag; + NfcTag* tag = self->pub.tag; NfcTarget* target = tag->target; org_sailfishos_nfc_tag_complete_get_all3(self->iface, call, @@ -838,8 +847,7 @@ dbus_service_tag_complete_get_all3( target->protocol, tag->type, self->interfaces ? self->interfaces : dbus_service_tag_default_interfaces, dbus_service_tag_get_ndef_rec_paths(self), - dbus_service_tag_get_poll_parameters(self->tag, - nfc_tag_param(self->tag))); + dbus_service_tag_get_poll_parameters(tag, nfc_tag_param(tag))); } static @@ -847,7 +855,7 @@ gboolean dbus_service_tag_handle_get_all3( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { /* Queue the call if the tag is not initialized yet */ return dbus_service_tag_handle_call(self, call, @@ -861,9 +869,9 @@ gboolean dbus_service_tag_handle_get_poll_parameters( OrgSailfishosNfcTag* iface, GDBusMethodInvocation* call, - DBusServiceTag* self) + DBusServiceTagPriv* self) { - NfcTag* tag = self->tag; + NfcTag* tag = self->pub.tag; org_sailfishos_nfc_tag_complete_get_poll_parameters(iface, call, dbus_service_tag_get_poll_parameters(tag, nfc_tag_param(tag))); @@ -950,20 +958,22 @@ dbus_service_tag_handle_transceive( static void dbus_service_tag_free_unexported( - DBusServiceTag* self) + DBusServiceTagPriv* self) { + DBusServiceTag* pub = &self->pub; + NfcTag* tag = pub->tag; DBusServiceTagCall* call; - nfc_target_remove_all_handlers(self->tag->target, self->target_event_id); - nfc_tag_remove_all_handlers(self->tag, self->tag_event_id); + nfc_target_remove_all_handlers(tag->target, self->target_event_id); + nfc_tag_remove_all_handlers(tag, self->tag_event_id); g_slist_free_full(self->ndefs, dbus_service_tag_free_ndef_rec); - g_slist_free_full(self->lock_waters, dbus_service_tag_lock_waiter_free1); + g_slist_free_full(self->lock_waiters, dbus_service_tag_lock_waiter_free1); dbus_service_isodep_free(self->isodep); dbus_service_tag_t2_free(self->t2); dbus_service_tag_lock_free(self->lock); - nfc_tag_unref(self->tag); + nfc_tag_unref(tag); gutil_disconnect_handlers(self->iface, self->call_id, CALL_COUNT); @@ -975,42 +985,28 @@ dbus_service_tag_free_unexported( } g_object_unref(self->iface); - g_object_unref(self->connection); + g_object_unref(pub->connection); - gutil_idle_pool_drain(self->pool); - gutil_idle_pool_unref(self->pool); + gutil_idle_pool_destroy(self->pool); g_free(self->interfaces); g_free(self->path); g_free(self); } -GDBusConnection* -dbus_service_tag_connection( - DBusServiceTag* self) -{ - return self->connection; -} - -const char* -dbus_service_tag_path( - DBusServiceTag* self) -{ - return self->path; -} - DBusServiceTag* dbus_service_tag_new( NfcTag* tag, const char* parent_path, GDBusConnection* connection) { - DBusServiceTag* self = g_new0(DBusServiceTag, 1); + DBusServiceTagPriv* self = g_new0(DBusServiceTagPriv, 1); + DBusServiceTag* pub = &self->pub; GError* error = NULL; - g_object_ref(self->connection = connection); - self->path = g_strconcat(parent_path, "/", tag->name, NULL); - self->tag = nfc_tag_ref(tag); + g_object_ref(pub->connection = connection); + pub->path = self->path = g_strconcat(parent_path, "/", tag->name, NULL); + pub->tag = nfc_tag_ref(tag); self->pool = gutil_idle_pool_new(); self->iface = org_sailfishos_nfc_tag_skeleton_new(); @@ -1074,7 +1070,7 @@ dbus_service_tag_new( if (g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON (self->iface), connection, self->path, &error)) { GDEBUG("Created D-Bus object %s", self->path); - return self; + return pub; } else { GERR("%s: %s", self->path, GERRMSG(error)); g_error_free(error); @@ -1085,9 +1081,11 @@ dbus_service_tag_new( void dbus_service_tag_free( - DBusServiceTag* self) + DBusServiceTag* pub) { - if (self) { + if (pub) { + DBusServiceTagPriv* self = dbus_service_tag_cast(pub); + GDEBUG("Removing D-Bus object %s", self->path); org_sailfishos_nfc_tag_emit_removed(self->iface); g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON diff --git a/plugins/dbus_service/dbus_service_tag_t2.c b/plugins/dbus_service/dbus_service_tag_t2.c index fcf43fb..2a6b775 100644 --- a/plugins/dbus_service/dbus_service_tag_t2.c +++ b/plugins/dbus_service/dbus_service_tag_t2.c @@ -527,8 +527,6 @@ dbus_service_tag_t2_new( NfcTagType2* t2, DBusServiceTag* owner) { - GDBusConnection* connection = dbus_service_tag_connection(owner); - const char* path = dbus_service_tag_path(owner); DBusServiceTagType2* self = g_new0(DBusServiceTagType2, 1); GError* error = NULL; @@ -569,11 +567,11 @@ dbus_service_tag_t2_new( G_CALLBACK(dbus_service_tag_t2_handle_write_data), self); if (g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON - (self->iface), connection, path, &error)) { - GDEBUG("Created D-Bus object %s (Type2)", path); + (self->iface), owner->connection, owner->path, &error)) { + GDEBUG("Created D-Bus object %s (Type2)", owner->path); return self; } else { - GERR("%s: %s", path, GERRMSG(error)); + GERR("%s: %s", owner->path, GERRMSG(error)); g_error_free(error); dbus_service_tag_t2_free_unexported(self); return NULL; @@ -585,8 +583,7 @@ dbus_service_tag_t2_free( DBusServiceTagType2* self) { if (self) { - GDEBUG("Removing D-Bus object %s (Type2)", - dbus_service_tag_path(self->owner)); + GDEBUG("Removing D-Bus object %s (Type2)", self->owner->path); g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON (self->iface)); dbus_service_tag_t2_free_unexported(self); diff --git a/plugins/dbus_service/org.sailfishos.nfc.Adapter.xml b/plugins/dbus_service/org.sailfishos.nfc.Adapter.xml index 29c3ed0..6c2e2bc 100644 --- a/plugins/dbus_service/org.sailfishos.nfc.Adapter.xml +++ b/plugins/dbus_service/org.sailfishos.nfc.Adapter.xml @@ -59,5 +59,22 @@ + + + + + + + + + + + + + + + + + diff --git a/plugins/dbus_service/org.sailfishos.nfc.Daemon.xml b/plugins/dbus_service/org.sailfishos.nfc.Daemon.xml index 325dd5e..932e012 100644 --- a/plugins/dbus_service/org.sailfishos.nfc.Daemon.xml +++ b/plugins/dbus_service/org.sailfishos.nfc.Daemon.xml @@ -12,7 +12,6 @@ - @@ -25,5 +24,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/dbus_service/org.sailfishos.nfc.LocalService.xml b/plugins/dbus_service/org.sailfishos.nfc.LocalService.xml new file mode 100644 index 0000000..03356dd --- /dev/null +++ b/plugins/dbus_service/org.sailfishos.nfc.LocalService.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/dbus_service/org.sailfishos.nfc.Peer.xml b/plugins/dbus_service/org.sailfishos.nfc.Peer.xml new file mode 100644 index 0000000..4109463 --- /dev/null +++ b/plugins/dbus_service/org.sailfishos.nfc.Peer.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/dbus_service/org.sailfishos.nfc.Tag.xml b/plugins/dbus_service/org.sailfishos.nfc.Tag.xml index 440c51c..6bd6123 100644 --- a/plugins/dbus_service/org.sailfishos.nfc.Tag.xml +++ b/plugins/dbus_service/org.sailfishos.nfc.Tag.xml @@ -18,7 +18,6 @@ 4 - Type 3 Tag 8 - Type 4A Tag (ISO-DEP, ISO 14443) 16 - Type 4B Tag,(ISO-DEP, ISO 14443) - 32 - NFC-DEP Protocol (ISO 18092) The above codes are bitmasks elsewhere, but here used as enum values. --> diff --git a/plugins/dbus_service/org.sailfishos.nfc.daemon.conf b/plugins/dbus_service/org.sailfishos.nfc.daemon.conf index 0922866..3df1ead 100644 --- a/plugins/dbus_service/org.sailfishos.nfc.daemon.conf +++ b/plugins/dbus_service/org.sailfishos.nfc.daemon.conf @@ -3,9 +3,11 @@ + + + = %{glib_version} diff --git a/src/nfcd.map b/src/nfcd.map index 2adb32d..0a7e7a4 100644 --- a/src/nfcd.map +++ b/src/nfcd.map @@ -3,8 +3,11 @@ nfc_adapter_*; nfc_core_*; nfc_crc_*; + nfc_initiator_*; + nfc_llc_*; nfc_manager_*; nfc_ndef_rec_*; + nfc_peer_*; nfc_plugin_*; nfc_tag_*; nfc_target_*; diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..4165b51 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,6 @@ +# -*- Mode: makefile-gmake -*- + +all: +%: + @$(MAKE) -C nfcdep-client $* + @$(MAKE) -C nfcdep-service $* diff --git a/test/README b/test/README new file mode 100644 index 0000000..e2afe16 --- /dev/null +++ b/test/README @@ -0,0 +1 @@ +Tests that can be run interactively on the device. diff --git a/test/nfcdep-client/Makefile b/test/nfcdep-client/Makefile new file mode 100644 index 0000000..998472e --- /dev/null +++ b/test/nfcdep-client/Makefile @@ -0,0 +1,166 @@ +# -*- Mode: makefile-gmake -*- + +.PHONY: all debug release clean install + +# +# Required packages +# + +PKGS = gio-unix-2.0 gio-2.0 libglibutil +LIB_PKGS = $(PKGS) + +# +# Default target +# + +all: debug release + +# +# Sources +# + +SRC = nfcdep-client.c + +GEN_SRC = \ + org.sailfishos.nfc.Adapter.c \ + org.sailfishos.nfc.Daemon.c \ + org.sailfishos.nfc.Peer.c + +ALL_SRC = $(GEN_SRC) $(SRC) + +# +# Directories +# + +SRC_DIR = . +BUILD_DIR = build +SPEC_DIR = ../../plugins/dbus_service +GEN_DIR = $(BUILD_DIR) +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +DEBUG_FLAGS = -g +RELEASE_FLAGS = +DEBUG_DEFS = -DDEBUG +RELEASE_DEFS = +WARNINGS = -Wall -Wstrict-aliasing -Wunused-result +INCLUDES = -I. -I../../core/include -I$(GEN_DIR) +FULL_CFLAGS = -fPIC $(CFLAGS) $(DEFINES) $(WARNINGS) $(INCLUDES) \ + -MMD -MP $(shell pkg-config --cflags $(PKGS)) +FULL_LDFLAGS = $(LDFLAGS) + +ifndef KEEP_SYMBOLS +KEEP_SYMBOLS = 0 +endif + +ifneq ($(KEEP_SYMBOLS),0) +RELEASE_FLAGS += -g +SUBMAKE_OPTS += KEEP_SYMBOLS=1 +endif + +DEBUG_CFLAGS = $(DEBUG_FLAGS) -DDEBUG $(FULL_CFLAGS) +RELEASE_CFLAGS = $(RELEASE_FLAGS) -O2 $(FULL_CFLAGS) +DEBUG_LDFLAGS = $(DEBUG_FLAGS) $(FULL_LDFLAGS) +RELEASE_LDFLAGS = $(RELEASE_FLAGS) $(FULL_LDFLAGS) + +LIBS = $(shell pkg-config --libs $(LIB_PKGS)) -ldl +DEBUG_LIBS = $(LIBS) +RELEASE_LIBS = $(LIBS) + +# +# Files +# + +DEBUG_OBJS = $(ALL_SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = $(ALL_SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) + +# +# Dependencies +# + +DEPS = \ + $(DEBUG_OBJS:%.o=%.d) \ + $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +$(GEN_SRC:%=$(GEN_DIR)/%): | $(GEN_DIR) +$(SRC): | $(GEN_SRC:%=$(GEN_DIR)/%) +$(DEBUG_OBJS): | $(DEBUG_BUILD_DIR) +$(RELEASE_OBJS): | $(RELEASE_BUILD_DIR) + +# +# Rules +# + +EXE = nfcdep-client +DEBUG_EXE = $(DEBUG_BUILD_DIR)/$(EXE) +RELEASE_EXE = $(RELEASE_BUILD_DIR)/$(EXE) + +debug: $(DEBUG_EXE) + +release: $(RELEASE_EXE) + +clean: + rm -fr $(BUILD_DIR) $(SRC_DIR)/*~ + +nfc_core_debug_lib: + make -C $(NFC_CORE_DIR) debug + +nfc_core_release_lib: + make -C $(NFC_CORE_DIR) release + +$(GEN_DIR)/%.c: $(SPEC_DIR)/%.xml + gdbus-codegen --generate-c-code $(@:%.c=%) $< + +$(GEN_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR)/%.o: $(GEN_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o: $(GEN_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_EXE): $(DEBUG_OBJS) + $(LD) $(DEBUG_LDFLAGS) $(DEBUG_OBJS) $(DEBUG_LIBS) -o $@ + +$(RELEASE_EXE): $(RELEASE_OBJS) + $(LD) $(RELEASE_LDFLAGS) $(RELEASE_OBJS) $(RELEASE_LIBS) -o $@ +ifeq ($(KEEP_SYMBOLS),0) + strip $@ +endif + +# +# Install +# + +INSTALL = install +INSTALL_BIN_DIR = $(DESTDIR)/usr/bin + +install: $(INSTALL_BIN_DIR) + $(INSTALL) -m 755 $(RELEASE_EXE) $(INSTALL_BIN_DIR) + +$(INSTALL_BIN_DIR): + $(INSTALL) -d $@ diff --git a/test/nfcdep-client/nfcdep-client.c b/test/nfcdep-client/nfcdep-client.c new file mode 100644 index 0000000..04f32f4 --- /dev/null +++ b/test/nfcdep-client/nfcdep-client.c @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_types.h" +#include "org.sailfishos.nfc.Daemon.h" +#include "org.sailfishos.nfc.Adapter.h" +#include "org.sailfishos.nfc.Peer.h" + +#include +#include +#include + +#include +#include + +#include + +#define NFC_BUS G_BUS_TYPE_SYSTEM +#define NFC_SERVICE "org.sailfishos.nfc.daemon" +#define NFC_DAEMON_PATH "/" + +#define RET_OK (0) +#define RET_CMDLINE (1) +#define RET_ERR (2) + +typedef struct app_data { + char** peers; + GMainLoop* loop; + guint sap; + const char* sn; + const char* input_name; + int input_fd; + gboolean reading_file; + gboolean stopped; + GIOChannel* llc_io; + GIOChannel* input_io; + GIOChannel* stdout_io; + guint llc_read_id; + guint local_read_id; + gulong written; +} AppData; + +static +gboolean +nfcdep_signal( + gpointer user_data) +{ + AppData* app = user_data; + + if (!app->stopped) { + GDEBUG("Signal caught, shutting down..."); + g_main_loop_quit(app->loop); + } + return G_SOURCE_CONTINUE; +} + +static +GIOChannel* +nfcdep_channel_new( + int fd) +{ + GIOChannel* io = g_io_channel_unix_new(fd); + + if (io) { + g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); + g_io_channel_set_encoding(io, NULL, NULL); + g_io_channel_set_buffered(io, FALSE); + } + return io; +} + +static +gboolean +nfcdep_write( + AppData* app, + const char* buf, + gsize size, + GIOChannel* out) +{ + gsize written = 0, total = 0; + + while (total < size) { + GError* error = NULL; + GIOStatus status = g_io_channel_write_chars(out, buf + total, + size - total, &written, &error); + + if (status == G_IO_STATUS_AGAIN) { + /* Need to block */ + g_io_channel_set_flags(out, 0, NULL); + status = g_io_channel_write_chars(out, buf + total, + size - total, &written, &error); + g_io_channel_set_flags(out, G_IO_FLAG_NONBLOCK, NULL); + } + + if (status == G_IO_STATUS_NORMAL) { + GVERBOSE("Written %u bytes", (guint) written); + app->written += written; + total += written; + written = 0; + } else { + if (error) { + GDEBUG("Write failed: %s", GERRMSG(error)); + g_error_free(error); + } + return FALSE; + } + } + return TRUE; +} + +static +gboolean +nfcdep_read( + AppData* app, + const char* what, + GIOChannel* in, + GIOChannel* out) +{ + char buf[512]; + gsize bytes_read = 0; + GError* error = NULL; + GIOStatus status = g_io_channel_read_chars(in, buf, sizeof(buf), + &bytes_read, &error); + + if (error) { + GDEBUG("%s read failed: %s", what, GERRMSG(error)); + g_error_free(error); + return FALSE; + } else if (status == G_IO_STATUS_EOF) { + GDEBUG("%s hung up", what); + return FALSE; + } else { + GVERBOSE("%s produced %u bytes", what, (guint)bytes_read); + return nfcdep_write(app, buf, bytes_read, out); + } +} + +static +gboolean +nfcdep_llc_read_cb( + GIOChannel* source, + GIOCondition condition, + gpointer user_data) +{ + AppData* app = user_data; + + if (nfcdep_read(app, "Peer", app->llc_io, app->stdout_io)) { + return G_SOURCE_CONTINUE; + } else { + app->llc_read_id = 0; + g_main_loop_quit(app->loop); + return G_SOURCE_REMOVE; + } +} + +static +gboolean +nfcdep_local_read_cb( + GIOChannel* source, + GIOCondition condition, + gpointer user_data) +{ + AppData* app = user_data; + + if (nfcdep_read(app, app->input_name, app->input_io, app->llc_io)) { + return G_SOURCE_CONTINUE; + } else { + app->local_read_id = 0; + + /* + * If we terminate the loop without waiting for hangup, the + * data buffered by nfcd won't be sent and the connection + * would terminate early because nfcd will most likely drop + * the connection once this process exits (if it was the only + * registered service). + * + * If we are copying standard input, it doesn't matter. + */ + if (!app->reading_file) { + g_main_loop_quit(app->loop); + } + return G_SOURCE_REMOVE; + } +} + +static +int +nfcdep_connected( + AppData* app, + int fd) +{ + app->llc_io = nfcdep_channel_new(fd); + if (app->llc_io) { + app->input_io = nfcdep_channel_new(app->input_fd); + if (app->input_io) { + app->stdout_io = nfcdep_channel_new(STDOUT_FILENO); + if (app->stdout_io) { + guint sigterm = g_unix_signal_add(SIGTERM, nfcdep_signal, app); + guint sigint = g_unix_signal_add(SIGINT, nfcdep_signal, app); + gint64 start_time = app->reading_file ? g_get_real_time() : 0; + + app->llc_read_id = g_io_add_watch(app->llc_io, + G_IO_IN | G_IO_ERR | G_IO_HUP, nfcdep_llc_read_cb, app); + app->local_read_id = g_io_add_watch(app->input_io, + G_IO_IN | G_IO_ERR | G_IO_HUP, nfcdep_local_read_cb, app); + g_main_loop_run(app->loop); + g_source_remove(sigterm); + g_source_remove(sigint); + if (app->llc_read_id) { + g_source_remove(app->llc_read_id); + app->llc_read_id = 0; + } + if (app->local_read_id) { + g_source_remove(app->local_read_id); + app->local_read_id = 0; + } + GDEBUG("%lu bytes written", app->written); + if (start_time) { + gint64 end_time = g_get_real_time(); + + if (end_time > start_time) { + GDEBUG("%ld bytes/sec", (long) (app->written * + G_TIME_SPAN_SECOND / (end_time - start_time))); + } + } + g_io_channel_flush(app->llc_io, NULL); + g_io_channel_unref(app->stdout_io); + app->stdout_io = NULL; + } + g_io_channel_unref(app->input_io); + app->input_io = NULL; + } + g_io_channel_unref(app->llc_io); + app->llc_io = NULL; + } + return RET_OK; +} + +static +int +nfcdep_connect( + AppData* app, + OrgSailfishosNfcPeer* peer) +{ + int ret = RET_ERR; + GError* error = NULL; + GVariant* res = NULL; + GUnixFDList* fdl = NULL; + + if (app->sn ? + org_sailfishos_nfc_peer_call_connect_service_name_sync(peer, + app->sn, NULL, &res, &fdl, NULL, &error) : + org_sailfishos_nfc_peer_call_connect_access_point_sync(peer, + app->sap, NULL, &res, &fdl, NULL, &error)) { + int fd = g_unix_fd_list_peek_fds(fdl, NULL)[0]; + + if (fd >= 0) { + GDEBUG("Connected!"); + ret = nfcdep_connected(app, fd); + shutdown(fd, SHUT_RDWR); + } + g_variant_unref(res); + g_object_unref(fdl); + } else { + GERR("%s: %s", g_dbus_proxy_get_object_path(G_DBUS_PROXY(peer)), + GERRMSG(error)); + g_error_free(error); + } + return ret; +} + +static +gboolean +nfcdep_peers_changed( + OrgSailfishosNfcAdapter* adapter, + const gchar* const* peers, + AppData* app) +{ + if (peers[0]) { + if (!app->peers) { + app->peers = g_strdupv((char**)peers); + } + GDEBUG("Peer detected"); + g_main_loop_quit(app->loop); + } + return TRUE; +} + +static +int +nfcdep_adapter( + AppData* app, + OrgSailfishosNfcAdapter* adapter) +{ + int ret = RET_ERR; + GError* error = NULL; + gulong signal_id = g_signal_connect(adapter, "peers-changed", + G_CALLBACK(nfcdep_peers_changed), app); + + if (org_sailfishos_nfc_adapter_call_get_peers_sync(adapter, &app->peers, + NULL, &error)) { + if (!app->peers || !app->peers[0]) { + guint sigterm = g_unix_signal_add(SIGTERM, nfcdep_signal, app); + guint sigint = g_unix_signal_add(SIGINT, nfcdep_signal, app); + + g_strfreev(app->peers); + app->peers = NULL; + GINFO("Waiting for peer..."); + g_main_loop_run(app->loop); + g_source_remove(sigterm); + g_source_remove(sigint); + } + + /* Don't need the signal anymore */ + g_signal_handler_disconnect(adapter, signal_id); + signal_id = 0; + + if (app->peers && app->peers[0]) { + const char* path = app->peers[0]; + OrgSailfishosNfcPeer* peer = + org_sailfishos_nfc_peer_proxy_new_for_bus_sync(NFC_BUS, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NFC_SERVICE, + path, NULL, &error); + + GDEBUG("Peer %s", path); + if (peer) { + ret = nfcdep_connect(app, peer); + g_object_unref(peer); + } else { + GERR("%s: %s", path, GERRMSG(error)); + g_error_free(error); + } + } else { + GINFO("Giving up..."); + } + } else { + GERR("%s: %s", g_dbus_proxy_get_object_path(G_DBUS_PROXY(adapter)), + GERRMSG(error)); + g_error_free(error); + g_signal_handler_disconnect(adapter, signal_id); + } + return ret; +} + +static +int +nfcdep_adapter_path( + AppData* app, + const char* path) +{ + int ret = RET_ERR; + GError* error = NULL; + OrgSailfishosNfcAdapter* adapter = + org_sailfishos_nfc_adapter_proxy_new_for_bus_sync(NFC_BUS, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NFC_SERVICE, + path, NULL, &error); + + GDEBUG("NFC adapter %s", path); + if (adapter) { + app->loop = g_main_loop_new(NULL, FALSE); + ret = nfcdep_adapter(app, adapter); + g_object_unref(adapter); + g_main_loop_unref(app->loop); + app->loop = NULL; + } else { + GERR("%s: %s", path, GERRMSG(error)); + g_error_free(error); + } + return ret; +} + +static +int +nfcdep_run( + AppData* app) +{ + int ret = RET_ERR; + GError* error = NULL; + OrgSailfishosNfcDaemon* daemon = + org_sailfishos_nfc_daemon_proxy_new_for_bus_sync(NFC_BUS, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NFC_SERVICE, + NFC_DAEMON_PATH, NULL, &error); + + if (daemon) { + guint mode_id = 0; + + /* Enable P2P modes */ + if (org_sailfishos_nfc_daemon_call_request_mode_sync(daemon, + NFC_MODES_P2P, NFC_MODE_READER_WRITER, &mode_id, NULL, &error)) { + char** adapters = NULL; + + if (org_sailfishos_nfc_daemon_call_get_adapters_sync(daemon, + &adapters, NULL, &error)) { + if (adapters && adapters[0]) { + ret = nfcdep_adapter_path(app, adapters[0]); + } else { + GERR("No NFC adapters found."); + } + g_strfreev(adapters); + } + } + g_object_unref(daemon); + } + if (error) { + GERR("%s", GERRMSG(error)); + g_error_free(error); + } + return ret; +} + +int main(int argc, char* argv[]) +{ + int ret = RET_ERR; + gboolean verbose = FALSE; + char* in_file = NULL; + GOptionEntry entries[] = { + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + "Enable verbose output", NULL }, + { "input", 'i', 0, G_OPTION_ARG_FILENAME, &in_file, + "Read input from FILE", "FILE" }, + { NULL } + }; + GOptionContext* opts = g_option_context_new(""); + GError* error = NULL; + + g_option_context_add_main_entries(opts, entries, NULL); + g_option_context_set_summary(opts, "Connects to NFC peer."); + if (g_option_context_parse(opts, &argc, &argv, &error) && argc == 2) { + const char* arg = argv[1]; + AppData app; + int sap; + + gutil_log_timestamp = FALSE; + gutil_log_default.level = verbose ? + GLOG_LEVEL_VERBOSE : + GLOG_LEVEL_INFO; + + memset(&app, 0, sizeof(app)); + if (gutil_parse_int(arg, 0, &sap) && sap > 0) { + app.sap = sap; + } else { + app.sn = arg; + } + if (in_file) { + app.input_name = in_file; + app.input_fd = open(in_file, O_RDONLY); + app.reading_file = TRUE; + } else { + app.input_name = "Standard input"; + app.input_fd = dup(STDIN_FILENO); + } + if (app.input_fd >= 0) { + ret = nfcdep_run(&app); + g_strfreev(app.peers); + close(app.input_fd); + } else { + GERR("Failed to open %s: %s", app.input_name, strerror(errno)); + } + } else { + ret = RET_CMDLINE; + if (error) { + fprintf(stderr, "%s\n", GERRMSG(error)); + g_error_free(error); + } else { + char* help = g_option_context_get_help(opts, TRUE, NULL); + + printf("%s", help); + g_free(help); + } + } + g_option_context_free(opts); + g_free(in_file); + return ret; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/test/nfcdep-service/Makefile b/test/nfcdep-service/Makefile new file mode 100644 index 0000000..33eb950 --- /dev/null +++ b/test/nfcdep-service/Makefile @@ -0,0 +1,165 @@ +# -*- Mode: makefile-gmake -*- + +.PHONY: all debug release clean install + +# +# Required packages +# + +PKGS = gio-unix-2.0 gio-2.0 libglibutil +LIB_PKGS = $(PKGS) + +# +# Default target +# + +all: debug release + +# +# Sources +# + +SRC = nfcdep-service.c + +GEN_SRC = \ + org.sailfishos.nfc.Daemon.c \ + org.sailfishos.nfc.LocalService.c + +ALL_SRC = $(GEN_SRC) $(SRC) + +# +# Directories +# + +SRC_DIR = . +BUILD_DIR = build +SPEC_DIR = ../../plugins/dbus_service +GEN_DIR = $(BUILD_DIR) +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +DEBUG_FLAGS = -g +RELEASE_FLAGS = +DEBUG_DEFS = -DDEBUG +RELEASE_DEFS = +WARNINGS = -Wall -Wstrict-aliasing -Wunused-result +INCLUDES = -I. -I$(GEN_DIR) +FULL_CFLAGS = -fPIC $(CFLAGS) $(DEFINES) $(WARNINGS) $(INCLUDES) \ + -MMD -MP $(shell pkg-config --cflags $(PKGS)) +FULL_LDFLAGS = $(LDFLAGS) + +ifndef KEEP_SYMBOLS +KEEP_SYMBOLS = 0 +endif + +ifneq ($(KEEP_SYMBOLS),0) +RELEASE_FLAGS += -g +SUBMAKE_OPTS += KEEP_SYMBOLS=1 +endif + +DEBUG_CFLAGS = $(DEBUG_FLAGS) -DDEBUG $(FULL_CFLAGS) +RELEASE_CFLAGS = $(RELEASE_FLAGS) -O2 $(FULL_CFLAGS) +DEBUG_LDFLAGS = $(DEBUG_FLAGS) $(FULL_LDFLAGS) +RELEASE_LDFLAGS = $(RELEASE_FLAGS) $(FULL_LDFLAGS) + +LIBS = $(shell pkg-config --libs $(LIB_PKGS)) -ldl +DEBUG_LIBS = $(LIBS) +RELEASE_LIBS = $(LIBS) + +# +# Files +# + +DEBUG_OBJS = $(ALL_SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = $(ALL_SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) + +# +# Dependencies +# + +DEPS = \ + $(DEBUG_OBJS:%.o=%.d) \ + $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +$(GEN_SRC:%=$(GEN_DIR)/%): | $(GEN_DIR) +$(SRC): | $(GEN_SRC:%=$(GEN_DIR)/%) +$(DEBUG_OBJS): | $(DEBUG_BUILD_DIR) +$(RELEASE_OBJS): | $(RELEASE_BUILD_DIR) + +# +# Rules +# + +EXE = nfcdep-service +DEBUG_EXE = $(DEBUG_BUILD_DIR)/$(EXE) +RELEASE_EXE = $(RELEASE_BUILD_DIR)/$(EXE) + +debug: $(DEBUG_EXE) + +release: $(RELEASE_EXE) + +clean: + rm -fr $(BUILD_DIR) $(SRC_DIR)/*~ + +nfc_core_debug_lib: + make -C $(NFC_CORE_DIR) debug + +nfc_core_release_lib: + make -C $(NFC_CORE_DIR) release + +$(GEN_DIR)/%.c: $(SPEC_DIR)/%.xml + gdbus-codegen --generate-c-code $(@:%.c=%) $< + +$(GEN_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR)/%.o: $(GEN_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o: $(GEN_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_EXE): $(DEBUG_OBJS) + $(LD) $(DEBUG_LDFLAGS) $(DEBUG_OBJS) $(DEBUG_LIBS) -o $@ + +$(RELEASE_EXE): $(RELEASE_OBJS) + $(LD) $(RELEASE_LDFLAGS) $(RELEASE_OBJS) $(RELEASE_LIBS) -o $@ +ifeq ($(KEEP_SYMBOLS),0) + strip $@ +endif + +# +# Install +# + +INSTALL = install +INSTALL_BIN_DIR = $(DESTDIR)/usr/bin + +install: $(INSTALL_BIN_DIR) + $(INSTALL) -m 755 $(RELEASE_EXE) $(INSTALL_BIN_DIR) + +$(INSTALL_BIN_DIR): + $(INSTALL) -d $@ diff --git a/test/nfcdep-service/nfcdep-service.c b/test/nfcdep-service/nfcdep-service.c new file mode 100644 index 0000000..c58db40 --- /dev/null +++ b/test/nfcdep-service/nfcdep-service.c @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "org.sailfishos.nfc.Daemon.h" +#include "org.sailfishos.nfc.LocalService.h" + +#include +#include +#include + +#include +#include + +#include + +#define NFC_BUS G_BUS_TYPE_SYSTEM +#define NFC_SERVICE "org.sailfishos.nfc.daemon" +#define NFC_DAEMON_PATH "/" + +#define RET_OK (0) +#define RET_CMDLINE (1) +#define RET_ERR (2) + +typedef struct app_data { + gboolean multiple; + GMainLoop* loop; + const char* path; + const char* sn; + gboolean stopped; + int output_fd; + GIOChannel* llc_io; + GIOChannel* stdin_io; + GIOChannel* output_io; + guint llc_read_id; + guint stdin_read_id; + gint64 start_time; + gint64 bytes_received; +} AppData; + +enum service_calls { + CALL_ACCEPT, + SIGNAL_PEER_ARRIVED, + SIGNAL_PEER_LEFT, + CALL_COUNT +}; + +static +gboolean +nfcdep_signal( + gpointer user_data) +{ + AppData* app = user_data; + + if (!app->stopped) { + GDEBUG("Signal caught, shutting down..."); + g_main_loop_quit(app->loop); + } + return G_SOURCE_CONTINUE; +} + +static +GIOChannel* +nfcdep_channel_new( + int fd) +{ + GIOChannel* io = g_io_channel_unix_new(fd); + + if (io) { + g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); + g_io_channel_set_encoding(io, NULL, NULL); + g_io_channel_set_buffered(io, FALSE); + } + return io; +} + +static +gboolean +nfcdep_read( + AppData* app, + const char* what, + GIOChannel* in, + GIOChannel* out) +{ + char buf[512]; + gsize bytes_read = 0; + GError* error = NULL; + GIOStatus status = g_io_channel_read_chars(in, buf, sizeof(buf), + &bytes_read, &error); + + if (error) { + GDEBUG("%s read failed: %s", what, GERRMSG(error)); + g_error_free(error); + return FALSE; + } else if (status == G_IO_STATUS_EOF) { + GDEBUG("%s hung up", what); + return FALSE; + } else { + GVERBOSE("%s produced %u bytes", what, (guint)bytes_read); + app->bytes_received += bytes_read; + if (bytes_read) { + gsize written = 0, total = 0; + + while (total < bytes_read && g_io_channel_write_chars(out, + buf + total, bytes_read - total, &written, &error) == + G_IO_STATUS_NORMAL) { + GVERBOSE("Written %u bytes", (guint) written); + total += written; + written = 0; + } + if (error) { + GDEBUG("Write failed: %s", GERRMSG(error)); + g_error_free(error); + return FALSE; + } + } + return TRUE; + } +} + +static +void +nfcdep_close_connection( + AppData* app) +{ + if (app->llc_io) { + gint64 end = g_get_real_time(); + + GDEBUG("%ld bytes received", (long) app->bytes_received); + if (end > app->start_time) { + GDEBUG("%ld bytes/sec", (long)(app->bytes_received * + G_TIME_SPAN_SECOND / (end - app->start_time))); + } + if (app->llc_read_id) { + g_source_remove(app->llc_read_id); + app->llc_read_id = 0; + } + g_io_channel_unref(app->llc_io); + app->llc_io = NULL; + } + if (app->stdin_io) { + if (app->stdin_read_id) { + g_source_remove(app->stdin_read_id); + app->stdin_read_id = 0; + } + g_io_channel_unref(app->stdin_io); + app->stdin_io = NULL; + } +} + +static +gboolean +nfcdep_llc_read_cb( + GIOChannel* source, + GIOCondition condition, + gpointer user_data) +{ + AppData* app = user_data; + + if (nfcdep_read(app, "Peer", app->llc_io, app->output_io)) { + return G_SOURCE_CONTINUE; + } else if (app->multiple) { + app->llc_read_id = 0; + nfcdep_close_connection(app); + return G_SOURCE_REMOVE; + } else { + g_main_loop_quit(app->loop); + return G_SOURCE_CONTINUE; + } +} + +static +gboolean +nfcdep_stdin_read_cb( + GIOChannel* source, + GIOCondition condition, + gpointer user_data) +{ + AppData* app = user_data; + + if (nfcdep_read(app, "Standard input", app->stdin_io, app->llc_io)) { + return G_SOURCE_CONTINUE; + } else { + app->stdin_read_id = 0; + g_main_loop_quit(app->loop); + return G_SOURCE_REMOVE; + } +} + +static +gboolean +nfcdep_accept( + AppData* app, + int fd) +{ + app->llc_io = nfcdep_channel_new(fd); + if (app->llc_io) { + app->stdin_io = nfcdep_channel_new(STDIN_FILENO); + if (app->stdin_io) { + app->llc_read_id = g_io_add_watch(app->llc_io, + G_IO_IN | G_IO_ERR | G_IO_HUP, nfcdep_llc_read_cb, app); + app->stdin_read_id = g_io_add_watch(app->stdin_io, + G_IO_IN | G_IO_ERR | G_IO_HUP, nfcdep_stdin_read_cb, app); + g_io_channel_set_close_on_unref(app->llc_io, TRUE); + app->start_time = g_get_real_time(); + app->bytes_received = 0; + return TRUE; + } + g_io_channel_unref(app->llc_io); + app->llc_io = NULL; + } + return FALSE; +} + +static +gboolean +nfcdep_handle_peer_arrived( + OrgSailfishosNfcLocalService* service, + GDBusMethodInvocation* call, + const char* path, + AppData* app) +{ + GDEBUG("Peer %s arrived", path); + org_sailfishos_nfc_local_service_complete_peer_arrived(service, call); + return TRUE; +} + +static +gboolean +nfcdep_handle_peer_left( + OrgSailfishosNfcLocalService* service, + GDBusMethodInvocation* call, + const char* path, + AppData* app) +{ + GDEBUG("Peer %s left", path); + org_sailfishos_nfc_local_service_complete_peer_left(service, call); + return TRUE; +} + +static +gboolean +nfcdep_handle_accept( + OrgSailfishosNfcLocalService* service, + GDBusMethodInvocation* call, + GUnixFDList* fdl, + guint rsap, + GVariant* var, + AppData* app) +{ + gboolean ok = FALSE; + + if (app->llc_io) { + GDEBUG("Refusing connection from %u", rsap); + } else { + int fd = g_unix_fd_list_get(fdl, 0, NULL); + + if (nfcdep_accept(app, fd)) { + GDEBUG("Accepted connection from %u (fd %d)", rsap, fd); + ok = TRUE; + } else { + close(fd); + GERR("Failed to set up connection"); + } + } + org_sailfishos_nfc_local_service_complete_accept(service, call, NULL, ok); + return TRUE; +} + +static +int +nfcdep_run_service( + AppData* app, + OrgSailfishosNfcDaemon* daemon) +{ + guint sap; + GError* error = NULL; + + if (org_sailfishos_nfc_daemon_call_register_local_service_sync(daemon, + app->path, app->sn, &sap, NULL, &error)) { + + GDEBUG("Registered sap %u", sap); + app->output_io = nfcdep_channel_new(app->output_fd); + if (app->output_io) { + guint sigterm = g_unix_signal_add(SIGTERM, nfcdep_signal, app); + guint sigint = g_unix_signal_add(SIGINT, nfcdep_signal, app); + + app->loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(app->loop); + g_source_remove(sigterm); + g_source_remove(sigint); + g_io_channel_unref(app->output_io); + app->output_io = NULL; + } + nfcdep_close_connection(app); + g_main_loop_unref(app->loop); + app->loop = NULL; + return RET_OK; + } else { + GERR("%s", GERRMSG(error)); + g_error_free(error); + return RET_ERR; + } +} + +static +int +nfcdep_run( + AppData* app) +{ + int ret = RET_ERR; + GError* error = NULL; + GDBusConnection* conn = g_bus_get_sync(NFC_BUS, NULL, NULL); + OrgSailfishosNfcDaemon* daemon = + org_sailfishos_nfc_daemon_proxy_new_sync(conn, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NFC_SERVICE, + NFC_DAEMON_PATH, NULL, &error); + + if (daemon) { + OrgSailfishosNfcLocalService* service = + org_sailfishos_nfc_local_service_skeleton_new(); + GDBusInterfaceSkeleton* skel = G_DBUS_INTERFACE_SKELETON(service); + gulong call_ids[CALL_COUNT]; + + call_ids[CALL_ACCEPT] = g_signal_connect(service, + "handle-accept", G_CALLBACK(nfcdep_handle_accept), app); + call_ids[SIGNAL_PEER_ARRIVED] = g_signal_connect(service, + "handle-peer-arrived", G_CALLBACK(nfcdep_handle_peer_arrived), app); + call_ids[SIGNAL_PEER_LEFT] = g_signal_connect(service, + "handle-peer-left", G_CALLBACK(nfcdep_handle_peer_left), app); + + if (g_dbus_interface_skeleton_export(skel, conn, app->path, &error)) { + nfcdep_run_service(app, daemon); + g_dbus_interface_skeleton_unexport(skel); + } else { + GERR("%s", GERRMSG(error)); + g_error_free(error); + } + gutil_disconnect_handlers(service, call_ids, G_N_ELEMENTS(call_ids)); + g_object_unref(service); + g_object_unref(daemon); + } else { + GERR("%s", GERRMSG(error)); + g_error_free(error); + } + g_object_unref(conn); + return ret; +} + +int main(int argc, char* argv[]) +{ + int ret = RET_ERR; + gboolean multiple = FALSE; + gboolean verbose = FALSE; + char* out_file = NULL; + GOptionEntry entries[] = { + { "output", 'o', 0, G_OPTION_ARG_FILENAME, &out_file, + "Write output to FILE", "FILE" }, + { "multiple", 'm', 0, G_OPTION_ARG_NONE, &multiple, + "Multiple connections", NULL }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + "Enable verbose output", NULL }, + { NULL } + }; + GOptionContext* opts = g_option_context_new("SAP"); + GError* error = NULL; + + g_option_context_add_main_entries(opts, entries, NULL); + g_option_context_set_summary(opts, "Waits for NFC peer to connect."); + if (g_option_context_parse(opts, &argc, &argv, &error) && argc == 2) { + AppData app; + + gutil_log_timestamp = FALSE; + gutil_log_default.level = verbose ? + GLOG_LEVEL_VERBOSE : + GLOG_LEVEL_INFO; + + memset(&app, 0, sizeof(app)); + app.sn = argv[1]; + app.path = "/test"; + app.multiple = multiple; + if (out_file) { + app.output_fd = open(out_file, O_RDWR | O_CREAT); + } else { + app.output_fd = dup(STDOUT_FILENO); + } + if (app.output_fd >= 0) { + ret = nfcdep_run(&app); + close(app.output_fd); + } else { + GERR("Failed to open %s: %s", out_file, strerror(errno)); + } + } else { + ret = RET_CMDLINE; + if (error) { + fprintf(stderr, "%s\n", GERRMSG(error)); + g_error_free(error); + } else { + char* help = g_option_context_get_help(opts, TRUE, NULL); + + printf("%s", help); + g_free(help); + } + } + g_option_context_free(opts); + g_free(out_file); + return ret; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/Makefile b/unit/Makefile index bfcc5ed..99aa481 100644 --- a/unit/Makefile +++ b/unit/Makefile @@ -3,14 +3,22 @@ all: %: @$(MAKE) -C core_adapter $* + @$(MAKE) -C core_initiator $* @$(MAKE) -C core_crc $* + @$(MAKE) -C core_llc $* + @$(MAKE) -C core_llc_param $* @$(MAKE) -C core_manager $* @$(MAKE) -C core_ndef_rec $* @$(MAKE) -C core_ndef_rec_sp $* @$(MAKE) -C core_ndef_rec_t $* @$(MAKE) -C core_ndef_rec_u $* + @$(MAKE) -C core_peer $* + @$(MAKE) -C core_peer_service $* + @$(MAKE) -C core_peer_services $* + @$(MAKE) -C core_peer_socket $* @$(MAKE) -C core_plugin $* @$(MAKE) -C core_plugins $* + @$(MAKE) -C core_snep $* @$(MAKE) -C core_tag $* @$(MAKE) -C core_tag_t2 $* @$(MAKE) -C core_tag_t4 $* @@ -25,6 +33,7 @@ all: @$(MAKE) -C plugins_dbus_handlers_type_text $* @$(MAKE) -C plugins_dbus_handlers_type_uri $* @$(MAKE) -C plugins_dbus_service_adapter $* + @$(MAKE) -C plugins_dbus_service_peer $* @$(MAKE) -C plugins_dbus_service_plugin $* @$(MAKE) -C plugins_dbus_service_tag $* @$(MAKE) -C plugins_dbus_service_util $* diff --git a/unit/common/Makefile b/unit/common/Makefile index 5c6f5c1..a576b0b 100644 --- a/unit/common/Makefile +++ b/unit/common/Makefile @@ -37,7 +37,7 @@ COVERAGE_BUILD_DIR = $(BUILD_DIR)/coverage # Required packages # -PKGS += libglibutil glib-2.0 gobject-2.0 +PKGS += libglibutil glib-2.0 gobject-2.0 gio-2.0 gio-unix-2.0 ifneq ($(GEN_SRC),) # Additional requirements for generated stubs @@ -140,11 +140,11 @@ DEBUG_EXE = $(DEBUG_BUILD_DIR)/$(EXE) RELEASE_EXE = $(RELEASE_BUILD_DIR)/$(EXE) COVERAGE_EXE = $(COVERAGE_BUILD_DIR)/$(EXE) -debug: $(DEBUG_EXE) +debug: debug_core_lib $(DEBUG_EXE) -release: $(RELEASE_EXE) +release: release_core_lib $(RELEASE_EXE) -coverage: $(COVERAGE_EXE) +coverage: coverage_core_lib $(COVERAGE_EXE) unitclean: rm -f *~ diff --git a/unit/common/test_common.h b/unit/common/test_common.h index 3a10e77..f3e128a 100644 --- a/unit/common/test_common.h +++ b/unit/common/test_common.h @@ -49,6 +49,11 @@ typedef struct test_opt { int flags; } TestOpt; +typedef struct test_tx { + GUtilData in; + GUtilData out; +} TestTx; + /* Should be invoked after g_test_init */ void test_init( diff --git a/unit/common/test_initiator.c b/unit/common/test_initiator.c new file mode 100644 index 0000000..60a1558 --- /dev/null +++ b/unit/common/test_initiator.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_initiator.h" +#include "nfc_initiator_impl.h" + +#include + +typedef NfcInitiatorClass TestInitiatorClass; +typedef struct test_initiator { + NfcInitiator initiator; + guint transmit_id; + guint response_id; + GSList* list; + gboolean stay_alive; +} TestInitiator; + +#define THIS_TYPE (test_initiator_get_type()) +#define THIS(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, THIS_TYPE, TestInitiator)) +#define PARENT_CLASS test_initiator_parent_class + +G_DEFINE_TYPE(TestInitiator, test_initiator, NFC_TYPE_INITIATOR) + +static +GUtilData* +test_initiator_next_data( + TestInitiator* self) +{ + if (self->list) { + GUtilData* expected = self->list->data; + + self->list = g_slist_delete_link(self->list, self->list); + return expected; + } + return NULL; +} + +static +gboolean +test_initiator_transmit( + gpointer user_data) +{ + TestInitiator* self = THIS(user_data); + NfcInitiator* initiator = &self->initiator; + GUtilData* data = test_initiator_next_data(self); + + g_assert(self->transmit_id); + self->transmit_id = 0; + if (data) { + nfc_initiator_transmit(initiator, data->bytes, data->size); + g_free(data); + } else if (!self->stay_alive) { + nfc_initiator_gone(initiator); + } + return G_SOURCE_REMOVE; +} + +static +gboolean +test_initiator_response_done( + gpointer user_data) +{ + TestInitiator* self = THIS(user_data); + + g_assert(!self->transmit_id); + g_assert(self->response_id); + self->response_id = 0; + self->transmit_id = g_idle_add(test_initiator_transmit, self); + nfc_initiator_response_sent(&self->initiator, NFC_TRANSMIT_STATUS_OK); + return G_SOURCE_REMOVE; +} + +static +gboolean +test_initiator_respond( + NfcInitiator* initiator, + const void* data, + guint len) +{ + TestInitiator* self = THIS(initiator); + GUtilData* expected = test_initiator_next_data(self); + + if (expected) { + g_assert_cmpuint(expected->size, ==, len); + g_assert(!memcmp(data, expected->bytes, len)); + g_free(expected); + self->response_id = g_idle_add(test_initiator_response_done, self); + return TRUE; + } else { + GDEBUG("Simulating response failure"); + return FALSE; + } +} + +static +void +test_initiator_finalize( + GObject* object) +{ + TestInitiator* self = THIS(object); + + if (self->transmit_id) { + g_source_remove(self->transmit_id); + } + if (self->response_id) { + g_source_remove(self->response_id); + } + g_slist_free_full(self->list, g_free); + G_OBJECT_CLASS(PARENT_CLASS)->finalize(object); +} + +static +void +test_initiator_init( + TestInitiator* self) +{ +} + +static +void +test_initiator_class_init( + NfcInitiatorClass* klass) +{ + klass->respond = test_initiator_respond; + klass->deactivate = nfc_initiator_gone; + G_OBJECT_CLASS(klass)->finalize = test_initiator_finalize; +} + +NfcInitiator* +test_initiator_new( + void) +{ + return test_initiator_new_with_tx(NULL, 0); +} + +NfcInitiator* +test_initiator_new_with_tx( + const TestTx* tx_list, + gsize tx_count) +{ + return test_initiator_new_with_tx2(tx_list, tx_count, FALSE); +} + +NfcInitiator* +test_initiator_new_with_tx2( + const TestTx* tx_list, + gsize tx_count, + gboolean stay_alive) +{ + gsize i; + TestInitiator* self = g_object_new(THIS_TYPE, NULL); + + self->stay_alive = stay_alive; + for (i = 0; i < tx_count; i++) { + const TestTx* tx = tx_list + i; + const GUtilData* in = &tx->in; + const GUtilData* out = &tx->out; + + if (in->bytes) { + self->list = g_slist_append(self->list, test_clone_data(in)); + if (out->bytes) { + self->list = g_slist_append(self->list, test_clone_data(out)); + } + } + } + if (tx_count) { + self->transmit_id = g_idle_add(test_initiator_transmit, self); + } + return NFC_INITIATOR(self); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/common/test_initiator.h b/unit/common/test_initiator.h new file mode 100644 index 0000000..4af5525 --- /dev/null +++ b/unit/common/test_initiator.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 Jolla Ltd. + * Copyright (C) 2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_INITIATOR_H +#define TEST_INITIATOR_H + +#include "nfc_types_p.h" +#include "test_common.h" + +NfcInitiator* +test_initiator_new( + void); + +NfcInitiator* +test_initiator_new_with_tx( + const TestTx* tx_list, + gsize tx_count); + +NfcInitiator* +test_initiator_new_with_tx2( + const TestTx* tx_list, + gsize tx_count, + gboolean stay_alive); + +#endif /* TEST_INITIATOR_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/common/test_target.c b/unit/common/test_target.c index 724a609..3e2ed0d 100644 --- a/unit/common/test_target.c +++ b/unit/common/test_target.c @@ -35,6 +35,9 @@ #include +#define THIS_TYPE TEST_TYPE_TARGET +#define THIS(obj) TEST_TARGET(obj) +#define PARENT_CLASS test_target_parent_class G_DEFINE_TYPE(TestTarget, test_target, NFC_TYPE_TARGET) static @@ -124,13 +127,13 @@ test_target_deactivate( { nfc_target_gone(target); } - + static void test_target_init( TestTarget* self) { - self->fail_transmit = -1; /* Always fail everything by default */ + self->fail_transmit = TEST_TARGET_FAIL_ALL; self->cmd_resp = g_ptr_array_new_with_free_func(g_free); } @@ -161,18 +164,24 @@ test_target_class_init( NfcTarget* test_target_new( - void) + int fail) { - return g_object_new(TEST_TYPE_TARGET, NULL); + TestTarget* self = g_object_new(TEST_TYPE_TARGET, NULL); + + self->fail_transmit = fail; + return NFC_TARGET(self); } NfcTarget* test_target_new_tech( - NFC_TECHNOLOGY tech) + NFC_TECHNOLOGY tech, + int fail) { - NfcTarget* target = test_target_new(); + TestTarget* self = g_object_new(TEST_TYPE_TARGET, NULL); + NfcTarget* target = NFC_TARGET(self); target->technology = tech; + self->fail_transmit = fail; return target; } @@ -187,7 +196,7 @@ test_target_new_tech_with_data( TestTarget* self = g_object_new(TEST_TYPE_TARGET, NULL); self->target.technology = tech; - self->fail_transmit = 0; + self->fail_transmit = TEST_TARGET_FAIL_NONE; g_ptr_array_add(self->cmd_resp, test_alloc_data(cmd_bytes, cmd_len)); g_ptr_array_add(self->cmd_resp, test_alloc_data(resp_bytes, resp_len)); return &self->target; @@ -203,11 +212,35 @@ test_target_add_data( { TestTarget* self = TEST_TARGET(target); - self->fail_transmit = 0; + self->fail_transmit = TEST_TARGET_FAIL_NONE; g_ptr_array_add(self->cmd_resp, test_alloc_data(cmd_bytes, cmd_len)); g_ptr_array_add(self->cmd_resp, test_alloc_data(resp_bytes, resp_len)); } +NfcTarget* +test_target_new_with_tx( + const TestTx* tx_list, + gsize tx_count) +{ + gsize i; + TestTarget* self = g_object_new(THIS_TYPE, NULL); + + self->fail_transmit = TEST_TARGET_FAIL_NONE; + for (i = 0; i < tx_count; i++) { + const TestTx* tx = tx_list + i; + const GUtilData* in = &tx->in; + const GUtilData* out = &tx->out; + + if (in->bytes) { + g_ptr_array_add(self->cmd_resp, test_clone_data(in)); + if (out->bytes) { + g_ptr_array_add(self->cmd_resp, test_clone_data(out)); + } + } + } + return NFC_TARGET(self); +} + /* * Local Variables: * mode: C diff --git a/unit/common/test_target.h b/unit/common/test_target.h index 96f5a34..47c3389 100644 --- a/unit/common/test_target.h +++ b/unit/common/test_target.h @@ -48,13 +48,17 @@ GType test_target_get_type(void); #define TEST_TARGET(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ TEST_TYPE_TARGET, TestTarget)) +#define TEST_TARGET_FAIL_ALL (-1) +#define TEST_TARGET_FAIL_NONE (FALSE) + NfcTarget* test_target_new( - void); + int fail); NfcTarget* test_target_new_tech( - NFC_TECHNOLOGY tech); + NFC_TECHNOLOGY tech, + int fail); NfcTarget* test_target_new_tech_with_data( @@ -75,6 +79,14 @@ test_target_add_data( const void* resp_bytes, guint resp_len); +NfcTarget* +test_target_new_with_tx( + const TestTx* tx_list, + gsize tx_count); + +#define test_target_tx_remaining(target) \ + (TEST_TARGET(target)->cmd_resp->len) + #endif /* TEST_TARGET_H */ /* diff --git a/unit/core_adapter/Makefile b/unit/core_adapter/Makefile index 7222270..85f328c 100644 --- a/unit/core_adapter/Makefile +++ b/unit/core_adapter/Makefile @@ -2,6 +2,6 @@ EXE = test_core_adapter -COMMON_SRC = test_main.c test_target.c +COMMON_SRC = test_main.c test_initiator.c test_target.c include ../common/Makefile diff --git a/unit/core_adapter/test_core_adapter.c b/unit/core_adapter/test_core_adapter.c index 4ab255e..2998e6f 100644 --- a/unit/core_adapter/test_core_adapter.c +++ b/unit/core_adapter/test_core_adapter.c @@ -32,16 +32,23 @@ #include "nfc_adapter_p.h" #include "nfc_adapter_impl.h" +#include "nfc_peer_services.h" +#include "nfc_initiator_impl.h" +#include "nfc_initiator_p.h" #include "nfc_target_impl.h" #include "nfc_tag_t2.h" +#include "nfc_peer.h" #include "test_common.h" #include "test_target.h" +#include "test_initiator.h" #include static TestOpt test_opt; +static const guint8 symm_data[] = { 0x00, 0x00 }; + static void test_adapter_inc( @@ -61,6 +68,16 @@ test_adapter_tag_inc( (*(int*)user_data)++; } +static +void +test_adapter_peer_inc( + NfcAdapter* adapter, + NfcPeer* peer, + void* user_data) +{ + (*(int*)user_data)++; +} + /*==========================================================================* * Test adapter *==========================================================================*/ @@ -220,29 +237,38 @@ test_null( { /* Public interfaces are NULL tolerant */ g_assert(!nfc_adapter_ref(NULL)); + g_assert(!nfc_adapter_peers(NULL)); g_assert(!nfc_adapter_request_mode(NULL, 0)); g_assert(!nfc_adapter_add_tag_t2(NULL, NULL, NULL)); g_assert(!nfc_adapter_add_tag_t4a(NULL, NULL, NULL, NULL)); g_assert(!nfc_adapter_add_tag_t4b(NULL, NULL, NULL, NULL)); - G_GNUC_BEGIN_IGNORE_DEPRECATIONS - g_assert(!nfc_adapter_add_other_tag(NULL, NULL)); - G_GNUC_END_IGNORE_DEPRECATIONS + g_assert(!nfc_adapter_add_peer_initiator_a(NULL, NULL, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_initiator_f(NULL, NULL, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_target_a(NULL, NULL, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_target_f(NULL, NULL, NULL, NULL)); g_assert(!nfc_adapter_add_target_presence_handler(NULL, NULL, NULL)); g_assert(!nfc_adapter_add_tag_added_handler(NULL, NULL, NULL)); g_assert(!nfc_adapter_add_tag_removed_handler(NULL, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_added_handler(NULL, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_removed_handler(NULL, NULL, NULL)); g_assert(!nfc_adapter_add_powered_changed_handler(NULL, NULL, NULL)); g_assert(!nfc_adapter_add_power_requested_handler(NULL, NULL, NULL)); g_assert(!nfc_adapter_add_mode_changed_handler(NULL, NULL, NULL)); g_assert(!nfc_adapter_add_mode_requested_handler(NULL, NULL, NULL)); g_assert(!nfc_adapter_add_enabled_changed_handler(NULL, NULL, NULL)); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + g_assert(!nfc_adapter_add_other_tag(NULL, NULL)); + G_GNUC_END_IGNORE_DEPRECATIONS nfc_adapter_set_name(NULL, NULL); + nfc_adapter_set_services(NULL, NULL); nfc_adapter_mode_notify(NULL, 0, FALSE); nfc_adapter_target_notify(NULL, FALSE); nfc_adapter_power_notify(NULL, FALSE, FALSE); nfc_adapter_set_enabled(NULL, TRUE); nfc_adapter_request_power(NULL, TRUE); nfc_adapter_remove_tag(NULL, NULL); + nfc_adapter_remove_peer(NULL, NULL); nfc_adapter_remove_handler(NULL, 0); nfc_adapter_remove_handlers(NULL, NULL, 0); nfc_adapter_unref(NULL); @@ -259,6 +285,7 @@ test_basic( { TestAdapter* test = test_adapter_new(); NfcAdapter* adapter = &test->adapter; + NfcPeerServices* services = nfc_peer_services_new(); const char* name = "test"; g_assert(!nfc_adapter_add_target_presence_handler(adapter, NULL, NULL)); @@ -269,21 +296,29 @@ test_basic( g_assert(!nfc_adapter_add_mode_changed_handler(adapter, NULL, NULL)); g_assert(!nfc_adapter_add_mode_requested_handler(adapter, NULL, NULL)); g_assert(!nfc_adapter_add_enabled_changed_handler(adapter, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_added_handler(adapter, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_removed_handler(adapter, NULL, NULL)); g_assert(!nfc_adapter_add_tag_t2(adapter, NULL, NULL)); g_assert(!nfc_adapter_add_tag_t4a(adapter, NULL, NULL, NULL)); g_assert(!nfc_adapter_add_tag_t4b(adapter, NULL, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_initiator_a(adapter, NULL, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_initiator_f(adapter, NULL, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_target_a(adapter, NULL, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_target_f(adapter, NULL, NULL, NULL)); + g_assert(!nfc_adapter_add_other_tag2(adapter, NULL, NULL)); G_GNUC_BEGIN_IGNORE_DEPRECATIONS g_assert(!nfc_adapter_add_other_tag(adapter, NULL)); G_GNUC_END_IGNORE_DEPRECATIONS - g_assert(!nfc_adapter_add_other_tag2(adapter, NULL, NULL)); nfc_adapter_remove_handler(adapter, 0); nfc_adapter_set_name(adapter, name); + nfc_adapter_set_services(adapter, services); g_assert(!g_strcmp0(adapter->name, name)); g_assert(nfc_adapter_ref(adapter) == adapter); nfc_adapter_unref(adapter); nfc_adapter_unref(adapter); + nfc_peer_services_unref(services); } /*==========================================================================* @@ -581,8 +616,8 @@ test_tags( void) { TestAdapter* test = test_adapter_new(); - NfcTarget* target0 = test_target_new_tech(NFC_TECHNOLOGY_A); - NfcTarget* target1 = test_target_new(); + NfcTarget* target0 = test_target_new_tech(NFC_TECHNOLOGY_A, FALSE); + NfcTarget* target1 = test_target_new(FALSE); NfcAdapter* adapter = &test->adapter; NfcTag* tag0; NfcTag* tag1; @@ -608,12 +643,17 @@ test_tags( /* Test "presence_changed" signal */ nfc_adapter_target_notify(adapter, TRUE); - nfc_adapter_target_notify(adapter, TRUE); - g_assert(adapter->target_present); - g_assert(presence_changed_count == 1); + g_assert(!adapter->target_present); + g_assert_cmpint(presence_changed_count, == ,0); memset(&poll, 0, sizeof(poll)); tag0 = nfc_adapter_add_tag_t2(adapter, target0, &poll.a); + g_assert(adapter->target_present); + g_assert_cmpint(presence_changed_count, == ,1); + + nfc_adapter_target_notify(adapter, TRUE); /* Has no effect */ + g_assert_cmpint(presence_changed_count, == ,1); + tag1 = nfc_adapter_add_other_tag2(adapter, target1, NULL); g_assert(tag0); g_assert(tag1); @@ -626,15 +666,15 @@ test_tags( nfc_adapter_target_notify(adapter, FALSE); g_assert(nfc_adapter_request_mode(adapter, NFC_MODE_NONE)); g_assert(adapter->target_present); - g_assert(presence_changed_count == 1); + g_assert_cmpint(presence_changed_count, == ,1); /* Remove the tags */ nfc_target_gone(target0); nfc_adapter_remove_tag(adapter, tag1->name); g_assert(!adapter->target_present); - g_assert(presence_changed_count == 2); - g_assert(tag_added == 2); - g_assert(tag_removed == 2); + g_assert_cmpint(presence_changed_count, == ,2); + g_assert_cmpint(tag_added, == ,2); + g_assert_cmpint(tag_removed, == ,2); /* These have no effect */ nfc_adapter_remove_tag(adapter, NULL); @@ -650,6 +690,164 @@ test_tags( nfc_target_unref(target1); } +/*==========================================================================* + * peer + *==========================================================================*/ + +static +void +test_peer( + void) +{ + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + } + }; + static const guint8 general_bytes [] = { + 0x46, 0x66, 0x6d, 0x01, 0x01, 0x11, 0x02, 0x02, + 0x07, 0xff, 0x03, 0x02, 0x00, 0x13, 0x04, 0x01, + 0xff + }; + static const NfcParamNfcDepInitiator initiator_param = { + { TEST_ARRAY_AND_SIZE(general_bytes) } + }; + + TestAdapter* test = test_adapter_new(); + NfcTarget* target0 = test_target_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + NfcTarget* target1 = test_target_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + NfcAdapter* adapter = &test->adapter; + NfcPeer* peer0; + NfcPeer* peer1; + int peer_added = 0, peer_removed = 0, presence_changed_count = 0; + gulong id[3]; + + /* Set up the adapter */ + nfc_adapter_set_name(adapter, "test"); + adapter->supported_modes = NFC_MODE_P2P_TARGET; + nfc_adapter_power_notify(adapter, TRUE, FALSE); + nfc_adapter_mode_notify(adapter, NFC_MODE_P2P_TARGET, FALSE); + g_assert(!adapter->target_present); + + id[0] = nfc_adapter_add_peer_added_handler(adapter, + test_adapter_peer_inc, &peer_added); + id[1] = nfc_adapter_add_peer_removed_handler(adapter, + test_adapter_peer_inc, &peer_removed); + id[2] = nfc_adapter_add_target_presence_handler(adapter, + test_adapter_inc, &presence_changed_count); + g_assert(id[0]); + g_assert(id[1]); + g_assert(id[2]); + + /* Two peers are unlikely in real life but API allows it */ + peer0 = nfc_adapter_add_peer_initiator_a(adapter, target0, NULL, + &initiator_param); + g_assert(peer0); + g_assert(adapter->target_present); + g_assert_cmpint(presence_changed_count, == ,1); + g_assert_cmpint(peer_added, == ,1); + g_assert_cmpint(peer_removed, == ,0); + + peer1 = nfc_adapter_add_peer_initiator_a(adapter, target1, NULL, + &initiator_param); + g_assert(peer1); + g_assert(adapter->target_present); + g_assert_cmpint(presence_changed_count, == ,1); + g_assert_cmpint(peer_added, == ,2); + g_assert_cmpint(peer_removed, == ,0); + + /* These two have no effect */ + nfc_adapter_remove_peer(adapter, NULL); + nfc_adapter_remove_peer(adapter, ""); + g_assert(adapter->target_present); + g_assert_cmpint(presence_changed_count, == ,1); + g_assert_cmpint(peer_added, == ,2); + g_assert_cmpint(peer_removed, == ,0); + + /* This one does */ + nfc_peer_ref(peer0); + nfc_adapter_remove_peer(adapter, peer0->name); + g_assert(adapter->target_present); /* One is still present */ + g_assert_cmpint(presence_changed_count, == ,1); + g_assert_cmpint(peer_added, == ,2); + g_assert_cmpint(peer_removed, == ,1); + nfc_peer_unref(peer0); + + /* The second one goes away by itself */ + nfc_target_gone(target1); + g_assert(!adapter->target_present); /* Both are gone now */ + g_assert_cmpint(presence_changed_count, == ,2); + g_assert_cmpint(peer_added, == ,2); + g_assert_cmpint(peer_removed, == ,2); + + /* Fail to add a non-present peer */ + g_assert(!nfc_adapter_add_peer_initiator_a(adapter, target1, NULL, + &initiator_param)); + + nfc_adapter_remove_all_handlers(adapter, id); + nfc_adapter_unref(adapter); + nfc_target_unref(target0); + nfc_target_unref(target1); +} + +/*==========================================================================* + * no_peer + *==========================================================================*/ + +static +void +test_no_peer( + void) +{ + TestAdapter* test = test_adapter_new(); + NfcTarget* target = test_target_new(FALSE); + NfcInitiator* initiator = test_initiator_new(); + NfcAdapter* adapter = &test->adapter; + int peer_added = 0, peer_removed = 0, presence_changed_count = 0; + gulong id[3]; + + /* Set up the adapter */ + nfc_adapter_set_name(adapter, "test"); + adapter->supported_modes = NFC_MODE_P2P_TARGET; + nfc_adapter_power_notify(adapter, TRUE, FALSE); + nfc_adapter_mode_notify(adapter, NFC_MODE_P2P_TARGET, FALSE); + g_assert(!adapter->target_present); + + id[0] = nfc_adapter_add_peer_added_handler(adapter, + test_adapter_peer_inc, &peer_added); + id[1] = nfc_adapter_add_peer_removed_handler(adapter, + test_adapter_peer_inc, &peer_removed); + id[2] = nfc_adapter_add_target_presence_handler(adapter, + test_adapter_inc, &presence_changed_count); + g_assert(id[0]); + g_assert(id[1]); + g_assert(id[2]); + + /* Try to add a peer (and fail) */ + g_assert(!nfc_adapter_add_peer_initiator_a(adapter, target, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_initiator_f(adapter, target, NULL, NULL)); + g_assert(!adapter->target_present); + g_assert_cmpint(presence_changed_count, == ,0); + g_assert_cmpint(peer_added, == ,0); + g_assert_cmpint(peer_removed, == ,0); + + g_assert(!nfc_adapter_add_peer_target_a(adapter, initiator, NULL, NULL)); + g_assert(!nfc_adapter_add_peer_target_f(adapter, initiator, NULL, NULL)); + g_assert(!adapter->target_present); + g_assert_cmpint(presence_changed_count, == ,0); + g_assert_cmpint(peer_added, == ,0); + g_assert_cmpint(peer_removed, == ,0); + + nfc_adapter_remove_all_handlers(adapter, id); + nfc_adapter_unref(adapter); + g_object_unref(initiator); + nfc_target_unref(target); +} + /*==========================================================================* * Common *==========================================================================*/ @@ -668,6 +866,8 @@ int main(int argc, char* argv[]) g_test_add_func(TEST_("power"), test_power); g_test_add_func(TEST_("mode"), test_mode); g_test_add_func(TEST_("tags"), test_tags); + g_test_add_func(TEST_("peer"), test_peer); + g_test_add_func(TEST_("no_peer"), test_no_peer); test_init(&test_opt, argc, argv); return g_test_run(); } diff --git a/unit/core_initiator/Makefile b/unit/core_initiator/Makefile new file mode 100644 index 0000000..a579d1c --- /dev/null +++ b/unit/core_initiator/Makefile @@ -0,0 +1,5 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_core_initiator + +include ../common/Makefile diff --git a/unit/core_initiator/test_core_initiator.c b/unit/core_initiator/test_core_initiator.c new file mode 100644 index 0000000..228c39c --- /dev/null +++ b/unit/core_initiator/test_core_initiator.c @@ -0,0 +1,690 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_common.h" + +#include "nfc_initiator_p.h" +#include "nfc_initiator_impl.h" + +#include + +static TestOpt test_opt; + +static const GUtilData test_in = { (const void*)"in", 2 }; +static const GUtilData test_out = { (const void*)"out", 3 }; + +static +void +test_initiator_inc( + NfcInitiator* initiator, + void* user_data) +{ + (*(int*)user_data)++; +} + +/*==========================================================================* + * Test initiator + *==========================================================================*/ + +typedef NfcInitiatorClass TestInitiator1Class; +typedef struct test_initiator1 { + NfcInitiator initiator; + GPtrArray* resp; + guint flags; + +#define TEST_INITIATOR_FAIL_RESPONSE (0x01) +#define TEST_INITIATOR_DONT_COMPLETE (0x02) + +} TestInitiator1; + +#define TEST_TYPE_INITIATOR1 (test_initiator1_get_type()) +#define TEST_INITIATOR1(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + TEST_TYPE_INITIATOR1, TestInitiator1)) +#define PARENT_CLASS test_initiator1_parent_class +G_DEFINE_TYPE(TestInitiator1, test_initiator1, NFC_TYPE_INITIATOR) + +static +gboolean +test_initiator1_respond( + NfcInitiator* initiator, + const void* data, + guint len) +{ + TestInitiator1* self = TEST_INITIATOR1(initiator); + + g_ptr_array_add(self->resp, test_alloc_data(data, len)); + if (self->flags & TEST_INITIATOR_FAIL_RESPONSE) { + /* Default callback return FALSE */ + return NFC_INITIATOR_CLASS(test_initiator1_parent_class)->respond + (initiator, data, len); + } else if (self->flags & TEST_INITIATOR_DONT_COMPLETE) { + GDEBUG("Queueing response"); + return TRUE; + } else { + nfc_initiator_response_sent(initiator, NFC_TRANSMIT_STATUS_OK); + return TRUE; + } +} + +static +void +test_initiator1_deactivate( + NfcInitiator* initiator) +{ + /* Base class does nothing */ + g_assert(initiator->present); + NFC_INITIATOR_CLASS(test_initiator1_parent_class)->deactivate(initiator); + g_assert(initiator->present); + nfc_initiator_gone(initiator); + g_assert(!initiator->present); +} + +static +void +test_initiator1_finalize( + GObject* object) +{ + TestInitiator1* self = TEST_INITIATOR1(object); + + g_ptr_array_free(self->resp, TRUE); + G_OBJECT_CLASS(test_initiator1_parent_class)->finalize(object); +} + +static +void +test_initiator1_init( + TestInitiator1* self) +{ + self->resp = g_ptr_array_new_with_free_func(g_free); +} + +static +void +test_initiator1_class_init( + TestInitiator1Class* klass) +{ + klass->respond = test_initiator1_respond; + klass->deactivate = test_initiator1_deactivate; + G_OBJECT_CLASS(klass)->finalize = test_initiator1_finalize; +} + +static +NfcInitiator* +test_initiator1_new( + guint flags) +{ + TestInitiator1* self = g_object_new(TEST_TYPE_INITIATOR1, NULL); + + self->flags = flags; + return NFC_INITIATOR(self); +} + +/*==========================================================================* + * null + *==========================================================================*/ + +static +void +test_null( + void) +{ + NfcInitiator* init = test_initiator1_new(0); + + /* Public interfaces are NULL tolerant */ + g_assert(!nfc_initiator_ref(NULL)); + g_assert(!nfc_initiator_add_transmission_handler(NULL, NULL, NULL)); + g_assert(!nfc_initiator_add_transmission_handler(init, NULL, NULL)); + g_assert(!nfc_initiator_add_gone_handler(init, NULL, NULL)); + g_assert(!nfc_initiator_add_gone_handler(NULL, NULL, NULL)); + nfc_initiator_deactivate(NULL); + nfc_initiator_remove_handler(NULL, 0); + nfc_initiator_remove_handler(init, 0); + nfc_initiator_remove_handlers(NULL, NULL, 0); + nfc_initiator_remove_handlers(init, NULL, 0); + nfc_initiator_transmit(NULL, NULL, 0); + nfc_initiator_response_sent(NULL, NFC_TRANSMIT_STATUS_ERROR); + nfc_initiator_gone(NULL); + nfc_initiator_unref(NULL); + + g_assert(!nfc_transmission_respond(NULL, NULL, 0, NULL, NULL)); + g_assert(!nfc_transmission_ref(NULL)); + nfc_transmission_unref(NULL); + + nfc_initiator_unref(init); +} + +/*==========================================================================* + * basic + *==========================================================================*/ + +static +void +test_basic_transmission_ok( + NfcTransmission* t, + gboolean ok, + void* user_data) +{ + g_assert(ok); + (*(int*)user_data)++; +} + +static +gboolean +test_basic_transmission_handler( + NfcInitiator* init, + NfcTransmission* t, + const GUtilData* data, + void* user_data) +{ + NfcTransmission** out = user_data; + + g_assert(!*out); + g_assert(data); + g_assert_cmpuint(data->size, == ,test_in.size); + g_assert(!memcmp(data->bytes, test_in.bytes, data->size)); + *out = nfc_transmission_ref(t); + return TRUE; +} + +static +void +test_basic( + void) +{ + NfcInitiator* init = test_initiator1_new(0); + NfcTransmission* trans = NULL; + int gone = 0, done = 0; + gulong id[2]; + + g_assert(nfc_initiator_ref(init) == init); + nfc_initiator_unref(init); + + id[0] = nfc_initiator_add_gone_handler(init, test_initiator_inc, &gone); + id[1] = nfc_initiator_add_transmission_handler(init, + test_basic_transmission_handler, &trans); + + g_assert(id[0]); + g_assert(id[1]); + + /* Simulate transmission */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert_cmpint(gone, == ,0); + g_assert(trans); + g_assert(nfc_transmission_respond(trans, test_out.bytes, test_out.size, + test_basic_transmission_ok, &done)); + nfc_transmission_unref(trans); + g_assert_cmpint(gone, == ,0); + g_assert_cmpint(done, == ,1); + + /* This call is wrong but it's ignored */ + nfc_initiator_response_sent(init, NFC_TRANSMIT_STATUS_OK); + + /* Simulate deactivation (second time has no effect) */ + nfc_initiator_deactivate(init); + g_assert_cmpint(gone, == ,1); + nfc_initiator_deactivate(init); + g_assert_cmpint(gone, == ,1); + + /* This one does nothing too since the thing is already gone */ + nfc_initiator_gone(init); + g_assert_cmpint(gone, == ,1); + + nfc_initiator_remove_all_handlers(init, id); + nfc_initiator_unref(init); +} + +/*==========================================================================* + * no_response + *==========================================================================*/ + +static +void +test_no_response( + void) +{ + NfcInitiator* init = test_initiator1_new(0); + int gone = 0; + gulong id = nfc_initiator_add_gone_handler(init, + test_initiator_inc, &gone); + + g_assert(id); + + /* Simulate transmission (no handler => deactivation) */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert(!init->present); + g_assert_cmpint(gone, == ,1); + + /* But the signal is issued only once */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert(!init->present); + g_assert_cmpint(gone, == ,1); + + nfc_initiator_remove_handler(init, id); + nfc_initiator_unref(init); +} + +/*==========================================================================* + * drop_transmission + *==========================================================================*/ + +static +void +test_drop_transmission( + void) +{ + NfcInitiator* init = test_initiator1_new(0); + NfcTransmission* trans = NULL; + int gone = 0; + gulong id[2]; + + id[0]= nfc_initiator_add_gone_handler(init, + test_initiator_inc, &gone); + /* NOTE: reusing test_basic_transmission_handler */ + id[1] = nfc_initiator_add_transmission_handler(init, + test_basic_transmission_handler, &trans); + g_assert(id[0]); + g_assert(id[1]); + + /* Simulate transmission */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert(trans); + g_assert(init->present); + g_assert_cmpint(gone, == ,0); + + /* Drop the transmission without responding */ + nfc_transmission_unref(trans); + + /* That's supposed to deactivate RF interface */ + g_assert(!init->present); + g_assert_cmpint(gone, == ,1); + + nfc_initiator_remove_all_handlers(init, id); + nfc_initiator_unref(init); +} + +/*==========================================================================* + * drop_transmission2 + *==========================================================================*/ + +static +void +test_drop_transmission2( + void) +{ + NfcInitiator* init = test_initiator1_new(TEST_INITIATOR_DONT_COMPLETE); + NfcTransmission* trans = NULL; + int gone = 0, done = 0; + gulong id[2]; + + /* NOTE: reusing test_basic_transmission_handler */ + id[0] = nfc_initiator_add_gone_handler(init, test_initiator_inc, &gone); + id[1] = nfc_initiator_add_transmission_handler(init, + test_basic_transmission_handler, &trans); + + g_assert(id[0]); + g_assert(id[1]); + + /* Simulate transmission */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert_cmpint(gone, == ,0); + g_assert(trans); + + /* NOTE: reusing test_basic_transmission_ok */ + g_assert(nfc_transmission_respond(trans, test_out.bytes, test_out.size, + test_basic_transmission_ok, &done)); + g_assert_cmpint(gone, == ,0); + g_assert_cmpint(done, == ,0); + + /* Second transmission is queued */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert_cmpint(gone, == ,0); + + /* Complete the first one and ignore the second (deactivating the link) */ + nfc_initiator_remove_handlers(init, id + 1, 1); + nfc_initiator_response_sent(init, NFC_TRANSMIT_STATUS_OK); + g_assert_cmpint(done, == ,1); + g_assert_cmpint(gone, == ,1); + g_assert(!init->present); + + nfc_transmission_unref(trans); + nfc_initiator_remove_all_handlers(init, id); + nfc_initiator_unref(init); +} + +/*==========================================================================* + * stray_transmission + *==========================================================================*/ + +static +void +test_stray_transmission( + void) +{ + NfcInitiator* init = test_initiator1_new(0); + NfcTransmission* trans = NULL; + int gone = 0; + gulong id[2]; + + id[0]= nfc_initiator_add_gone_handler(init, + test_initiator_inc, &gone); + /* NOTE: reusing test_basic_transmission_handler */ + id[1] = nfc_initiator_add_transmission_handler(init, + test_basic_transmission_handler, &trans); + g_assert(id[0]); + g_assert(id[1]); + + /* Legitimate transmission */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert(trans); + g_assert(init->present); + g_assert_cmpint(gone, == ,0); + + /* Unexpected transmission (before the first one is replied to) */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + + /* That deactivates RF interface */ + g_assert(!init->present); + g_assert_cmpint(gone, == ,1); + + nfc_transmission_unref(trans); + nfc_initiator_remove_all_handlers(init, id); + nfc_initiator_unref(init); +} + +/*==========================================================================* + * stray_transmission2 + *==========================================================================*/ + +static +void +test_stray_transmission2( + void) +{ + NfcInitiator* init = test_initiator1_new(TEST_INITIATOR_DONT_COMPLETE); + NfcTransmission* trans = NULL; + int gone = 0, done = 0; + gulong id[2]; + + id[0]= nfc_initiator_add_gone_handler(init, + test_initiator_inc, &gone); + /* NOTE: reusing test_basic_transmission_handler */ + id[1] = nfc_initiator_add_transmission_handler(init, + test_basic_transmission_handler, &trans); + g_assert(id[0]); + g_assert(id[1]); + + /* Legitimate transmission */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert(init->present); + g_assert_cmpint(gone, == ,0); + g_assert(trans); + + /* Respond to it (but don't complete it yet) */ + g_assert(nfc_transmission_respond(trans, test_out.bytes, test_out.size, + test_basic_transmission_ok, &done)); + g_assert_cmpint(gone, == ,0); + g_assert_cmpint(done, == ,0); + + /* Next transmission (still legitimate) */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert(init->present); + g_assert_cmpint(gone, == ,0); + + /* But this is too much (RF interface gets deactivated) */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert(!init->present); + g_assert_cmpint(gone, == ,1); + g_assert_cmpint(done, == ,0); + + nfc_transmission_unref(trans); + nfc_initiator_remove_all_handlers(init, id); + nfc_initiator_unref(init); +} + +/*==========================================================================* + * queued_transmission + *==========================================================================*/ + +static +void +test_queued_transmission( + void) +{ + NfcInitiator* init = test_initiator1_new(TEST_INITIATOR_DONT_COMPLETE); + NfcTransmission* trans = NULL; + NfcTransmission* trans1 = NULL; + int gone = 0, done = 0; + gulong id[2]; + + /* NOTE: reusing test_basic_transmission_handler */ + id[0] = nfc_initiator_add_gone_handler(init, test_initiator_inc, &gone); + id[1] = nfc_initiator_add_transmission_handler(init, + test_basic_transmission_handler, &trans); + + g_assert(id[0]); + g_assert(id[1]); + + /* Simulate transmission */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert_cmpint(gone, == ,0); + g_assert(trans); + trans1 = trans; + trans = NULL; + + /* NOTE: reusing test_basic_transmission_ok */ + g_assert(nfc_transmission_respond(trans1, test_out.bytes, test_out.size, + test_basic_transmission_ok, &done)); + g_assert_cmpint(gone, == ,0); + g_assert_cmpint(done, == ,0); + + /* Second transmission is queued */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert_cmpint(gone, == ,0); + g_assert(!trans); + + /* Complete the first one and receive the second */ + nfc_initiator_response_sent(init, NFC_TRANSMIT_STATUS_OK); + g_assert_cmpint(done, == ,1); + g_assert(trans); + + /* Dropping the current (second) transmission deactivate RF interface */ + nfc_transmission_unref(trans); + g_assert_cmpint(gone, == ,1); + g_assert_cmpint(done, == ,1); + + nfc_transmission_unref(trans1); + nfc_initiator_remove_all_handlers(init, id); + nfc_initiator_unref(init); +} + +/*==========================================================================* + * fail_respond + *==========================================================================*/ + +static +void +test_fail_respond( + void) +{ + NfcInitiator* init = test_initiator1_new(TEST_INITIATOR_FAIL_RESPONSE); + NfcTransmission* trans = NULL; + int gone = 0, done = 0; + gulong id[2]; + + /* NOTE: reusing test_basic_transmission_handler */ + id[0] = nfc_initiator_add_gone_handler(init, test_initiator_inc, &gone); + id[1] = nfc_initiator_add_transmission_handler(init, + test_basic_transmission_handler, &trans); + + g_assert(id[0]); + g_assert(id[1]); + + /* Simulate transmission */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert_cmpint(gone, == ,0); + g_assert(trans); + /* NOTE: reusing test_basic_transmission_ok */ + g_assert(!nfc_transmission_respond(trans, test_out.bytes, test_out.size, + test_basic_transmission_ok, &done)); + g_assert_cmpint(gone, == ,0); + g_assert_cmpint(done, == ,0); + + /* Second response fails too, albeit in a different way */ + g_assert(!nfc_transmission_respond(trans, test_out.bytes, test_out.size, + test_basic_transmission_ok, &done)); + nfc_transmission_unref(trans); + g_assert_cmpint(gone, == ,0); + g_assert_cmpint(done, == ,0); + + nfc_initiator_remove_all_handlers(init, id); + nfc_initiator_unref(init); +} + +/*==========================================================================* + * queue_response + *==========================================================================*/ + +static +void +test_queue_response( + void) +{ + NfcInitiator* init = test_initiator1_new(TEST_INITIATOR_DONT_COMPLETE); + NfcTransmission* trans = NULL; + NfcTransmission* trans1 = NULL; + int gone = 0, done = 0; + gulong id[2]; + + /* NOTE: reusing test_basic_transmission_handler */ + id[0] = nfc_initiator_add_gone_handler(init, test_initiator_inc, &gone); + id[1] = nfc_initiator_add_transmission_handler(init, + test_basic_transmission_handler, &trans); + + g_assert(id[0]); + g_assert(id[1]); + + /* Simulate transmission */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert_cmpint(gone, == ,0); + g_assert(trans); + trans1 = trans; + trans = NULL; + + /* NOTE: reusing test_basic_transmission_ok */ + g_assert(nfc_transmission_respond(trans1, test_out.bytes, test_out.size, + test_basic_transmission_ok, &done)); + g_assert_cmpint(gone, == ,0); + g_assert_cmpint(done, == ,0); + + /* Second transmission is queued */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert_cmpint(gone, == ,0); + g_assert(!trans); + + /* Dropping the first transmission doesnt't deactivate RF interface */ + nfc_transmission_unref(trans1); + g_assert_cmpint(gone, == ,0); + g_assert_cmpint(done, == ,0); + + nfc_initiator_remove_all_handlers(init, id); + nfc_initiator_unref(init); +} + +/*==========================================================================* + * early_destroy + *==========================================================================*/ + +static +void +test_early_destroy( + void) +{ + NfcInitiator* init = test_initiator1_new(0); + NfcTransmission* trans = NULL; + int done = 0; + gulong id; + + /* NOTE: reusing test_basic_transmission_handler */ + id = nfc_initiator_add_transmission_handler(init, + test_basic_transmission_handler, &trans); + g_assert(id); + + /* Simulate transmission */ + nfc_initiator_transmit(init, test_in.bytes, test_in.size); + g_assert(trans); + + /* Unref the initiator before responding */ + nfc_initiator_remove_handler(init, id); + nfc_initiator_unref(init); + + /* Obviously, respond must fail now */ + /* NOTE: reusing test_basic_transmission_handler */ + g_assert(!nfc_transmission_respond(trans, test_out.bytes, test_out.size, + test_basic_transmission_ok, &done)); + g_assert_cmpint(done, == ,0); + nfc_transmission_unref(trans); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +#define TEST_(name) "/core/initiator/" name + +int main(int argc, char* argv[]) +{ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + g_type_init(); + G_GNUC_END_IGNORE_DEPRECATIONS; + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("basic"), test_basic); + g_test_add_func(TEST_("no_response"), test_no_response); + g_test_add_func(TEST_("drop_transmission"), test_drop_transmission); + g_test_add_func(TEST_("drop_transmission2"), test_drop_transmission2); + g_test_add_func(TEST_("stray_transmission"), test_stray_transmission); + g_test_add_func(TEST_("stray_transmission2"), test_stray_transmission2); + g_test_add_func(TEST_("queued_transmission"), test_queued_transmission); + g_test_add_func(TEST_("fail_respond"), test_fail_respond); + g_test_add_func(TEST_("queue_response"), test_queue_response); + g_test_add_func(TEST_("early_destroy"), test_early_destroy); + test_init(&test_opt, argc, argv); + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/core_llc/Makefile b/unit/core_llc/Makefile new file mode 100644 index 0000000..53b2905 --- /dev/null +++ b/unit/core_llc/Makefile @@ -0,0 +1,7 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_core_llc + +COMMON_SRC = test_main.c test_target.c test_initiator.c + +include ../common/Makefile diff --git a/unit/core_llc/test_core_llc.c b/unit/core_llc/test_core_llc.c new file mode 100644 index 0000000..6afb52d --- /dev/null +++ b/unit/core_llc/test_core_llc.c @@ -0,0 +1,2252 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_llc.h" +#include "nfc_llc_io.h" +#include "nfc_llc_param.h" +#include "nfc_peer_services.h" +#include "nfc_peer_service_p.h" +#include "nfc_peer_service_impl.h" +#include "nfc_peer_connection_p.h" +#include "nfc_peer_connection_impl.h" +#include "nfc_initiator.h" +#include "nfc_target_impl.h" + +#include "test_common.h" +#include "test_target.h" +#include "test_initiator.h" + +#include + +static TestOpt test_opt; + +#define TEST_PREFIX "/core/llc/" +#define TEST_(name) TEST_PREFIX name + +static const guint8 symm_pdu_data[] = { 0x00, 0x00 }; +static const guint8 connect_urn_nfc_sn_handover_data[] = { + 0x05, 0x21, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f, 0x06, 0x13, 0x75, 0x72, 0x6e, 0x3a, 0x6e, + 0x66, 0x63, 0x3a, 0x73, 0x6e, 0x3a, 0x68, 0x61, + 0x6e, 0x64, 0x6f, 0x76, 0x65, 0x72 +}; +static const guint8 connect_2_data[] = { + 0x11, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + /*^ DSAP = 2 */ + 0x0f +}; +static const guint8 connect_sdp_empty_data[] = { + 0x05, 0x20 +}; +static const guint8 llc_param_tlv_data[] = { + 0x01, 0x01, 0x11, 0x02, 0x02, 0x07, 0xff, 0x03, + 0x02, 0x00, 0x13, 0x04, 0x01, 0xff, 0x07, 0x01, + 0x03 +}; +static const GUtilData llc_param_tlv = { + TEST_ARRAY_AND_SIZE(llc_param_tlv_data) +}; + +static +void +test_llc_quit_loop_cb( + NfcLlc* llc, + void* user_data) +{ + g_main_loop_quit((GMainLoop*)user_data); +} + +static +void +test_llc_quit_when_dead_cb( + NfcPeerConnection* conn, + void* user_data) +{ + GDEBUG("Connection state %d", conn->state); + + if (conn->state == NFC_LLC_CO_DEAD) { + g_main_loop_quit((GMainLoop*)user_data); + } +} + +/*==========================================================================* + * Test connection + *==========================================================================*/ + +typedef NfcPeerConnectionClass TestConnectionClass; +typedef struct test_connection TestConnection; +typedef void (*TestConnectionHookFunc)(TestConnection* conn, void* user_data); +typedef struct test_connection_hook { + TestConnectionHookFunc proc; + void* user_data; +} TestConnectionHook; + +struct test_connection { + NfcPeerConnection connection; + TestConnectionHook state_change_hook; + TestConnectionHook finalize_hook; + gboolean accept_connection; + GByteArray* received; +}; + +G_DEFINE_TYPE(TestConnection, test_connection, NFC_TYPE_PEER_CONNECTION) +#define TEST_TYPE_CONNECTION (test_connection_get_type()) +#define TEST_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + TEST_TYPE_CONNECTION, TestConnection)) +#define TEST_CONNECTION_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), \ + TEST_TYPE_CONNECTION, TestConnectionClass) + +static +void +test_connection_accept( + NfcPeerConnection* conn) +{ + TestConnection* test = TEST_CONNECTION(conn); + + if (test->accept_connection) { + nfc_peer_connection_accepted(conn); + } else { + nfc_peer_connection_rejected(conn); + } +} + +static +void +test_connection_state_changed( + NfcPeerConnection* conn) +{ + TestConnection* test = TEST_CONNECTION(conn); + + if (test->state_change_hook.proc) { + test->state_change_hook.proc(test, test->state_change_hook.user_data); + } + NFC_PEER_CONNECTION_CLASS(test_connection_parent_class)-> + state_changed(conn); +} + +static +void +test_connection_data_received( + NfcPeerConnection* conn, + const void* data, + guint len) +{ + TestConnection* test = TEST_CONNECTION(conn); + + g_byte_array_append(test->received, data, len); + NFC_PEER_CONNECTION_CLASS(test_connection_parent_class)-> + data_received(conn, data, len); +} + +static +void +test_connection_init( + TestConnection* test) +{ + test->received = g_byte_array_new(); +} + +static +void +test_connection_finalize( + GObject* object) +{ + TestConnection* test = TEST_CONNECTION(object); + + if (test->finalize_hook.proc) { + test->finalize_hook.proc(test, test->finalize_hook.user_data); + } + g_byte_array_free(test->received, TRUE); + G_OBJECT_CLASS(test_connection_parent_class)->finalize(object); +} + +static +void +test_connection_class_init( + TestConnectionClass* klass) +{ + klass->accept = test_connection_accept; + klass->state_changed = test_connection_state_changed; + klass->data_received = test_connection_data_received; + G_OBJECT_CLASS(klass)->finalize = test_connection_finalize; +} + +static +TestConnection* +test_connection_new_connect( + NfcPeerService* svc, + guint8 rsap, + const char* name) +{ + TestConnection* test = g_object_new(TEST_TYPE_CONNECTION, NULL); + + nfc_peer_connection_init_connect(&test->connection, svc, rsap, name); + return test; +} + +static +TestConnection* +test_connection_new_accept( + NfcPeerService* svc, + guint8 rsap) +{ + TestConnection* test = g_object_new(TEST_TYPE_CONNECTION, NULL); + + nfc_peer_connection_init_accept(&test->connection, svc, rsap); + return test; +} + +/*==========================================================================* + * Test service + *==========================================================================*/ + +typedef NfcPeerServiceClass TestServiceClass; +typedef struct test_service { + NfcPeerService service; + TestConnectionHook connection_state_change_hook; + TestConnectionHook connection_finalize_hook; + gboolean allow_connections; + gboolean accept_connections; + gboolean cancel_connections; + int accept_count; +} TestService; + +G_DEFINE_TYPE(TestService, test_service, NFC_TYPE_PEER_SERVICE) +#define TEST_TYPE_SERVICE (test_service_get_type()) +#define TEST_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + TEST_TYPE_SERVICE, TestService)) + +static +NfcPeerConnection* +test_service_new_connect( + NfcPeerService* service, + guint8 rsap, + const char* name) +{ + TestService* test = TEST_SERVICE(service); + + if (test->allow_connections) { + TestConnection* conn = test_connection_new_connect(service, rsap, name); + + conn->state_change_hook = test->connection_state_change_hook; + conn->finalize_hook = test->connection_finalize_hook; + if (test->cancel_connections) { + /* Will return dead connection */ + nfc_peer_connection_disconnect(&conn->connection); + } + return &conn->connection; + } else { + return NFC_PEER_SERVICE_CLASS(test_service_parent_class)-> + new_connect(service, rsap, name); + } +} + +static +NfcPeerConnection* +test_service_new_accept( + NfcPeerService* service, + guint8 rsap) +{ + TestService* test = TEST_SERVICE(service); + + if (test->allow_connections) { + TestConnection* conn = test_connection_new_accept(service, rsap); + + test->accept_count++; + conn->state_change_hook = test->connection_state_change_hook; + conn->finalize_hook = test->connection_finalize_hook; + conn->accept_connection = test->accept_connections; + if (test->cancel_connections) { + /* Will return dead connection */ + nfc_peer_connection_disconnect(&conn->connection); + } + return &conn->connection; + } else { + return NFC_PEER_SERVICE_CLASS(test_service_parent_class)-> + new_accept(service, rsap); + } +} + +static +void +test_service_datagram_received( + NfcPeerService* service, + guint8 ssap, + const void* data, + guint len) +{ + GDEBUG("%u byte(s) received", len); + NFC_PEER_SERVICE_CLASS(test_service_parent_class)-> + datagram_received(service, ssap, data, len); +} + +static +void +test_service_init( + TestService* self) +{ + self->allow_connections = TRUE; + self->accept_connections = TRUE; +} + +static +void +test_service_finalize( + GObject* object) +{ + G_OBJECT_CLASS(test_service_parent_class)->finalize(object); +} + +static +void +test_service_class_init( + TestServiceClass* klass) +{ + klass->new_connect = test_service_new_connect; + klass->new_accept = test_service_new_accept; + klass->datagram_received = test_service_datagram_received; + G_OBJECT_CLASS(klass)->finalize = test_service_finalize; +} + +static +TestService* +test_service_new( + const char* name) +{ + TestService* service = g_object_new(TEST_TYPE_SERVICE, NULL); + + nfc_peer_service_init_base(&service->service, name); + return service; +} + +/*==========================================================================* + * null + *==========================================================================*/ + +static +void +test_null( + void) +{ + GBytes* pdu = g_bytes_new_static(TEST_ARRAY_AND_SIZE(symm_pdu_data)); + NfcTarget* target = test_target_new(FALSE); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc = nfc_llc_new(io, NULL, NULL); + + g_assert(!nfc_llc_io_target_new(NULL)); + g_assert(!nfc_llc_io_initiator_new(NULL)); + g_assert(!nfc_llc_new(NULL, NULL, NULL)); + g_assert(!nfc_llc_add_state_changed_handler(NULL, NULL, NULL)); + g_assert(!nfc_llc_add_idle_changed_handler(NULL, NULL, NULL)); + g_assert(!nfc_llc_add_wks_changed_handler(NULL, NULL, NULL)); + g_assert(!nfc_peer_connection_rmiu(NULL)); + g_assert(!nfc_peer_connection_key(NULL)); + g_assert(!nfc_peer_connection_ref(NULL)); + nfc_llc_submit_i_pdu(NULL, NULL, NULL, 0); + nfc_peer_connection_disconnect(NULL); + g_assert(!nfc_peer_connection_send(NULL, NULL)); + nfc_llc_connection_dead(NULL, NULL); + nfc_llc_connection_dead(llc, NULL); + g_assert(!nfc_peer_connection_cancel(NULL)); + g_assert(!nfc_peer_connection_add_state_changed_handler(NULL, NULL, NULL)); + nfc_peer_connection_remove_handler(NULL, 0); + nfc_peer_connection_unref(NULL); + g_assert(!nfc_llc_connect_sn(NULL, NULL, NULL, NULL, NULL, NULL)); + g_assert(!nfc_llc_connect_sn(llc, NULL, NULL, NULL, NULL, NULL)); + g_assert(!nfc_llc_connect(NULL, NULL, 0, NULL, NULL, NULL)); + g_assert(!nfc_llc_connect(llc, NULL, 0, NULL, NULL, NULL)); + g_assert(!nfc_llc_cancel_connect_request(NULL, NULL)); + g_assert(!nfc_llc_cancel_connect_request(llc, NULL)); + g_assert(!nfc_llc_i_pdu_queued(NULL, NULL)); + g_assert(!nfc_llc_i_pdu_queued(llc, NULL)); + nfc_llc_submit_disc_pdu(NULL, 0, 0); + nfc_llc_submit_dm_pdu(NULL, 0, 0, 0); + nfc_llc_submit_cc_pdu(NULL, NULL); + nfc_llc_ack(NULL, NULL, FALSE); + nfc_llc_ack(llc, NULL, FALSE); + nfc_llc_remove_handler(NULL, 0); + nfc_llc_remove_handler(NULL, 1); + nfc_llc_remove_handlers(NULL, NULL, 0); + nfc_llc_free(NULL); + + g_assert(!nfc_llc_io_ref(NULL)); + g_assert(!nfc_llc_io_start(NULL)); + g_assert(!nfc_llc_io_send(NULL, NULL)); + g_assert(!nfc_llc_io_add_can_send_handler(NULL, NULL, NULL)); + g_assert(!nfc_llc_io_add_receive_handler(NULL, NULL, NULL)); + g_assert(!nfc_llc_io_add_error_handler(NULL, NULL, NULL)); + nfc_llc_io_unref(NULL); + + g_bytes_unref(pdu); + nfc_llc_free(llc); + nfc_llc_io_unref(io); + nfc_target_unref(target); +} + +/*==========================================================================* + * basic + *==========================================================================*/ + +static +void +test_basic( + void) +{ + /* The initial SYMM transmit will fail */ + NfcTarget* target = test_target_new(TEST_TARGET_FAIL_ALL); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc = nfc_llc_new(io, NULL, NULL); + + g_assert(llc); + g_assert_cmpint(llc->state, == ,NFC_LLC_STATE_PEER_LOST); + + g_assert(!nfc_llc_add_state_changed_handler(llc, NULL, NULL)); + g_assert(!nfc_llc_add_idle_changed_handler(llc, NULL, NULL)); + g_assert(!nfc_llc_add_wks_changed_handler(llc, NULL, NULL)); + nfc_llc_submit_i_pdu(llc, NULL, NULL, 0); /* NULL PDU is ignored */ + nfc_llc_submit_cc_pdu(llc, NULL); /* NULL connection is ignored */ + nfc_llc_remove_handler(llc, 0); /* Zero id is ignored */ + nfc_llc_io_unref(io); + nfc_llc_free(llc); + nfc_target_unref(target); +} + +/*==========================================================================* + * initiator + *==========================================================================*/ + +static +void +test_initiator( + void) +{ + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } + }; + NfcInitiator* init = test_initiator_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + NfcLlcIo* io = nfc_llc_io_target_new(init); + NfcLlc* llc = nfc_llc_new(io, NULL, NULL); + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + gulong id; + + g_assert(llc->state == NFC_LLC_STATE_START); + id = nfc_llc_add_state_changed_handler(llc, test_llc_quit_loop_cb, loop); + test_run(&test_opt, loop); + g_assert(llc->state == NFC_LLC_STATE_ACTIVE); + nfc_llc_remove_handler(llc, id); + + g_main_loop_unref(loop); + nfc_initiator_unref(init); + nfc_llc_io_unref(io); + nfc_llc_free(llc); +} + +/*==========================================================================* + * advanced + *==========================================================================*/ + +typedef struct test_advanced_data { + const char* name; + const TestTx* tx; + guint ntx; + gboolean allow_connections; + gboolean accept_connections; + gboolean cancel_connections; + TestConnectionHookFunc connection_state_hook; +} TestAdvancedData; + +static +void +test_advanced_disconnect_when_active( + TestConnection* test, + void* user_data) +{ + NfcPeerConnection* conn = &test->connection; + + switch (conn->state) { + case NFC_LLC_CO_ACTIVE: + GDEBUG("Initiating local disconnect"); + nfc_peer_connection_disconnect(conn); + break; + case NFC_LLC_CO_DISCONNECTING: + case NFC_LLC_CO_DEAD: + nfc_peer_connection_disconnect(conn); /* This has no effect */ + nfc_peer_connection_apply_remote_params(conn, NULL); /* This too */ + break; + default: + break; + } +} + +static +void +test_advanced( + gconstpointer test_data) +{ + const TestAdvancedData* test = test_data; + NfcPeerService* snep = NFC_PEER_SERVICE + (test_service_new(NFC_LLC_NAME_SNEP)); + TestService* test_service = test_service_new("foo"); + NfcTarget* target = test_target_new_with_tx(test->tx, test->ntx); + NfcPeerService* service = NFC_PEER_SERVICE(test_service); + NfcLlcParam** params = nfc_llc_param_decode(&llc_param_tlv); + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc; + gulong id; + + test_service->allow_connections = test->allow_connections; + test_service->accept_connections = test->accept_connections; + test_service->cancel_connections = test->cancel_connections; + test_service->connection_state_change_hook.user_data = loop; + test_service->connection_state_change_hook.proc = + test->connection_state_hook; + + g_assert(nfc_peer_services_add(services, service)); + g_assert(nfc_peer_services_add(services, snep)); + g_assert_cmpuint(service->sap, == ,NFC_LLC_SAP_NAMED); + g_assert_cmpuint(snep->sap, == ,NFC_LLC_SAP_SNEP); + + llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(llc); + g_assert(llc->state == NFC_LLC_STATE_START); + + /* Wait for the conversation to start */ + id = nfc_llc_add_state_changed_handler(llc, test_llc_quit_loop_cb, loop); + test_run(&test_opt, loop); + if (llc->state == NFC_LLC_STATE_ACTIVE) { + /* Now wait until transfer error terminates the loop */ + test_run(&test_opt, loop); + g_assert(llc->state == NFC_LLC_STATE_PEER_LOST); + } + nfc_llc_remove_handler(llc, id); + g_main_loop_unref(loop); + + /* All data must have been sent */ + g_assert_cmpuint(test_target_tx_remaining(target), == ,0); + + nfc_llc_free(llc); + nfc_llc_io_unref(io); + nfc_llc_param_free(params); + nfc_peer_service_unref(service); + nfc_peer_service_unref(snep); + nfc_peer_services_unref(services); + nfc_target_unref(target); +} + +static const guint8 connect_foo_name_data[] = { + 0x05, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f, 0x06, 0x03, 0x66, 0x6f, 0x6f +}; +static const guint8 connect_foo_sap_data[] = { + 0x41, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f, 0x06 +}; +static const guint8 cc_foo_data[] = { + 0x81, 0x90, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f +}; +static const guint8 dm_reject_foo_data[] = { 0x81, 0xd0, 0x03 }; +static const guint8 remote_disc_foo_data[] = { 0x41, 0x60 }; +static const guint8 local_disc_foo_data[] = { 0x81, 0x50 }; +static const guint8 remote_dm_disc_data[] = { 0x41, 0xe0, 0x00 }; +static const guint8 local_dm_disc_data[] = { 0x81, 0xd0, 0x00 }; +static const guint8 frmr_disc_data[] = { 0x82, 0x10, 0x45, 0x00, 0x00, 0x00 }; +static const guint8 frmr_dm_data[] = { 0x82, 0x10, 0x47, 0x00, 0x00, 0x00 }; +static const guint8 frmr_cc_data[] = { 0x42, 0x00, 0x46, 0x00, 0x00, 0x00 }; +static const guint8 snl_empty_data[] = { 0x06, 0x41 }; +static const guint8 snl_foo_bar_sdreq_data[] = { + 0x06, 0x41, + 0x08, 0x04, 0x01, 0x66, 0x6f, 0x6f, /* foo */ + 0x08, 0x04, 0x02, 0x62, 0x61, 0x72, /* bar */ + 0x08, 0x0f, 0x03, 0x75, 0x72, 0x6e, 0x3a, 0x6e, + 0x66, 0x63, 0x3a, 0x73, 0x6e, 0x3a, 0x73, 0x64, + 0x70, /* urn:nfc:sn:sdp */ + /* This one doesn't make sense and will be ignored: */ + 0x01, 0x01, 0x11 + }; +static const guint8 snl_foo_bar_sdres_data[] = { + 0x06, 0x41, 0x09, 0x02, 0x01, 0x10, 0x09, 0x02, + 0x02, 0x00, 0x09, 0x02, 0x03, 0x01 +}; +static const guint8 snl_malformed_dsap_data[] = { + 0x82, 0x41, 0x09, 0x02, 0x01, 0x10 +}; +static const guint8 frmr_snl_malformed_dsap_data[] = { + 0x06, 0x20, 0x49, 0x00, 0x00, 0x00 + }; +static const guint8 snl_malformed_ssap_data[] = { + 0x06, 0x60, 0x09, 0x02, 0x01, 0x10 +}; +static const guint8 frmr_snl_malformed_ssap_data[] = { + 0x82, 0x01, 0x49, 0x00, 0x00, 0x00 +}; +static const guint8 pax_data[] = { + 0x00, 0x40, 0x01, 0x01, 0x11, 0x02, 0x02, 0x07, + 0xff, 0x03, 0x02, 0x00, 0x13, 0x04, 0x01, 0x00 +}; +static const guint8 agf_pax_data[] = { + 0x00, 0x80, + /* Encapsulated PAX PDU */ + 0x00, 0x10, + 0x00, 0x40, 0x01, 0x01, 0x11, 0x02, 0x02, 0x07, + 0xff, 0x03, 0x02, 0x00, 0x13, 0x04, 0x01, 0x00, + /* Empty PDU (ignored) */ + 0x00, 0x00 +}; +static const guint8 pax_malformed_dsap_data[] = { 0x04, 0x40 }; +static const guint8 pax_malformed_ssap_data[] = { 0x00, 0x41 }; +static const guint8 frmr_pax_malformed_dsap_data[] = { + 0x02, 0x01, 0x41, 0x00, 0x00, 0x00 +}; +static const guint8 frmr_pax_malformed_ssap_data[] = { + 0x06, 0x00, 0x41, 0x00, 0x00, 0x00 +}; +static const guint8 ui_valid_data[] = { 0x40, 0xc1, 0x01, 0x02, 0x03 }; +static const guint8 ui_invalid_data[] = { 0x80, 0xc1, 0x01, 0x02, 0x03 }; +static const guint8 frmr_ui_invalid_data[] = { + 0x06, 0x20, 0x43, 0x00, 0x00, 0x00 +}; +static const guint8 frmr_rr_32_32_i_data[] = { + 0x82, 0x20, 0x4d, 0x00, 0x00, 0x00 +}; +static const guint8 frmr_rnr_32_32_i_data[] = { + 0x82, 0x20, 0x4e, 0x00, 0x00, 0x00 +}; +static const guint8 frmr_i_32_16_i_data[] = { + 0x42, 0x20, 0x4c, 0x00, 0x00, 0x00 +}; +static const guint8 rr_32_16_0_pdu_data[] = { 0x43, 0x60, 0x00 }; +static const guint8 rr_32_32_0_pdu_data[] = { 0x83, 0x60, 0x00 }; +static const guint8 rr_16_32_1_pdu_data[] = { 0x83, 0x50, 0x01 }; +static const guint8 rnr_32_16_0_pdu_data[] = { 0x43, 0xa0, 0x00 }; +static const guint8 rnr_32_32_0_pdu_data[] = { 0x83, 0xa0, 0x00 }; +static const guint8 i_0_0_1_pdu_data[] = { 0x43, 0x20, 0x00, 0x01 }; +static const guint8 connect_sap_32_17_data[] = { + 0x45, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f +}; +static const guint8 connect_sap_32_3_data[] = { + 0x0d, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f +}; +static const guint8 dm_noservice_17_32_data[] = { 0x81, 0xd1, 0x02 }; +static const guint8 dm_noservice_3_32_data[] = { 0x81, 0xc3, 0x02 }; + +static const TestTx advanced_pkt_1 [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx advanced_pkt_2 [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx advanced_pkt_3 [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_urn_nfc_sn_handover_data) } + } +}; +static const TestTx advanced_pkt_4 [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_sdp_empty_data) } + } +}; +static const TestTx advanced_pkt_5 [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_2_data) } + } +}; +static const TestTx advanced_empty_snl [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(snl_empty_data) } + },{ + { TEST_ARRAY_AND_SIZE(snl_empty_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_snl [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(snl_foo_bar_sdreq_data) } + },{ + { TEST_ARRAY_AND_SIZE(snl_foo_bar_sdres_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_accept_name_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_name_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_foo_data) }, + { TEST_ARRAY_AND_SIZE(rr_32_16_0_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(i_0_0_1_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(rr_16_32_1_pdu_data) }, + { TEST_ARRAY_AND_SIZE(rr_32_32_0_pdu_data) } /* Invalid RR */ + },{ + { TEST_ARRAY_AND_SIZE(frmr_rr_32_32_i_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_accept_sap_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_foo_data) }, + { TEST_ARRAY_AND_SIZE(rnr_32_16_0_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(i_0_0_1_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(rr_16_32_1_pdu_data) }, + { TEST_ARRAY_AND_SIZE(rnr_32_32_0_pdu_data) } /* Invalid RNR */ + },{ + { TEST_ARRAY_AND_SIZE(frmr_rnr_32_32_i_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_accept_remote_disc_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_foo_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(remote_disc_foo_data) } + } +}; +static const TestTx advanced_accept_remote_frmr_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_foo_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(frmr_i_32_16_i_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx advanced_accept_remote_double_disc_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_foo_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(remote_disc_foo_data) } + },{ + { TEST_ARRAY_AND_SIZE(local_dm_disc_data) }, + { TEST_ARRAY_AND_SIZE(remote_disc_foo_data) } + },{ + { TEST_ARRAY_AND_SIZE(frmr_disc_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_accept_remote_disc_invalid_dm_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_foo_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(remote_disc_foo_data) } + },{ + { TEST_ARRAY_AND_SIZE(local_dm_disc_data) }, + { TEST_ARRAY_AND_SIZE(remote_dm_disc_data) } + },{ + { TEST_ARRAY_AND_SIZE(frmr_dm_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_accept_local_disc_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_foo_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(local_disc_foo_data) }, + { TEST_ARRAY_AND_SIZE(remote_dm_disc_data) } + } +}; +static const TestTx advanced_connect_duplicate_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_foo_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(dm_reject_foo_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_connect_reject_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_name_data) } + },{ + { TEST_ARRAY_AND_SIZE(dm_reject_foo_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_connect_reject_sap_17_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_sap_32_17_data) } + },{ + { TEST_ARRAY_AND_SIZE(dm_noservice_17_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_connect_reject_sap_3_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_sap_32_3_data) } + },{ + { TEST_ARRAY_AND_SIZE(dm_noservice_3_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_unexpected_cc_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(cc_foo_data) } + },{ + { TEST_ARRAY_AND_SIZE(frmr_cc_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_snl_malformed_dsap_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(snl_malformed_dsap_data) } + },{ + { TEST_ARRAY_AND_SIZE(frmr_snl_malformed_dsap_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_snl_malformed_ssap_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(snl_malformed_ssap_data) } + },{ + { TEST_ARRAY_AND_SIZE(frmr_snl_malformed_ssap_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_pax_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(pax_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx advanced_pax_malformed_dsap_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(pax_malformed_dsap_data) } + },{ + { TEST_ARRAY_AND_SIZE(frmr_pax_malformed_dsap_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_pax_malformed_ssap_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(pax_malformed_ssap_data) } + },{ + { TEST_ARRAY_AND_SIZE(frmr_pax_malformed_ssap_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx advanced_agf_pax_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(agf_pax_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx advanced_ui_valid_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(ui_valid_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx advanced_ui_invalid_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(ui_invalid_data) } + },{ + { TEST_ARRAY_AND_SIZE(frmr_ui_invalid_data) }, + { NULL, 0 } + } +}; + +static const TestAdvancedData advanced_tests [] = { + { + "abort/1", + TEST_ARRAY_AND_COUNT(advanced_pkt_1) + },{ + "abort/2", + TEST_ARRAY_AND_COUNT(advanced_pkt_2) + },{ + "abort/3", + TEST_ARRAY_AND_COUNT(advanced_pkt_3) + },{ + "abort/4", + TEST_ARRAY_AND_COUNT(advanced_pkt_4) + },{ + "abort/5", + TEST_ARRAY_AND_COUNT(advanced_pkt_5) + },{ + "empty_snl", + TEST_ARRAY_AND_COUNT(advanced_empty_snl) + },{ + "snl", + TEST_ARRAY_AND_COUNT(advanced_snl) + },{ + "accept_name", + TEST_ARRAY_AND_COUNT(advanced_accept_name_pkt), + TRUE, TRUE + },{ + "accept_sap", + TEST_ARRAY_AND_COUNT(advanced_accept_sap_pkt), + TRUE, TRUE + },{ + "accept_remote_disc", + TEST_ARRAY_AND_COUNT(advanced_accept_remote_disc_pkt), + TRUE, TRUE + },{ + "accept_remote_frmr", + TEST_ARRAY_AND_COUNT(advanced_accept_remote_frmr_pkt), + TRUE, TRUE + },{ + "accept_remote_double_disc", + TEST_ARRAY_AND_COUNT(advanced_accept_remote_double_disc_pkt), + TRUE, TRUE + },{ + "accept_remote_disc_invalid_dm", + TEST_ARRAY_AND_COUNT(advanced_accept_remote_disc_invalid_dm_pkt), + TRUE, TRUE + },{ + "accept_local_disc", + TEST_ARRAY_AND_COUNT(advanced_accept_local_disc_pkt), + TRUE, TRUE, FALSE, + test_advanced_disconnect_when_active + },{ + "duplicate", + TEST_ARRAY_AND_COUNT(advanced_connect_duplicate_pkt), + TRUE, TRUE + },{ + "cancel", + TEST_ARRAY_AND_COUNT(advanced_connect_reject_pkt), + TRUE, TRUE, TRUE + },{ + "reject1", + TEST_ARRAY_AND_COUNT(advanced_connect_reject_pkt), + TRUE, FALSE + },{ + "reject2", + TEST_ARRAY_AND_COUNT(advanced_connect_reject_pkt), + },{ + "reject_sap1", + TEST_ARRAY_AND_COUNT(advanced_connect_reject_sap_3_pkt), + },{ + "reject_sap2", + TEST_ARRAY_AND_COUNT(advanced_connect_reject_sap_17_pkt), + },{ + "unexpected_cc", + TEST_ARRAY_AND_COUNT(advanced_unexpected_cc_pkt), + },{ + "snl_malformed_dsap", + TEST_ARRAY_AND_COUNT(advanced_snl_malformed_dsap_pkt), + },{ + "snl_malformed_ssap", + TEST_ARRAY_AND_COUNT(advanced_snl_malformed_ssap_pkt), + },{ + "pax", + TEST_ARRAY_AND_COUNT(advanced_pax_pkt), + },{ + "pax_malformed_dsap", + TEST_ARRAY_AND_COUNT(advanced_pax_malformed_dsap_pkt), + },{ + "pax_malformed_ssap", + TEST_ARRAY_AND_COUNT(advanced_pax_malformed_ssap_pkt), + },{ + "agf_pax", + TEST_ARRAY_AND_COUNT(advanced_agf_pax_pkt), + },{ + "ui_valid", + TEST_ARRAY_AND_COUNT(advanced_ui_valid_pkt), + },{ + "ui_invalid", + TEST_ARRAY_AND_COUNT(advanced_ui_invalid_pkt), + } +}; + +/*==========================================================================* + * connect + *==========================================================================*/ + +typedef struct test_connect_run TestConnectRun; +typedef void (*TestConnectFunc)(TestConnectRun* run); +typedef struct test_connect_data { + const char* name; + const TestTx* tx; + guint ntx; + TestConnectFunc connect_proc; + NfcLlcConnectFunc connect_complete; + NFC_PEER_CONNECT_RESULT connect_result; + gboolean exit_when_connected; + NFC_LLC_STATE exit_state; + GUtilData data_received; +} TestConnectData; + +struct test_connect_run { + const TestConnectData* test; + NfcLlc* llc; + NfcPeerService* service; + GMainLoop* loop; + gboolean connect_complete; + gboolean connect_done; +}; + +static +void +test_connect_done( + gpointer user_data) +{ + TestConnectRun* run = user_data; + + g_assert(!run->connect_done); + run->connect_done = TRUE; +} + +static +void +test_connect_disconnected( + TestConnection* conn, + void* user_data) +{ + TestConnectRun* run = user_data; + const TestConnectData* test = run->test; + + if (test->data_received.bytes) { + const GUtilData* expect = &test->data_received; + + g_assert_cmpuint(expect->size, == ,conn->received->len); + g_assert(!memcmp(expect->bytes, conn->received->data, expect->size)); + } +} + +static +void +test_connect_complete( + NfcPeerConnection* conn, + NFC_PEER_CONNECT_RESULT result, + void* user_data) +{ + TestConnectRun* run = user_data; + const TestConnectData* test = run->test; + + GDEBUG("Connection status %d", result); + g_assert_cmpint(test->connect_result, == ,result); + g_assert(!run->connect_complete); + run->connect_complete = TRUE; + if (test->exit_when_connected) { + g_main_loop_quit(run->loop); + } +} + +static +void +test_connect_cancel( + TestConnectRun* run) +{ + NfcPeerConnection* connection = nfc_llc_connect_sn(run->llc, + run->service, NFC_LLC_NAME_SNEP, run->test->connect_complete, + test_connect_done, run); + + g_assert(connection); + nfc_peer_connection_disconnect(connection); +} + +static +void +test_connect_snep_sn( + TestConnectRun* run) +{ + g_assert(nfc_llc_connect_sn(run->llc, run->service, NFC_LLC_NAME_SNEP, + run->test->connect_complete, test_connect_done, run)); +} + +static +void +test_connect_snep_sap( + TestConnectRun* run) +{ + g_assert(nfc_llc_connect(run->llc, run->service, NFC_LLC_SAP_SNEP, + run->test->connect_complete, test_connect_done, run)); +} + +static +void +test_connect( + gconstpointer test_data) +{ + const TestConnectData* test = test_data; + TestConnectRun run; + TestService* test_service = test_service_new(NULL); + NfcTarget* target = test_target_new_with_tx(test->tx, test->ntx); + NfcLlcParam** params = nfc_llc_param_decode(&llc_param_tlv); + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + gulong id; + + memset(&run, 0, sizeof(run)); + run.test = test; + run.loop = g_main_loop_new(NULL, TRUE); + run.service = NFC_PEER_SERVICE(test_service); + + test_service->connection_finalize_hook.proc = test_connect_disconnected; + test_service->connection_finalize_hook.user_data = &run; + g_assert(nfc_peer_services_add(services, run.service)); + g_assert_cmpuint(run.service->sap, == ,NFC_LLC_SAP_UNNAMED); + + run.llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(run.llc); + g_assert_cmpint(run.llc->state, == ,NFC_LLC_STATE_START); + + /* Initiate the connection */ + if (test->connect_proc) { + test->connect_proc(&run); + } + + /* Wait for the conversation to start */ + id = nfc_llc_add_state_changed_handler(run.llc, test_llc_quit_loop_cb, + run.loop); + test_run(&test_opt, run.loop); + if (run.llc->state == NFC_LLC_STATE_ACTIVE) { + /* Now wait until transfer error or something else breaks the loop */ + test_run(&test_opt, run.loop); + } + g_assert_cmpint(run.llc->state, == ,test->exit_state); + g_assert(run.connect_complete == (test->connect_complete != NULL)); + g_assert(run.connect_done); + nfc_llc_remove_handler(run.llc, id); + g_main_loop_unref(run.loop); + + /* All data must have been sent */ + g_assert_cmpuint(test_target_tx_remaining(target), == ,0); + + nfc_llc_free(run.llc); + nfc_llc_io_unref(io); + nfc_llc_param_free(params); + nfc_peer_service_unref(run.service); + nfc_peer_services_unref(services); + nfc_target_unref(target); +} + +static const guint8 connect_snep_name_data[] = { + 0x05, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f, 0x06, 0x0f, 0x75, 0x72, 0x6e, 0x3a, 0x6e, + 0x66, 0x63, 0x3a, 0x73, 0x6e, 0x3a, 0x73, 0x6e, + 0x65, 0x70 +}; +static const guint8 connect_snep_sap_data[] = { + 0x11, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f +}; +static const guint8 cc_snep_data[] = { + 0x81, 0x84, 0x02, 0x02, 0x07, 0xff, 0x04, 0x01, + 0xff, 0x05, 0x01, 0x0f +}; +static const guint8 disc_4_32_pdu_data[] = { 0x11, 0x60 }; +static const guint8 dm_32_4_pdu_data[] = { 0x81, 0xc4, 0x00 }; +static const guint8 connect_snep_name_ok_transfer_expected_data[] = { 0x01 }; +static const guint8 i_32_4_1_pdu_data[] = { 0x83, 0x04, 0x00, 0x01 }; +static const guint8 i_33_4_1_pdu_data[] = { 0x87, 0x04, 0x00, 0x02 }; +static const guint8 rr_4_32_0_pdu_data[] = { 0x13, 0x60, 0x01 }; +static const guint8 frmr_connect_data[] = { + 0x82, 0x00, 0x84, 0x00, 0x00, 0x00 +}; +static const guint8 frmr_invalid_reject_data[] = { + 0x02, 0x10, 0x47, 0x00, 0x00, 0x00 +}; +static const guint8 frmr_i_4_32_s_data[] = { + 0x12, 0x20, 0x1c, 0x00, 0x01, 0x01 +}; +static const guint8 frmr_i_4_33_i_data[] = { + 0x12, 0x21, 0x4c, 0x00, 0x00, 0x00 +}; +static const guint8 dm_snep_noservice_data[] = { 0x81, 0xc0, 0x02 }; +static const guint8 dm_snep_reject_data[] = { 0x81, 0xc0, 0x03 }; +static const guint8 dm_snep_invalid_reject_data[] = { 0x41, 0xc0, 0x03 }; +static const guint8 cc_invalid_sap_data[] = { 0x41, 0x90 }; +static const TestTx connect_snep_name_ok_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_name_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx connect_snep_name_ok_cancel_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_name_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(disc_4_32_pdu_data) }, + { TEST_ARRAY_AND_SIZE(dm_32_4_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx connect_snep_name_ok_transfer_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_name_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(i_32_4_1_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(rr_4_32_0_pdu_data) }, + { TEST_ARRAY_AND_SIZE(i_33_4_1_pdu_data) } /* Invalid SAP */ + },{ + { TEST_ARRAY_AND_SIZE(frmr_i_4_33_i_data) }, + { TEST_ARRAY_AND_SIZE(i_32_4_1_pdu_data) } /* Invalid N(S) */ + },{ + { TEST_ARRAY_AND_SIZE(frmr_i_4_32_s_data) }, + { NULL, 0 } + } +}; +static const TestTx connect_snep_sap_ok_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_sap_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx connect_snep_name_noservice_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_name_data) }, + { TEST_ARRAY_AND_SIZE(dm_snep_noservice_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx connect_snep_name_reject_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_name_data) }, + { TEST_ARRAY_AND_SIZE(dm_snep_invalid_reject_data) } + },{ + { TEST_ARRAY_AND_SIZE(frmr_invalid_reject_data) }, + { TEST_ARRAY_AND_SIZE(dm_snep_reject_data) } /* The actual reject */ + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx connect_snep_name_reject_frmr_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_name_data) }, + { TEST_ARRAY_AND_SIZE(frmr_connect_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx connect_invalid_cc_ok_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_name_data) }, + { TEST_ARRAY_AND_SIZE(cc_invalid_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(frmr_cc_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; + +static const TestConnectData connect_tests [] = { + { + "snep_name_ok/1", + TEST_ARRAY_AND_COUNT(connect_snep_name_ok_pkt), + test_connect_snep_sn, NULL, NFC_PEER_CONNECT_OK, FALSE, + NFC_LLC_STATE_PEER_LOST + },{ + "snep_name_ok/2", + TEST_ARRAY_AND_COUNT(connect_snep_name_ok_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_OK, FALSE, NFC_LLC_STATE_PEER_LOST + },{ + "snep_name_ok/3", + TEST_ARRAY_AND_COUNT(connect_snep_name_ok_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_OK, TRUE, NFC_LLC_STATE_ACTIVE + },{ + "snep_name_ok_cancel", + TEST_ARRAY_AND_COUNT(connect_snep_name_ok_cancel_pkt), + test_connect_cancel, NULL, NFC_PEER_CONNECT_OK, FALSE, + NFC_LLC_STATE_PEER_LOST + },{ + "snep_name_ok_transfer", + TEST_ARRAY_AND_COUNT(connect_snep_name_ok_transfer_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_OK, FALSE, NFC_LLC_STATE_PEER_LOST, + { TEST_ARRAY_AND_SIZE(connect_snep_name_ok_transfer_expected_data) } + },{ + "snep_sap_ok/1", + TEST_ARRAY_AND_COUNT(connect_snep_sap_ok_pkt), + test_connect_snep_sap, NULL, NFC_PEER_CONNECT_OK, FALSE, + NFC_LLC_STATE_PEER_LOST + },{ + "snep_sap_ok/2", + TEST_ARRAY_AND_COUNT(connect_snep_sap_ok_pkt), + test_connect_snep_sap, test_connect_complete, + NFC_PEER_CONNECT_OK, FALSE, NFC_LLC_STATE_PEER_LOST + },{ + "snep_sap_ok/3", + TEST_ARRAY_AND_COUNT(connect_snep_sap_ok_pkt), + test_connect_snep_sap, test_connect_complete, + NFC_PEER_CONNECT_OK, TRUE, NFC_LLC_STATE_ACTIVE + },{ + "snep_name_noservice/1", + TEST_ARRAY_AND_COUNT(connect_snep_name_noservice_pkt), + test_connect_snep_sn, NULL, NFC_PEER_CONNECT_NO_SERVICE, FALSE, + NFC_LLC_STATE_PEER_LOST + },{ + "snep_name_noservice/2", + TEST_ARRAY_AND_COUNT(connect_snep_name_noservice_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_NO_SERVICE, FALSE, NFC_LLC_STATE_PEER_LOST + },{ + "snep_name_noservice/3", + TEST_ARRAY_AND_COUNT(connect_snep_name_noservice_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_NO_SERVICE, TRUE, NFC_LLC_STATE_ACTIVE + },{ + "snep_name_reject/1", + TEST_ARRAY_AND_COUNT(connect_snep_name_reject_pkt), + test_connect_snep_sn, NULL, NFC_PEER_CONNECT_REJECTED, FALSE, + NFC_LLC_STATE_PEER_LOST + },{ + "snep_name_reject/2", + TEST_ARRAY_AND_COUNT(connect_snep_name_reject_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_REJECTED, FALSE, NFC_LLC_STATE_PEER_LOST + },{ + "snep_name_reject/3", + TEST_ARRAY_AND_COUNT(connect_snep_name_reject_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_REJECTED, TRUE, NFC_LLC_STATE_ACTIVE + },{ + "snep_name_reject_frmr/1", + TEST_ARRAY_AND_COUNT(connect_snep_name_reject_frmr_pkt), + test_connect_snep_sn, NULL, NFC_PEER_CONNECT_REJECTED, FALSE, + NFC_LLC_STATE_PEER_LOST + },{ + "snep_name_reject_frmr/2", + TEST_ARRAY_AND_COUNT(connect_snep_name_reject_frmr_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_REJECTED, FALSE, NFC_LLC_STATE_PEER_LOST + },{ + "snep_name_reject_frmr/3", + TEST_ARRAY_AND_COUNT(connect_snep_name_reject_frmr_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_REJECTED, TRUE, NFC_LLC_STATE_ACTIVE + },{ + "invalid_cc_ok/1", + TEST_ARRAY_AND_COUNT(connect_invalid_cc_ok_pkt), + test_connect_snep_sn, NULL, NFC_PEER_CONNECT_OK, FALSE, + NFC_LLC_STATE_PEER_LOST + },{ + "invalid_cc_ok/2", + TEST_ARRAY_AND_COUNT(connect_invalid_cc_ok_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_OK, FALSE, NFC_LLC_STATE_PEER_LOST + },{ + "invalid_cc_ok/3", + TEST_ARRAY_AND_COUNT(connect_invalid_cc_ok_pkt), + test_connect_snep_sn, test_connect_complete, + NFC_PEER_CONNECT_OK, TRUE, NFC_LLC_STATE_ACTIVE + } +}; + +/*==========================================================================* + * send + *==========================================================================*/ + +typedef enum test_send_flags { + TEST_SEND_NO_FLAGS = 0, + TEST_SEND_LATER = 0x01 +} TEST_SEND_FLAGS; + +typedef struct test_send_config { + const char* name; + const GUtilData* send; + guint nsend; + const TestTx* tx; + guint ntx; + void (*after_send_fn)(NfcPeerConnection* self); + TEST_SEND_FLAGS flags; + gsize bytes_sent; + NFC_LLC_CO_STATE exit_conn_state; + NFC_LLC_STATE exit_llc_state; +} TestSendConfig; + +typedef struct test_send_data { + const TestSendConfig* config; + GMainLoop* loop; + NfcPeerConnection* conn; + gulong quit_id; +} TestSendData; + +static +void +test_send_connected_abort( + NfcPeerConnection* conn) +{ + /* + * nfc_peer_connection_cancel returns FALSE because the connection + * has already been established but it still srops all unsent data + * and moves the connection to DISCONNECTING state. + */ + g_assert(!nfc_peer_connection_cancel(conn)); +} + +static +void +test_send_now( + TestSendData* test) +{ + const TestSendConfig* config = test->config; + NfcPeerConnection* conn = test->conn; + guint i; + + /* Send the data */ + for (i = 0; i < config->nsend; i++) { + const GUtilData* data = config->send + i; + GBytes* bytes = g_bytes_new_static(data->bytes, data->size); + + g_assert(nfc_peer_connection_send(conn, bytes)); + g_bytes_unref(bytes); + } + if (config->after_send_fn) { + config->after_send_fn(conn); + } +} + +static +gboolean +test_send_later( + void* user_data) +{ + test_send_now((TestSendData*)user_data); + return G_SOURCE_REMOVE; +} + +static +void +test_send_connected( + NfcPeerConnection* conn, + NFC_PEER_CONNECT_RESULT result, + void* user_data) +{ + TestSendData* test = user_data; + const TestSendConfig* config = test->config; + + GDEBUG("Connection status %d", result); + g_assert_cmpint(result, == ,NFC_PEER_CONNECT_OK); + g_assert(!test->conn); + g_assert(nfc_peer_connection_rmiu(conn) == 128); + test->conn = nfc_peer_connection_ref(conn); + test->quit_id = nfc_peer_connection_add_state_changed_handler(conn, + test_llc_quit_when_dead_cb, test->loop); + if (config->flags & TEST_SEND_LATER) { + g_idle_add(test_send_later, test); + } else { + test_send_now(test); + } +} + +static +void +test_send( + gconstpointer test_data) +{ + const TestSendConfig* config = test_data; + TestService* test_service = test_service_new(NULL); + TestSendData test; + NfcTarget* target = test_target_new_with_tx(config->tx, config->ntx); + NfcLlcParam** params = nfc_llc_param_decode(&llc_param_tlv); + NfcPeerService* service = NFC_PEER_SERVICE(test_service); + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc; + gulong id; + + memset(&test, 0, sizeof(test)); + test.config = config; + test.loop = g_main_loop_new(NULL, TRUE); + + g_assert(nfc_peer_services_add(services, service)); + g_assert_cmpuint(service->sap, == ,NFC_LLC_SAP_UNNAMED); + + llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(llc); + g_assert(llc->state == NFC_LLC_STATE_START); + + /* Initiate the connection */ + g_assert(nfc_llc_connect(llc, service, NFC_LLC_SAP_SNEP, + test_send_connected, NULL, &test)); + + /* Wait for the conversation to start */ + id = nfc_llc_add_state_changed_handler(llc, test_llc_quit_loop_cb, + test.loop); + test_run(&test_opt, test.loop); + if (llc->state == NFC_LLC_STATE_ACTIVE) { + /* Now wait until transfer error or something else breaks the loop */ + test_run(&test_opt, test.loop); + } + g_assert(llc->state == config->exit_llc_state); + nfc_llc_remove_handler(llc, id); + + /* Must have connection */ + g_assert(test.conn); + g_assert_cmpuint(test.conn->bytes_queued, ==, 0); + g_assert_cmpuint(test.conn->bytes_received, == , 0); + g_assert_cmpuint(test.conn->bytes_sent, == ,config->bytes_sent); + g_assert(test.conn->state == config->exit_conn_state); + g_assert(nfc_peer_connection_send(test.conn, NULL) == + (test.conn->state <= NFC_LLC_CO_ACTIVE)); + nfc_peer_connection_remove_handler(test.conn, test.quit_id); + nfc_peer_connection_unref(test.conn); + g_main_loop_unref(test.loop); + + /* All data must have been sent */ + g_assert_cmpuint(test_target_tx_remaining(target), == ,0); + + nfc_llc_free(llc); + nfc_llc_io_unref(io); + nfc_llc_param_free(params); + nfc_peer_service_unref(service); + nfc_peer_services_unref(services); + nfc_target_unref(target); +} + +static const guint8 send_cc_snep_data[] = { + 0x81, 0x84, 0x02, 0x02, 0x00, 0x00, 0x04, 0x01, + 0xff, 0x05, 0x01, 0x02, +}; +static const guint8 send_frame_264[] = { + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x13, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x23, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x33, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x43, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x53, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x63, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x73, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x83, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x93, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa3, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb3, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc3, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd3, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe3, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf3, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07 +}; +static const GUtilData send_small_frame_send_data [] = { + { send_frame_264, 1 } +}; +static const GUtilData send_small_frames_send_data [] = { + { send_frame_264, 1 }, + { send_frame_264 + 1, 1 }, + { send_frame_264 + 2, 1 }, + { send_frame_264 + 3, 1 }, + { send_frame_264 + 4, 1 }, + { send_frame_264 + 5, 1 }, + { send_frame_264 + 6, 1 }, + { send_frame_264 + 7, 1 }, +}; +static const GUtilData send_large_frame_send_data [] = { + { send_frame_264, 128 } +}; +static const GUtilData send_large_frames_send_data [] = { + { send_frame_264, 200 }, + { send_frame_264 + 200, 64 } +}; +static const GUtilData send_extra_large_frame_send_data [] = { + { send_frame_264, 129 } +}; + +static const guint8 send_small_frame_i[] = { 0x13, 0x20, 0x00, 0x00 }; +static const guint8 send_small_frames_i[] = { + 0x13, 0x20, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07, +}; +static const guint8 send_frame_rr_1[] = { 0x83, 0x44, 0x01 }; +static const guint8 send_frame_rr_2[] = { 0x83, 0x44, 0x02 }; +static const guint8 send_frame_rr_3[] = { 0x83, 0x44, 0x03 }; +static const guint8 send_large_frame_i[] = { + 0x13, 0x20, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x13, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x23, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x33, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x43, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x53, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x63, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x73, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f +}; +static const guint8 send_large_frames_i_1[] = { + 0x13, 0x20, 0x10, + 0x80, 0x81, 0x82, 0x83, 0x83, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x93, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa3, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb3, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc3, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd3, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe3, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf3, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; +static const guint8 send_large_frames_i_2[] = { + 0x13, 0x20, 0x20, + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07 +}; +static const guint8 send_extra_large_frame_i_1[] = { 0x13, 0x20, 0x10, 0x80 }; +static const TestTx send_no_frames_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_sap_data) }, + { TEST_ARRAY_AND_SIZE(send_cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(disc_4_32_pdu_data) }, + { TEST_ARRAY_AND_SIZE(dm_32_4_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx send_small_frame_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_sap_data) }, + { TEST_ARRAY_AND_SIZE(send_cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(send_small_frame_i) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_1) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx send_small_frame_abort_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_sap_data) }, + { TEST_ARRAY_AND_SIZE(send_cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ /* I-Frame is still sent */ + { TEST_ARRAY_AND_SIZE(send_small_frame_i) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_1) } + },{ + { TEST_ARRAY_AND_SIZE(disc_4_32_pdu_data) }, + { TEST_ARRAY_AND_SIZE(dm_32_4_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx send_small_frames_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_sap_data) }, + { TEST_ARRAY_AND_SIZE(send_cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(send_small_frames_i) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_1) } + } +}; +static const TestTx send_large_frame_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_sap_data) }, + { TEST_ARRAY_AND_SIZE(send_cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(send_large_frame_i) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_1) } + } +}; +static const TestTx send_large_frames_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_sap_data) }, + { TEST_ARRAY_AND_SIZE(send_cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(send_large_frame_i) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_1) } + },{ + { TEST_ARRAY_AND_SIZE(send_large_frames_i_1) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_2) } + },{ + { TEST_ARRAY_AND_SIZE(send_large_frames_i_2) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_3) } + } +}; +static const TestTx send_large_frames_abort_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_sap_data) }, + { TEST_ARRAY_AND_SIZE(send_cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(send_large_frame_i) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_1) } + },{ /* The remaining data are dropped */ + { TEST_ARRAY_AND_SIZE(disc_4_32_pdu_data) }, + { TEST_ARRAY_AND_SIZE(dm_32_4_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx send_large_frames_disconnect_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_sap_data) }, + { TEST_ARRAY_AND_SIZE(send_cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(send_large_frame_i) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_1) } + },{ + { TEST_ARRAY_AND_SIZE(send_large_frames_i_1) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_2) } + },{ + { TEST_ARRAY_AND_SIZE(send_large_frames_i_2) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_3) } + },{ + { TEST_ARRAY_AND_SIZE(disc_4_32_pdu_data) }, + { TEST_ARRAY_AND_SIZE(dm_32_4_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + } +}; +static const TestTx send_extra_large_frame_pkt [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(symm_pdu_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_snep_sap_data) }, + { TEST_ARRAY_AND_SIZE(send_cc_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(send_large_frame_i) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_1) } + },{ + { TEST_ARRAY_AND_SIZE(send_extra_large_frame_i_1) }, + { TEST_ARRAY_AND_SIZE(send_frame_rr_2) } + } +}; +static const TestSendConfig send_tests [] = { + { + "no_frames", + NULL, 0, TEST_ARRAY_AND_COUNT(send_no_frames_pkt), + nfc_peer_connection_disconnect, TEST_SEND_LATER, + 0, NFC_LLC_CO_DEAD, NFC_LLC_STATE_ACTIVE + },{ + "small_frame", + TEST_ARRAY_AND_COUNT(send_small_frame_send_data), + TEST_ARRAY_AND_COUNT(send_small_frame_pkt), + NULL, TEST_SEND_LATER, + 1, NFC_LLC_CO_ACTIVE, NFC_LLC_STATE_PEER_LOST + },{ + "small_frame_abort", + TEST_ARRAY_AND_COUNT(send_small_frame_send_data), + TEST_ARRAY_AND_COUNT(send_small_frame_abort_pkt), + test_send_connected_abort, TEST_SEND_LATER, + 1, NFC_LLC_CO_DEAD, NFC_LLC_STATE_ACTIVE + },{ + "small_frames", + TEST_ARRAY_AND_COUNT(send_small_frames_send_data), + TEST_ARRAY_AND_COUNT(send_small_frames_pkt), + NULL, TEST_SEND_NO_FLAGS, + 8, NFC_LLC_CO_ACTIVE, NFC_LLC_STATE_PEER_LOST + },{ + "large_frame", + TEST_ARRAY_AND_COUNT(send_large_frame_send_data), + TEST_ARRAY_AND_COUNT(send_large_frame_pkt), + NULL, TEST_SEND_NO_FLAGS, + 128, NFC_LLC_CO_ACTIVE, NFC_LLC_STATE_PEER_LOST + },{ + "large_frames", + TEST_ARRAY_AND_COUNT(send_large_frames_send_data), + TEST_ARRAY_AND_COUNT(send_large_frames_pkt), + NULL, TEST_SEND_NO_FLAGS, + 264, NFC_LLC_CO_ACTIVE, NFC_LLC_STATE_PEER_LOST + },{ + "large_frames_abort", + TEST_ARRAY_AND_COUNT(send_large_frames_send_data), + TEST_ARRAY_AND_COUNT(send_large_frames_abort_pkt), + test_send_connected_abort, TEST_SEND_LATER, + 128, NFC_LLC_CO_DEAD, NFC_LLC_STATE_ACTIVE + },{ + "large_frames_disconnect", + TEST_ARRAY_AND_COUNT(send_large_frames_send_data), + TEST_ARRAY_AND_COUNT(send_large_frames_disconnect_pkt), + nfc_peer_connection_disconnect, TEST_SEND_LATER, + 264, NFC_LLC_CO_DEAD, NFC_LLC_STATE_ACTIVE + },{ + "extra_large_frame", + TEST_ARRAY_AND_COUNT(send_extra_large_frame_send_data), + TEST_ARRAY_AND_COUNT(send_extra_large_frame_pkt), + NULL, TEST_SEND_NO_FLAGS, + 129, NFC_LLC_CO_ACTIVE, NFC_LLC_STATE_PEER_LOST + } +}; + +/*==========================================================================* + * protocol_error + *==========================================================================*/ + +typedef struct test_protocol_error_data { + const char* name; + const TestTx* tx; + guint ntx; +} TestProtocolErrorData; + +static +void +test_protocol_error( + gconstpointer test_data) +{ + const TestProtocolErrorData* test = test_data; + TestService* test_service = test_service_new("foo"); + NfcPeerService* service = NFC_PEER_SERVICE(test_service); + NfcTarget* target = test_target_new_with_tx(test->tx, test->ntx); + NfcLlcParam** params = nfc_llc_param_decode(&llc_param_tlv); + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc; + gulong id; + + g_assert(nfc_peer_services_add(services, service)); + g_assert_cmpuint(service->sap, == ,NFC_LLC_SAP_NAMED); + + llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(llc); + g_assert(llc->state == NFC_LLC_STATE_START); + + /* Wait for the conversation to start */ + id = nfc_llc_add_state_changed_handler(llc, test_llc_quit_loop_cb, loop); + test_run(&test_opt, loop); + if (llc->state == NFC_LLC_STATE_ACTIVE) { + /* Protocol error terminates the loop */ + test_run(&test_opt, loop); + } + g_assert(llc->state == NFC_LLC_STATE_ERROR); + nfc_llc_remove_handler(llc, id); + g_main_loop_unref(loop); + + /* All data must have been sent */ + g_assert_cmpuint(test_target_tx_remaining(target), == ,0); + + nfc_llc_free(llc); + nfc_llc_io_unref(io); + nfc_llc_param_free(params); + nfc_peer_service_unref(service); + nfc_peer_services_unref(services); + nfc_target_unref(target); +} + +static const guint8 protocol_error_packet_too_short_data[] = { 0xaa }; +static const guint8 protocol_error_unhandled_ptype_data[] = { 0x02, 0xc0 }; +static const guint8 protocol_error_symm_too_long_data[] = { 0x00, 0x00, 0x00 }; +static const guint8 protocol_error_symm_invalid_dsap_data[] = { 0x04, 0x00 }; +static const guint8 protocol_error_symm_invalid_ssap_data[] = { 0x00, 0x01 }; +static const guint8 protocol_error_disc_too_long_data[] = { 0x41, 0x60, 0x00 }; +static const guint8 protocol_error_dm_too_short_data[] = { 0x41, 0xe0 }; +static const guint8 protocol_error_frmr_too_short_data[] = { 0x82, 0x00 }; +static const guint8 protocol_error_agf_invalid_dsap_data[] = { 0x04, 0x80 }; +static const guint8 protocol_error_agf_invalid_ssap_data[] = { 0x00, 0x81 }; +static const guint8 protocol_error_agf_broken_1_data[] = { + 0x00, 0x80, 0x00, 0x01 /* Out of bounds */ +}; +static const guint8 protocol_error_agf_broken_2_data[] = { + 0x00, 0x80, 0x00, 0x01, 0x00 /* Encapsulated packet of size 1 */ +}; +static const guint8 protocol_error_agf_broken_3_data[] = { + 0x00, 0x80, 0x00, 0x00, 0x00 /* Garbage at the end */ +}; +static const guint8 protocol_error_i_too_short_data[] = { 0x07, 0x01 }; +static const guint8 protocol_error_rr_too_short_data[] = { 0x07, 0x41 }; +static const guint8 protocol_error_rnr_too_short_data[] = { 0x07, 0x81 }; + +static const TestTx protocol_error_packet_too_short [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_packet_too_short_data) } + } +}; +static const TestTx protocol_error_unhandled_ptype [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_unhandled_ptype_data) } + } +}; +static const TestTx protocol_error_symm_too_long [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_symm_too_long_data) } + } +}; +static const TestTx protocol_error_symm_invalid_dsap [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_symm_invalid_dsap_data) } + } +}; +static const TestTx protocol_error_symm_invalid_ssap [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_symm_invalid_ssap_data) } + } +}; +static const TestTx protocol_error_disc_too_long [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_foo_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_disc_too_long_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx protocol_error_dm_too_short [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(connect_foo_sap_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_foo_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_dm_too_short_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { NULL, 0 } + } +}; +static const TestTx protocol_error_frmr_too_short [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_frmr_too_short_data) } + } +}; +static const TestTx protocol_error_agf_invalid_dsap [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_agf_invalid_dsap_data) } + } +}; +static const TestTx protocol_error_agf_invalid_ssap [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_agf_invalid_ssap_data) } + } +}; +static const TestTx protocol_error_agf_broken_1 [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_agf_broken_1_data) } + } +}; +static const TestTx protocol_error_agf_broken_2 [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_agf_broken_2_data) } + } +}; +static const TestTx protocol_error_agf_broken_3 [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_agf_broken_3_data) } + } +}; +static const TestTx protocol_error_i_too_short [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_i_too_short_data) } + } +}; +static const TestTx protocol_error_rr_too_short [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_rr_too_short_data) } + } +}; +static const TestTx protocol_error_rnr_too_short [] = { + { + { TEST_ARRAY_AND_SIZE(symm_pdu_data) }, + { TEST_ARRAY_AND_SIZE(protocol_error_rnr_too_short_data) } + } +}; + +static const TestProtocolErrorData protocol_error_tests [] = { + { + "packet_too_short", + TEST_ARRAY_AND_COUNT(protocol_error_packet_too_short) + },{ + "unhandled_ptype", + TEST_ARRAY_AND_COUNT(protocol_error_unhandled_ptype) + },{ + "symm_too_long", + TEST_ARRAY_AND_COUNT(protocol_error_symm_too_long) + },{ + "symm_invalid_dsap", + TEST_ARRAY_AND_COUNT(protocol_error_symm_invalid_dsap) + },{ + "symm_invalid_ssap", + TEST_ARRAY_AND_COUNT(protocol_error_symm_invalid_ssap) + },{ + "disc_too_long", + TEST_ARRAY_AND_COUNT(protocol_error_disc_too_long) + },{ + "dm_too_short", + TEST_ARRAY_AND_COUNT(protocol_error_dm_too_short) + },{ + "frmr_too_short", + TEST_ARRAY_AND_COUNT(protocol_error_frmr_too_short) + },{ + "agf_invalid_dsap", + TEST_ARRAY_AND_COUNT(protocol_error_agf_invalid_dsap) + },{ + "agf_invalid_ssap", + TEST_ARRAY_AND_COUNT(protocol_error_agf_invalid_ssap) + },{ + "agf_broken/1", + TEST_ARRAY_AND_COUNT(protocol_error_agf_broken_1) + },{ + "agf_broken/2", + TEST_ARRAY_AND_COUNT(protocol_error_agf_broken_2) + },{ + "agf_broken/3", + TEST_ARRAY_AND_COUNT(protocol_error_agf_broken_3) + },{ + "i_too_short", + TEST_ARRAY_AND_COUNT(protocol_error_i_too_short) + },{ + "rr_too_short", + TEST_ARRAY_AND_COUNT(protocol_error_rr_too_short) + },{ + "rnr_too_short", + TEST_ARRAY_AND_COUNT(protocol_error_rnr_too_short) + } +}; + +/*==========================================================================* + * Common + *==========================================================================*/ + +int main(int argc, char* argv[]) +{ + guint i; + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("basic"), test_basic); + g_test_add_func(TEST_("initiator"), test_initiator); + for (i = 0; i < G_N_ELEMENTS(advanced_tests); i++) { + const TestAdvancedData* test = advanced_tests + i; + char* path = g_strconcat(TEST_("advanced/"), test->name, NULL); + + g_test_add_data_func(path, test, test_advanced); + g_free(path); + } + for (i = 0; i < G_N_ELEMENTS(connect_tests); i++) { + const TestConnectData* test = connect_tests + i; + char* path = g_strconcat(TEST_("connect/"), test->name, NULL); + + g_test_add_data_func(path, test, test_connect); + g_free(path); + } + for (i = 0; i < G_N_ELEMENTS(send_tests); i++) { + const TestSendConfig* test = send_tests + i; + char* path = g_strconcat(TEST_("send/"), test->name, NULL); + + g_test_add_data_func(path, test, test_send); + g_free(path); + } + for (i = 0; i < G_N_ELEMENTS(protocol_error_tests); i++) { + const TestProtocolErrorData* test = protocol_error_tests + i; + char* path = g_strconcat(TEST_("protocol_error/"), test->name, NULL); + + g_test_add_data_func(path, test, test_protocol_error); + g_free(path); + } + test_init(&test_opt, argc, argv); + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/core_llc_param/Makefile b/unit/core_llc_param/Makefile new file mode 100644 index 0000000..31b7896 --- /dev/null +++ b/unit/core_llc_param/Makefile @@ -0,0 +1,5 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_core_llc_param + +include ../common/Makefile diff --git a/unit/core_llc_param/test_core_llc_param.c b/unit/core_llc_param/test_core_llc_param.c new file mode 100644 index 0000000..56cab39 --- /dev/null +++ b/unit/core_llc_param/test_core_llc_param.c @@ -0,0 +1,738 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_common.h" + +#include "nfc_llc_param.h" + +static TestOpt test_opt; + +#define TEST_(name) "/core/llc_param/" name +#define TESTE_(name) TEST_("encode/" name) +#define TESTD_(name) TEST_("decode/" name) + +typedef struct test_single_param_data { + const char* name; + GUtilData tlv; + NfcLlcParam param; +} TestSingleParamData; + +static const guint8 tlv_version_1_0[] = { + NFC_LLC_PARAM_VERSION, 0x01, NFC_LLCP_VERSION_1_0 +}; +static const guint8 tlv_wks[] = { + NFC_LLC_PARAM_WKS, 0x02, 0x01, 0x03 +}; +static const guint8 tlv_lto[] = { + NFC_LLC_PARAM_LTO, 0x01, 0x01 +}; +static const guint8 tlv_sn[] = { + NFC_LLC_PARAM_SN, 0x0f, + 'u', 'r', 'n', ':', 'n', 'f', 'c', ':', + 's', 'n', ':', 's', 'n', 'e', 'p' +}; +static const guint8 tlv_empty_sn[] = { + NFC_LLC_PARAM_SN, 0x00 +}; +static const guint8 tlv_opt[] = { + NFC_LLC_PARAM_OPT, 0x01, NFC_LLC_OPT_CL | NFC_LLC_OPT_CO +}; +static const guint8 tlv_empty_sdreq[] = { + NFC_LLC_PARAM_SDREQ, 0x01, 0x0a +}; +static const guint8 tlv_sdreq[] = { + NFC_LLC_PARAM_SDREQ, 0x10, 0x0a, 'u', 'r', 'n', ':', + 'n', 'f', 'c', ':', 's', 'n', ':', 's', 'n', 'e', 'p' +}; + +/*==========================================================================* + * null + *==========================================================================*/ + +static +void +test_null( + void) +{ + g_assert(!nfc_llc_param_encode(NULL, NULL, 0)); + g_assert(!nfc_llc_param_decode(NULL)); + nfc_llc_param_free(NULL); +} + +/*==========================================================================* + * empty + *==========================================================================*/ + +static +void +test_empty( + void) +{ + GUtilData tlv; + NfcLlcParam** params; + static const guint8 data[] = { 0x00, 0x00 }; + GByteArray* bytes; + + memset(&tlv, 0, sizeof(tlv)); + params = nfc_llc_param_decode(&tlv); + g_assert(params); + g_assert(!params[0]); + nfc_llc_param_free(params); + + TEST_BYTES_SET(tlv, data); + params = nfc_llc_param_decode(&tlv); + g_assert(params); + g_assert(!params[0]); + + /* Encoding empty (but non-NULL) list produces empty (but non-NULL) + * byte array */ + bytes = nfc_llc_param_encode(nfc_llc_param_constify(params), NULL, 0); + g_assert(bytes); + g_assert(!bytes->len); + g_byte_array_free(bytes, TRUE); + + nfc_llc_param_free(params); +} + +/*==========================================================================* + * find + *==========================================================================*/ + +static +void +test_find( + void) +{ + GUtilData tlv; + NfcLlcParam** params; + const NfcLlcParam* param; + + g_assert(!nfc_llc_param_find(NULL, NFC_LLC_PARAM_VERSION)); + + TEST_BYTES_SET(tlv, tlv_version_1_0); + params = nfc_llc_param_decode(&tlv); + g_assert(params); + param = params[0]; + g_assert(param); + g_assert(param->type == NFC_LLC_PARAM_VERSION); + g_assert(!params[1]); + + g_assert(nfc_llc_param_find(nfc_llc_param_constify(params), + NFC_LLC_PARAM_VERSION) == param); + g_assert(!nfc_llc_param_find(nfc_llc_param_constify(params), + NFC_LLC_PARAM_SN)); + + nfc_llc_param_free(params); +} + +/*==========================================================================* + * count + *==========================================================================*/ + +static +void +test_count( + void) +{ + const NfcLlcParam* params[2]; + static NfcLlcParam param = { NFC_LLC_PARAM_VERSION, .value.version = + NFC_LLCP_VERSION_1_0 }; + + params[0] = NULL; + g_assert(!nfc_llc_param_count(NULL)); + g_assert(!nfc_llc_param_count(params)); + + params[0] = ¶m; + params[1] = NULL; + g_assert_cmpuint(nfc_llc_param_count(params), == ,1); +} + +/*==========================================================================* + * truncate + *==========================================================================*/ + +static +void +test_truncate( + void) +{ + static NfcLlcParam param1 = { NFC_LLC_PARAM_VERSION, .value.version = + NFC_LLCP_VERSION_1_0 }; + static NfcLlcParam param2 = { NFC_LLC_PARAM_VERSION, .value.version = + NFC_LLCP_VERSION_1_1 }; + static const guint8 tlv[] = { + NFC_LLC_PARAM_VERSION, 0x01, NFC_LLCP_VERSION_1_0, + NFC_LLC_PARAM_VERSION, 0x01, NFC_LLCP_VERSION_1_1 + }; + const NfcLlcParam* params[3]; + GByteArray* bytes = NULL; + + /* Nothing fits at all */ + params[0] = ¶m1; + params[1] = NULL; + bytes = nfc_llc_param_encode(params, bytes, 1); + g_assert(bytes); + g_assert_cmpuint(bytes->len, == ,0); + + /* Still nothing fits */ + params[1] = ¶m2; + params[2] = NULL; + bytes = nfc_llc_param_encode(params, bytes, 1); + g_assert(bytes); + g_assert_cmpuint(bytes->len, == ,0); + + /* One thing fits */ + bytes = nfc_llc_param_encode(params, bytes, 3); + g_assert(bytes); + g_assert_cmpuint(bytes->len, == ,3); + g_assert(!memcmp(tlv, bytes->data, bytes->len)); + + /* Both things fit (limited by exact size) */ + g_byte_array_set_size(bytes, 0); + bytes = nfc_llc_param_encode(params, bytes, 6); + g_assert(bytes); + g_assert_cmpuint(bytes->len, == ,sizeof(tlv)); + g_assert(!memcmp(tlv, bytes->data, bytes->len)); + + /* Both things fit again (plenty of space available) */ + g_byte_array_set_size(bytes, 0); + bytes = nfc_llc_param_encode(params, bytes, 2 * sizeof(tlv)); + g_assert(bytes); + g_assert_cmpuint(bytes->len, == ,sizeof(tlv)); + g_assert(!memcmp(tlv, bytes->data, bytes->len)); + + g_byte_array_free(bytes, TRUE); +} + +/*==========================================================================* + * encode_list + *==========================================================================*/ + +static +void +test_encode_list( + void) +{ + static NfcLlcParam invalid = { 0 }; + static NfcLlcParam version = { NFC_LLC_PARAM_VERSION, .value.version = + NFC_LLCP_VERSION_1_0 }; + const NfcLlcParam* params[3] = { &invalid, &version, NULL }; + GByteArray* bytes = nfc_llc_param_encode(params, NULL, 0); + + g_assert(bytes); + g_assert_cmpuint(bytes->len, == ,sizeof(tlv_version_1_0)); + g_assert(!memcmp(bytes->data, tlv_version_1_0, bytes->len)); + g_byte_array_free(bytes, TRUE); +} + +/*==========================================================================* + * decode_list + *==========================================================================*/ + +static +void +test_decode_list( + void) +{ + GUtilData tlv; + NfcLlcParam** params; + const NfcLlcParam* param; + static const guint8 data[] = { + 0x00, 0x00, + NFC_LLC_PARAM_VERSION, 0x01, NFC_LLCP_VERSION_1_0, + NFC_LLC_PARAM_WKS, 0x02, 0x00, 0x03 + }; + + TEST_BYTES_SET(tlv, data); + params = nfc_llc_param_decode(&tlv); + g_assert(params); + + param = params[0]; + g_assert(param); + g_assert_cmpint(param->type, == ,NFC_LLC_PARAM_VERSION); + g_assert_cmpint(param->value.version, == ,NFC_LLCP_VERSION_1_0); + + param = params[1]; + g_assert(param); + g_assert_cmpint(param->type, == ,NFC_LLC_PARAM_WKS); + g_assert_cmpint(param->value.wks, == ,0x03); + + g_assert(!params[2]); + nfc_llc_param_free(params); +} + +/*==========================================================================* + * decode_bytes + *==========================================================================*/ + +static +void +test_decode_bytes( + void) +{ + NfcLlcParam** params; + const NfcLlcParam* param; + static const guint8 data[] = { + NFC_LLC_PARAM_VERSION, 0x01, NFC_LLCP_VERSION_1_0 + }; + + g_assert(!nfc_llc_param_decode_bytes(NULL, 0)); + + params = nfc_llc_param_decode_bytes(data, 0); + g_assert(params); + g_assert(!params[0]); + nfc_llc_param_free(params); + + params = nfc_llc_param_decode_bytes(data, sizeof(data)); + g_assert(params); + + param = params[0]; + g_assert(param); + g_assert_cmpint(param->type, == ,NFC_LLC_PARAM_VERSION); + g_assert_cmpint(param->value.version, == ,NFC_LLCP_VERSION_1_0); + g_assert(!params[1]); + + nfc_llc_param_free(params); +} + +/*==========================================================================* + * encode_single_param + *==========================================================================*/ + +static const guint8 tlv_encode_miux[] = { + NFC_LLC_PARAM_MIUX, 0x02, 0x00, 0x02 +}; +static const guint8 tlv_encode_miux_default[] = { + NFC_LLC_PARAM_MIUX, 0x02, 0x00, 0x00 +}; +static const guint8 tlv_encode_lto_max[] = { + NFC_LLC_PARAM_LTO, 0x01, 0xff +}; +static const guint8 tlv_encode_rw[] = { + NFC_LLC_PARAM_RW, 0x01, 0x07 +}; +static const guint8 tlv_encode_rw_max[] = { + NFC_LLC_PARAM_RW, 0x01, 0x0f +}; +static const guint8 tlv_encode_truncate_sn[] = { + NFC_LLC_PARAM_SN, 0xff, + 'u', 'r', 'n', ':', 'n', 'f', 'c', ':', + 'x', 's', 'n', ':', 't', 'e', 's', 't', + ':', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x' +}; +static const guint8 tlv_encode_truncate_sdreq[] = { + NFC_LLC_PARAM_SDREQ, 0xff, 0x0a, + 'u', 'r', 'n', ':', 'n', 'f', 'c', ':', + 'x', 's', 'n', ':', 't', 'e', 's', 't', + ':', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', + 'x', 'x', 'x', 'x', 'x', 'x' +}; +static const guint8 tlv_encode_sdres[] = { + NFC_LLC_PARAM_SDRES, 0x02, 0x01, 0x04 +}; + +static const TestSingleParamData encode_single_param_tests[] = { + { + TESTE_("version"), + { TEST_ARRAY_AND_SIZE(tlv_version_1_0) }, + { NFC_LLC_PARAM_VERSION, .value.version = NFC_LLCP_VERSION_1_0 } + },{ + TESTE_("miux"), + { TEST_ARRAY_AND_SIZE(tlv_encode_miux) }, + { NFC_LLC_PARAM_MIUX, .value.miu = NFC_LLC_MIU_DEFAULT + 2 } + },{ + TESTE_("miux_default"), + { TEST_ARRAY_AND_SIZE(tlv_encode_miux_default) }, + { NFC_LLC_PARAM_MIUX, .value.miu = NFC_LLC_MIU_DEFAULT } + },{ + TESTE_("wks"), + { TEST_ARRAY_AND_SIZE(tlv_wks) }, + { NFC_LLC_PARAM_WKS, .value.wks = 0x0103 } + },{ + TESTE_("lto"), + { TEST_ARRAY_AND_SIZE(tlv_lto) }, + { NFC_LLC_PARAM_LTO, .value.lto = 10 } + },{ + TESTE_("lto_max"), + { TEST_ARRAY_AND_SIZE(tlv_encode_lto_max) }, + { NFC_LLC_PARAM_LTO, .value.lto = 3000 /* anything above 2550 */ } + },{ + TESTE_("rw"), + { TEST_ARRAY_AND_SIZE(tlv_encode_rw) }, + { NFC_LLC_PARAM_RW, .value.rw = 0x07 } + },{ + TESTE_("rw_max"), + { TEST_ARRAY_AND_SIZE(tlv_encode_rw_max) }, + { NFC_LLC_PARAM_RW, .value.rw = 0x10 /* anything above 0x0f */} + },{ + TESTE_("sn"), + { TEST_ARRAY_AND_SIZE(tlv_sn) }, + { NFC_LLC_PARAM_SN, .value.sn = "urn:nfc:sn:snep" } + },{ + TESTE_("empty_sn"), + { TEST_ARRAY_AND_SIZE(tlv_empty_sn) }, + { NFC_LLC_PARAM_SN, .value.sn = "" } + },{ + TESTE_("null_sn"), + { TEST_ARRAY_AND_SIZE(tlv_empty_sn) }, + { NFC_LLC_PARAM_SN } + },{ + TESTE_("truncate_sn"), + { TEST_ARRAY_AND_SIZE(tlv_encode_truncate_sn) }, + { NFC_LLC_PARAM_SN, .value.sn = + "urn:nfc:xsn:test:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + },{ + TESTE_("opt"), + { TEST_ARRAY_AND_SIZE(tlv_opt) }, + { NFC_LLC_PARAM_OPT, .value.opt = NFC_LLC_OPT_CL | NFC_LLC_OPT_CO } + },{ + TESTE_("sdreq"), + { TEST_ARRAY_AND_SIZE(tlv_sdreq) }, + { NFC_LLC_PARAM_SDREQ, .value.sdreq = { .tid = 0x0a, .uri = + "urn:nfc:sn:snep"} } + },{ + TESTE_("null_sdreq"), + { TEST_ARRAY_AND_SIZE(tlv_empty_sdreq) }, + { NFC_LLC_PARAM_SDREQ, .value.sdreq = { .tid = 0x0a } } + },{ + TESTE_("truncate_sdreq"), + { TEST_ARRAY_AND_SIZE(tlv_encode_truncate_sdreq) }, + { NFC_LLC_PARAM_SDREQ, .value.sdreq = { .tid = 0x0a, .uri = + "urn:nfc:xsn:test:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}}, + },{ + TESTE_("sdres"), + { TEST_ARRAY_AND_SIZE(tlv_encode_sdres) }, + { NFC_LLC_PARAM_SDRES, .value.sdres = { .tid = 0x01, .sap = 0x04 } } + } +}; + +static +void +test_encode_single_param( + gconstpointer test_data) +{ + const TestSingleParamData* test = test_data; + const NfcLlcParam* params[2]; + GByteArray* bytes; + + params[0] = &test->param; + params[1] = NULL; + bytes = nfc_llc_param_encode(params, g_byte_array_new(), 0); + + g_assert(bytes); + g_assert_cmpint(bytes->len, == ,test->tlv.size); + g_assert(!memcmp(bytes->data, test->tlv.bytes, test->tlv.size)); + g_byte_array_free(bytes, TRUE); +} + +/*==========================================================================* + * decode_single_param + *==========================================================================*/ + +static const guint8 tlv_decode_miux[] = { + NFC_LLC_PARAM_MIUX, 0x02, 0x80 /* bit 0x80 is ignored */, 0x02 +}; +static const guint8 tlv_lto_default[] = { + NFC_LLC_PARAM_LTO, 0x01, 0x00 +}; +static const guint8 tlv_decode_rw[] = { + NFC_LLC_PARAM_RW, 0x01, 0x1f /* bit 0x10 is ignored */ +}; +static const guint8 tlv_decode_sdres[] = { + NFC_LLC_PARAM_SDRES, 0x02, 0x01, 0xff /* bits 0xc0 are ignored */ +}; + +static const TestSingleParamData decode_single_param_tests[] = { + { + TESTD_("version"), + { TEST_ARRAY_AND_SIZE(tlv_version_1_0) }, + { NFC_LLC_PARAM_VERSION, .value.version = NFC_LLCP_VERSION_1_0 } + },{ + TESTD_("miux"), + { TEST_ARRAY_AND_SIZE(tlv_decode_miux) }, + { NFC_LLC_PARAM_MIUX, .value.miu = NFC_LLC_MIU_DEFAULT + 2 } + },{ + TESTD_("wks"), + { TEST_ARRAY_AND_SIZE(tlv_wks) }, + { NFC_LLC_PARAM_WKS, .value.wks = 0x0103 } + },{ + TESTD_("lto"), + { TEST_ARRAY_AND_SIZE(tlv_lto) }, + { NFC_LLC_PARAM_LTO, .value.lto = 10 } + },{ + TESTD_("lto_default"), + { TEST_ARRAY_AND_SIZE(tlv_lto_default) }, + { NFC_LLC_PARAM_LTO, .value.lto = NFC_LLC_LTO_DEFAULT } + },{ + TESTD_("rw"), + { TEST_ARRAY_AND_SIZE(tlv_decode_rw) }, + { NFC_LLC_PARAM_RW, .value.rw = 0x0f } + },{ + TESTD_("opt"), + { TEST_ARRAY_AND_SIZE(tlv_opt) }, + { NFC_LLC_PARAM_OPT, .value.opt = NFC_LLC_OPT_CL | NFC_LLC_OPT_CO } + },{ + TESTD_("sdres"), + { TEST_ARRAY_AND_SIZE(tlv_decode_sdres) }, + { NFC_LLC_PARAM_SDRES, .value.sdres = { .tid = 0x01, .sap = 0x3f } } + } +}; + +static +void +test_decode_single_param( + gconstpointer test_data) +{ + const TestSingleParamData* test = test_data; + NfcLlcParam** params; + const NfcLlcParam* param; + + params = nfc_llc_param_decode(&test->tlv); + g_assert(params); + param = params[0]; + g_assert(param); + g_assert_cmpint(param->type, == ,test->param.type); + g_assert(!memcmp(¶m->value, &test->param.value, sizeof(param->value))); + + g_assert(!params[1]); + nfc_llc_param_free(params); +} + +/*==========================================================================* + * sn + *==========================================================================*/ + +static +void +test_sn( + void) +{ + GUtilData tlv; + NfcLlcParam** params; + const NfcLlcParam* param; + + TEST_BYTES_SET(tlv, tlv_sn); + params = nfc_llc_param_decode(&tlv); + g_assert(params); + param = params[0]; + g_assert(param); + g_assert_cmpint(param->type, == ,NFC_LLC_PARAM_SN); + g_assert_cmpstr(param->value.sn, == ,"urn:nfc:sn:snep"); + + g_assert(!params[1]); + nfc_llc_param_free(params); +} + +/*==========================================================================* + * sdreq + *==========================================================================*/ + +static +void +test_sdreq( + void) +{ + GUtilData tlv; + NfcLlcParam** params; + const NfcLlcParam* param; + + TEST_BYTES_SET(tlv, tlv_sdreq); + params = nfc_llc_param_decode(&tlv); + g_assert(params); + param = params[0]; + g_assert(param); + g_assert_cmpint(param->type, == ,NFC_LLC_PARAM_SDREQ); + g_assert_cmpint(param->value.sdreq.tid, == ,0x0a); + g_assert_cmpstr(param->value.sdreq.uri, == ,"urn:nfc:sn:snep"); + + g_assert(!params[1]); + nfc_llc_param_free(params); +} + +/*==========================================================================* + * decode_invalid_param + *==========================================================================*/ + +typedef struct test_invalid_param_data { + const char* name; + GUtilData tlv; +} TestInvalidParamData; + +static const guint8 tlv_oob[] = { 0x00, 0x01 /* out of bounds */ }; +static const guint8 tlv_invalid_version[] = { NFC_LLC_PARAM_VERSION, 0x00 }; +static const guint8 tlv_invalid_miux[] = { NFC_LLC_PARAM_MIUX, 0x01, 0x00 }; +static const guint8 tlv_invalid_wks[] = { NFC_LLC_PARAM_WKS, 0x01, 0x00 }; +static const guint8 tlv_invalid_lto[] = { NFC_LLC_PARAM_LTO, 0x00 }; +static const guint8 tlv_invalid_rw[] = { NFC_LLC_PARAM_RW, 0x00 }; +static const guint8 tlv_invalid_opt[] = { NFC_LLC_PARAM_OPT, 0x00 }; +static const guint8 tlv_invalid_sdreq[] = { NFC_LLC_PARAM_SDREQ, 0x00 }; +static const guint8 tlv_invalid_sdres[] = { NFC_LLC_PARAM_SDRES, 0x01, 0x00 }; + +static const TestInvalidParamData decode_invalid_param_tests[] = { + { TESTD_("oob"), {TEST_ARRAY_AND_SIZE(tlv_oob)} }, + { TESTD_("invalid_version"), {TEST_ARRAY_AND_SIZE(tlv_invalid_version)} }, + { TESTD_("invalid_miux"), {TEST_ARRAY_AND_SIZE(tlv_invalid_miux)} }, + { TESTD_("invalid_wks"), {TEST_ARRAY_AND_SIZE(tlv_invalid_wks)} }, + { TESTD_("invalid_lto"), {TEST_ARRAY_AND_SIZE(tlv_invalid_lto)} }, + { TESTD_("invalid_rw"), {TEST_ARRAY_AND_SIZE(tlv_invalid_rw)} }, + { TESTD_("invalid_opt"), {TEST_ARRAY_AND_SIZE(tlv_invalid_opt)} }, + { TESTD_("invalid_sdreq"), {TEST_ARRAY_AND_SIZE(tlv_invalid_sdreq)} }, + { TESTD_("invalid_sdres"), {TEST_ARRAY_AND_SIZE(tlv_invalid_sdres)} } +}; + +static +void +test_decode_invalid_param( + gconstpointer test_data) +{ + const TestInvalidParamData* test = test_data; + NfcLlcParam** params = nfc_llc_param_decode(&test->tlv); + + g_assert(params); + g_assert(!params[0]); + nfc_llc_param_free(params); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +int main(int argc, char* argv[]) +{ + guint i; + + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("empty"), test_empty); + g_test_add_func(TEST_("find"), test_find); + g_test_add_func(TEST_("count"), test_count); + g_test_add_func(TESTE_("truncate"), test_truncate); + g_test_add_func(TESTE_("list"), test_encode_list); + g_test_add_func(TESTD_("list"), test_decode_list); + g_test_add_func(TESTD_("bytes"), test_decode_bytes); + g_test_add_func(TESTD_("sn"), test_sn); + g_test_add_func(TESTD_("sdreq"), test_sdreq); + for (i = 0; i < G_N_ELEMENTS(encode_single_param_tests); i++) { + const TestSingleParamData* test = encode_single_param_tests + i; + + g_test_add_data_func(test->name, test, test_encode_single_param); + } + for (i = 0; i < G_N_ELEMENTS(decode_single_param_tests); i++) { + const TestSingleParamData* test = decode_single_param_tests + i; + + g_test_add_data_func(test->name, test, test_decode_single_param); + } + for (i = 0; i < G_N_ELEMENTS(decode_invalid_param_tests); i++) { + const TestInvalidParamData* test = decode_invalid_param_tests + i; + + g_test_add_data_func(test->name, test, test_decode_invalid_param); + } + test_init(&test_opt, argc, argv); + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/core_manager/test_core_manager.c b/unit/core_manager/test_core_manager.c index b8a7bdf..b22f730 100644 --- a/unit/core_manager/test_core_manager.c +++ b/unit/core_manager/test_core_manager.c @@ -183,12 +183,17 @@ test_null( g_assert(!nfc_manager_add_adapter_added_handler(NULL, NULL, NULL)); g_assert(!nfc_manager_add_adapter_removed_handler(NULL, NULL, NULL)); g_assert(!nfc_manager_add_enabled_changed_handler(NULL, NULL, NULL)); + g_assert(!nfc_manager_add_mode_changed_handler(NULL, NULL, NULL)); g_assert(!nfc_manager_add_stopped_handler(NULL, NULL, NULL)); + g_assert(!nfc_manager_mode_request_new(NULL, 0, 0)); + nfc_manager_mode_request_free(NULL); nfc_manager_stop(NULL, 0); nfc_manager_set_enabled(NULL, FALSE); nfc_manager_request_power(NULL, FALSE); nfc_manager_request_mode(NULL, NFC_MODE_NONE); + nfc_manager_register_service(NULL, NULL); + nfc_manager_unregister_service(NULL, NULL); nfc_manager_remove_adapter(NULL, NULL); nfc_manager_remove_handler(NULL, 0); nfc_manager_remove_handlers(NULL, NULL, 0); @@ -218,6 +223,10 @@ test_basic( g_assert(plugins); g_assert(!plugins[0]); + /* NULL services are ignored */ + g_assert(!nfc_manager_register_service(manager, NULL)); + nfc_manager_unregister_service(manager, NULL); + /* No adapters */ g_assert(!nfc_manager_get_adapter(manager, "foo")); id = nfc_manager_add_adapter_removed_handler(manager, @@ -348,6 +357,87 @@ test_adapter( nfc_manager_unref(manager); } +/*==========================================================================* + * mode + *==========================================================================*/ + +static +void +test_mode( + void) +{ + NfcPluginsInfo pi; + NfcManager* manager; + int count = 0; + gulong id; + NfcModeRequest* enable_p2p; + NfcModeRequest* enable_all; + NfcModeRequest* enable_all2; + NfcModeRequest* disable_p2p; + + memset(&pi, 0, sizeof(pi)); + manager = nfc_manager_new(&pi); + nfc_manager_request_mode(manager, NFC_MODE_READER_WRITER); + + /* Add the listener */ + g_assert(!nfc_manager_add_mode_changed_handler(manager, NULL, NULL)); + id = nfc_manager_add_mode_changed_handler(manager, + test_manager_inc, &count); + + /* Core is refusing to create mode requests with no mode */ + g_assert(!nfc_manager_mode_request_new(manager, 0, 0)); + + /* Enable P2P modes (NFC_MODE_P2P_INITIATOR disable bit gets ignored) */ + enable_p2p = nfc_manager_mode_request_new(manager, NFC_MODES_P2P, + NFC_MODE_P2P_INITIATOR); + g_assert_cmpint(manager->mode, == ,NFC_MODES_P2P | NFC_MODE_READER_WRITER); + g_assert_cmpint(count, == ,1); + count = 0; + + /* Try to disable those but they stay enabled */ + disable_p2p = nfc_manager_mode_request_new(manager, 0, NFC_MODES_P2P); + g_assert_cmpint(manager->mode, == ,NFC_MODES_P2P | NFC_MODE_READER_WRITER); + g_assert_cmpint(count, == ,0); + + /* Add another enable request on top of that */ + enable_all = nfc_manager_mode_request_new(manager, NFC_MODES_ALL, 0); + g_assert_cmpint(manager->mode, == , NFC_MODES_ALL); + g_assert_cmpint(count, == ,1); + count = 0; + + /* And the same request (no changes are signaled this time) */ + enable_all2 = nfc_manager_mode_request_new(manager, NFC_MODES_ALL, 0); + g_assert_cmpint(manager->mode, == , NFC_MODES_ALL); + g_assert_cmpint(count, == ,0); + + /* P2P modes get disabled when we release enable_p2p request */ + nfc_manager_mode_request_free(enable_p2p); + g_assert_cmpint(manager->mode, == ,NFC_MODE_READER_WRITER | + NFC_MODE_CARD_EMILATION); + g_assert_cmpint(count, == ,1); + count = 0; + + /* And re-enabled when we release disable_p2p */ + nfc_manager_mode_request_free(disable_p2p); + g_assert_cmpint(manager->mode, == , NFC_MODES_ALL); + g_assert_cmpint(count, == ,1); + count = 0; + + /* enable_all2 remains active after we release enable_all */ + nfc_manager_mode_request_free(enable_all); + g_assert_cmpint(manager->mode, == , NFC_MODES_ALL); + g_assert_cmpint(count, == ,0); + + /* We are back to the default when all requests are released */ + nfc_manager_mode_request_free(enable_all2); + g_assert_cmpint(manager->mode, == , NFC_MODE_READER_WRITER); + g_assert_cmpint(count, == ,1); + count = 0; + + nfc_manager_remove_handler(manager, id); + nfc_manager_unref(manager); +} + /*==========================================================================* * Common *==========================================================================*/ @@ -363,6 +453,7 @@ int main(int argc, char* argv[]) g_test_add_func(TEST_("null"), test_null); g_test_add_func(TEST_("basic"), test_basic); g_test_add_func(TEST_("adapter"), test_adapter); + g_test_add_func(TEST_("mode"), test_mode); test_init(&test_opt, argc, argv); return g_test_run(); } diff --git a/unit/core_peer/Makefile b/unit/core_peer/Makefile new file mode 100644 index 0000000..2d08a8b --- /dev/null +++ b/unit/core_peer/Makefile @@ -0,0 +1,7 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_core_peer + +COMMON_SRC = test_main.c test_target.c test_initiator.c + +include ../common/Makefile diff --git a/unit/core_peer/test_core_peer.c b/unit/core_peer/test_core_peer.c new file mode 100644 index 0000000..78171d7 --- /dev/null +++ b/unit/core_peer/test_core_peer.c @@ -0,0 +1,944 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_peer_p.h" +#include "nfc_target.h" +#include "nfc_initiator.h" +#include "nfc_peer_services.h" +#include "nfc_peer_service_impl.h" +#include "nfc_peer_socket.h" +#include "nfc_ndef.h" + +#include "test_common.h" +#include "test_target.h" +#include "test_initiator.h" + +#include + +static TestOpt test_opt; + +static const guint8 initial_llcp_params [] = { + 0x46, 0x66, 0x6d, 0x01, 0x01, 0x11, 0x02, 0x02, + 0x07, 0xff, 0x03, 0x02, 0x00, 0x13, 0x04, 0x01, + 0xff +}; + +static const NfcParamNfcDepTarget target_params = { + { TEST_ARRAY_AND_SIZE(initial_llcp_params) } +}; + +static const NfcParamNfcDepInitiator initiator_params = { + { TEST_ARRAY_AND_SIZE(initial_llcp_params) } +}; + +static const guint8 symm_data[] = { 0x00, 0x00 }; + +static +void +test_peer_quit_loop_cb( + NfcPeer* peer, + void* user_data) +{ + g_main_loop_quit((GMainLoop*)user_data); +} + +static +void +test_peer_not_reached_cb( + NfcPeer* peer, + void* user_data) +{ + g_assert_not_reached(); +} + +static +void +test_peer_inc( + NfcPeer* peer, + void* user_data) +{ + (*((int*)user_data))++; +} + +/*==========================================================================* + * Test service + *==========================================================================*/ + +typedef NfcPeerServiceClass TestServiceClass; +typedef struct test_service { + NfcPeerService service; + gboolean fail_connect; + int peer_in; + int peer_out; +} TestService; + +G_DEFINE_TYPE(TestService, test_service, NFC_TYPE_PEER_SERVICE) +#define TEST_TYPE_SERVICE (test_service_get_type()) +#define TEST_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + TEST_TYPE_SERVICE, TestService)) + +static +void +test_service_peer_arrived( + NfcPeerService* service, + NfcPeer* peer) +{ + TEST_SERVICE(service)->peer_in++; +} + +static +void +test_service_peer_left( + NfcPeerService* service, + NfcPeer* peer) +{ + TEST_SERVICE(service)->peer_out++; +} + +static +NfcPeerConnection* +test_service_new_connect( + NfcPeerService* service, + guint8 rsap, + const char* name) +{ + return TEST_SERVICE(service)->fail_connect ? NULL : + NFC_PEER_CONNECTION(nfc_peer_socket_new_connect(service, rsap, name)); +} + +static +void +test_service_init( + TestService* self) +{ +} + +static +void +test_service_class_init( + TestServiceClass* klass) +{ + klass->peer_arrived = test_service_peer_arrived; + klass->peer_left = test_service_peer_left; + klass->new_connect = test_service_new_connect; +} + +static +TestService* +test_service_client_new( + void) +{ + NfcPeerService* service = g_object_new(TEST_TYPE_SERVICE, NULL); + + nfc_peer_service_init_base(service, NULL); + return TEST_SERVICE(service); +} + +static +TestService* +test_service_client_new_fail( + void) +{ + TestService* self = g_object_new(TEST_TYPE_SERVICE, NULL); + NfcPeerService* service = &self->service; + + nfc_peer_service_init_base(service, NULL); + service->sap = NFC_LLC_SAP_UNNAMED; + self->fail_connect = TRUE; + return self; +} + +/*==========================================================================* + * null + *==========================================================================*/ + +static +void +test_null( + void) +{ + /* Public interfaces are NULL tolerant */ + g_assert(!nfc_peer_new_target(NULL, NFC_TECHNOLOGY_A, NULL, NULL)); + g_assert(!nfc_peer_new_initiator(NULL, NFC_TECHNOLOGY_A, NULL, NULL)); + g_assert(!nfc_peer_ref(NULL)); + g_assert(!nfc_peer_connect(NULL, NULL, 0, NULL, NULL, NULL)); + g_assert(!nfc_peer_connect_sn(NULL, NULL, NULL, NULL, NULL, NULL)); + g_assert(!nfc_peer_add_wks_changed_handler(NULL, NULL, NULL)); + g_assert(!nfc_peer_add_ndef_changed_handler(NULL, NULL, NULL)); + g_assert(!nfc_peer_add_initialized_handler(NULL, NULL, NULL)); + g_assert(!nfc_peer_add_gone_handler(NULL, NULL, NULL)); + g_assert(!nfc_peer_register_service(NULL, NULL)); + nfc_peer_deactivate(NULL); + nfc_peer_unregister_service(NULL, NULL); + nfc_peer_remove_handler(NULL, 0); + nfc_peer_unref(NULL); +} + +/*==========================================================================* + * name + *==========================================================================*/ + +static +void +test_name( + void) +{ + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { NULL, 0 } + } + }; + + NfcTarget* target = test_target_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + NfcPeer* peer = nfc_peer_new_initiator(target, NFC_TECHNOLOGY_A, + &initiator_params, NULL); + const char* name = "foo"; + + g_assert(peer); + g_assert(!peer->name); + nfc_peer_set_name(peer, NULL); + g_assert(!peer->name); + nfc_peer_set_name(peer, name); + g_assert_cmpstr(peer->name, == ,name); + + nfc_peer_unref(peer); + nfc_target_unref(target); +} + +/*==========================================================================* + * no_magic + *==========================================================================*/ + +static +void +test_no_magic( + gconstpointer data) +{ + NfcTarget* target = test_target_new(TEST_TARGET_FAIL_ALL); + const NfcParamNfcDepInitiator* param = data; + + g_assert(!nfc_peer_new_initiator(target, NFC_TECHNOLOGY_A, param, NULL)); + nfc_target_unref(target); +} + +static const guint8 no_magic_data_1 [] = { 0x46, 0x66 }; +static const guint8 no_magic_data_2 [] = { 0x66, 0x66, 0x66, 0x66 }; +static const NfcParamNfcDepInitiator no_magic_1 = { + { TEST_ARRAY_AND_SIZE(no_magic_data_1) } +}; +static const NfcParamNfcDepInitiator no_magic_2 = { + { TEST_ARRAY_AND_SIZE(no_magic_data_2) } +}; + +/*==========================================================================* + * no_param + *==========================================================================*/ + +static +void +test_no_param_target( + void) +{ + NfcInitiator* initiator = test_initiator_new(); + + g_assert(!nfc_peer_new_target(initiator, NFC_TECHNOLOGY_A, NULL, NULL)); + nfc_initiator_unref(initiator); +} + +static +void +test_no_param_initiator( + void) +{ + NfcTarget* target = test_target_new(TEST_TARGET_FAIL_ALL); + + g_assert(!nfc_peer_new_initiator(target, NFC_TECHNOLOGY_A, NULL, NULL)); + nfc_target_unref(target); +} + +/*==========================================================================* + * ndef + *==========================================================================*/ + +static +void +test_ndef( + void) +{ + static const guint8 atr_res_g [] = { + 0x46, 0x66, 0x6d, 0x01, 0x01, 0x11, 0x02, 0x02, + 0x07, 0xff, 0x03, 0x02, 0x00, 0x13, 0x04, 0x01, + 0xff + }; + static const NfcParamNfcDepInitiator params = { + { TEST_ARRAY_AND_SIZE(atr_res_g) } + }; + static const guint8 connect_snep_data[] = { + 0x05, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f, 0x06, 0x0f, 0x75, 0x72, 0x6e, 0x3a, 0x6e, + 0x66, 0x63, 0x3a, 0x73, 0x6e, 0x3a, 0x73, 0x6e, + 0x65, 0x70 + }; + static const guint8 cc_snep_data[] = { + 0x81, 0x84, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f + }; + static const guint8 i_snep_4_32_put_data[] = { + 0x13, 0x20, 0x00, + 0x10, 0x02, 0x00, 0x00, 0x00, 0x1f, + 0xd1, 0x02, 0x1a, 0x53, 0x70, 0x91, 0x01, 0x0a, + 0x55, 0x03, 0x6a, 0x6f, 0x6c, 0x6c, 0x61, 0x2e, + 0x63, 0x6f, 0x6d, 0x51, 0x01, 0x08, 0x54, 0x02, + 0x65, 0x6e, 0x4a, 0x6f, 0x6c, 0x6c, 0x61 + }; + static const guint8 rnr_32_4_data[] = { 0x83, 0x84, 0x01 }; + static const guint8 disc_32_4_data[] = { 0x81, 0x44 }; + static const guint8 dm_4_32_data[] = { 0x11, 0xe0, 0x00 }; + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(connect_snep_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_snep_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_4_32_put_data) } + },{ + { TEST_ARRAY_AND_SIZE(rnr_32_4_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ + { TEST_ARRAY_AND_SIZE(disc_32_4_data) }, + { TEST_ARRAY_AND_SIZE(dm_4_32_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + } + }; + + const NFC_TECHNOLOGY tech = NFC_TECHNOLOGY_A; + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + NfcTarget* target = test_target_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + NfcPeer* peer = nfc_peer_new_initiator(target, tech, ¶ms, NULL); + gulong id; + + g_assert(peer); + g_assert(peer->technology == tech); + + /* Not initialized yet */ + g_assert(!(peer->flags & NFC_PEER_FLAG_INITIALIZED)); + + /* Let it initialize */ + id = nfc_peer_add_initialized_handler(peer, test_peer_quit_loop_cb, loop); + test_run(&test_opt, loop); + nfc_peer_remove_handler(peer, id); + + /* Now it must be initialized */ + g_assert(peer->present); + g_assert(peer->flags & NFC_PEER_FLAG_INITIALIZED); + g_assert(peer->ndef); + g_assert(NFC_IS_NDEF_REC_SP(peer->ndef)); + + nfc_peer_unref(peer); + nfc_target_unref(target); + g_main_loop_unref(loop); +} + +/*==========================================================================* + * no_ndef + *==========================================================================*/ + +static +void +test_no_ndef( + void) +{ + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { NULL, 0 } + } + }; + + const NFC_TECHNOLOGY tech = NFC_TECHNOLOGY_F; + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + NfcTarget* target = test_target_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + NfcPeer* peer = nfc_peer_new_initiator(target, tech, &initiator_params, + NULL); + gulong id; + + g_assert(peer); + g_assert(peer->technology == tech); + + /* Not initialized yet */ + g_assert(!(peer->flags & NFC_PEER_FLAG_INITIALIZED)); + + /* Let it initialize */ + id = nfc_peer_add_initialized_handler(peer, test_peer_quit_loop_cb, loop); + test_run(&test_opt, loop); + nfc_peer_remove_handler(peer, id); + + /* Now it must be initialized */ + g_assert(peer->flags & NFC_PEER_FLAG_INITIALIZED); + + /* But there's no NDEF resord */ + g_assert(!peer->ndef); + + nfc_peer_unref(peer); + nfc_target_unref(target); + g_main_loop_unref(loop); +} + +/*==========================================================================* + * connect + *==========================================================================*/ + +typedef struct test_connect_data { + gboolean destroyed; + gboolean connected; +} TestConnectData; + +static const guint8 cc_32_32_data[] = { + 0x81, 0xa0, 0x02, 0x02, 0x00, 0x00, 0x05, 0x01, 0x0f +}; +static const guint8 disc_32_32_data[] = { 0x81, 0x60 }; +static const guint8 dm_32_32_0_data[] = { 0x81, 0xe0, 0x00 }; +static const guint8 connect_32_32_data[] = { + 0x81, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f +}; +static const guint8 connect_32_test_data[] = { + 0x05, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f, 0x06, 0x04, 0x74, 0x65, 0x73, 0x74 +}; + +static +void +test_connect_complete( + NfcPeer* peer, + NfcPeerConnection* connection, + NFC_PEER_CONNECT_RESULT result, + void* user_data) +{ + TestConnectData* test = user_data; + + GDEBUG("Connection status %d", result); + g_assert(result == NFC_PEER_CONNECT_OK); + g_assert(!test->connected); + test->connected = TRUE; +} + +static +void +test_connect_destroy( + gpointer user_data) +{ + TestConnectData* test = user_data; + + GDEBUG("Connection data finalized"); + g_assert(!test->destroyed); + test->destroyed = TRUE; +} + +static +void +test_connect_sap_common( + NfcPeer* peer, + TestService* ts) +{ + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + NfcPeerService* service = NFC_PEER_SERVICE(ts); + NfcPeerConnection* conn; + TestConnectData test; + gulong id; + + memset(&test, 0, sizeof(test)); + g_assert(!nfc_peer_register_service(peer, service)); /* Duplicate */ + + /* Not initialized yet */ + g_assert(!(peer->flags & NFC_PEER_FLAG_INITIALIZED)); + + /* Request the connection */ + conn = nfc_peer_connection_ref(nfc_peer_connect(peer, service, + NFC_LLC_SAP_UNNAMED, test_connect_complete, test_connect_destroy, + &test)); + + id = nfc_peer_add_initialized_handler(peer, test_peer_quit_loop_cb, loop); + test_run(&test_opt, loop); + nfc_peer_remove_handler(peer, id); + + g_assert(conn->state == NFC_LLC_CO_DEAD); + g_assert(peer->flags & NFC_PEER_FLAG_INITIALIZED); + g_assert(!peer->ndef); + g_assert(peer->present); + g_assert(test.connected); + g_assert(test.destroyed); + + g_assert_cmpint(ts->peer_in, == ,1); + g_assert_cmpint(ts->peer_out, == ,0); + + /* Now let it disappear */ + id = nfc_peer_add_gone_handler(peer, test_peer_quit_loop_cb, loop); + test_run(&test_opt, loop); + nfc_peer_remove_handler(peer, id); + g_assert(!peer->present); + g_assert_cmpint(ts->peer_in, == ,1); + g_assert_cmpint(ts->peer_out, == ,1); + + nfc_peer_connection_unref(conn); + g_main_loop_unref(loop); +} + +static +void +test_connect_sap_target( + void) +{ + /* Connection is quickly established and terminated */ + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(connect_32_32_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_32_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ + { TEST_ARRAY_AND_SIZE(disc_32_32_data) }, + { TEST_ARRAY_AND_SIZE(dm_32_32_0_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ /* At this point LLCP gets into idle state */ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { NULL, 0 } + } + }; + + const NFC_TECHNOLOGY tech = NFC_TECHNOLOGY_A; + NfcInitiator* init = test_initiator_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + TestService* ts = test_service_client_new(); + NfcPeerService* service = NFC_PEER_SERVICE(ts); + NfcPeerServices* services = nfc_peer_services_new(); + NfcPeer* peer; + + nfc_peer_services_add(services, service); + peer = nfc_peer_new_target(init, tech, &target_params, services); + g_assert(peer); + + test_connect_sap_common(peer, ts); + + nfc_peer_services_unref(services); + nfc_peer_service_unref(service); + nfc_peer_unregister_service(peer, service); + nfc_peer_unref(peer); + nfc_initiator_unref(init); +} + +static +void +test_connect_sap_initiator( + void) +{ + /* Connection is quickly established and terminated */ + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_32_32_data) }, + { TEST_ARRAY_AND_SIZE(cc_32_32_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(disc_32_32_data) } + },{ + { TEST_ARRAY_AND_SIZE(dm_32_32_0_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + },{ /* At this point LLCP gets into idle state */ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { NULL, 0 } + } + }; + + const NFC_TECHNOLOGY tech = NFC_TECHNOLOGY_A; + NfcTarget* target = test_target_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + TestService* ts = test_service_client_new(); + NfcPeerService* service = NFC_PEER_SERVICE(ts); + NfcPeerServices* services = nfc_peer_services_new(); + NfcPeer* peer; + + nfc_peer_services_add(services, service); + peer = nfc_peer_new_initiator(target, tech, &initiator_params, services); + g_assert(peer); + + test_connect_sap_common(peer, ts); + + nfc_peer_services_unref(services); + nfc_peer_service_unref(service); + nfc_peer_unregister_service(peer, service); + nfc_peer_unref(peer); + nfc_target_unref(target); +} + +static +void +test_connect_sn_common( + NfcPeer* peer, + NfcPeerService* service) +{ + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + NfcPeerConnection* conn; + TestConnectData test; + gulong id; + + memset(&test, 0, sizeof(test)); + g_assert(!nfc_peer_register_service(peer, service)); /* Duplicate */ + + /* Not initialized yet */ + g_assert(!(peer->flags & NFC_PEER_FLAG_INITIALIZED)); + + /* Request the connection */ + conn = nfc_peer_connection_ref(nfc_peer_connect_sn(peer, service, + "test", test_connect_complete, test_connect_destroy, &test)); + + id = nfc_peer_add_initialized_handler(peer, test_peer_quit_loop_cb, loop); + test_run(&test_opt, loop); + + g_assert(conn->state == NFC_LLC_CO_DEAD); + g_assert(peer->flags & NFC_PEER_FLAG_INITIALIZED); + g_assert(!peer->ndef); + g_assert(peer->present); + g_assert(test.connected); + g_assert(test.destroyed); + + nfc_peer_connection_unref(conn); + nfc_peer_remove_handler(peer, id); + g_main_loop_unref(loop); +} + +static +void +test_connect_sn_target( + void) +{ + /* Connection is quickly established and terminated */ + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(connect_32_test_data) } + },{ + { TEST_ARRAY_AND_SIZE(cc_32_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ + { TEST_ARRAY_AND_SIZE(disc_32_32_data) }, + { TEST_ARRAY_AND_SIZE(dm_32_32_0_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ /* At this point LLCP gets into idle state */ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { NULL, 0 } + } + }; + + const NFC_TECHNOLOGY tech = NFC_TECHNOLOGY_A; + NfcInitiator* init = test_initiator_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + NfcPeerService* service = NFC_PEER_SERVICE(test_service_client_new()); + NfcPeerServices* services = nfc_peer_services_new(); + NfcPeer* peer; + + nfc_peer_services_add(services, service); + peer = nfc_peer_new_target(init, tech, &target_params, services); + g_assert(peer->technology == tech); + + test_connect_sn_common(peer, service); + + nfc_peer_services_unref(services); + nfc_peer_service_unref(service); + nfc_peer_unregister_service(peer, service); + nfc_peer_unref(peer); + nfc_initiator_unref(init); +} + +static +void +test_connect_sn_initiator( + void) +{ + /* Connection is quickly established and terminated */ + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ + { TEST_ARRAY_AND_SIZE(connect_32_test_data) }, + { TEST_ARRAY_AND_SIZE(cc_32_32_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(disc_32_32_data) } + },{ + { TEST_ARRAY_AND_SIZE(dm_32_32_0_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + },{ /* At this point LLCP gets into idle state */ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { NULL, 0 } + } + }; + + const NFC_TECHNOLOGY tech = NFC_TECHNOLOGY_A; + NfcTarget* target = test_target_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + NfcPeerService* service = NFC_PEER_SERVICE(test_service_client_new()); + NfcPeerServices* services = nfc_peer_services_new(); + NfcPeer* peer; + + nfc_peer_services_add(services, service); + peer = nfc_peer_new_initiator(target, tech, &initiator_params, services); + g_assert(peer->technology == tech); + + test_connect_sn_common(peer, service); + + nfc_peer_services_unref(services); + nfc_peer_service_unref(service); + nfc_peer_unregister_service(peer, service); + nfc_peer_unref(peer); + nfc_target_unref(target); +} + +/*==========================================================================* + * connect_fail + *==========================================================================*/ + +static +void +test_connect_fail( + void) +{ + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { NULL, 0 } + } + }; + + const NFC_TECHNOLOGY tech = NFC_TECHNOLOGY_A; + NfcTarget* target = test_target_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + NfcPeerService* service = NFC_PEER_SERVICE(test_service_client_new_fail()); + NfcPeer* peer = nfc_peer_new_initiator(target, tech, &initiator_params, + NULL); + + g_assert(nfc_peer_register_service(peer, service)); + g_assert(peer->technology == tech); + + /* Service refuses to create connections */ + g_assert(!nfc_peer_connect(peer, service, 0, NULL, NULL, NULL)); + g_assert(!nfc_peer_connect_sn(peer, service, "foo", NULL, NULL, NULL)); + + nfc_peer_service_unref(service); + nfc_peer_unref(peer); + nfc_target_unref(target); +} + +/*==========================================================================* + * error + *==========================================================================*/ + +static +void +test_error( + void) +{ + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { NULL, 0 } + } + }; + + NFC_TECHNOLOGY tech = NFC_TECHNOLOGY_A; + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + NfcTarget* target = test_target_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + TestService* ts = test_service_client_new(); + NfcPeerService* service = NFC_PEER_SERVICE(ts); + NfcPeerServices* services = nfc_peer_services_new(); + NfcPeer* peer; + gulong id[2]; + + nfc_peer_services_add(services, service); + peer = nfc_peer_new_initiator(target, tech, &initiator_params, services); + g_assert(peer); + g_assert(peer->technology == tech); + + /* Not initialized yet */ + g_assert(!(peer->flags & NFC_PEER_FLAG_INITIALIZED)); + + /* Give it a try */ + id[0] = nfc_peer_add_gone_handler(peer, test_peer_quit_loop_cb, loop); + id[1] = nfc_peer_add_initialized_handler(peer, + test_peer_not_reached_cb, loop); + test_run(&test_opt, loop); + nfc_peer_remove_all_handlers(peer, id); + + /* It must be gone and not initialized */ + g_assert(!(peer->flags & NFC_PEER_FLAG_INITIALIZED)); + g_assert(!peer->ndef); + g_assert(!peer->present); + + /* Never arriuved and never left */ + g_assert_cmpint(ts->peer_in, == ,0); + g_assert_cmpint(ts->peer_out, == ,0); + + nfc_peer_unref(peer); + nfc_target_unref(target); + nfc_peer_services_unref(services); + nfc_peer_service_unref(service); + g_main_loop_unref(loop); +} + +/*==========================================================================* + * wks + *==========================================================================*/ + +static +void +test_wks( + void) +{ + static const guint8 atr_res_g [] = { + 0x46, 0x66, 0x6d, 0x01, 0x01, 0x11, 0x02, 0x02, + 0x07, 0xff, 0x03, 0x02, 0x00, 0x01, 0x04, 0x01, + 0xff + }; + static const guint8 pax_data[] = { + 0x00, 0x40, 0x03, 0x02, 0x00, 0x11 + }; + static const NfcParamNfcDepInitiator params = { + { TEST_ARRAY_AND_SIZE(atr_res_g) } + }; + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(pax_data) } + },{ + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + } + }; + + const NFC_TECHNOLOGY tech = NFC_TECHNOLOGY_A; + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + NfcTarget* target = test_target_new_with_tx(TEST_ARRAY_AND_COUNT(tx)); + NfcPeer* peer = nfc_peer_new_initiator(target, tech, ¶ms, NULL); + gulong id[2]; + int count; + + g_assert(peer); + g_assert_cmpuint(peer->wks, == ,0x01); + g_assert(peer->technology == tech); + + /* Not initialized yet */ + g_assert(!(peer->flags & NFC_PEER_FLAG_INITIALIZED)); + + /* These do nothing */ + g_assert(!nfc_peer_add_wks_changed_handler(peer, NULL, NULL)); + g_assert(!nfc_peer_add_ndef_changed_handler(peer, NULL, NULL)); + g_assert(!nfc_peer_add_initialized_handler(peer, NULL, NULL)); + g_assert(!nfc_peer_add_gone_handler(peer, NULL, NULL)); + nfc_peer_remove_handler(peer, 0); + + /* Wait for it to initialize */ + count = 0; + id[0] = nfc_peer_add_wks_changed_handler(peer, test_peer_inc, &count); + id[1] = nfc_peer_add_initialized_handler(peer, + test_peer_quit_loop_cb, loop); + test_run(&test_opt, loop); + nfc_peer_remove_handler(peer, id[0]); + nfc_peer_remove_handler(peer, id[1]); + + /* Must be initialized and wks must have changed */ + g_assert_cmpuint(count, == ,1); + g_assert_cmpuint(peer->wks, == ,0x11); + g_assert(peer->flags & NFC_PEER_FLAG_INITIALIZED); + g_assert(peer->present); + g_assert(!peer->ndef); + + nfc_peer_unref(peer); + nfc_target_unref(target); + g_main_loop_unref(loop); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +#define TEST_(name) "/core/peer/" name + +int main(int argc, char* argv[]) +{ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + g_type_init(); + G_GNUC_END_IGNORE_DEPRECATIONS; + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("name"), test_name); + g_test_add_func(TEST_("error"), test_error); + g_test_add_func(TEST_("wks"), test_wks); + g_test_add_data_func(TEST_("no_magic/1"), &no_magic_1, test_no_magic); + g_test_add_data_func(TEST_("no_magic/2"), &no_magic_2, test_no_magic); + g_test_add_func(TEST_("no_param/target"), test_no_param_target); + g_test_add_func(TEST_("no_param/initiator"), test_no_param_initiator); + g_test_add_func(TEST_("ndef"), test_ndef); + g_test_add_func(TEST_("no_ndef"), test_no_ndef); + g_test_add_func(TEST_("connect/sap/target"), test_connect_sap_target); + g_test_add_func(TEST_("connect/sap/initiator"), test_connect_sap_initiator); + g_test_add_func(TEST_("connect/sn/target"), test_connect_sn_target); + g_test_add_func(TEST_("connect/sn/initiator"), test_connect_sn_initiator); + g_test_add_func(TEST_("connect/fail"), test_connect_fail); + test_init(&test_opt, argc, argv); + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/core_peer_service/Makefile b/unit/core_peer_service/Makefile new file mode 100644 index 0000000..0dd08f2 --- /dev/null +++ b/unit/core_peer_service/Makefile @@ -0,0 +1,5 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_core_peer_service + +include ../common/Makefile diff --git a/unit/core_peer_service/test_core_peer_service.c b/unit/core_peer_service/test_core_peer_service.c new file mode 100644 index 0000000..263d229 --- /dev/null +++ b/unit/core_peer_service/test_core_peer_service.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_common.h" + +#include "nfc_llc.h" +#include "nfc_peer_service_impl.h" +#include "nfc_peer_service_p.h" + +#include + +static TestOpt test_opt; + +#define TEST_(name) "/core/peer_service/" name + +/*==========================================================================* + * Test service + *==========================================================================*/ + +typedef NfcPeerServiceClass TestServiceClass; +typedef NfcPeerService TestService; + +G_DEFINE_TYPE(TestService, test_service, NFC_TYPE_PEER_SERVICE) +#define TEST_TYPE_SERVICE (test_service_get_type()) +#define TEST_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + TEST_TYPE_SERVICE, TestService)) + +static +void +test_service_init( + TestService* self) +{ +} + +static +void +test_service_class_init( + TestServiceClass* klass) +{ +} + +static +TestService* +test_service_new( + const char* name) +{ + TestService* service = g_object_new(TEST_TYPE_SERVICE, NULL); + + nfc_peer_service_init_base(service, name); + return service; +} + +/*==========================================================================* + * null + *==========================================================================*/ + +static +void +test_null( + void) +{ + g_assert(!nfc_peer_service_ref(NULL)); + nfc_peer_service_unref(NULL); + nfc_peer_service_disconnect_all(NULL); +} + +/*==========================================================================* + * basic + *==========================================================================*/ + +static +void +test_basic( + void) +{ + TestService* test_service = test_service_new("foo"); + NfcPeerService* service = NFC_PEER_SERVICE(test_service); + + g_assert_cmpuint(service->sap, == ,0); + + /* Default implementation doesn't support connections */ + g_assert(!nfc_peer_service_new_connect(service, 0, NULL)); + g_assert(!nfc_peer_service_new_accept(service, 0)); + + g_assert(nfc_peer_service_ref(service) == service); + nfc_peer_service_unref(service); + nfc_peer_service_unref(service); +} + +/*==========================================================================* + * snep_sap + *==========================================================================*/ + +static +void +test_snep_sap( + void) +{ + TestService* test_service = test_service_new(NFC_LLC_NAME_SNEP); + NfcPeerService* service = NFC_PEER_SERVICE(test_service); + + /* NFC_LLC_SAP_SNEP is automatically assigned */ + g_assert_cmpuint(service->sap, == ,NFC_LLC_SAP_SNEP); + nfc_peer_service_unref(service); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +int main(int argc, char* argv[]) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("basic"), test_basic); + g_test_add_func(TEST_("snep_sap"), test_snep_sap); + test_init(&test_opt, argc, argv); + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/core_peer_services/Makefile b/unit/core_peer_services/Makefile new file mode 100644 index 0000000..7a79e6d --- /dev/null +++ b/unit/core_peer_services/Makefile @@ -0,0 +1,5 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_core_peer_services + +include ../common/Makefile diff --git a/unit/core_peer_services/test_core_peer_services.c b/unit/core_peer_services/test_core_peer_services.c new file mode 100644 index 0000000..1a9bf86 --- /dev/null +++ b/unit/core_peer_services/test_core_peer_services.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2020 Jolla Ltd. + * Copyright (C) 2020 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_common.h" + +#include "nfc_peer_services.h" +#include "nfc_peer_service_impl.h" + +#include + +static TestOpt test_opt; + +#define TEST_(name) "/core/peer_services/" name + +static +guint +test_services_count( + NfcPeerServices* services) +{ + guint n = 0; + + if (services) { + NfcPeerService* const* ptr = services->list; + + while (*ptr++) n++; + } + return n; +} + +/*==========================================================================* + * Test service + *==========================================================================*/ + +typedef NfcPeerServiceClass TestServiceClass; +typedef struct test_service { + NfcPeerService service; + int peer_arrived; + int peer_left; +} TestService; + +G_DEFINE_TYPE(TestService, test_service, NFC_TYPE_PEER_SERVICE) +#define TEST_TYPE_SERVICE (test_service_get_type()) +#define TEST_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + TEST_TYPE_SERVICE, TestService)) + +static +void +test_service_peer_arrived( + NfcPeerService* service, + NfcPeer* peer) +{ + TEST_SERVICE(service)->peer_arrived++; + NFC_PEER_SERVICE_CLASS(test_service_parent_class)-> + peer_arrived(service, peer); +} + +static +void +test_service_peer_left( + NfcPeerService* service, + NfcPeer* peer) +{ + TEST_SERVICE(service)->peer_left++; + NFC_PEER_SERVICE_CLASS(test_service_parent_class)-> + peer_left(service, peer); +} + +static +void +test_service_init( + TestService* self) +{ +} + +static +void +test_service_class_init( + TestServiceClass* klass) +{ + klass->peer_arrived = test_service_peer_arrived; + klass->peer_left = test_service_peer_left; +} + +static +TestService* +test_service_new( + const char* name) +{ + TestService* service = g_object_new(TEST_TYPE_SERVICE, NULL); + + nfc_peer_service_init_base(&service->service, name); + return service; +} + +/*==========================================================================* + * null + *==========================================================================*/ + +static +void +test_null( + void) +{ + g_assert(!nfc_peer_services_ref(NULL)); + nfc_peer_services_unref(NULL); + g_assert(!nfc_peer_services_copy(NULL)); + g_assert(!nfc_peer_services_find_sn(NULL, NULL)); + g_assert(!nfc_peer_services_find_sap(NULL, 0)); + g_assert(!nfc_peer_services_add(NULL, NULL)); + g_assert(!nfc_peer_services_remove(NULL, NULL)); + nfc_peer_services_peer_arrived(NULL, NULL); + nfc_peer_services_peer_left(NULL, NULL); +} + +/*==========================================================================* + * basic + *==========================================================================*/ + +static +void +test_basic( + void) +{ + NfcPeerServices* services = nfc_peer_services_new(); + TestService* ts1 = test_service_new("foo"); + TestService* ts2 = test_service_new("bar"); + TestService* ts3 = test_service_new(""); + TestService* ts4 = test_service_new(NULL); + TestService* ts5 = test_service_new("foo"); /* Duplicate name */ + NfcPeerService* s1 = NFC_PEER_SERVICE(ts1); + NfcPeerService* s2 = NFC_PEER_SERVICE(ts2); + NfcPeerService* s3 = NFC_PEER_SERVICE(ts3); + NfcPeerService* s4 = NFC_PEER_SERVICE(ts4); + NfcPeerService* s5 = NFC_PEER_SERVICE(ts5); + + g_assert(services->list); + g_assert_cmpuint(test_services_count(services), == ,0); + g_assert(nfc_peer_services_ref(services) == services); + nfc_peer_services_unref(services); + + /* Make sure that add a) works and b) doesn't add the same thing twice */ + g_assert(!nfc_peer_services_add(services, NULL)); + g_assert(nfc_peer_services_add(services, s1)); + g_assert(!nfc_peer_services_add(services, s5)); /* Duplicate name */ + g_assert_cmpuint(s1->sap, == ,NFC_LLC_SAP_NAMED); + g_assert(!s5->sap); + g_assert_cmpuint(test_services_count(services), == ,1); + g_assert(!nfc_peer_services_add(services, s1)); + g_assert_cmpuint(test_services_count(services), == ,1); + g_assert(nfc_peer_services_add(services, s2)); + g_assert_cmpuint(test_services_count(services), == ,2); + g_assert_cmpuint(s2->sap, == ,NFC_LLC_SAP_NAMED + 1); + g_assert(nfc_peer_services_add(services, s3)); + g_assert_cmpuint(test_services_count(services), == ,3); + g_assert_cmpuint(s3->sap, == ,NFC_LLC_SAP_UNNAMED); + g_assert(nfc_peer_services_add(services, s4)); + g_assert_cmpuint(test_services_count(services), == ,4); + g_assert_cmpuint(s4->sap, == ,NFC_LLC_SAP_UNNAMED + 1); + + /* Search */ + g_assert(nfc_peer_services_find_sn(services, "foo") == s1); + g_assert(nfc_peer_services_find_sn(services, "bar") == s2); + g_assert(!nfc_peer_services_find_sn(services, NFC_LLC_NAME_SDP)); + g_assert(!nfc_peer_services_find_sn(services, NULL)); + g_assert(!nfc_peer_services_find_sn(services, "")); + g_assert(!nfc_peer_services_find_sap(services, 0)); + g_assert(!nfc_peer_services_find_sap(services, NFC_LLC_SAP_SDP)); + g_assert(!nfc_peer_services_find_sap(services, NFC_LLC_SAP_SNEP)); + g_assert(!nfc_peer_services_find_sap(services, s4->sap + 1)); + g_assert(nfc_peer_services_find_sap(services, s1->sap) == s1); + g_assert(nfc_peer_services_find_sap(services, s2->sap) == s2); + g_assert(nfc_peer_services_find_sap(services, s3->sap) == s3); + g_assert(nfc_peer_services_find_sap(services, s4->sap) == s4); + + /* Notifications (those don't check peer pointer, so it can be NULL) */ + nfc_peer_services_peer_arrived(services, NULL); + g_assert_cmpuint(ts1->peer_arrived, == ,1); + g_assert_cmpuint(ts2->peer_arrived, == ,1); + g_assert_cmpuint(ts3->peer_arrived, == ,1); + g_assert_cmpuint(ts4->peer_arrived, == ,1); + + nfc_peer_services_peer_left(services, NULL); + g_assert_cmpuint(ts1->peer_left, == ,1); + g_assert_cmpuint(ts2->peer_left, == ,1); + g_assert_cmpuint(ts3->peer_left, == ,1); + g_assert_cmpuint(ts4->peer_left, == ,1); + + /* Test removal */ + g_assert(!nfc_peer_services_remove(services, NULL)); + g_assert(nfc_peer_services_remove(services, s1)); + g_assert(!nfc_peer_services_remove(services, s1)); + g_assert_cmpuint(test_services_count(services), == ,3); + g_assert(!nfc_peer_services_find_sn(services, "foo")); + g_assert(nfc_peer_services_remove(services, s2)); + g_assert(!nfc_peer_services_remove(services, s2)); + g_assert_cmpuint(test_services_count(services), == ,2); + g_assert(!nfc_peer_services_find_sn(services, "bar")); + g_assert(nfc_peer_services_remove(services, s3)); + g_assert(!nfc_peer_services_remove(services, s3)); + g_assert_cmpuint(test_services_count(services), == ,1); + g_assert(nfc_peer_services_remove(services, s4)); + g_assert(!nfc_peer_services_remove(services, s4)); + g_assert_cmpuint(test_services_count(services), == ,0); + + /* These do nothing with empty list */ + nfc_peer_services_peer_arrived(services, NULL); + nfc_peer_services_peer_left(services, NULL); + + /* Add some services back */ + nfc_peer_services_add(services, s1); + nfc_peer_services_add(services, s2); + + /* And deallocate everything */ + nfc_peer_service_unref(s1); + nfc_peer_service_unref(s2); + nfc_peer_service_unref(s3); + nfc_peer_service_unref(s4); + nfc_peer_service_unref(s5); + nfc_peer_services_unref(services); +} + +/*==========================================================================* + * copy + *==========================================================================*/ + +static +void +test_copy( + void) +{ + NfcPeerServices* services = nfc_peer_services_new(); + NfcPeerServices* copy = nfc_peer_services_copy(services); + TestService* ts1 = test_service_new("foo"); + TestService* ts2 = test_service_new("bar"); + TestService* ts3 = test_service_new(NULL); + NfcPeerService* s1 = NFC_PEER_SERVICE(ts1); + NfcPeerService* s2 = NFC_PEER_SERVICE(ts2); + NfcPeerService* s3 = NFC_PEER_SERVICE(ts3); + guint n, i; + + g_assert(services->list); + g_assert(copy->list); + g_assert_cmpuint(test_services_count(services), == ,0); + g_assert_cmpuint(test_services_count(copy), == ,0); + + g_assert(nfc_peer_services_add(services, s1)); + g_assert(nfc_peer_services_add(services, s2)); + g_assert(nfc_peer_services_add(services, s3)); + g_assert_cmpuint(test_services_count(services), == ,3); + nfc_peer_service_unref(s1); + nfc_peer_service_unref(s2); + nfc_peer_service_unref(s3); + + nfc_peer_services_unref(copy); + copy = nfc_peer_services_copy(services); + n = test_services_count(copy); + g_assert_cmpuint(n, == ,3); + for (i = 0; i <= n /* including NULL */; i++) { + g_assert(services->list[i] == copy->list[i]); + } + + nfc_peer_services_unref(services); + nfc_peer_services_unref(copy); +} + +/*==========================================================================* + * reserved + *==========================================================================*/ + +static +void +test_reserved( + void) +{ + NfcPeerService* sdp = NFC_PEER_SERVICE(test_service_new(NFC_LLC_NAME_SDP)); + NfcPeerService* snep = NFC_PEER_SERVICE(test_service_new(NFC_LLC_NAME_SNEP)); + NfcPeerServices* services = nfc_peer_services_new(); + + g_assert(!nfc_peer_services_add(services, sdp)); /* Not allowed */ + g_assert(nfc_peer_services_add(services, snep)); + g_assert_cmpuint(test_services_count(services), == ,1); + g_assert_cmpuint(snep->sap, == ,NFC_LLC_SAP_SNEP); + + nfc_peer_service_unref(sdp); + nfc_peer_service_unref(snep); + nfc_peer_services_unref(services); +} + +/*==========================================================================* + * too_many + *==========================================================================*/ + +static +void +test_too_many( + void) +{ + NfcPeerServices* services = nfc_peer_services_new(); + TestService* ts; + NfcPeerService* ps; + guint i; + + for (i = 0; i < 32; i++) { + ts = test_service_new(NULL); + ps = NFC_PEER_SERVICE(ts); + g_assert(nfc_peer_services_add(services, ps)); + g_assert_cmpuint(ps->sap, == ,NFC_LLC_SAP_UNNAMED + i); + nfc_peer_service_unref(ps); + } + + /* And this one doesn't fit */ + ts = test_service_new(NULL); + ps = NFC_PEER_SERVICE(ts); + g_assert(!nfc_peer_services_add(services, ps)); + nfc_peer_service_unref(ps); + + nfc_peer_services_unref(services); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +int main(int argc, char* argv[]) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("basic"), test_basic); + g_test_add_func(TEST_("copy"), test_copy); + g_test_add_func(TEST_("reserved"), test_reserved); + g_test_add_func(TEST_("too_many"), test_too_many); + test_init(&test_opt, argc, argv); + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/core_peer_socket/Makefile b/unit/core_peer_socket/Makefile new file mode 100644 index 0000000..5121d07 --- /dev/null +++ b/unit/core_peer_socket/Makefile @@ -0,0 +1,5 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_core_peer_socket + +include ../common/Makefile diff --git a/unit/core_peer_socket/test_core_peer_socket.c b/unit/core_peer_socket/test_core_peer_socket.c new file mode 100644 index 0000000..b808845 --- /dev/null +++ b/unit/core_peer_socket/test_core_peer_socket.c @@ -0,0 +1,940 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_types_p.h" +#include "nfc_target_impl.h" +#include "nfc_peer_connection_impl.h" +#include "nfc_peer_services.h" +#include "nfc_peer_service_p.h" +#include "nfc_peer_service_impl.h" +#include "nfc_peer_socket.h" +#include "nfc_llc_param.h" +#include "nfc_llc_io.h" +#include "nfc_llc.h" +#include "nfc_ndef.h" + +#include "test_common.h" + +#include + +#include +#include +#include + +static TestOpt test_opt; + +#define TEST_(name) "/core/peer_socket/" name + +#define TEST_SERVICE_NAME "test" + +static const guint8 param_tlv_data[] = { + 0x01, 0x01, 0x11, 0x02, 0x02, 0x07, 0xff, 0x03, + 0x02, 0x00, 0x13, 0x04, 0x01, 0xff, 0x07, 0x01, + 0x03 +}; +static const GUtilData param_tlv = { TEST_ARRAY_AND_SIZE(param_tlv_data) }; +static const guint8 symm_data[] = { 0x00, 0x00 }; + +static +void +test_int_inc( + gpointer data) +{ + (*((int*)data))++; +} + +static +void +test_connection_dead_quit_loop_cb( + NfcPeerConnection* connection, + void* user_data) +{ + if (connection->state == NFC_LLC_CO_DEAD) { + GDEBUG("Done"); + g_main_loop_quit((GMainLoop*)user_data); + } +} + +/*==========================================================================* + * Test service + *==========================================================================*/ + +typedef +void +(*TestServiceAcceptFunc)( + NfcPeerService* service, + NfcPeerSocket* socket, + void* user_data); + +typedef NfcPeerServiceClass TestServiceClass; +typedef struct test_service { + NfcPeerService service; + TestServiceAcceptFunc accept_fn; + void* accept_data; +} TestService; + +G_DEFINE_TYPE(TestService, test_service, NFC_TYPE_PEER_SERVICE) +#define TEST_TYPE_SERVICE (test_service_get_type()) +#define TEST_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + TEST_TYPE_SERVICE, TestService)) + +static +NfcPeerConnection* +test_service_new_connect( + NfcPeerService* self, + guint8 rsap, + const char* name) +{ + NfcPeerSocket* s = nfc_peer_socket_new_connect(self, rsap, name); + + return s ? NFC_PEER_CONNECTION(s) : NULL; +} + +static +NfcPeerConnection* +test_service_new_accept( + NfcPeerService* service, + guint8 rsap) +{ + TestService* self = TEST_SERVICE(service); + NfcPeerSocket* s = nfc_peer_socket_new_accept(service, rsap); + + if (s) { + if (self->accept_fn) { + self->accept_fn(service, s, self->accept_data); + } + return NFC_PEER_CONNECTION(s); + } else { + return NULL; + } +} + +static +void +test_service_init( + TestService* self) +{ +} + +static +void +test_service_class_init( + TestServiceClass* klass) +{ + klass->new_connect = test_service_new_connect; + klass->new_accept = test_service_new_accept; +} + +static +NfcPeerService* +test_service_client_new( + guint8 sap) +{ + TestService* self = g_object_new(TEST_TYPE_SERVICE, NULL); + NfcPeerService* service = &self->service; + + nfc_peer_service_init_base(service, NULL); + service->sap = sap; + return service; +} + +static +NfcPeerService* +test_service_server_new( + const char* name, + guint8 sap, + TestServiceAcceptFunc accept_fn, + void* accept_data) +{ + TestService* self = g_object_new(TEST_TYPE_SERVICE, NULL); + NfcPeerService* service = &self->service; + + self->accept_fn = accept_fn; + self->accept_data = accept_data; + nfc_peer_service_init_base(service, name); + service->sap = sap; + return service; +} + +/*==========================================================================* + * Test target + *==========================================================================*/ + +typedef NfcTargetClass TestTargetClass; +typedef struct test_target { + NfcTarget target; + guint transmit_id; + GPtrArray* cmd_resp; +} TestTarget; + +G_DEFINE_TYPE(TestTarget, test_target, NFC_TYPE_TARGET) +#define TEST_TYPE_TARGET (test_target_get_type()) +#define TEST_TARGET(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + TEST_TYPE_TARGET, TestTarget)) + +static +GUtilData* +test_target_next_data( + TestTarget* self) +{ + if (self->cmd_resp->len) { + GUtilData* data = self->cmd_resp->pdata[0]; + + self->cmd_resp->pdata[0] = NULL; + g_ptr_array_remove_index(self->cmd_resp, 0); + return data; + } + return NULL; +} + +static +void +test_target_cancel_transmit( + NfcTarget* target) +{ + TestTarget* self = TEST_TARGET(target); + + g_assert(self->transmit_id); + g_source_remove(self->transmit_id); + self->transmit_id = 0; +} + +static +gboolean +test_target_transmit_done( + gpointer user_data) +{ + TestTarget* self = TEST_TARGET(user_data); + NfcTarget* target = &self->target; + + g_assert(self->transmit_id); + self->transmit_id = 0; + if (self->cmd_resp->len) { + GUtilData* data = test_target_next_data(self); + + if (data) { + nfc_target_transmit_done(target, NFC_TRANSMIT_STATUS_OK, + data->bytes, data->size); + g_free(data); + } else { + nfc_target_transmit_done(target, NFC_TRANSMIT_STATUS_OK, NULL, 0); + } + } else { + nfc_target_transmit_done(target, NFC_TRANSMIT_STATUS_ERROR, NULL, 0); + } + return G_SOURCE_REMOVE; +} + +static +gboolean +test_target_transmit( + NfcTarget* target, + const void* data, + guint len) +{ + TestTarget* self = TEST_TARGET(target); + GUtilData* expected = test_target_next_data(self); + + if (expected) { + g_assert_cmpuint(expected->size, ==, len); + g_assert(!memcmp(data, expected->bytes, len)); + g_free(expected); + } + self->transmit_id = g_idle_add(test_target_transmit_done, self); + return TRUE; +} + +static +void +test_target_init( + TestTarget* self) +{ + self->cmd_resp = g_ptr_array_new_with_free_func(g_free); +} + +static +void +test_target_finalize( + GObject* object) +{ + TestTarget* self = TEST_TARGET(object); + + if (self->transmit_id) { + g_source_remove(self->transmit_id); + } + g_ptr_array_free(self->cmd_resp, TRUE); + G_OBJECT_CLASS(test_target_parent_class)->finalize(object); +} + +static +void +test_target_class_init( + NfcTargetClass* klass) +{ + klass->transmit = test_target_transmit; + klass->cancel_transmit = test_target_cancel_transmit; + G_OBJECT_CLASS(klass)->finalize = test_target_finalize; +} + +static +void +test_target_add_cmd( + TestTarget* self, + const void* bytes, + guint len) +{ + g_ptr_array_add(self->cmd_resp, test_alloc_data(bytes, len)); +} + +/*==========================================================================* + * null + *==========================================================================*/ + +static +void +test_null( + void) +{ + NfcPeerSocket* socket = g_object_new(NFC_TYPE_PEER_SOCKET, NULL); + + g_assert(!nfc_peer_socket_new_connect(NULL, 0, NULL)); + g_assert(!nfc_peer_socket_new_accept(NULL, 0)); + g_assert_cmpint(nfc_peer_socket_fd(NULL), == ,-1); + g_assert_cmpint(nfc_peer_socket_fd(socket), == ,-1); + nfc_peer_socket_set_max_send_queue(NULL, 0); + g_object_unref(socket); +} + +/*==========================================================================* + * connect + *==========================================================================*/ + +static +void +test_never_connect( + NfcPeerConnection* connection, + NFC_PEER_CONNECT_RESULT result, + void* user_data) +{ + g_assert_not_reached(); +} + +static +void +test_connect_cancelled( + NfcPeerConnection* connection, + NFC_PEER_CONNECT_RESULT result, + void* user_data) +{ + g_assert_cmpint(result, == ,NFC_PEER_CONNECT_CANCELLED); +} + +static +void +test_connect_success( + NfcPeerConnection* connection, + NFC_PEER_CONNECT_RESULT result, + void* user_data) +{ + int* count = user_data; + + g_assert_cmpint(*count, == ,0); + (*count)++; +} + +static +void +test_connect( + void) +{ + static const guint8 connect_16_32_data[] = { + 0x41, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f + }; + static const guint8 cc_32_16_data[] = { + 0x81, 0x90, 0x02, 0x02, 0x00, 0x00, 0x05, 0x01, + 0x0f, 0x06 + }; + static const guint8 disc_16_32_data[] = { 0x41, 0x60 }; + static const guint8 dm_32_16_0_data[] = { 0x81, 0xd0, 0x00 }; + static const guint8 connect_32_test_data[] = { + 0x05, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f, 0x06, 0x04, 0x74, 0x65, 0x73, 0x74 + }; + static const guint8 cc_32_32_data[] = { + 0x81, 0xa0, 0x02, 0x02, 0x00, 0x00, 0x05, 0x01, + 0x0f + }; + static const guint8 disc_32_32_data[] = { 0x81, 0x60 }; + static const guint8 dm_32_32_0_data[] = { 0x81, 0xe0, 0x00 }; + static const guint8 i_send_data[] = { + 0x83, 0x20, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + static const guint8 data[] = { + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + static const guint8 i_recv_data[] = { + 0x83, 0x20, 0x01, + 0x10, 0x11, 0x12, 0x13, 0x13, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + static const guint8 recv_data[] = { + 0x10, 0x11, 0x12, 0x13, 0x13, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + static const guint8 rr_32_32_1_data[] = { 0x83, 0x60, 0x01 }; + TestTarget* tt = g_object_new(TEST_TYPE_TARGET, NULL); + NfcPeerService* service = test_service_client_new(NFC_LLC_SAP_UNNAMED); + NfcLlcParam** params = nfc_llc_param_decode(¶m_tlv); + NfcTarget* target = NFC_TARGET(tt); + NfcPeerConnection* connection; + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc; + gulong connection_state_id; + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + int count1 = 0, count2 = 0, count3 = 0; + NfcPeerSocket* socket; + guint8 buf[sizeof(recv_data) + 1]; + int fd; + + /* Connect/diconnect (connection #1) */ + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(connect_16_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(cc_32_16_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(disc_16_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(dm_32_16_0_data)); + + /* Connect/diconnect (connection #2) */ + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(connect_32_test_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(cc_32_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(disc_32_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(dm_32_32_0_data)); + + /* Connection #3 */ + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(connect_32_test_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(cc_32_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(i_send_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(i_recv_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(rr_32_32_1_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(disc_32_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(dm_32_32_0_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + + g_assert(nfc_peer_services_add(services, service)); + llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(llc->state == NFC_LLC_STATE_START); + + /* This has no effect since there are no connections yet */ + nfc_peer_service_disconnect_all(service); + + /* Connection #1 (canceled) */ + connection = nfc_llc_connect(llc, service, 16, test_never_connect, + test_int_inc, &count1); + g_assert(connection); + g_assert(nfc_peer_connection_cancel(connection)); + + /* Connection #2 (abandoned) */ + connection = nfc_llc_connect_sn(llc, service, TEST_SERVICE_NAME, + test_connect_cancelled, test_int_inc, &count2); + g_assert(connection); + nfc_peer_service_disconnect_all(service); + + /* Connection #3 (succeeds) */ + connection = nfc_llc_connect_sn(llc, service, TEST_SERVICE_NAME, + test_connect_success, test_int_inc, &count3); + g_assert(connection); + nfc_peer_connection_ref(connection); + socket = NFC_PEER_SOCKET(connection); + nfc_peer_socket_set_max_send_queue(socket, 0); + nfc_peer_socket_set_max_send_queue(socket, 0); /* No effect second time */ + fd = nfc_peer_socket_fd(socket); + g_assert(fd >= 0); + g_assert(fcntl(fd, F_SETFL, O_NONBLOCK) >= 0); + g_assert_cmpint(write(fd, data, sizeof(data)), == ,sizeof(data)); + + /* Verify NULL resitance for additional parameters */ + g_assert(nfc_peer_connection_send(connection, NULL)); + nfc_peer_connection_remove_handler(connection, 0); + g_assert(!nfc_peer_connection_add_state_changed_handler(connection, + NULL, NULL)); + + /* Now wait until connection terminates */ + connection_state_id = nfc_peer_connection_add_state_changed_handler + (connection, test_connection_dead_quit_loop_cb, loop); + test_run(&test_opt, loop); + g_assert(connection->state == NFC_LLC_CO_DEAD); + g_assert_cmpuint(connection->bytes_received, == ,sizeof(data)); + g_assert(llc->state == NFC_LLC_STATE_ACTIVE); + + /* Read the data from the socket */ + memset(buf, 0, sizeof(buf)); + g_assert_cmpint(read(fd, buf, sizeof(buf)), == ,sizeof(recv_data)); + nfc_peer_connection_remove_handler(connection, connection_state_id); + + /* These calls have no effect at this point */ + g_assert(!nfc_peer_connection_cancel(connection)); + nfc_peer_connection_accepted(connection); + nfc_peer_connection_rejected(connection); + + /* Drop the connection */ + nfc_peer_connection_unref(connection); + + g_assert(count1 == 1); + g_assert(count2 == 1); + g_assert(count3 == 2); + g_main_loop_unref(loop); + nfc_llc_param_free(params); + nfc_peer_service_unref(service); + nfc_peer_services_unref(services); + nfc_llc_io_unref(io); + nfc_llc_free(llc); + nfc_target_unref(target); +} + +/*==========================================================================* + * connect_eof + *==========================================================================*/ + +static +void +test_connect_eof_idle_cb( + NfcLlc* llc, + void* user_data) +{ + if (llc->idle) { + NfcPeerSocket* socket = NFC_PEER_SOCKET(user_data); + + g_assert(shutdown(nfc_peer_socket_fd(socket), SHUT_RDWR) == 0); + } +} + +static +void +test_connect_eof( + void) +{ + static const guint8 connect_32_test_data[] = { + 0x05, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f, 0x06, 0x04, 0x74, 0x65, 0x73, 0x74 + }; + static const guint8 cc_32_32_data[] = { + 0x81, 0xa0, 0x02, 0x02, 0x00, 0x00, 0x05, 0x01, + 0x0f + }; + static const guint8 disc_32_32_data[] = { 0x81, 0x60 }; + static const guint8 dm_32_32_0_data[] = { 0x81, 0xe0, 0x00 }; + static const guint8 i_send_data[] = { + 0x83, 0x20, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + static const guint8 data[] = { + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + static const guint8 i_recv_data[] = { + 0x83, 0x20, 0x01, + 0x10, 0x11, 0x12, 0x13, 0x13, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + static const guint8 recv_data[] = { + 0x10, 0x11, 0x12, 0x13, 0x13, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + static const guint8 rr_32_32_1_data[] = { 0x83, 0x60, 0x01 }; + TestTarget* tt = g_object_new(TEST_TYPE_TARGET, NULL); + NfcPeerService* service = test_service_client_new(NFC_LLC_SAP_UNNAMED); + NfcLlcParam** params = nfc_llc_param_decode(¶m_tlv); + NfcTarget* target = NFC_TARGET(tt); + NfcPeerConnection* connection; + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc; + gulong llc_idle_id, connection_state_id; + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + int count = 0; + NfcPeerSocket* socket; + guint8 buf[sizeof(recv_data) + 1]; + int fd; + + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(connect_32_test_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(cc_32_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(i_send_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(i_recv_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(rr_32_32_1_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + /* ==> At this point LLC becomes idle <== */ + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(disc_32_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(dm_32_32_0_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + + g_assert(nfc_peer_services_add(services, service)); + llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(llc->state == NFC_LLC_STATE_START); + + /* Establish the connection */ + connection = nfc_llc_connect_sn(llc, service, TEST_SERVICE_NAME, + NULL, test_int_inc, &count); + g_assert(connection); + nfc_peer_connection_ref(connection); + socket = NFC_PEER_SOCKET(connection); + fd = nfc_peer_socket_fd(socket); + g_assert(fd >= 0); + g_assert(fcntl(fd, F_SETFL, O_NONBLOCK) >= 0); + g_assert_cmpint(write(fd, data, sizeof(data)), == ,sizeof(data)); + + /* We shutdown the socket when connection becomes idle */ + llc_idle_id = nfc_llc_add_idle_changed_handler(llc, + test_connect_eof_idle_cb, connection); + connection_state_id = nfc_peer_connection_add_state_changed_handler + (connection, test_connection_dead_quit_loop_cb, loop); + test_run(&test_opt, loop); + g_assert(llc->state == NFC_LLC_STATE_ACTIVE); + g_assert(connection->state == NFC_LLC_CO_DEAD); + + /* Read the data from the socket */ + memset(buf, 0, sizeof(buf)); + g_assert_cmpint(read(fd, buf, sizeof(buf)), == ,sizeof(recv_data)); + nfc_peer_connection_remove_handler(connection, connection_state_id); + g_assert(!nfc_peer_connection_cancel(connection)); + nfc_peer_connection_unref(connection); + + g_assert(count == 1); + g_main_loop_unref(loop); + nfc_llc_param_free(params); + nfc_peer_service_unref(service); + nfc_peer_services_unref(services); + nfc_llc_remove_handler(llc, llc_idle_id); + nfc_llc_io_unref(io); + nfc_llc_free(llc); + nfc_target_unref(target); +} + +/*==========================================================================* + * connect_error + *==========================================================================*/ + +static +void +test_connect_error_idle_cb( + NfcLlc* llc, + void* user_data) +{ + if (llc->idle) { + NfcPeerSocket* socket = NFC_PEER_SOCKET(user_data); + + g_assert(shutdown(nfc_peer_socket_fd(socket), SHUT_RD) == 0); + } +} + +static +void +test_connect_error( + void) +{ + static const guint8 connect_32_test_data[] = { + 0x05, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f, 0x06, 0x04, 0x74, 0x65, 0x73, 0x74 + }; + static const guint8 cc_32_32_data[] = { + 0x81, 0xa0, 0x02, 0x02, 0x00, 0x00, 0x05, 0x01, + 0x0f + }; + static const guint8 disc_32_32_data[] = { 0x81, 0x60 }; + static const guint8 dm_32_32_0_data[] = { 0x81, 0xe0, 0x00 }; + static const guint8 i_recv_data[] = { + 0x83, 0x20, 0x00, + 0x10, 0x11, 0x12, 0x13, 0x13, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + static const guint8 rr_32_32_1_data[] = { 0x83, 0x60, 0x01 }; + TestTarget* tt = g_object_new(TEST_TYPE_TARGET, NULL); + NfcPeerService* service = test_service_client_new(NFC_LLC_SAP_UNNAMED); + NfcLlcParam** params = nfc_llc_param_decode(¶m_tlv); + NfcTarget* target = NFC_TARGET(tt); + NfcPeerConnection* connection; + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc; + gulong llc_idle_id, connection_state_id; + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + int count = 0; + NfcPeerSocket* socket; + guint8 buf[1]; + int fd; + + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(connect_32_test_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(cc_32_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + /* ==> At this point LLC becomes idle <== */ + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(i_recv_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(rr_32_32_1_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(disc_32_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(dm_32_32_0_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + + g_assert(nfc_peer_services_add(services, service)); + llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(llc->state == NFC_LLC_STATE_START); + + /* Establish the connection */ + connection = nfc_llc_connect_sn(llc, service, TEST_SERVICE_NAME, NULL, + test_int_inc, &count); + g_assert(connection); + nfc_peer_connection_ref(connection); + socket = NFC_PEER_SOCKET(connection); + fd = nfc_peer_socket_fd(socket); + g_assert(fd >= 0); + g_assert(fcntl(fd, F_SETFL, O_NONBLOCK) >= 0); + + /* We shutdown the socket when connection becomes idle */ + llc_idle_id = nfc_llc_add_idle_changed_handler(llc, + test_connect_error_idle_cb, connection); + connection_state_id = nfc_peer_connection_add_state_changed_handler + (connection, test_connection_dead_quit_loop_cb, loop); + test_run(&test_opt, loop); + g_assert(llc->state == NFC_LLC_STATE_ACTIVE); + g_assert(connection->state == NFC_LLC_CO_DEAD); + + /* Try to read the data from the socket (and get nothing) */ + memset(buf, 0, sizeof(buf)); + g_assert_cmpint(read(fd, buf, sizeof(buf)), == ,0); + nfc_peer_connection_remove_handler(connection, connection_state_id); + g_assert(!nfc_peer_connection_cancel(connection)); + nfc_peer_connection_unref(connection); + + g_assert(count == 1); + g_main_loop_unref(loop); + nfc_llc_param_free(params); + nfc_peer_service_unref(service); + nfc_peer_services_unref(services); + nfc_llc_remove_handler(llc, llc_idle_id); + nfc_llc_io_unref(io); + nfc_llc_free(llc); + nfc_target_unref(target); +} + +/*==========================================================================* + * listen + *==========================================================================*/ + +typedef struct test_listen_data { + GMainLoop* loop; + NfcPeerSocket* socket; + gulong connection_state_id; +} TestListenData; + +static +void +test_listen_idle_cb( + NfcLlc* llc, + void* user_data) +{ + TestListenData* test = user_data; + + g_assert(test->socket); + g_assert(shutdown(nfc_peer_socket_fd(test->socket), SHUT_RDWR) == 0); +} + +static +void +test_listen_accept_cb( + NfcPeerService* service, + NfcPeerSocket* socket, + void* user_data) +{ + TestListenData* test = user_data; + NfcPeerConnection* pc = &socket->connection; + int fd; + static const guint8 data[] = { + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + + g_assert(!test->socket); + test->socket = socket; + nfc_peer_connection_ref(pc); + test->connection_state_id = nfc_peer_connection_add_state_changed_handler + (pc, test_connection_dead_quit_loop_cb, test->loop); + + fd = nfc_peer_socket_fd(socket); + g_assert(fd >= 0); + g_assert(fcntl(fd, F_SETFL, O_NONBLOCK) >= 0); + g_assert_cmpint(write(fd, data, sizeof(data)), == , sizeof(data)); +} + +static +void +test_listen( + void) +{ + static const guint8 connect_test_32_data[] = { + 0x05, 0x20, 0x02, 0x02, 0x00, 0x00, 0x05, 0x01, + 0x0f, 0x06, 0x04, 0x74, 0x65, 0x73, 0x74 + }; + static const guint8 cc_32_16_data[] = { + 0x81, 0x90, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f + }; + static const guint8 i_send_data[] = { + 0x83, 0x10, 0x00, /* Matches write_data in test_listen_accept_cb: */ + 0x00, 0x01, 0x02, 0x03, 0x03, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + static const guint8 i_recv_1_data[] = { + 0x43, 0x20, 0x01, /* First chunk */ + 0x10, 0x11, 0x12, 0x13, 0x13, 0x15, 0x16, 0x17, + }; + static const guint8 i_recv_2_data[] = { + 0x43, 0x20, 0x11, /* Second chunk */ + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + static const guint8 recv_data[] = { + 0x10, 0x11, 0x12, 0x13, 0x13, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + static const guint8 rr_32_16_1_data[] = { 0x83, 0x50, 0x01 }; + static const guint8 rr_32_16_2_data[] = { 0x83, 0x50, 0x02 }; + static const guint8 disc_32_16_data[] = { 0x81, 0x50 }; + static const guint8 dm_16_32_0_data[] = { 0x41, 0xe0, 0x00 }; + TestTarget* tt = g_object_new(TEST_TYPE_TARGET, NULL); + NfcPeerService* service; + NfcLlcParam** params = nfc_llc_param_decode(¶m_tlv); + NfcTarget* target = NFC_TARGET(tt); + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc; + gulong llc_idle_id; + guint8 buf[sizeof(recv_data) + 1]; + TestListenData test; + + memset(&test, 0, sizeof(test)); + test.loop = g_main_loop_new(NULL, TRUE); + service = test_service_server_new(TEST_SERVICE_NAME, NFC_LLC_SAP_NAMED, + test_listen_accept_cb, &test); + + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(connect_test_32_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(cc_32_16_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(i_send_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(i_recv_1_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(rr_32_16_1_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(i_recv_2_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(rr_32_16_2_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + /* ==> At this point LLC becomes idle <== */ + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(disc_32_16_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(dm_16_32_0_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + + g_assert(nfc_peer_services_add(services, service)); + llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(llc->state == NFC_LLC_STATE_START); + + /* We shutdown the socket when connection becomes idle */ + llc_idle_id = nfc_llc_add_idle_changed_handler(llc, + test_listen_idle_cb, &test); + test_run(&test_opt, test.loop); + g_assert(llc->state == NFC_LLC_STATE_ACTIVE); + g_assert(test.socket); + g_assert(test.connection_state_id); + + /* Read the data from the socket */ + memset(buf, 0, sizeof(buf)); + g_assert_cmpint(read(nfc_peer_socket_fd(test.socket), buf, sizeof(buf)), + == , sizeof(recv_data)); + g_assert(!nfc_peer_connection_cancel(NFC_PEER_CONNECTION(test.socket))); + nfc_peer_connection_remove_handler(NFC_PEER_CONNECTION(test.socket), + test.connection_state_id); + nfc_peer_connection_unref(NFC_PEER_CONNECTION(test.socket)); + + g_main_loop_unref(test.loop); + nfc_llc_remove_handler(llc, llc_idle_id); + nfc_llc_param_free(params); + nfc_peer_service_unref(service); + nfc_peer_services_unref(services); + nfc_llc_io_unref(io); + nfc_llc_free(llc); + nfc_target_unref(target); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +int main(int argc, char* argv[]) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("connect"), test_connect); + g_test_add_func(TEST_("connect_eof"), test_connect_eof); + g_test_add_func(TEST_("connect_error"), test_connect_error); + g_test_add_func(TEST_("listen"), test_listen); + signal(SIGPIPE, SIG_IGN); + test_init(&test_opt, argc, argv); + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/core_snep/Makefile b/unit/core_snep/Makefile new file mode 100644 index 0000000..abb321c --- /dev/null +++ b/unit/core_snep/Makefile @@ -0,0 +1,5 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_core_snep + +include ../common/Makefile diff --git a/unit/core_snep/test_core_snep.c b/unit/core_snep/test_core_snep.c new file mode 100644 index 0000000..d3e4cf1 --- /dev/null +++ b/unit/core_snep/test_core_snep.c @@ -0,0 +1,680 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_types_p.h" +#include "nfc_ndef.h" +#include "nfc_snep_server.h" +#include "nfc_target_impl.h" +#include "nfc_peer_services.h" +#include "nfc_llc.h" +#include "nfc_llc_io.h" +#include "nfc_llc_param.h" + +#include "test_common.h" + +#include + +static TestOpt test_opt; + +#define TEST_(name) "/core/snep/" name + +static +void +test_llc_quit_loop_cb( + NfcLlc* llc, + void* user_data) +{ + g_main_loop_quit((GMainLoop*)user_data); +} + +static +void +test_snep_event_counter( + NfcSnepServer* snep, + void* user_data) +{ + (*((int*)user_data))++; +} + +/*==========================================================================* + * Test target + *==========================================================================*/ + +typedef NfcTargetClass TestTargetClass; +typedef struct test_target { + NfcTarget target; + guint transmit_id; + GPtrArray* cmd_resp; +} TestTarget; + +G_DEFINE_TYPE(TestTarget, test_target, NFC_TYPE_TARGET) +#define TEST_TYPE_TARGET (test_target_get_type()) +#define TEST_TARGET(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, \ + TEST_TYPE_TARGET, TestTarget)) + +static +GUtilData* +test_target_next_data( + TestTarget* self) +{ + if (self->cmd_resp->len) { + GUtilData* data = self->cmd_resp->pdata[0]; + + self->cmd_resp->pdata[0] = NULL; + g_ptr_array_remove_index(self->cmd_resp, 0); + return data; + } + return NULL; +} + +static +void +test_target_cancel_transmit( + NfcTarget* target) +{ + TestTarget* self = TEST_TARGET(target); + + g_assert(self->transmit_id); + g_source_remove(self->transmit_id); + self->transmit_id = 0; +} + +static +gboolean +test_target_transmit_done( + gpointer user_data) +{ + TestTarget* self = TEST_TARGET(user_data); + NfcTarget* target = &self->target; + + g_assert(self->transmit_id); + self->transmit_id = 0; + if (self->cmd_resp->len) { + GUtilData* data = test_target_next_data(self); + + if (data) { + nfc_target_transmit_done(target, NFC_TRANSMIT_STATUS_OK, + data->bytes, data->size); + g_free(data); + } else { + nfc_target_transmit_done(target, NFC_TRANSMIT_STATUS_OK, NULL, 0); + } + } else { + nfc_target_transmit_done(target, NFC_TRANSMIT_STATUS_ERROR, NULL, 0); + } + return G_SOURCE_REMOVE; +} + +static +gboolean +test_target_transmit( + NfcTarget* target, + const void* data, + guint len) +{ + TestTarget* self = TEST_TARGET(target); + GUtilData* expected = test_target_next_data(self); + + if (expected) { + g_assert_cmpuint(expected->size, ==, len); + g_assert(!memcmp(data, expected->bytes, len)); + g_free(expected); + } + self->transmit_id = g_idle_add(test_target_transmit_done, self); + return TRUE; +} + +static +void +test_target_init( + TestTarget* self) +{ + self->cmd_resp = g_ptr_array_new_with_free_func(g_free); +} + +static +void +test_target_finalize( + GObject* object) +{ + TestTarget* self = TEST_TARGET(object); + + if (self->transmit_id) { + g_source_remove(self->transmit_id); + } + g_ptr_array_free(self->cmd_resp, TRUE); + G_OBJECT_CLASS(test_target_parent_class)->finalize(object); +} + +static +void +test_target_class_init( + NfcTargetClass* klass) +{ + klass->transmit = test_target_transmit; + klass->cancel_transmit = test_target_cancel_transmit; + G_OBJECT_CLASS(klass)->finalize = test_target_finalize; +} + +static +void +test_target_add_cmd_data( + TestTarget* self, + const GUtilData* data) +{ + g_ptr_array_add(self->cmd_resp, test_clone_data(data)); +} + +static +void +test_target_add_cmd( + TestTarget* self, + const void* bytes, + guint len) +{ + g_ptr_array_add(self->cmd_resp, test_alloc_data(bytes, len)); +} + +/*==========================================================================* + * null + *==========================================================================*/ + +static +void +test_null( + void) +{ + nfc_snep_server_remove_handler(NULL, 0); + nfc_snep_server_remove_handlers(NULL, NULL, 0); + g_assert(!nfc_snep_server_add_state_changed_handler(NULL, NULL, NULL)); + g_assert(!nfc_snep_server_add_ndef_changed_handler(NULL, NULL, NULL)); +} + +/*==========================================================================* + * idle + *==========================================================================*/ + +static +void +test_idle( + void) +{ + static const guint8 param_tlv_data[] = { + 0x01, 0x01, 0x11, 0x02, 0x02, 0x07, 0xff, 0x03, + 0x02, 0x00, 0x13, 0x04, 0x01, 0xff, 0x07, 0x01, + 0x03 + }; + static const guint8 symm_data[] = { 0x00, 0x00 }; + static const GUtilData param_tlv = { TEST_ARRAY_AND_SIZE(param_tlv_data) }; + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + TestTarget* tt = g_object_new(TEST_TYPE_TARGET, NULL); + NfcLlcParam** params = nfc_llc_param_decode(¶m_tlv); + NfcSnepServer* snep = nfc_snep_server_new(); + NfcPeerService* service = NFC_PEER_SERVICE(snep); + NfcTarget* target = NFC_TARGET(tt); + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc; + gulong id; + + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + test_target_add_cmd(tt, TEST_ARRAY_AND_SIZE(symm_data)); + + /* These have no effect */ + g_assert(!nfc_snep_server_add_state_changed_handler(snep, NULL, NULL)); + g_assert(!nfc_snep_server_add_ndef_changed_handler(snep, NULL, NULL)); + nfc_snep_server_remove_handler(snep, 0); + + g_assert(nfc_peer_services_add(services, service)); + g_assert_cmpuint(service->sap, == ,NFC_LLC_SAP_SNEP); + llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(llc->state == NFC_LLC_STATE_START); + + /* Wait for the conversation to start */ + id = nfc_llc_add_state_changed_handler(llc, test_llc_quit_loop_cb, loop); + test_run(&test_opt, loop); + if (llc->state == NFC_LLC_STATE_ACTIVE) { + /* Now wait until transfer error */ + test_run(&test_opt, loop); + } + + g_assert(!snep->ndef); + g_main_loop_unref(loop); + nfc_llc_remove_handler(llc, id); + nfc_llc_param_free(params); + nfc_peer_service_unref(service); + nfc_peer_services_unref(services); + nfc_llc_io_unref(io); + nfc_llc_free(llc); + nfc_target_unref(target); +} + +/*==========================================================================* + * ndef + *==========================================================================*/ + +static const guint8 param_tlv_data[] = { + 0x01, 0x01, 0x11, 0x02, 0x02, 0x07, 0xff, 0x03, + 0x02, 0x00, 0x13, 0x04, 0x01, 0xff, 0x07, 0x01, + 0x03 +}; +static const GUtilData param_tlv = { TEST_ARRAY_AND_SIZE(param_tlv_data) }; + +static const guint8 symm_data[] = { 0x00, 0x00 }; +static const guint8 connect_snep_data[] = { + 0x05, 0x20, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f, 0x06, 0x0f, 0x75, 0x72, 0x6e, 0x3a, 0x6e, + 0x66, 0x63, 0x3a, 0x73, 0x6e, 0x3a, 0x73, 0x6e, + 0x65, 0x70 +}; +static const guint8 cc_snep_data[] = { + 0x81, 0x84, 0x02, 0x02, 0x07, 0xff, 0x05, 0x01, + 0x0f +}; + +static +void +test_ndef( + const GUtilData* packets, + guint count) +{ + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + TestTarget* tt = g_object_new(TEST_TYPE_TARGET, NULL); + NfcLlcParam** params = nfc_llc_param_decode(¶m_tlv); + NfcSnepServer* snep = nfc_snep_server_new(); + NfcPeerService* service = NFC_PEER_SERVICE(snep); + NfcTarget* target = NFC_TARGET(tt); + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc; + int snep_state_change_count = 0, snep_ndef_change_count = 0; + gulong id, snep_id[2]; + guint i; + + for (i = 0; i < count; i++) { + test_target_add_cmd_data(tt, packets + i); + } + + g_assert(nfc_peer_services_add(services, service)); + g_assert_cmpuint(service->sap, == ,NFC_LLC_SAP_SNEP); + llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(llc->state == NFC_LLC_STATE_START); + + /* Count NfcSnepServer events */ + snep_id[0] = nfc_snep_server_add_state_changed_handler(snep, + test_snep_event_counter, &snep_state_change_count); + snep_id[1] = nfc_snep_server_add_ndef_changed_handler(snep, + test_snep_event_counter, &snep_ndef_change_count); + + /* Wait for the conversation to start */ + id = nfc_llc_add_state_changed_handler(llc, test_llc_quit_loop_cb, loop); + test_run(&test_opt, loop); + if (llc->state == NFC_LLC_STATE_ACTIVE) { + /* Now wait until transfer error or something else breaks the loop */ + test_run(&test_opt, loop); + } + + /* Assert that we have received expected number of events */ + g_assert_cmpint(snep_state_change_count, == ,2); + g_assert_cmpint(snep_ndef_change_count, == ,1); + nfc_snep_server_remove_handler(snep, snep_id[0]); + nfc_snep_server_remove_handler(snep, snep_id[1]); + + /* Assert that we have received the NDEF */ + g_assert(snep->ndef); + g_assert(NFC_IS_NDEF_REC_SP(snep->ndef)); + g_main_loop_unref(loop); + nfc_llc_remove_handler(llc, id); + nfc_llc_param_free(params); + nfc_peer_service_unref(service); + nfc_peer_services_unref(services); + nfc_llc_io_unref(io); + nfc_llc_free(llc); + nfc_target_unref(target); +} + +static +void +test_ndef_complete( + void) +{ + static const guint8 i_snep_4_32_put_data[] = { + 0x13, 0x20, 0x00, + 0x10, 0x02, 0x00, 0x00, 0x00, 0x1f, + 0xd1, 0x02, 0x1a, 0x53, 0x70, 0x91, 0x01, 0x0a, + 0x55, 0x03, 0x6a, 0x6f, 0x6c, 0x6c, 0x61, 0x2e, + 0x63, 0x6f, 0x6d, 0x51, 0x01, 0x08, 0x54, 0x02, + 0x65, 0x6e, 0x4a, 0x6f, 0x6c, 0x6c, 0x61 + }; + static const guint8 rnr_32_4_data[] = { 0x83, 0x84, 0x01 }; + static const guint8 disc_32_4_data[] = { 0x81, 0x44 }; + static const guint8 dm_4_32_data[] = { 0x11, 0xe0, 0x00 }; + static const GUtilData packets[] = { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(connect_snep_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_4_32_put_data) }, + { TEST_ARRAY_AND_SIZE(rnr_32_4_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(disc_32_4_data) }, + { TEST_ARRAY_AND_SIZE(dm_4_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + }; + test_ndef(TEST_ARRAY_AND_COUNT(packets)); +} + +static +void +test_ndef_flagmented( + void) +{ + static const guint8 i_snep_4_32_put_data[] = { + 0x13, 0x20, 0x00, + 0x10, 0x02, 0x00, 0x00, 0x00, 0x1f + }; + static const guint8 i_snep_32_4_continue_data[] = { + 0x83, 0x04, 0x01, + 0x10, 0x80, 0x00, 0x00, 0x00, 0x00 + }; + static const guint8 i_snep_4_32_ndef_data[] = { + 0x13, 0x20, 0x11, + 0xd1, 0x02, 0x1a, 0x53, 0x70, 0x91, 0x01, 0x0a, + 0x55, 0x03, 0x6a, 0x6f, 0x6c, 0x6c, 0x61, 0x2e, + 0x63, 0x6f, 0x6d, 0x51, 0x01, 0x08, 0x54, 0x02, + 0x65, 0x6e, 0x4a, 0x6f, 0x6c, 0x6c, 0x61 + }; + static const guint8 rnr_32_4_data[] = { 0x83, 0x84, 0x02 }; + static const guint8 disc_32_4_data[] = { 0x81, 0x44 }; + static const guint8 dm_4_32_data[] = { 0x11, 0xe0, 0x00 }; + static const GUtilData packets[] = { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(connect_snep_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_4_32_put_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_32_4_continue_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_4_32_ndef_data) }, + { TEST_ARRAY_AND_SIZE(rnr_32_4_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(disc_32_4_data) }, + { TEST_ARRAY_AND_SIZE(dm_4_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + }; + test_ndef(TEST_ARRAY_AND_COUNT(packets)); +} + +/*==========================================================================* + * fail + *==========================================================================*/ + +static +void +test_fail( + const GUtilData* packets, + guint count) +{ + GMainLoop* loop = g_main_loop_new(NULL, TRUE); + TestTarget* tt = g_object_new(TEST_TYPE_TARGET, NULL); + NfcLlcParam** params = nfc_llc_param_decode(¶m_tlv); + NfcSnepServer* snep = nfc_snep_server_new(); + NfcPeerService* service = NFC_PEER_SERVICE(snep); + NfcTarget* target = NFC_TARGET(tt); + NfcPeerServices* services = nfc_peer_services_new(); + NfcLlcIo* io = nfc_llc_io_initiator_new(target); + NfcLlc* llc; + int snep_state_change_count = 0, snep_ndef_change_count = 0; + gulong id, snep_id[2]; + guint i; + + for (i = 0; i < count; i++) { + test_target_add_cmd_data(tt, packets + i); + } + + g_assert(nfc_peer_services_add(services, service)); + g_assert_cmpuint(service->sap, == ,NFC_LLC_SAP_SNEP); + llc = nfc_llc_new(io, services, nfc_llc_param_constify(params)); + g_assert(llc->state == NFC_LLC_STATE_START); + + /* Count NfcSnepServer events */ + snep_id[0] = nfc_snep_server_add_state_changed_handler(snep, + test_snep_event_counter, &snep_state_change_count); + snep_id[1] = nfc_snep_server_add_ndef_changed_handler(snep, + test_snep_event_counter, &snep_ndef_change_count); + + /* Wait for the conversation to start */ + id = nfc_llc_add_state_changed_handler(llc, test_llc_quit_loop_cb, loop); + test_run(&test_opt, loop); + if (llc->state == NFC_LLC_STATE_ACTIVE) { + /* Now wait until transfer error or something else breaks the loop */ + test_run(&test_opt, loop); + } + + /* Assert that we have received expected number of events */ + g_assert_cmpint(snep_state_change_count, == ,2); + g_assert_cmpint(snep_ndef_change_count, == ,0); + nfc_snep_server_remove_handler(snep, snep_id[0]); + nfc_snep_server_remove_handler(snep, snep_id[1]); + + /* Assert that we have received the NDEF */ + g_assert(!snep->ndef); + g_main_loop_unref(loop); + nfc_llc_remove_handler(llc, id); + nfc_llc_param_free(params); + nfc_peer_service_unref(service); + nfc_peer_services_unref(services); + nfc_llc_io_unref(io); + nfc_llc_free(llc); + nfc_target_unref(target); +} + +static +void +test_fail_short( + void) +{ + static const guint8 i_snep_4_32_short_data[] = { + 0x13, 0x20, 0x00, + 0x20, 0x02 + }; + static const guint8 rnr_32_4_data[] = { 0x83, 0x84, 0x01 }; + static const guint8 disc_32_4_data[] = { 0x81, 0x44 }; + static const guint8 dm_4_32_data[] = { 0x11, 0xe0, 0x00 }; + static const GUtilData packets[] = { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(connect_snep_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_4_32_short_data) }, + { TEST_ARRAY_AND_SIZE(rnr_32_4_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(disc_32_4_data) }, + { TEST_ARRAY_AND_SIZE(dm_4_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + }; + test_fail(TEST_ARRAY_AND_COUNT(packets)); +} + +static +void +test_fail_version( + void) +{ + static const guint8 i_snep_4_32_put_data[] = { + 0x13, 0x20, 0x00, + 0x20, 0x02, 0x00, 0x00, 0x00, 0x00 + }; + static const guint8 i_snep_32_4_resp_data[] = { + 0x83, 0x04, 0x01, + 0x10, 0xe1, 0x00, 0x00, 0x00, 0x00 + }; + static const guint8 rnr_4_32_data[] = { 0x13, 0xa0, 0x01 }; + static const guint8 disc_32_4_data[] = { 0x81, 0x44 }; + static const guint8 dm_4_32_data[] = { 0x11, 0xe0, 0x00 }; + static const GUtilData packets[] = { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(connect_snep_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_4_32_put_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_32_4_resp_data) }, + { TEST_ARRAY_AND_SIZE(rnr_4_32_data) }, + { TEST_ARRAY_AND_SIZE(disc_32_4_data) }, + { TEST_ARRAY_AND_SIZE(dm_4_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + }; + test_fail(TEST_ARRAY_AND_COUNT(packets)); +} + +static +void +test_fail_get( + void) +{ + static const guint8 i_snep_4_32_get_data[] = { + 0x13, 0x20, 0x00, + 0x10, 0x01, 0x00, 0x00, 0x00, 0x00 + }; + static const guint8 i_snep_32_4_resp_data[] = { + 0x83, 0x04, 0x01, + 0x10, 0xe0, 0x00, 0x00, 0x00, 0x00 + }; + static const guint8 rnr_4_32_data[] = { 0x13, 0xa0, 0x01 }; + static const guint8 disc_32_4_data[] = { 0x81, 0x44 }; + static const guint8 dm_4_32_data[] = { 0x11, 0xe0, 0x00 }; + static const GUtilData packets[] = { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(connect_snep_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_4_32_get_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_32_4_resp_data) }, + { TEST_ARRAY_AND_SIZE(rnr_4_32_data) }, + { TEST_ARRAY_AND_SIZE(disc_32_4_data) }, + { TEST_ARRAY_AND_SIZE(dm_4_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + }; + test_fail(TEST_ARRAY_AND_COUNT(packets)); +} + +static +void +test_fail_bad_request( + void) +{ + static const guint8 i_snep_4_32_get_data[] = { + 0x13, 0x20, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const guint8 i_snep_32_4_resp_data[] = { + 0x83, 0x04, 0x01, + 0x10, 0xc2, 0x00, 0x00, 0x00, 0x00 + }; + static const guint8 rnr_4_32_data[] = { 0x13, 0xa0, 0x01 }; + static const guint8 disc_32_4_data[] = { 0x81, 0x44 }; + static const guint8 dm_4_32_data[] = { 0x11, 0xe0, 0x00 }; + static const GUtilData packets[] = { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(connect_snep_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_4_32_get_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_32_4_resp_data) }, + { TEST_ARRAY_AND_SIZE(rnr_4_32_data) }, + { TEST_ARRAY_AND_SIZE(disc_32_4_data) }, + { TEST_ARRAY_AND_SIZE(dm_4_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + }; + test_fail(TEST_ARRAY_AND_COUNT(packets)); +} + +static +void +test_fail_extra_data( + void) +{ + static const guint8 i_snep_4_32_broken_data[] = { + 0x13, 0x20, 0x00, + 0x10, 0x02, 0x00, 0x00, 0x00, 0x1f, + 0xd1, 0x02, 0x1a, 0x53, 0x70, 0x91, 0x01, 0x0a, + 0x55, 0x03, 0x6a, 0x6f, 0x6c, 0x6c, 0x61, 0x2e, + 0x63, 0x6f, 0x6d, 0x51, 0x01, 0x08, 0x54, 0x02, + 0x65, 0x6e, 0x4a, 0x6f, 0x6c, 0x6c, 0x61, + 0x00 /* Extra byte */ + }; + static const guint8 rnr_32_4_data[] = { 0x83, 0x84, 0x01 }; + static const guint8 disc_32_4_data[] = { 0x81, 0x44 }; + static const guint8 dm_4_32_data[] = { 0x11, 0xe0, 0x00 }; + static const GUtilData packets[] = { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(connect_snep_data) }, + { TEST_ARRAY_AND_SIZE(cc_snep_data) }, + { TEST_ARRAY_AND_SIZE(i_snep_4_32_broken_data) }, + { TEST_ARRAY_AND_SIZE(rnr_32_4_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(disc_32_4_data) }, + { TEST_ARRAY_AND_SIZE(dm_4_32_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + }; + test_fail(TEST_ARRAY_AND_COUNT(packets)); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +int main(int argc, char* argv[]) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("idle"), test_idle); + g_test_add_func(TEST_("ndef/complete"), test_ndef_complete); + g_test_add_func(TEST_("ndef/flagmented"), test_ndef_flagmented); + g_test_add_func(TEST_("fail/short"), test_fail_short); + g_test_add_func(TEST_("fail/version"), test_fail_version); + g_test_add_func(TEST_("fail/get"), test_fail_get); + g_test_add_func(TEST_("fail/bad_request"), test_fail_bad_request); + g_test_add_func(TEST_("fail/extra_data"), test_fail_extra_data); + test_init(&test_opt, argc, argv); + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/core_tag/test_core_tag.c b/unit/core_tag/test_core_tag.c index 7514e35..01ad2f8 100644 --- a/unit/core_tag/test_core_tag.c +++ b/unit/core_tag/test_core_tag.c @@ -81,7 +81,7 @@ test_basic( void) { NfcTag* tag = g_object_new(NFC_TYPE_TAG, NULL); - NfcTarget* target = test_target_new(); + NfcTarget* target = test_target_new(FALSE); NfcParamPoll poll; const char* name = "test"; int init_count = 0; @@ -146,7 +146,7 @@ test_basic_a( static const GUtilData nfcid1_data = { TEST_ARRAY_AND_SIZE(nfcid1) }; NfcTag* tag = g_object_new(NFC_TYPE_TAG, NULL); - NfcTarget* target = test_target_new_tech(NFC_TECHNOLOGY_A); + NfcTarget* target = test_target_new_tech(NFC_TECHNOLOGY_A, FALSE); NfcParamPoll poll; const NfcParamPollA* poll_a; @@ -191,7 +191,7 @@ test_basic_b( static const guint8 app_data_empty[] = {0x00, 0x00, 0x00, 0x00}; NfcTag* tag = g_object_new(NFC_TYPE_TAG, NULL); - NfcTarget* target = test_target_new_tech(NFC_TECHNOLOGY_B); + NfcTarget* target = test_target_new_tech(NFC_TECHNOLOGY_B, FALSE); NfcParamPoll poll; const NfcParamPollB* poll_b; diff --git a/unit/core_target/test_core_target.c b/unit/core_target/test_core_target.c index f7c0210..3eb390c 100644 --- a/unit/core_target/test_core_target.c +++ b/unit/core_target/test_core_target.c @@ -41,8 +41,6 @@ static TestOpt test_opt; -#define TEST_TIMEOUT (10) /* seconds */ - static void test_quit_loop( diff --git a/unit/coverage/run b/unit/coverage/run index 19b033c..1603143 100755 --- a/unit/coverage/run +++ b/unit/coverage/run @@ -6,13 +6,21 @@ TESTS="\ core_adapter \ core_crc \ +core_initiator \ +core_llc \ +core_llc_param \ core_manager \ core_ndef_rec \ core_ndef_rec_sp \ core_ndef_rec_t \ core_ndef_rec_u \ +core_peer \ +core_peer_service \ +core_peer_services \ +core_peer_socket \ core_plugin \ core_plugins \ +core_snep \ core_tag \ core_tag_t2 \ core_tag_t4 \ @@ -27,6 +35,7 @@ plugins_dbus_handlers_type_sp \ plugins_dbus_handlers_type_text \ plugins_dbus_handlers_type_uri \ plugins_dbus_service_adapter \ +plugins_dbus_service_peer \ plugins_dbus_service_plugin \ plugins_dbus_service_tag \ plugins_dbus_service_util" diff --git a/unit/plugins_dbus_service_adapter/Makefile b/unit/plugins_dbus_service_adapter/Makefile index 84c94b6..27f175f 100644 --- a/unit/plugins_dbus_service_adapter/Makefile +++ b/unit/plugins_dbus_service_adapter/Makefile @@ -2,6 +2,7 @@ EXE = test_plugins_dbus_service_adapter -COMMON_SRC = test_dbus.c test_main.c test_adapter.c test_target.c +COMMON_SRC = test_dbus.c test_main.c test_adapter.c test_target.c \ + test_initiator.c include ../common/Makefile.plugins diff --git a/unit/plugins_dbus_service_adapter/test_plugins_dbus_service_adapter.c b/unit/plugins_dbus_service_adapter/test_plugins_dbus_service_adapter.c index 9213cb7..e854cf5 100644 --- a/unit/plugins_dbus_service_adapter/test_plugins_dbus_service_adapter.c +++ b/unit/plugins_dbus_service_adapter/test_plugins_dbus_service_adapter.c @@ -33,6 +33,8 @@ #include "nfc_adapter_p.h" #include "nfc_plugins.h" #include "nfc_adapter_impl.h" +#include "nfc_peer.h" +#include "nfc_initiator_impl.h" #include "nfc_target_impl.h" #include "nfc_tag.h" @@ -43,9 +45,11 @@ #include "test_common.h" #include "test_adapter.h" #include "test_target.h" +#include "test_initiator.h" #include "test_dbus.h" #define NFC_ADAPTER_INTERFACE "org.sailfishos.nfc.Adapter" +#define NFC_ADAPTER_INTERFACE_VERSION (2) static TestOpt test_opt; @@ -53,6 +57,7 @@ typedef struct test_data { GMainLoop* loop; NfcManager* manager; NfcAdapter* adapter; + NfcInitiator* initiator; DBusServiceAdapter* service; } TestData; @@ -78,6 +83,7 @@ test_data_cleanup( { nfc_adapter_unref(test->adapter); nfc_manager_unref(test->manager); + nfc_initiator_unref(test->initiator); dbus_service_adapter_free(test->service); g_main_loop_unref(test->loop); } @@ -107,6 +113,7 @@ void test_null( void) { + g_assert(!dbus_service_adapter_find_peer(NULL, NULL)); dbus_service_adapter_free(NULL); } @@ -176,10 +183,11 @@ test_get_all_done( GDEBUG("version=%d, enabled=%d, powered=%d, modes=%0x04X, mode=0x%04X, " "target_present=%d, %u tags", version, enabled, powered, modes, mode, target_present, g_strv_length(tags)); - g_assert(version >= 1); + g_assert(version >= NFC_ADAPTER_INTERFACE_VERSION); g_assert(enabled); g_assert(!powered); g_assert(!target_present); + g_assert(tags); g_variant_unref(var); g_strfreev(tags); test_quit_later(test->loop); @@ -211,6 +219,71 @@ test_get_all( test_dbus_free(dbus); } +/*==========================================================================* + * get_all2 + *==========================================================================*/ + +static +void +test_get_all2_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + gint version = 0; + gboolean enabled = FALSE, powered = TRUE, target_present = FALSE; + guint modes, mode; + gchar** tags = NULL; + gchar** peers = NULL; + GVariant* var = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, NULL); + + g_assert(var); + g_variant_get(var, "(ibbuub^ao^ao)", &version, &enabled, &powered, + &modes, &mode, &target_present, &tags, &peers); + GDEBUG("version=%d, enabled=%d, powered=%d, modes=%0x04X, mode=0x%04X, " + "target_present=%d, %u tags, %u peers", version, enabled, powered, + modes, mode, target_present, g_strv_length(tags), + g_strv_length(peers)); + g_assert(version >= NFC_ADAPTER_INTERFACE_VERSION); + g_assert(enabled); + g_assert(!powered); + g_assert(!target_present); + g_assert(tags); + g_assert(peers); + g_variant_unref(var); + g_strfreev(tags); + g_strfreev(peers); + test_quit_later(test->loop); +} + +static +void +test_get_all2_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + test_start_and_get((TestData*)user_data, client, server, + "GetAll2", test_get_all2_done); +} + +static +void +test_get_all2( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new(test_get_all2_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + /*==========================================================================* * get_interface_version *==========================================================================*/ @@ -551,7 +624,7 @@ test_get_tags_start( g_assert(test->service); /* Add second tag after creating DBusServiceAdapter */ - target = test_target_new(); + target = test_target_new(FALSE); memset(&poll, 0, sizeof(poll)); g_assert(nfc_adapter_add_other_tag2(test->adapter, target, &poll)); nfc_target_unref(target); @@ -575,7 +648,7 @@ test_get_tags( test_data_init(&test); /* Add one tag before creating DBusServiceAdapter */ - target = test_target_new(); + target = test_target_new(FALSE); memset(&poll, 0, sizeof(poll)); g_assert(nfc_adapter_add_other_tag2(test.adapter, target, &poll)); nfc_target_unref(target); @@ -586,6 +659,65 @@ test_get_tags( test_dbus_free(dbus); } +/*==========================================================================* + * get_peers + *==========================================================================*/ + +static +void +test_get_peers_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + gchar** peers = NULL; + GVariant* var = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, NULL); + + g_assert(var); + g_variant_get(var, "(^ao)", &peers); + g_assert(peers); + GDEBUG("%u peer(s)", g_strv_length(peers)); + g_variant_unref(var); + g_strfreev(peers); + test_quit_later_n(test->loop, 100); /* Allow everything to complete */ +} + +static +void +test_get_peers_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + TestData* test = user_data; + + test->service = dbus_service_adapter_new(test->adapter, server); + g_assert(!dbus_service_adapter_find_peer(test->service, NULL)); + g_assert(test->service); + + g_dbus_connection_call(client, NULL, + dbus_service_adapter_path(test->service), NFC_ADAPTER_INTERFACE, + "GetPeers", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, + test_get_peers_done, test); +} + +static +void +test_get_peers( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new(test_get_peers_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + /*==========================================================================* * enabled_changed *==========================================================================*/ @@ -818,7 +950,7 @@ test_tag_added_start( test, NULL)); /* Add a tag */ - target = test_target_new(); + target = test_target_new(FALSE); memset(&poll, 0, sizeof(poll)); g_assert(nfc_adapter_add_other_tag2(test->adapter, target, &poll)); nfc_target_unref(target); @@ -903,7 +1035,7 @@ test_tag_removed( test_data_init(&test); - target = test_target_new(); + target = test_target_new(FALSE); memset(&poll, 0, sizeof(poll)); g_assert(nfc_adapter_add_other_tag2(test.adapter, target, &poll)); nfc_target_unref(target); @@ -914,6 +1046,155 @@ test_tag_removed( test_dbus_free(dbus); } +/*==========================================================================* + * peer_added + *==========================================================================*/ + +static const guint8 atr_req_general_bytes [] = { + 0x46, 0x66, 0x6d, 0x01, 0x01, 0x11, 0x02, 0x02, + 0x07, 0xff, 0x03, 0x02, 0x00, 0x13, 0x04, 0x01, + 0xff +}; +static const NfcParamNfcDepTarget peer_target_param = { + { TEST_ARRAY_AND_SIZE(atr_req_general_bytes) } +}; + +static +void +test_peer_added_handler( + GDBusConnection* connection, + const char* sender, + const char* path, + const char* iface, + const char* name, + GVariant* args, + gpointer user_data) +{ + TestData* test = user_data; + gchar** peers = NULL; + + g_variant_get(args, "(^ao)", &peers); + g_assert(peers); + GDEBUG("%u peer(s)", g_strv_length(peers)); + g_assert(g_strv_length(peers) == 1); + g_strfreev(peers); + test_quit_later(test->loop); +} + +static +void +test_peer_added_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + TestData* test = user_data; + NfcInitiator* initiator; + + test->service = dbus_service_adapter_new(test->adapter, server); + g_assert(test->service); + + g_assert(g_dbus_connection_signal_subscribe(client, NULL, + NFC_ADAPTER_INTERFACE, "PeersChanged", + dbus_service_adapter_path(test->service), NULL, + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, test_peer_added_handler, + test, NULL)); + + /* Add a peer */ + initiator = test_initiator_new(); + g_assert(nfc_adapter_add_peer_target_a(test->adapter, initiator, NULL, + &peer_target_param)); + nfc_initiator_unref(initiator); +} + +static +void +test_peer_added( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new(test_peer_added_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * peer_removed + *==========================================================================*/ + +static +void +test_peer_removed_handler( + GDBusConnection* connection, + const char* sender, + const char* path, + const char* iface, + const char* name, + GVariant* args, + gpointer user_data) +{ + TestData* test = user_data; + gchar** peers = NULL; + + g_variant_get(args, "(^ao)", &peers); + g_assert(peers); + GDEBUG("%u peer(s)", g_strv_length(peers)); + g_assert(g_strv_length(peers) == 0); + g_strfreev(peers); + test_quit_later(test->loop); +} + +static +void +test_peer_removed_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + TestData* test = user_data; + NfcPeer* peer = nfc_peer_ref(nfc_adapter_peers(test->adapter)[0]); + + test->service = dbus_service_adapter_new(test->adapter, server); + g_assert(test->service); + g_assert(peer); + g_assert(dbus_service_adapter_find_peer(test->service, peer)); + + g_assert(g_dbus_connection_signal_subscribe(client, NULL, + NFC_ADAPTER_INTERFACE, "PeersChanged", + dbus_service_adapter_path(test->service), NULL, + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, test_peer_removed_handler, + test, NULL)); + + /* Remove the peer */ + nfc_initiator_gone(test->initiator); + g_assert(!dbus_service_adapter_find_peer(test->service, peer)); + nfc_peer_unref(peer); +} + +static +void +test_peer_removed( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + + test.initiator = test_initiator_new(); + g_assert(nfc_adapter_add_peer_target_a(test.adapter, test.initiator, NULL, + &peer_target_param)); + + dbus = test_dbus_new(test_peer_removed_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + /*==========================================================================* * Common *==========================================================================*/ @@ -929,6 +1210,7 @@ int main(int argc, char* argv[]) g_test_add_func(TEST_("null"), test_null); g_test_add_func(TEST_("basic"), test_basic); g_test_add_func(TEST_("get_all"), test_get_all); + g_test_add_func(TEST_("get_all2"), test_get_all2); g_test_add_func(TEST_("get_interface_version"), test_get_interface_version); g_test_add_func(TEST_("get_enabled"), test_get_enabled); g_test_add_func(TEST_("get_powered"), test_get_powered); @@ -936,11 +1218,14 @@ int main(int argc, char* argv[]) g_test_add_func(TEST_("get_mode"), test_get_mode); g_test_add_func(TEST_("get_target_present"), test_get_target_present); g_test_add_func(TEST_("get_tags"), test_get_tags); + g_test_add_func(TEST_("get_peers"), test_get_peers); g_test_add_func(TEST_("enabled_changed"), test_enabled_changed); g_test_add_func(TEST_("powered_changed"), test_powered_changed); g_test_add_func(TEST_("mode_changed"), test_mode_changed); g_test_add_func(TEST_("tag_added"), test_tag_added); g_test_add_func(TEST_("tag_removed"), test_tag_removed); + g_test_add_func(TEST_("peer_added"), test_peer_added); + g_test_add_func(TEST_("peer_removed"), test_peer_removed); test_init(&test_opt, argc, argv); return g_test_run(); } diff --git a/unit/plugins_dbus_service_peer/Makefile b/unit/plugins_dbus_service_peer/Makefile new file mode 100644 index 0000000..ce63b83 --- /dev/null +++ b/unit/plugins_dbus_service_peer/Makefile @@ -0,0 +1,7 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_plugins_dbus_service_peer + +COMMON_SRC = test_dbus.c test_main.c test_adapter.c test_initiator.c + +include ../common/Makefile.plugins diff --git a/unit/plugins_dbus_service_peer/test_plugins_dbus_service_peer.c b/unit/plugins_dbus_service_peer/test_plugins_dbus_service_peer.c new file mode 100644 index 0000000..615fdb7 --- /dev/null +++ b/unit/plugins_dbus_service_peer/test_plugins_dbus_service_peer.c @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2020-2021 Jolla Ltd. + * Copyright (C) 2020-2021 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nfc_types_p.h" +#include "nfc_adapter.h" +#include "nfc_initiator.h" +#include "nfc_plugins.h" +#include "nfc_peer.h" +#include "nfc_peer_service.h" +#include "internal/nfc_manager_i.h" + +#include "dbus_service/dbus_service.h" + +#include "test_common.h" +#include "test_adapter.h" +#include "test_initiator.h" +#include "test_dbus.h" + +#define NFC_PEER_INTERFACE "org.sailfishos.nfc.Peer" +#define NFC_PEER_INTERFACE_VERSION (1) +#define NFC_PEER_DEFAULT_WKS \ + ((1 << NFC_LLC_SAP_SDP) | \ + (1 << NFC_LLC_SAP_SNEP) | \ + 0x01) + +static TestOpt test_opt; + +typedef struct test_data { + GMainLoop* loop; + NfcManager* manager; + NfcAdapter* adapter; + NfcPeer* peer; + DBusServicePeer* service; +} TestData; + +static +void +test_data_init( + TestData* test) +{ + static const guint8 atr_req_general_bytes [] = { + 0x46, 0x66, 0x6d, 0x01, 0x01, 0x11, 0x02, 0x02, + 0x07, 0xff, 0x03, 0x02, 0x00, 0x13, 0x04, 0x01, + 0xff + }; + static const NfcParamNfcDepTarget peer_target_param = { + { TEST_ARRAY_AND_SIZE(atr_req_general_bytes) } + }; + static const guint8 symm_data[] = { 0x00, 0x00 }; + static const TestTx tx[] = { + { + { TEST_ARRAY_AND_SIZE(symm_data) }, + { TEST_ARRAY_AND_SIZE(symm_data) } + } + }; + NfcPluginsInfo pi; + NfcInitiator* initiator; + + memset(test, 0, sizeof(*test)); + memset(&pi, 0, sizeof(pi)); + g_assert((test->manager = nfc_manager_new(&pi)) != NULL); + g_assert((test->adapter = test_adapter_new()) != NULL); + g_assert(nfc_manager_add_adapter(test->manager, test->adapter)); + initiator = test_initiator_new_with_tx2(TEST_ARRAY_AND_COUNT(tx), TRUE); + test->peer = nfc_peer_ref(nfc_adapter_add_peer_target_a(test->adapter, + initiator, NULL, &peer_target_param)); + g_assert(test->peer); + nfc_initiator_unref(initiator); + test->loop = g_main_loop_new(NULL, TRUE); +} + +static +void +test_data_cleanup( + TestData* test) +{ + dbus_service_peer_free(test->service); + nfc_peer_unref(test->peer); + nfc_adapter_unref(test->adapter); + nfc_manager_unref(test->manager); + g_main_loop_unref(test->loop); +} + +static +void +test_start_and_get( + TestData* test, + GDBusConnection* client, + GDBusConnection* server, + const char* method, + GAsyncReadyCallback callback) +{ + test->service = dbus_service_peer_new(test->peer, "/nfc0", server); + g_assert(test->service); + g_dbus_connection_call(client, NULL, test->service->path, + NFC_PEER_INTERFACE, method, NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, + NULL, callback, test); +} + +/*==========================================================================* + * null + *==========================================================================*/ + +static +void +test_null( + void) +{ + dbus_service_peer_free(NULL); +} + +/*==========================================================================* + * get_all + *==========================================================================*/ + +static +void +test_get_all_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + gint version = 0; + gboolean present = FALSE; + gchar** ifaces = NULL; + guint tech, wks; + GVariant* var = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, NULL); + + g_assert(var); + g_variant_get(var, "(ibu^asu)", &version, &present, &tech, &ifaces, &wks); + GDEBUG("version=%d, present=%d, tech=%u, %u interface(s), wks=0x%02x", + version, present, tech, g_strv_length(ifaces), wks); + g_assert(version >= NFC_PEER_INTERFACE_VERSION); + g_assert(present); + g_assert_cmpuint(g_strv_length(ifaces), > ,0); + g_assert_cmpstr(ifaces[0], == ,NFC_PEER_INTERFACE); + g_assert_cmpuint(tech, == ,NFC_TECHNOLOGY_A); + g_assert_cmpuint(wks, == ,NFC_PEER_DEFAULT_WKS); + g_strfreev(ifaces); + g_variant_unref(var); + test_quit_later(test->loop); +} + +static +void +test_get_all_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + TestData* test = user_data; + + test_start_and_get(test, client, server, "GetAll", test_get_all_done); +} + +static +void +test_get_all( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new(test_get_all_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * get_interface_version + *==========================================================================*/ + +static +void +test_get_interface_version_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + gint version = 0; + GVariant* var = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, NULL); + + g_assert(var); + g_variant_get(var, "(i)", &version); + GDEBUG("version=%d", version); + g_assert_cmpint(version, >= ,NFC_PEER_INTERFACE_VERSION); + g_variant_unref(var); + test_quit_later(test->loop); +} + +static +void +test_get_interface_version_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + test_start_and_get((TestData*)user_data, client, server, + "GetInterfaceVersion", test_get_interface_version_done); +} + +static +void +test_get_interface_version( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new(test_get_interface_version_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * get_present + *==========================================================================*/ + +static +void +test_get_present_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + gboolean present = FALSE; + GVariant* var = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, NULL); + + g_assert(var); + g_variant_get(var, "(b)", &present); + GDEBUG("present=%d", present); + g_assert(present); + g_variant_unref(var); + test_quit_later(test->loop); +} + +static +void +test_get_present_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + test_start_and_get((TestData*)user_data, client, server, + "GetPresent", test_get_present_done); +} + +static +void +test_get_present( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new(test_get_present_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * get_technology + *==========================================================================*/ + +static +void +test_get_technology_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + guint tech = 0; + GVariant* var = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, NULL); + + g_assert(var); + g_variant_get(var, "(u)", &tech); + GDEBUG("tech=%u", tech); + g_assert_cmpuint(tech, == ,NFC_TECHNOLOGY_A); + g_variant_unref(var); + test_quit_later(test->loop); +} + +static +void +test_get_technology_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + test_start_and_get((TestData*)user_data, client, server, + "GetTechnology", test_get_technology_done); +} + +static +void +test_get_technology( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new(test_get_technology_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * get_interfaces + *==========================================================================*/ + +static +void +test_get_interfaces_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + gchar** ifaces = NULL; + GVariant* var = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, NULL); + + g_assert(var); + g_variant_get(var, "(^as)", &ifaces); + g_assert(ifaces); + GDEBUG("%u interface(s)", g_strv_length(ifaces)); + g_assert_cmpuint(g_strv_length(ifaces), > ,0); + g_assert_cmpstr(ifaces[0], == ,NFC_PEER_INTERFACE); + g_strfreev(ifaces); + g_variant_unref(var); + test_quit_later(test->loop); +} + +static +void +test_get_interfaces_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + test_start_and_get((TestData*)user_data, client, server, + "GetInterfaces", test_get_interfaces_done); +} + +static +void +test_get_interfaces( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new(test_get_interfaces_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * get_wks + *==========================================================================*/ + +static +void +test_get_wks_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + guint wks = 0; + GVariant* var = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, NULL); + + g_assert(var); + g_variant_get(var, "(u)", &wks); + GDEBUG("wks=0x%02x", wks); + g_assert_cmpuint(wks, == ,NFC_PEER_DEFAULT_WKS); + g_variant_unref(var); + test_quit_later(test->loop); +} + +static +void +test_get_wks_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + test_start_and_get((TestData*)user_data, client, server, + "GetWellKnownServices", test_get_wks_done); +} + +static +void +test_get_wks( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new(test_get_wks_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * deactivate + *==========================================================================*/ + +static +void +test_deactivate_removed( + GDBusConnection* connection, + const char* sender, + const char* path, + const char* iface, + const char* name, + GVariant* args, + gpointer user_data) +{ + TestData* test = user_data; + + GDEBUG("%s removed", path); + g_assert(!test->peer->present); + test_quit_later(test->loop); +} + +static +void +test_deactivate_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + GVariant* var = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, NULL); + + g_assert(var); + g_variant_unref(var); + GDEBUG("%s deactivated", test->service->path); + g_assert(!test->peer->present); + dbus_service_peer_free(test->service); + test->service = NULL; +} + +static +void +test_get_deactivate_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + TestData* test = user_data; + + test->service = dbus_service_peer_new(test->peer, "/nfc0", server); + g_assert(test->service); + g_assert(g_dbus_connection_signal_subscribe(client, NULL, + NFC_PEER_INTERFACE, "Removed", test->service->path, NULL, + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, test_deactivate_removed, + test, NULL)); + g_dbus_connection_call(client, NULL, test->service->path, + NFC_PEER_INTERFACE, "Deactivate", NULL, NULL, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, test_deactivate_done, test); +} + +static +void +test_deactivate( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new(test_get_deactivate_start, &test); + test_run(&test_opt, test.loop); + g_assert(!test.peer->present); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +#define TEST_(name) "/plugins/dbus_service/peer/" name + +int main(int argc, char* argv[]) +{ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + g_type_init(); + G_GNUC_END_IGNORE_DEPRECATIONS; + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("get_all"), test_get_all); + g_test_add_func(TEST_("get_interface_version"), test_get_interface_version); + g_test_add_func(TEST_("get_present"), test_get_present); + g_test_add_func(TEST_("get_technology"), test_get_technology); + g_test_add_func(TEST_("get_interfaces"), test_get_interfaces); + g_test_add_func(TEST_("get_wks"), test_get_wks); + g_test_add_func(TEST_("deactivate"), test_deactivate); + test_init(&test_opt, argc, argv); + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/unit/plugins_dbus_service_plugin/test_plugins_dbus_service_plugin.c b/unit/plugins_dbus_service_plugin/test_plugins_dbus_service_plugin.c index 215bccb..8e5e1fa 100644 --- a/unit/plugins_dbus_service_plugin/test_plugins_dbus_service_plugin.c +++ b/unit/plugins_dbus_service_plugin/test_plugins_dbus_service_plugin.c @@ -42,21 +42,27 @@ #include "test_adapter.h" #include "test_dbus.h" +#define NFC_DAEMON_PATH "/" #define NFC_DAEMON_INTERFACE "org.sailfishos.nfc.Daemon" +#define NFC_DAEMON_INTERFACE_VERSION (3) static TestOpt test_opt; static GDBusConnection* test_server; +static DBusServicePlugin* test_plugin; +static const char* dbus_sender = ":1.0"; typedef struct test_data { GMainLoop* loop; NfcManager* manager; NfcAdapter* adapter; + GDBusConnection* client; /* Owned by TestDBus */ } TestData; static void -test_data_init( - TestData* test) +test_data_init2( + TestData* test, + gboolean add_adapter) { NfcPluginsInfo pi; static const NfcPluginDesc* const test_builtin_plugins[] = { @@ -69,10 +75,20 @@ test_data_init( pi.builtins = test_builtin_plugins; g_assert((test->manager = nfc_manager_new(&pi)) != NULL); g_assert((test->adapter = test_adapter_new()) != NULL); - g_assert(nfc_manager_add_adapter(test->manager, test->adapter)); + if (add_adapter) { + g_assert(nfc_manager_add_adapter(test->manager, test->adapter)); + } test->loop = g_main_loop_new(NULL, TRUE); } +static +void +test_data_init( + TestData* test) +{ + test_data_init2(test, TRUE); +} + static void test_data_cleanup( @@ -105,15 +121,43 @@ test_call( const char* method, GAsyncReadyCallback callback) { - g_dbus_connection_call(client, NULL, "/", NFC_DAEMON_INTERFACE, + g_dbus_connection_call(client, NULL, NFC_DAEMON_PATH, NFC_DAEMON_INTERFACE, method, NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, callback, test); } +static +void +test_call_register_local_service( + GDBusConnection* client, + const char* path, + const char* name, + GAsyncReadyCallback callback, + TestData* test) +{ + g_dbus_connection_call(client, NULL, NFC_DAEMON_PATH, NFC_DAEMON_INTERFACE, + "RegisterLocalService", g_variant_new ("(os)", path, name), NULL, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, callback, test); +} + +static +void +test_call_unregister_local_service( + GDBusConnection* client, + const char* path, + GAsyncReadyCallback callback, + TestData* test) +{ + g_dbus_connection_call(client, NULL, NFC_DAEMON_PATH, NFC_DAEMON_INTERFACE, + "UnregisterLocalService", g_variant_new ("(o)", path), NULL, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, callback, test); +} + /*==========================================================================* * Stubs *==========================================================================*/ #define TEST_NAME_OWN_ID (1) +#define TEST_NAME_WATCH_ID (2) typedef struct test_bus_acquired_data { char* name; @@ -141,6 +185,7 @@ test_bus_acquired_free( { TestBusAcquiredData* data = user_data; + g_assert(data->plugin == test_plugin); g_free(data->name); g_free(data); } @@ -155,7 +200,7 @@ dbus_service_name_own( { TestBusAcquiredData* data = g_new(TestBusAcquiredData, 1); - data->plugin = plugin; + data->plugin = test_plugin = plugin; data->name = g_strdup(name); data->bus_acquired = bus_acquired; data->name_acquired = name_acquired; @@ -168,9 +213,73 @@ void dbus_service_name_unown( guint id) { + g_assert(test_plugin); + test_plugin = NULL; g_assert(id == TEST_NAME_OWN_ID); } +const gchar* +g_dbus_method_invocation_get_sender( + GDBusMethodInvocation* call) +{ + return dbus_sender; +} + +guint +g_bus_watch_name_on_connection( + GDBusConnection* connection, + const gchar* name, + GBusNameWatcherFlags flags, + GBusNameAppearedCallback name_appeared_handler, + GBusNameVanishedCallback name_vanished_handler, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + g_assert_cmpstr(name, == ,dbus_sender); + return TEST_NAME_WATCH_ID; +} + +void +g_bus_unwatch_name( + guint watcher_id) +{ + g_assert_cmpuint(watcher_id, == ,TEST_NAME_WATCH_ID); +} + +/*==========================================================================* + * no_peers + *==========================================================================*/ + +static +void +test_no_peers_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + TestData* test = user_data; + + g_assert(test_plugin); + g_assert(!dbus_service_plugin_find_peer(test_plugin, NULL)); + test_quit_later(test->loop); +} + +static +void +test_no_peers( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new2(test_start, test_no_peers_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); + test_server = NULL; +} + /*==========================================================================* * get_all *==========================================================================*/ @@ -193,7 +302,7 @@ test_get_all_done( g_assert(!error); g_variant_get(var, "(i^ao)", &version, &adapters); GDEBUG("version=%d, %u adapter", version, g_strv_length(adapters)); - g_assert(version >= 1); + g_assert(version >= NFC_DAEMON_INTERFACE_VERSION); g_assert(g_strv_length(adapters) == 1); g_variant_unref(var); g_strfreev(adapters); @@ -247,7 +356,7 @@ test_get_interface_version_done( g_assert(!error); g_variant_get(var, "(i)", &version); GDEBUG("version=%d", version); - g_assert(version >= 1); + g_assert(version >= NFC_DAEMON_INTERFACE_VERSION); g_variant_unref(var); test_quit_later(test->loop); } @@ -355,7 +464,7 @@ test_get_all2_done( g_variant_get(var, "(i^aoi)", &version, &adapters, &core_version); GDEBUG("version=%d, %u adapter, core_version=%d", version, g_strv_length(adapters), core_version); - g_assert(version >= 1); + g_assert(version >= NFC_DAEMON_INTERFACE_VERSION); g_assert(g_strv_length(adapters) == 1); g_assert(core_version == NFC_CORE_VERSION); g_variant_unref(var); @@ -409,8 +518,8 @@ test_get_daemon_version_done( g_assert(var); g_assert(!error); g_variant_get(var, "(i)", &version); - GDEBUG("version=%d", version); - g_assert(version >= 1); + GDEBUG("version=0x%08x", version); + g_assert(version == NFC_CORE_VERSION); g_variant_unref(var); test_quit_later(test->loop); } @@ -441,6 +550,266 @@ test_get_daemon_version( test_dbus_free(dbus); } +/*==========================================================================* + * register_service + *==========================================================================*/ + +static const char test_register_service_path[] = "/test"; +static const char test_register_service_name[] = "test"; + +static +void +test_register_service_unregister_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + GError* error = NULL; + GVariant* ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, &error); + + g_assert(ret); + g_variant_unref(ret); + test_quit_later(test->loop); +} + +static +void +test_register_service_fail( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + GError* error = NULL; + + g_assert(!g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, &error)); + g_assert(g_error_matches(error, DBUS_SERVICE_ERROR, + DBUS_SERVICE_ERROR_ALREADY_EXISTS)); + g_error_free(error); + + /* Unregister it */ + test_call_unregister_local_service(test->client, test_register_service_path, + test_register_service_unregister_done, test); +} + +static +void +test_register_service_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + GError* error = NULL; + guint sap = 0; + GVariant* ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, &error); + + g_assert(ret); + g_variant_get(ret, "(u)", &sap); + g_variant_unref(ret); + GDEBUG("sap=%u", sap); + g_assert(sap); + + /* Second call will fail */ + test_call_register_local_service(test->client, test_register_service_path, + test_register_service_name, test_register_service_fail, test); +} + +static +void +test_register_service_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + TestData* test = user_data; + + test->client = client; + test_call_register_local_service(client, test_register_service_path, + test_register_service_name, test_register_service_done, test); +} + +static +void +test_register_service( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new2(test_start, test_register_service_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * unregister_service_error + *==========================================================================*/ + +static +void +test_unregister_svc_err_done( + GObject* object, + GAsyncResult* result, + gpointer user_data) +{ + TestData* test = user_data; + GError* error = NULL; + + g_assert(!g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), + result, &error)); + g_assert(g_error_matches(error, DBUS_SERVICE_ERROR, + DBUS_SERVICE_ERROR_NOT_FOUND)); + g_error_free(error); + test_quit_later(test->loop); +} + +static +void +test_unregister_svc_err_start( + GDBusConnection* client, + GDBusConnection* server, + void* test) +{ + test_call_unregister_local_service(client, "/none", + test_unregister_svc_err_done, test); +} + +static +void +test_unregister_svc_err( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new2(test_start, test_unregister_svc_err_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * adapter_added + *==========================================================================*/ + +static +void +test_adapter_added_handler( + GDBusConnection* connection, + const char* sender, + const char* path, + const char* iface, + const char* name, + GVariant* args, + gpointer user_data) +{ + TestData* test = user_data; + gchar** adapters = NULL; + + g_variant_get(args, "(^ao)", &adapters); + g_assert(adapters); + GDEBUG("%u adapters(s)", g_strv_length(adapters)); + g_assert(g_strv_length(adapters) == 1); + g_strfreev(adapters); + test_quit_later(test->loop); +} + +static +void +test_adapter_added_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + TestData* test = user_data; + + g_assert(g_dbus_connection_signal_subscribe(client, NULL, + NFC_DAEMON_INTERFACE, "AdaptersChanged", NFC_DAEMON_PATH, + NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, test_adapter_added_handler, + test, NULL)); + g_assert(nfc_manager_add_adapter(test->manager, test->adapter)); +} + +static +void +test_adapter_added( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init2(&test, FALSE); + dbus = test_dbus_new2(test_start, test_adapter_added_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + +/*==========================================================================* + * adapter_removed + *==========================================================================*/ + +static +void +test_adapter_removed_handler( + GDBusConnection* connection, + const char* sender, + const char* path, + const char* iface, + const char* name, + GVariant* args, + gpointer user_data) +{ + TestData* test = user_data; + gchar** adapters = NULL; + + g_variant_get(args, "(^ao)", &adapters); + g_assert(adapters); + GDEBUG("%u adapters(s)", g_strv_length(adapters)); + g_assert(g_strv_length(adapters) == 0); + g_strfreev(adapters); + test_quit_later(test->loop); +} + +static +void +test_adapter_removed_start( + GDBusConnection* client, + GDBusConnection* server, + void* user_data) +{ + TestData* test = user_data; + + g_assert(g_dbus_connection_signal_subscribe(client, NULL, + NFC_DAEMON_INTERFACE, "AdaptersChanged", NFC_DAEMON_PATH, + NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, test_adapter_removed_handler, + test, NULL)); + nfc_manager_remove_adapter(test->manager, test->adapter->name); +} + +static +void +test_adapter_removed( + void) +{ + TestData test; + TestDBus* dbus; + + test_data_init(&test); + dbus = test_dbus_new2(test_start, test_adapter_removed_start, &test); + test_run(&test_opt, test.loop); + test_data_cleanup(&test); + test_dbus_free(dbus); +} + /*==========================================================================* * Common *==========================================================================*/ @@ -453,11 +822,16 @@ int main(int argc, char* argv[]) g_type_init(); G_GNUC_END_IGNORE_DEPRECATIONS; g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("no_peers"), test_no_peers); g_test_add_func(TEST_("get_all"), test_get_all); g_test_add_func(TEST_("get_interface_version"), test_get_interface_version); g_test_add_func(TEST_("get_adapters"), test_get_adapters); g_test_add_func(TEST_("get_all2"), test_get_all2); g_test_add_func(TEST_("get_daemon_version"), test_get_daemon_version); + g_test_add_func(TEST_("register_service"), test_register_service); + g_test_add_func(TEST_("unregister_service_error"), test_unregister_svc_err); + g_test_add_func(TEST_("adapter_added"), test_adapter_added); + g_test_add_func(TEST_("adapter_removed"), test_adapter_removed); test_init(&test_opt, argc, argv); return g_test_run(); } diff --git a/unit/plugins_dbus_service_tag/test_plugins_dbus_service_tag.c b/unit/plugins_dbus_service_tag/test_plugins_dbus_service_tag.c index 2793d10..f18d264 100644 --- a/unit/plugins_dbus_service_tag/test_plugins_dbus_service_tag.c +++ b/unit/plugins_dbus_service_tag/test_plugins_dbus_service_tag.c @@ -84,7 +84,7 @@ test_data_init( g_assert((test->manager = nfc_manager_new(&pi)) != NULL); g_assert((test->adapter = test_adapter_new()) != NULL); - target = test_target_new(); + target = test_target_new(FALSE); memset(&poll, 0, sizeof(poll)); g_assert(nfc_adapter_add_other_tag2(test->adapter, target, &poll)); nfc_target_unref(target); @@ -1848,7 +1848,7 @@ test_data_init_tag_b( g_assert((test->manager = nfc_manager_new(&pi)) != NULL); g_assert((test->adapter = test_adapter_new()) != NULL); - target = test_target_new_tech(NFC_TECHNOLOGY_B); + target = test_target_new_tech(NFC_TECHNOLOGY_B, FALSE); memset(&poll, 0, sizeof(poll)); poll.b.nfcid0 = nfcid0_data; poll.b.prot_info = prot_info_data; @@ -1982,7 +1982,7 @@ test_data_init_tag_a( g_assert((test->manager = nfc_manager_new(&pi)) != NULL); g_assert((test->adapter = test_adapter_new()) != NULL); - target = test_target_new_tech(NFC_TECHNOLOGY_A); + target = test_target_new_tech(NFC_TECHNOLOGY_A, FALSE); memset(&poll, 0, sizeof(poll)); poll.a.sel_res = 1; poll.a.nfcid1 = nfcid1_data;