--- /dev/null
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Flora License, Version 1.1 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "car_connection_manager.h"
+#include <stdlib.h>
+#include <glib.h>
+#include <string.h>
+#include "log.h"
+#include "messages/message.h"
+#include "messages/message_command.h"
+#include "messages/message_manager.h"
+#include "messages/message_factory.h"
+#include "messages/message_ack.h"
+
+#define KEEP_ALIVE_ATTEMPTS 5
+#define HELLO_START_ATTEMPTS 5
+#define KEEP_ALIVE_INTERVAL 1000 //In ms
+#define HELLO_INTERVAL 1000 //In ms
+
+#define SAFE_SOURCE_REMOVE(source) do{if(source) { _W("Removing source no %d", source) ;g_source_remove(source); } source = 0;}while(0)
+
+typedef struct _car_connection_manager_info {
+ car_connection_state_e state;
+ char *car_address;
+ int car_port;
+ connection_state_cb state_cb;
+ unsigned int keep_alive_attempts_left;
+ unsigned int hello_attempts_left;
+ guint keep_alive_timer;
+ guint hello_timer;
+ message_factory_t *message_factory;
+ unsigned long long int last_acked;
+} _car_connection_manager_s;
+
+static _car_connection_manager_s s_info = {
+ .state = CAR_CONNECTION_STATE_DISCONNECTED,
+ .car_address = NULL,
+ .state_cb = NULL,
+ .keep_alive_attempts_left = 0,
+ .hello_attempts_left = 0,
+ .hello_timer = 0,
+ .keep_alive_timer = 0
+};
+
+static void _set_state(car_connection_state_e state);
+static int _start_hello_timer();
+static int _start_keep_alive_timer();
+static void _receive_cb(message_t *message, void *data);
+static gboolean _send_hello();
+static void _reset_counters();
+static gboolean _send_keep_alive();
+static gboolean _try_connect_cb(gpointer data);
+static gboolean _keep_alive_cb(gpointer data);
+static int _addr_cmp(const char *addr1, int port1, const char *addr2, int port2);
+
+int car_connection_manager_init()
+{
+ s_info.message_factory = message_factory_create();
+ if(!s_info.message_factory) {
+ _E("Failed to create message factory");
+ return -1;
+ }
+ message_manager_set_receive_message_cb(_receive_cb, NULL);
+ return 0;
+}
+
+car_connection_state_e car_connection_manager_get_state()
+{
+ return s_info.state;
+}
+
+void car_connection_manager_set_state_change_cb(connection_state_cb callback)
+{
+ s_info.state_cb = callback;
+}
+
+int car_connection_manager_connect(const char *address, int port)
+{
+ if(s_info.state != CAR_CONNECTION_STATE_DISCONNECTED) {
+ _E("Failed to connect with %s:%d - connected already initiated with %s:%d", address, port, s_info.car_address, s_info.car_port);
+ return -1;
+ }
+
+ if(!address) {
+ _E("Address cannot be NULL");
+ return -1;
+ }
+
+ s_info.car_address = strdup(address);
+ if(!s_info.car_address) {
+ _E("Failed to save new address");
+ return -1;
+ }
+ s_info.car_port = port;
+ _I("Connecting with %s:%d...", address, port);
+ _set_state(CAR_CONNECTION_STATE_CONNECTING);
+ _reset_counters();
+ message_manager_set_receive_message_cb(_receive_cb, NULL);
+ _start_hello_timer();
+ return 0;
+}
+
+void car_connection_manager_disconnect()
+{
+ if(s_info.state == CAR_CONNECTION_STATE_DISCONNECTED) {
+ _E("Failed to disconnect - connection hasn't been initiated");
+ return;
+ }
+
+ message_t *message = message_factory_create_message(s_info.message_factory, MESSAGE_BYE);
+ message_set_receiver(message, s_info.car_address, s_info.car_port);
+
+ if(!message) {
+ _W("Failed to create BYE message");
+ }
+
+ if(message && message_manager_send_message(message)) {
+ _W("Failed to send BYE message");
+ }
+
+ if(message) {
+ message_destroy(message);
+ }
+ _I("Disconnected from %s:%d", s_info.car_address, s_info.car_port);
+ free(s_info.car_address);
+ s_info.car_address = NULL;
+ s_info.car_port = -1;
+ SAFE_SOURCE_REMOVE(s_info.hello_timer);
+ SAFE_SOURCE_REMOVE(s_info.keep_alive_timer);
+ _set_state(CAR_CONNECTION_STATE_DISCONNECTED);
+}
+
+void car_connection_manager_handle_message(message_t *message)
+{
+ const char *msg_address;
+ int msg_port;
+ message_get_sender(message, &msg_address, &msg_port);
+ if(_addr_cmp(s_info.car_address, s_info.car_port, msg_address, msg_port)) {
+ _W("Received message from unconnected sender");
+ return;
+ }
+
+ switch(message_get_type(message)) {
+ case MESSAGE_CONNECT_ACCEPTED:
+ if(s_info.state == CAR_CONNECTION_STATE_CONNECTING) {
+ SAFE_SOURCE_REMOVE(s_info.hello_timer);
+ _D("Received CONNECT_ACCEPTED");
+ _I("Connected with car %s:%d", s_info.car_address, s_info.car_port);
+ s_info.keep_alive_attempts_left = KEEP_ALIVE_ATTEMPTS;
+ _set_state(CAR_CONNECTION_STATE_CONNECTED);
+ _start_keep_alive_timer();
+ s_info.last_acked = 0;
+ } else {
+ _W("Unexpectedly received CONNECT_ACCEPTED");
+ }
+ break;
+ case MESSAGE_ACK:
+ if(s_info.state == CAR_CONNECTION_STATE_CONNECTED) {
+ unsigned long long int serial = message_ack_get_ack_serial((message_ack_t*)message);
+ if(serial > s_info.last_acked) {
+ _D("Received KEEP_ALIVE_ACK");
+ s_info.keep_alive_attempts_left = KEEP_ALIVE_ATTEMPTS;
+ s_info.last_acked = serial;
+ }
+ else {
+ _W("Received late ACK (%d, when last is %d)", serial, s_info.last_acked);
+ }
+ } else {
+ _W("Unexpectedly received ACK");
+ }
+ break;
+ case MESSAGE_CONNECT_REFUSED:
+ if(s_info.state == CAR_CONNECTION_STATE_CONNECTING) {
+ _D("Received CONNECT_REFUSE");
+ _I("Car refused connecting");
+ _set_state(CAR_CONNECTION_STATE_DISCONNECTED);
+ } else {
+ _W("Unexpectedly received CONNECT_REFUSE");
+ }
+ break;
+ default:
+ _W("Unknown message received");
+ break;
+ }
+}
+
+int car_connection_manager_send_command(command_s command)
+{
+ if(s_info.state != CAR_CONNECTION_STATE_CONNECTED) {
+ _E("Connection not established, command not sent");
+ return -1;
+
+ }
+
+ message_t *message = message_factory_create_message(s_info.message_factory, MESSAGE_COMMAND);
+ if(!message) {
+ _E("Failed to create COMMAND message");
+ return -1;
+ }
+ message_set_receiver(message, s_info.car_address, s_info.car_port);
+ message_command_set_command((message_command_t*)message, &command);
+ int res = message_manager_send_message(message);
+ message_destroy(message);
+ if(res) {
+ _E("Failed to send command");
+ return -1;
+ }
+ return 0;
+}
+
+void car_connection_manager_fini()
+{
+ if(s_info.state == CAR_CONNECTION_STATE_CONNECTED || s_info.state == CAR_CONNECTION_STATE_CONNECTING)
+ car_connection_manager_disconnect();
+ message_manager_shutdown();
+}
+
+static void _receive_cb(message_t *message, void *data)
+{
+ car_connection_manager_handle_message(message);
+}
+
+static void _set_state(car_connection_state_e state)
+{
+ if(s_info.state == state) {
+ return;
+ }
+
+ car_connection_state_e previous = s_info.state;
+ s_info.state = state;
+ _I("Connection state changed from %d to %d", previous, state);
+
+ if(s_info.state_cb) {
+ s_info.state_cb(previous, state);
+ }
+}
+
+static gboolean _send_hello()
+{
+ if(s_info.state != CAR_CONNECTION_STATE_CONNECTING) {
+ s_info.hello_attempts_left = 0;
+ s_info.hello_timer = 0;
+ _E("Cannot send when not connecting");
+ return FALSE;
+ }
+
+ if(!s_info.hello_attempts_left--) {
+ _W("Made all attempts for hello - disconnecting started");
+ car_connection_manager_disconnect();
+ return FALSE;
+ }
+
+ message_t *message = message_factory_create_message(s_info.message_factory, MESSAGE_CONNECT);
+ if(message) {
+ message_set_receiver(message, s_info.car_address, s_info.car_port);
+ int res = message_manager_send_message(message);
+ if(res) {
+ _E("Failed to send HELLO message");
+ }
+ message_destroy(message);
+ } else {
+ _E("Failed to create HELLO message");
+ }
+
+ return TRUE;
+}
+static gboolean _send_keep_alive()
+{
+ if(s_info.state == CAR_CONNECTION_STATE_DISCONNECTED) {
+ s_info.keep_alive_attempts_left = 0;
+ s_info.keep_alive_timer = 0;
+ return FALSE;
+ }
+
+ if(s_info.state == CAR_CONNECTION_STATE_CONNECTING) {
+ return TRUE;
+ }
+
+ if(!s_info.keep_alive_attempts_left--) {
+ car_connection_manager_disconnect();
+ return FALSE;
+ }
+
+ message_t *message = message_factory_create_message(s_info.message_factory, MESSAGE_KEEP_ALIVE);
+ if(!message) {
+ _E("Failed to create KEEP_ALIVE message");
+ return TRUE;
+ }
+
+ message_set_receiver(message, s_info.car_address, s_info.car_port);
+ int res = message_manager_send_message(message);
+ message_destroy(message);
+ if(res) {
+ _E("Failed to send KEEP_ALIVE message");
+ }
+
+ return TRUE;
+}
+
+static gboolean _keep_alive_cb(gpointer data)
+{
+ return _send_keep_alive();
+}
+
+static gboolean _try_connect_cb(gpointer data)
+{
+ return _send_hello();
+}
+
+static int _start_hello_timer()
+{
+ if(_send_hello()) {
+ s_info.hello_timer = g_timeout_add(HELLO_INTERVAL, _try_connect_cb, NULL);
+ return 0;
+ }
+ return -1;
+}
+
+static int _start_keep_alive_timer()
+{
+ if(_send_keep_alive()) {
+ s_info.keep_alive_timer = g_timeout_add(KEEP_ALIVE_INTERVAL, _keep_alive_cb, NULL);
+ return 0;
+ }
+ return -1;
+}
+
+static void _reset_counters()
+{
+ s_info.keep_alive_attempts_left = HELLO_START_ATTEMPTS;
+ s_info.hello_attempts_left = KEEP_ALIVE_ATTEMPTS;
+}
+
+static int _addr_cmp(const char *addr1, int port1, const char *addr2, int port2)
+{
+ if(addr1 == NULL || addr2 == NULL) {
+ return -1;
+ }
+
+ unsigned int address_length = strlen(addr2);
+ return port1 != port2 || strlen(addr1) != address_length || strncmp(addr1, addr2, address_length);
+}
+