--- /dev/null
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 <glib.h>
+#include <list>
+#include <iterator>
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <time.h>
+#include <vine.h>
+#include <vine-log.h>
+
+#include "vine-test-utils.h"
+
+#define MAX_EVENTS 16
+#define TEST_DEBUG
+
+#define CHECK_SESSION \
+ do { \
+ if (!g_session) { \
+ _test_print_error("No Session"); \
+ return; \
+ } \
+ } while (0)
+
+#define MAX_SERVICE_TYPE_LEN 15
+#define MAX_SERVICE_NAME_LEN 15
+#define VINE_MAC_LEN 17
+
+GMainLoop *main_loop = NULL;
+static vine_session_h g_session = NULL;
+static vine_service_h g_service = NULL;
+static vine_dp_h g_server_dp = NULL;
+static vine_dp_h g_client_dp = NULL;
+
+static std::list<vine_dp_h> g_datapath_list;
+static std::list<vine_service_h> g_service_list;
+
+static bool test_get_user_string(const char *msg, char *buf, int buf_size)
+{
+ if (msg == NULL || buf == NULL || buf_size < 2)
+ return false;
+
+ int ret;
+ printf("%s", msg);
+ fflush(stdout);
+ memset(buf, 0, buf_size);
+ ret = read(0, buf, buf_size - 1);
+
+ if (ret < 0 || buf[0] == '\0' || buf[0] == '\n' || buf[0] == '\r') {
+ buf[0] = '\0';
+ return false;
+ }
+
+ buf[ret - 1] = '\0';
+ return true;
+}
+
+static void __quit()
+{
+ PRINT_IF_ERROR(vine_deinitialize(), "vine_initialize()");
+ g_main_loop_quit(main_loop);
+ exit(1);
+}
+
+static void __init()
+{
+ PRINT_IF_ERROR(vine_set_event_loop(VINE_EVENT_LOOP_EXTERNAL_GLIB), "vine_set_event_loop");
+ PRINT_IF_ERROR(vine_initialize(), "vine_initialize()");
+}
+
+static void __registered_cb(vine_session_h session,
+ const char *service_name, vine_error_e error, void *user_data)
+{
+ if (error == VINE_ERROR_NONE) {
+ PRINT_RESULT(error, "Successfully registered");
+ printf("Service Name: %s\n", service_name);
+ } else if (error == VINE_ERROR_NAME_CONFLICT) {
+ PRINT_RESULT(error, "Successfully registered, but service name conflicted");
+ printf("Service Name: %s\n", service_name);
+ } else {
+ PRINT_IF_ERROR(error, "Failed to register a service");
+ }
+}
+
+static void __discovered_cb(vine_session_h session, vine_service_h service,
+ vine_service_state_e state, void *user_data)
+{
+ printf("\t++ Service Discovered\n");
+
+ int ret;
+ char *service_type;
+ char *service_name;
+ char *mac;
+
+ ret = vine_service_get_type(service, &service_type);
+ PRINT_IF_ERROR(ret, "vine_service_get_type");
+ ret = vine_service_get_name(service, &service_name);
+ PRINT_IF_ERROR(ret, "vine_service_get_name");
+ ret = vine_service_get_mac(service, &mac);
+ PRINT_IF_ERROR(ret, "vine_service_get_mac");
+
+ printf("\t > State: %s\n",
+ state == VINE_SERVICE_UNAVAILABLE ? "Unavailable" : "Available");
+ printf("\t > Service Type: %s\n", service_type);
+ printf("\t > Service Name: %s\n", service_name);
+ printf("\t > Mac: %s\n", mac);
+ printf("\n");
+ fflush(stdout);
+
+ vine_service_h s;
+ vine_service_clone(service, &s);
+ g_service_list.push_back(s);
+ free(service_type);
+ free(service_name);
+ free(mac);
+}
+
+static void __print_received_data(unsigned char *buf, size_t len)
+{
+ for (int i = 0; i < (int)len; i++)
+ printf("%c", buf[i]);
+ printf("\n");
+}
+
+static void __received_cb(vine_dp_h dp, size_t received_len, void *user_data)
+{
+ unsigned char buf[20] = {0, };
+ size_t bytes = 0;
+
+ printf(COLOR_GRN "[RECV_CB] %p received %zd bytes." COLOR_RESET "\n", dp, received_len);
+ do {
+ vine_dp_recv(dp, buf, 20, &bytes);
+ __print_received_data(buf, bytes);
+ received_len -= bytes;
+ } while (received_len > 0);
+}
+
+static void __terminated_cb(vine_dp_h dp, void *user_data)
+{
+ printf(COLOR_GRN "[TERMINATED_CB] %p is terminated." COLOR_RESET "\n", dp);
+ g_datapath_list.remove(dp);
+ vine_dp_destroy(dp);
+}
+
+static void __add_new_dp(vine_dp_h dp)
+{
+ g_datapath_list.push_back(dp);
+ vine_dp_set_received_cb(dp, __received_cb, NULL);
+ vine_dp_set_terminated_cb(dp, __terminated_cb, NULL);
+}
+
+static void __opened_cb(vine_dp_h dp, vine_error_e result, void *user_data)
+{
+ printf(COLOR_GRN "[OPENED_CB] %p is %s." COLOR_RESET "\n",
+ dp, (result == VINE_ERROR_NONE) ? "opened" : "not opened");
+ if (dp == g_client_dp)
+ __add_new_dp(dp);
+}
+
+static void __set_callbacks()
+{
+ CHECK_SESSION;
+ PRINT_IF_ERROR(vine_session_set_registered_cb(g_session, __registered_cb, NULL),
+ "vine_session_set_registered_cb");
+ PRINT_IF_ERROR(vine_session_set_discovered_cb(g_session, __discovered_cb, NULL),
+ "vine_session_set_discovered_cb");
+}
+
+static void __create_session()
+{
+ int ret;
+ ret = vine_session_create(&g_session);
+ if (ret != VINE_ERROR_NONE) {
+ PRINT_IF_ERROR(ret, "vine_session_create");
+ return;
+ }
+
+ ret = vine_session_set_discovery_method(g_session, VINE_DISCOVERY_METHOD_BLE);
+ if (ret != VINE_ERROR_NONE) {
+ PRINT_IF_ERROR(ret, "vine_session_create");
+ vine_session_destroy(g_session);
+ return;
+ }
+
+ __set_callbacks();
+ printf("Session Created\n");
+}
+
+static void __destroy_session()
+{
+ CHECK_SESSION;
+ PRINT_IF_ERROR(vine_session_destroy(g_session),
+ "vine_session_destroy");
+}
+
+static void __set_service_type()
+{
+ CHECK_SESSION;
+ char type[MAX_SERVICE_TYPE_LEN + 1];
+ printf(" >> Service Type: ");
+ if (scanf(" %15s", type) < 1) {
+ _test_print_error("Scan failed");
+ return;
+ }
+ PRINT_RESULT(vine_service_set_type(g_service, type),
+ "vine_service_set_type");
+}
+
+static void __set_service_name()
+{
+ CHECK_SESSION;
+ char name[MAX_SERVICE_NAME_LEN + 1];
+ printf(" >> Service Name (Max length 15): ");
+ if (scanf(" %15s", name) < 1) {
+ _test_print_error("Scan failed");
+ return;
+ }
+ PRINT_RESULT(vine_service_set_name(g_service, name),
+ "vine_service_set_name");
+}
+
+static void __create_service()
+{
+ int ret;
+ ret = vine_service_create(&g_service);
+ if (ret != VINE_ERROR_NONE) {
+ PRINT_IF_ERROR(ret, "vine_service_create");
+ return;
+ }
+
+ __set_service_type();
+ __set_service_name();
+
+ printf("Service Created\n");
+}
+
+static void __destroy_service()
+{
+ CHECK_SESSION;
+ PRINT_IF_ERROR(vine_service_destroy(g_service), "vine_service_destroy");
+}
+
+static void __register()
+{
+ CHECK_SESSION;
+ PRINT_IF_ERROR(vine_session_register(g_session, g_service, NULL), "vine_session_register");
+ printf("Registered\n");
+}
+
+static void __unregister()
+{
+ CHECK_SESSION;
+ PRINT_IF_ERROR(vine_session_unregister(g_session), "vine_session_unregister");
+ printf("Unregistered\n");
+}
+
+static void __start_discovery()
+{
+ CHECK_SESSION;
+ char type[20];
+ printf(" >> Service Type: ");
+ if (scanf(" %19s", type) < 1) {
+ _test_print_error("Scan failed");
+ return;
+ }
+
+ PRINT_IF_ERROR(vine_session_start_discovery(g_session, type, NULL),
+ "vine_session_start_discovery");
+ printf("Discovery started\n");
+}
+
+static void __stop_discovery()
+{
+ CHECK_SESSION;
+ PRINT_IF_ERROR(vine_session_stop_discovery(g_session), "vine_session_stop_discovery");
+ printf("Discovery stopped\n");
+}
+
+#ifdef TEST_DEBUG
+#define VINE_LOGGER_TIME_MESSAGE_LEN 17
+static void __get_current_time(char *buf)
+{
+ int hour, min, sec, day, month;
+ time_t now;
+ struct tm *local;
+
+ time(&now);
+ local = localtime(&now);
+
+ hour = local->tm_hour;
+ min = local->tm_min;
+ sec = local->tm_sec;
+ day = local->tm_mday;
+ month = local->tm_mon + 1;
+
+ snprintf(buf, VINE_LOGGER_TIME_MESSAGE_LEN,
+ "[%02u-%02u %02u:%02u:%02u]", month, day, hour, min, sec);
+}
+
+void __logger(int log_level, const char *log)
+{
+ char timestamp[VINE_LOGGER_TIME_MESSAGE_LEN];
+ __get_current_time(timestamp);
+ printf("%s[T%5u] ", timestamp, (unsigned int)syscall(__NR_gettid));
+
+ switch (log_level) {
+ case VINE_LOG_DEBUG:
+ printf("D/");
+ break;
+ case VINE_LOG_INFO:
+ printf("I/");
+ break;
+ case VINE_LOG_ERROR:
+ printf(COLOR_RED "E/");
+ break;
+ }
+ printf("%s" COLOR_RESET "\n", log);
+}
+#endif
+
+static void __debug_on_off()
+{
+ static int __debug_on = 0;
+ __debug_on = 1 - __debug_on;
+ printf("Debug %s\n", __debug_on ? "ON" : "OFF");
+#ifdef TEST_DEBUG
+ if (__debug_on) {
+ vine_set_logger(__logger);
+ vine_set_log_level(VINE_LOG_DEBUG | VINE_LOG_INFO | VINE_LOG_ERROR);
+ } else {
+ vine_set_logger(NULL);
+ }
+#endif
+}
+
+static void __accepted_cb(vine_dp_h dp, vine_dp_h accepted_dp, void *user_data)
+{
+ printf(COLOR_GRN "[ACCEPTED_CB] %p is accepted." COLOR_RESET "\n", accepted_dp);
+ __add_new_dp(accepted_dp);
+}
+
+static void __open_server()
+{
+ CHECK_SESSION;
+
+ vine_dp_create(g_session, VINE_DP_TYPE_SERVER, &g_server_dp);
+ vine_dp_set_method(g_server_dp, VINE_DP_METHOD_BLE_GATT);
+ vine_dp_set_accepted_cb(g_server_dp, __accepted_cb, NULL);
+ vine_dp_set_terminated_cb(g_server_dp, __terminated_cb, NULL);
+
+ PRINT_RESULT(vine_dp_open(g_server_dp, __opened_cb, NULL), "vine_dp_open");
+}
+
+static void __connect_server()
+{
+ CHECK_SESSION;
+
+ char mac[VINE_MAC_LEN + 1] = {0, };
+
+ printf(" >> Mac address: ");
+ if (scanf(" %17s", mac) < 1) {
+ _test_print_error("Scan failed");
+ return;
+ }
+
+ vine_dp_create(g_session, VINE_DP_TYPE_CLIENT, &g_client_dp);
+ vine_dp_set_method(g_client_dp, VINE_DP_METHOD_BLE_GATT);
+ vine_dp_set_remote_address(g_client_dp, mac);
+
+ PRINT_RESULT(vine_dp_open(g_client_dp, __opened_cb, NULL), "vine_dp_open");
+}
+
+void __print_datapath_list()
+{
+ int idx = -1;
+ for (const auto &dp : g_datapath_list)
+ printf("\t %d: datapath[%p]\n", ++idx, dp);
+}
+
+static void __send_data()
+{
+ CHECK_SESSION;
+
+ const char *test_str[3] = {
+ "12345678910",
+ "Hello! This is a test string.",
+ "Tizen is an open and flexible operating system" \
+ "built from the ground up to address the needs of all stakeholders of the mobile"\
+ "and connected device ecosystem, including device manufacturers, mobile operators,"\
+ "application developers and independent software vendors (ISVs)."\
+ "Tizen is developed by a community of developers, under open source governance,"\
+ "and is open to all members who wish to participate."
+ };
+
+ __print_datapath_list();
+
+ int idx;
+ printf(" >> Select peer: \n");
+ if (scanf("%d", &idx) < 0)
+ return;
+
+ auto it = g_datapath_list.begin();
+ std::advance(it, idx);
+ vine_dp_h dp = *it;
+
+ for (int i = 0; i < 3; i++) {
+ int ret = vine_dp_send(dp,
+ (unsigned char *)test_str[i], strlen(test_str[i]));
+ PRINT_IF_ERROR(ret, "vine_data_path_write");
+ }
+}
+
+static void __close_data_path()
+{
+ __print_datapath_list();
+
+ int idx;
+ printf(" >> Select peer: \n");
+ if (scanf("%d", &idx) < 0)
+ return;
+
+ auto it = g_datapath_list.begin();
+ std::advance(it, idx);
+ vine_dp_h dp = *it;
+
+ PRINT_RESULT(vine_dp_close(dp), "vine_dp_close");
+ g_datapath_list.remove(dp);
+ PRINT_RESULT(vine_dp_destroy(dp), "vine_dp_destroy");
+}
+
+enum {
+ CMD_QUIT = 0,
+ CMD_CREATE_SESSION,
+ CMD_DESTROY_SESSION,
+ CMD_CREATE_SERVICE,
+ CMD_DESTROY_SERVICE,
+ CMD_REGISTER,
+ CMD_UNREGISTER,
+ CMD_START_DISCOVERY,
+ CMD_STOP_DISCOVERY,
+ CMD_OPEN_SERVER,
+ CMD_CONNECT_SERVER,
+ CMD_SEND_DATA,
+ CMD_CLOSE_DATA_PATH,
+ CMD_DEBUG,
+ CMD_INVALID,
+};
+
+static struct {
+ const char *str;
+ void (*func)(void);
+} __menus[] = {
+ [CMD_QUIT] = {"quit", __quit},
+ [CMD_CREATE_SESSION] = {"Create session", __create_session},
+ [CMD_DESTROY_SESSION] = {"Destroy session", __destroy_session},
+ [CMD_CREATE_SERVICE] = {"Create service", __create_service},
+ [CMD_DESTROY_SERVICE] = {"Destroy service", __destroy_service},
+ [CMD_REGISTER] = {"Register a service", __register},
+ [CMD_UNREGISTER] = {"Unregister a service", __unregister},
+ [CMD_START_DISCOVERY] = {"Start discovery", __start_discovery},
+ [CMD_STOP_DISCOVERY] = {"Stop discovery", __stop_discovery},
+ [CMD_OPEN_SERVER] = {"Open server", __open_server},
+ [CMD_CONNECT_SERVER] = {"Connect to server", __connect_server},
+ [CMD_SEND_DATA] = {"Send data to peer", __send_data},
+ [CMD_CLOSE_DATA_PATH] = {"Close datapath", __close_data_path},
+ [CMD_DEBUG] = {"Debug on/off", __debug_on_off},
+ {NULL, NULL},
+};
+
+static void __show_menus()
+{
+ printf("\n-- Vine Test\n");
+ for (int i = 0; __menus[i].str; ++i)
+ printf("%02d %s\n", i, __menus[i].str);
+ printf("> ");
+ fflush(stdout);
+}
+
+static inline int __is_digit(const char* str)
+{
+ int len;
+ int i;
+
+ if (str == NULL)
+ return -1;
+
+ if (strlen(str) == 0)
+ return -1;
+
+ len = strlen(str);
+ for (i = 0; i < len; i++) {
+ if (str[i] < '0' || str[i] > '9')
+ return -2;
+ }
+
+ return 0;
+}
+
+static void __process_input(const char *input)
+{
+ int cmd = -1;
+
+ cmd = strtol(input, NULL, 0);
+ if (__is_digit(input) < 0 || strlen(input) == 0 || errno == ERANGE || errno
+ == EINVAL)
+ cmd = CMD_INVALID;
+
+ if (cmd >= CMD_INVALID || cmd < CMD_QUIT) {
+ printf("Invalid CMD\n");
+ return;
+ }
+ __menus[cmd].func();
+}
+
+static void __terminal_read_std_input()
+{
+ int fd = 0;
+
+ static char buf[1024];
+ int n;
+
+ errno = 0;
+ n = read(fd, buf, 1024);
+ if (n == 0) {
+ printf("Error: read() from stdin returns 0.\n");
+ } else if (n < 0) {
+ printf("input: read, err\n");
+ } else if (n - 1 > 0 && n < 1024) {
+ buf[n - 1] = '\0'; /* remove new line... */
+ printf("\n\n");
+ __process_input(buf);
+ } else {
+ if (buf[0] == '\n' || buf[0] == '\r')
+ __show_menus();
+ else
+ printf("invalid input\n");
+ }
+}
+
+static gboolean __io_handler(GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ __terminal_read_std_input();
+ return TRUE;
+}
+
+int main()
+{
+ __init();
+ __show_menus();
+
+ main_loop = g_main_loop_new(NULL, FALSE);
+
+ GIOChannel *channel = g_io_channel_unix_new(0);
+ g_io_add_watch(channel, (GIOCondition)(G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL),
+ __io_handler, NULL);
+ g_main_loop_run(main_loop);
+ g_main_loop_unref(main_loop);
+
+ return 0;
+}