From: Michal Kolodziejski Date: Thu, 20 Sep 2018 10:52:52 +0000 (+0200) Subject: Cloud API added X-Git-Url: http://review.tizen.org/git/?p=apps%2Fnative%2Fgear-racing-car.git;a=commitdiff_plain;h=de74f5b3b2ff949c3543ab8690c94c660e6293a5 Cloud API added Change-Id: If689e3f336be2b13ddc6ac597135c3692c28a51e Signed-off-by: Michal Kolodziejski --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e5aac8..37f165f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,8 @@ pkg_check_modules(APP_PKGS REQUIRED capi-appfw-application capi-appfw-service-application capi-system-peripheral-io + libcurl + json-glib-1.0 glib-2.0 gio-2.0 capi-network-connection @@ -42,6 +44,10 @@ ADD_EXECUTABLE(${PROJECT_NAME} ${PROJECT_ROOT_DIR}/src/resource/resource_motor_driver_L298N.c ${PROJECT_ROOT_DIR}/src/resource/resource_PCA9685.c ${PROJECT_ROOT_DIR}/src/resource/resource_servo_motor.c + ${PROJECT_ROOT_DIR}/src/cloud/car_info.c + ${PROJECT_ROOT_DIR}/src/cloud/car_info_serializer.c + ${PROJECT_ROOT_DIR}/src/cloud/cloud_request.c + ${PROJECT_ROOT_DIR}/src/cloud/http_request.c ) TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${pkgs_LDFLAGS} -lm) diff --git a/inc/cloud/car_info.h b/inc/cloud/car_info.h new file mode 100644 index 0000000..da95734 --- /dev/null +++ b/inc/cloud/car_info.h @@ -0,0 +1,138 @@ +/* + * 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. + */ + +#ifndef __CAR_INFO_H_ +#define __CAR_INFO_H_ + +#include + +/** + * @brief The car info structure template. + */ +typedef struct car_info car_info_t; + +/** + * @brief Creates new instance of car_info_t. + * @return new car_info_t object, or NULL on error. + */ +car_info_t *car_info_create(); + +/** + * @brief Crates deep copy of given car_info_t object. + * @return Copied car_info_t object, or NULL on error. + */ +car_info_t *car_info_copy(const car_info_t *car_info); + +/** + * @brief Releases car_info_t. + * @param[in] car_info Car info struct. + */ +void car_info_destroy(car_info_t *car_info); + +/** + * @brief Checks if car_info_t structure core fields are set. + * @param[in] car_info Car info struct. + * @returns Returns true if object is valid, otherwise false. + */ +bool car_info_is_valid(car_info_t *car_info); + +/** + * @brief Gets car id. + * @param[in] car_info Car info struct. + * @param[out] car_id The car id. + * @return Returns car id or NULL on error. + * @remark This value is valid only during car_info life. + */ +const char *car_info_get_car_id(car_info_t *car_info); + +/** + * @brief Sets car id. + * @param[in] car_info Car info struct. + * @param[in] car_id The car id. + * @return Returns 0 on success, -1 otherwise. + */ +int car_info_set_car_id(car_info_t *car_info, const char *car_id); + +/** + * @brief Gets car name. + * @param[in] car_info Car info struct. + * @param[out] car_id The car id. + * @return Returns car name or NULL on error. + * @remark This value is valid only during car_info life. + */ +const char *car_info_get_car_name(car_info_t *car_info); + +/** + * @brief Sets car name. + * @param[in] car_info Car info struct. + * @param[in] car_name The car name. + * @return Returns 0 on success, -1 otherwise. + */ +int car_info_set_car_name(car_info_t *car_info, const char *car_name); + +/** + * @brief Gets car ip. + * @param[in] car_info Car info struct. + * @param[out] car_ip The car ip. + * @return Returns car ip or NULL on error. + * @remark This value is valid only during car_info life. + */ +const char *car_info_get_car_ip(car_info_t *car_info); + +/** + * @brief Sets car ip. + * @param[in] car_info Car info struct. + * @param[in] car_ip The car ip. + * @return Returns 0 on success, -1 otherwise. + * @remarks Param car_ip should be valid ip address. + */ +int car_info_set_car_ip(car_info_t *car_info, const char *car_ip); + +/** + * @brief Gets access point mac. + * @param[in] car_info Car info struct. + * @param[out] ap_mac The access point mac address. + * @return Returns access po id or NULL on error. + * @remark This value is valid only during car_info life. + */ +const char *car_info_get_ap_mac(car_info_t *car_info); + +/** + * @brief Sets access point mac. + * @param[in] car_info Car info struct. + * @param[in] ap_mac The access point mac address. + * @return Returns 0 on success, -1 otherwise. + * @remarks Param ap_max should be valid mac address. + */ +int car_info_set_car_ap_mac(car_info_t *car_info, const char *ap_mac); + +/** + * @brief Gets access point ssid. + * @param[in] car_info Car info struct. + * @param[out] ap_ssid The access point ssid. + * @return Returns -1 if any error occurred, 0 otherwise. + */ +const char *car_info_get_ap_ssid(car_info_t *car_info); + +/** + * @brief Sets access point ssid. + * @param[out] ap_ssid The access point ssid. + * @param[in] car_id The car id. + * @return Returns 0 on success, -1 otherwise. + */ +int car_info_set_ap_ssid(car_info_t *car_info, const char *ap_ssid); + +#endif \ No newline at end of file diff --git a/inc/cloud/car_info_serializer.h b/inc/cloud/car_info_serializer.h new file mode 100644 index 0000000..ef7885d --- /dev/null +++ b/inc/cloud/car_info_serializer.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef __CAR_INFO_SERIALIZER_H_ +#define __CAR_INFO_SERIALIZER_H_ + +#include "car_info.h" + +/** + * @brief Serializes car_info_t struct into json string. + * @param[in] car_info The struct with car data. + * @return Json with car data. + * @remarks Returned value should be freed. + */ +char *car_info_serializer_serialize(car_info_t *car_info); + +/** + * @brief Deserializes json string to array of car_info_t structs. + * @param[in] json_data Json string with config. + * @param[out] size Length of car_info_t array. + * @return Dynamically allocated car_info_t array or NULL on error. + * @remarks Returned array should be released with @free and its every element with @car_info_destroy. + */ +car_info_t **car_info_serializer_deserialize_array(const char *json_data, int *size); + +#endif \ No newline at end of file diff --git a/inc/cloud/cloud_request.h b/inc/cloud/cloud_request.h new file mode 100644 index 0000000..8137fed --- /dev/null +++ b/inc/cloud/cloud_request.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#ifndef __CLOUD_REQUEST_H_ +#define __CLOUD_REQUEST_H_ + +#include +#include "car_info.h" + +/** + * @brief Enum that indicates if HTTP request finished succesfully or not. +*/ +typedef enum +{ + SUCCESS = 0, + FAILURE +} request_result_e; + +/** + * @brief Called when data from HTTP /api/racing GET request was obtained. + * + * @param[in] result Result of request. + * @param[in] car_info The array of car_info struct. + * @param[in] size Length of the car_info array + * @param[in] user_data User data passed in @cloud_request_api_racing_get function. + */ +typedef void(*cloud_request_car_list_data_cb)(request_result_e result, car_info_t **car_info, int size, void *user_data); + +/** + * @brief Called when data from HTTP /api/racing POST request was obtained. + * + * @param[in] result Result of request. + * @param[in] user_data User data passed in @cloud_request_api_racing_post function. + */ +typedef void(*cloud_request_car_post_finish_cb)(request_result_e result, void *user_data); + +/** + * @brief Sends cloud request that obtains list of registered cars. + * + * @param[in] ap_mac Mac address of access point that device is connected to. + * @param[in] callback Function that will be invoked, when request will be finished. + * + * @return Returns @GCancellable object that allows to cancel this request. + * @remark To cancel task function g_cancellable_cancel should be called. + */ +GCancellable *cloud_request_api_racing_get(const char *ap_mac, cloud_request_car_list_data_cb callback, void *user_data); + +/** + * @brief Sends cloud request registering the car. + * + * @param[in] car_info The car_info object with car data. + * @param[in] callback Function that will be invoked, when request will be finished. + * + * @return Returns @GCancellable object that allows to cancel this request. + * @remark To cancel task function g_cancellable_cancel should be called. + */ +GCancellable *cloud_request_api_racing_post(const car_info_t *car_info, cloud_request_car_post_finish_cb callback, void *user_data); + +#endif \ No newline at end of file diff --git a/inc/cloud/http_request.h b/inc/cloud/http_request.h new file mode 100644 index 0000000..ddaf958 --- /dev/null +++ b/inc/cloud/http_request.h @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#ifndef __HTTP_REQUEST_H_ +#define __HTTP_REQUEST_H_ + +/** + * @brief Generic HTTP GET. + * @param[in] url Url of request. + * @param[out] response Response from the request. + * @param[out] response_code HTTP response code. + * @return Returns 0 on success, -1 otherwise. + * @remarks Response should be released with @free. + */ +int http_request_get(const char *url, char **response, long *response_code); + +/** + * @brief Generic HTTP POST. + * @param[in] url URL of request. + * @param[in] json Content of the response in json format. + * @param[out] response Response from the request. + * @param[out] response_code HTTP response code. + * @return Returns 0 on success, -1 otherwise. + * @remarks Response should be released with @free. + */ +int http_request_post(const char *url, const char *json, char **response, long *response_code); + +#endif \ No newline at end of file diff --git a/packaging/car-app.spec b/packaging/car-app.spec index b957a3e..62ea0a2 100644 --- a/packaging/car-app.spec +++ b/packaging/car-app.spec @@ -20,7 +20,9 @@ BuildRequires: pkgconfig(capi-appfw-service-application) BuildRequires: pkgconfig(capi-system-peripheral-io) BuildRequires: pkgconfig(gio-2.0) BuildRequires: pkgconfig(glib-2.0) -BuildRequires: pkgconfig(capi-network-connection) +BuildRequires: pkgconfig(json-glib-1.0) +BuildRequires: pkgconfig(libcurl) +BuildRequires: pkgconfig(capi-network-connection) %description Car application diff --git a/src/cloud/car_info.c b/src/cloud/car_info.c new file mode 100644 index 0000000..7c977d7 --- /dev/null +++ b/src/cloud/car_info.c @@ -0,0 +1,220 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include "cloud/car_info.h" +#include "log.h" + +#define MAX_LENGTH 256 +#define MAX_LENGTH_IP 16 +#define MAX_LENGTH_MAC 18 +#define MAX_LENGTH_SSID 33 + +#define SAFE_STR_CPY(src, obj, size)\ +do {\ + if(obj)\ + src = strndup(obj, size);\ +} while(0) + +static int validate_ip_address(const char *ip_address); +static int validate_mac_address(const char *mac_address); + +struct car_info +{ + char *id; + char *name; + char *ip; + char *ap_mac; + char *ap_ssid; +}; + +car_info_t *car_info_create() +{ + struct car_info *car_info = g_new0(struct car_info, 1); + retvm_if(!car_info, NULL, "Could not allocate memory!"); + + return car_info; +} + +car_info_t *car_info_copy(const car_info_t *car_info) +{ + retv_if(!car_info, NULL); + + struct car_info *car_info_cpy = g_new0(struct car_info, 1); + SAFE_STR_CPY(car_info_cpy->id, car_info->id, MAX_LENGTH); + SAFE_STR_CPY(car_info_cpy->name, car_info->name, MAX_LENGTH); + SAFE_STR_CPY(car_info_cpy->ip, car_info->ip, MAX_LENGTH_IP); + SAFE_STR_CPY(car_info_cpy->ap_mac, car_info->ap_mac, MAX_LENGTH_MAC); + SAFE_STR_CPY(car_info_cpy->ap_ssid, car_info->ap_ssid, MAX_LENGTH_SSID); + + return car_info_cpy; +} + +void car_info_destroy(car_info_t *car_info) +{ + ret_if(!car_info); + + free(car_info->id); + free(car_info->name); + free(car_info->ip); + free(car_info->ap_mac); + free(car_info->ap_ssid); + + g_free(car_info); +} + +bool car_info_is_valid(car_info_t *car_info) +{ + return (car_info->id && car_info->ip && car_info->ap_mac && car_info->ap_ssid); +} + +const char *car_info_get_car_id(car_info_t *car_info) +{ + retv_if(!car_info, NULL); + + return car_info->id; +} + +int car_info_set_car_id(car_info_t *car_info, const char *car_id) +{ + retv_if(!car_info, -1); + retv_if(car_id == NULL, -1); + retv_if(strlen(car_id) >= MAX_LENGTH, -1); + + if (!car_info->id) + car_info->id = (char *)g_malloc(MAX_LENGTH * sizeof(char)); + + snprintf(car_info->id, MAX_LENGTH, "%s", car_id); + return 0; +} + +const char *car_info_get_car_name(car_info_t *car_info) +{ + retv_if(!car_info, NULL); + return car_info->name; +} + +int car_info_set_car_name(car_info_t *car_info, const char *car_name) +{ + retv_if(!car_info, -1); + retv_if(car_name == NULL, -1); + retv_if(strlen(car_name) >= MAX_LENGTH, -1); + + if (!car_info->name) + car_info->name = (char *)g_malloc(MAX_LENGTH * sizeof(char)); + + snprintf(car_info->name, MAX_LENGTH, "%s", car_name); + return 0; +} + +const char *car_info_get_car_ip(car_info_t *car_info) +{ + retv_if(!car_info, NULL); + return car_info->ip; +} + +int car_info_set_car_ip(car_info_t *car_info, const char *car_ip) +{ + retv_if(!car_info, -1); + retv_if(car_ip == NULL, -1); + retv_if(strlen(car_ip) >= MAX_LENGTH_IP, -1); + retv_if(validate_ip_address(car_ip) != 0, -1); + + if (!car_info->ip) + car_info->ip = (char *)g_malloc(MAX_LENGTH_IP * sizeof(char)); + + snprintf(car_info->ip, MAX_LENGTH_IP, "%s", car_ip); + return 0; +} + +const char *car_info_get_ap_mac(car_info_t *car_info) +{ + retv_if(!car_info, NULL); + return car_info->ap_mac ? car_info->ap_mac : "NULL"; +} + +int car_info_set_car_ap_mac(car_info_t *car_info, const char *ap_mac) +{ + retv_if(!car_info, -1); + retv_if(ap_mac == NULL, -1); + retv_if(strlen(ap_mac) >= MAX_LENGTH_MAC, -1); + retv_if(validate_mac_address(ap_mac) != 0, -1); + + if (!car_info->ap_mac) + car_info->ap_mac = (char *)g_malloc(MAX_LENGTH_MAC * sizeof(char)); + + snprintf(car_info->ap_mac, MAX_LENGTH_MAC, "%s", ap_mac); + return 0; +} + +const char *car_info_get_ap_ssid(car_info_t *car_info) +{ + retv_if(!car_info, NULL); + return car_info->ap_ssid; +} + +int car_info_set_ap_ssid(car_info_t *car_info, const char *ap_ssid) +{ + retv_if(!car_info, -1); + retv_if(ap_ssid == NULL, -1); + retv_if(strlen(ap_ssid) >= MAX_LENGTH_SSID, -1); + + if (!car_info->ap_ssid) + car_info->ap_ssid = (char *)g_malloc(MAX_LENGTH_SSID * sizeof(char)); + + snprintf(car_info->ap_ssid, MAX_LENGTH_SSID, "%s", ap_ssid); + return 0; +} + +static int validate_ip_address(const char *ip_address) +{ + struct sockaddr_in sa; + int result = inet_pton(AF_INET, ip_address, &(sa.sin_addr)); + return result == 1 ? 0 : -1; +} + +static int validate_mac_address(const char *mac_address) +{ + int i = 0; + int segments = 1; + + while(*mac_address) + { + if (isxdigit(*mac_address)) + { + i++; + if (i > 2) return -1; + } + else if (*mac_address == ':') + { + if (i != 2) return -1; + i = 0; + segments++; + } + else { + return -1; + } + mac_address++; + } + + return (segments == 6 && i == 2) ? 0 : -1; +} diff --git a/src/cloud/car_info_serializer.c b/src/cloud/car_info_serializer.c new file mode 100644 index 0000000..3ee74f6 --- /dev/null +++ b/src/cloud/car_info_serializer.c @@ -0,0 +1,147 @@ +/* + * 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 "cloud/car_info_serializer.h" +#include +#include +#include +#include +#include "log.h" +#include "cloud/car_info.h" +#include "string.h" + +#define JSON_SCHEMA_CAR_ID "carId" +#define JSON_SCHEMA_RESPONSE_CAR_ID "id" +#define JSON_SCHEMA_CAR_NAME "carName" +#define JSON_SCHEMA_CAR_IP "carIp" +#define JSON_SCHEMA_AP_MAC "apMac" +#define JSON_SCHEMA_AP_SSID "apSsid" + +static JsonNode *parse_string(JsonParser *parser, const char *config_json); +static void car_info_array_iterate_cb(JsonArray *array, guint index, JsonNode *element, gpointer user_data); + +char *car_info_serializer_serialize(car_info_t *car_info) +{ + JsonGenerator *generator = json_generator_new(); + JsonBuilder *builder = json_builder_new(); + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, JSON_SCHEMA_CAR_ID); + json_builder_add_string_value(builder, car_info_get_car_id(car_info)); + + json_builder_set_member_name(builder, JSON_SCHEMA_CAR_NAME); + json_builder_add_string_value(builder, car_info_get_car_name(car_info)); + + json_builder_set_member_name(builder, JSON_SCHEMA_CAR_IP); + json_builder_add_string_value(builder, car_info_get_car_ip(car_info)); + + json_builder_set_member_name(builder, JSON_SCHEMA_AP_MAC); + json_builder_add_string_value(builder, car_info_get_ap_mac(car_info)); + + json_builder_set_member_name(builder, JSON_SCHEMA_AP_SSID); + json_builder_add_string_value(builder, car_info_get_ap_ssid(car_info)); + + json_builder_end_object(builder); + + JsonNode *root = json_builder_get_root(builder); + json_generator_set_root(generator, root); + + char *json_data = json_generator_to_data(generator, NULL); + + g_object_unref(builder); + g_object_unref(generator); + return json_data; +} + +car_info_t **car_info_serializer_deserialize_array(const char *json_data, int *size) +{ + JsonParser *parser = json_parser_new(); + + JsonNode *root = parse_string(parser, json_data); + JsonArray *array = json_node_get_array(root); + if (!array) + { + _E("Json is invalid!"); + g_object_unref(parser); + return NULL; + } + + *size = json_array_get_length(array); + + if (*size == 0) { + return NULL; + } + + car_info_t **car_info_array = g_malloc(*size * sizeof(car_info_t *)); + + json_array_foreach_element(array, car_info_array_iterate_cb, car_info_array); + + g_object_unref(parser); + return car_info_array; +} + +static JsonNode *parse_string(JsonParser *parser, const char *json) +{ + GError *err = NULL; + + if (!json_parser_load_from_data(parser, json, -1, &err)) + { + _E("Function \"json_parser_load_from_data()\" failed with message: %s", err->message); + g_error_free(err); + return NULL; + } + + return json_parser_get_root(parser); +} + +static void car_info_array_iterate_cb(JsonArray *array, guint index, JsonNode *element, gpointer user_data) +{ + car_info_t **car_info_array = (car_info_t **)user_data; + + JsonObject *entry = json_node_get_object(element); + + car_info_array[index] = car_info_create(); + + if (json_object_has_member(entry, JSON_SCHEMA_RESPONSE_CAR_ID) && + car_info_set_car_id(car_info_array[index], json_object_get_string_member(entry, JSON_SCHEMA_RESPONSE_CAR_ID)) != 0) + { + _E("Couldn't set car id!"); + } + + if (json_object_has_member(entry, JSON_SCHEMA_CAR_NAME) && + car_info_set_car_name(car_info_array[index], json_object_get_string_member(entry, JSON_SCHEMA_CAR_NAME)) != 0) + { + _E("Couldn't set car name!"); + } + + if (json_object_has_member(entry, JSON_SCHEMA_CAR_IP) && + car_info_set_car_ip(car_info_array[index], json_object_get_string_member(entry, JSON_SCHEMA_CAR_IP)) != 0) + { + _E("Couldn't set car ip!"); + } + + if (json_object_has_member(entry, JSON_SCHEMA_AP_MAC) && + car_info_set_car_ap_mac(car_info_array[index], json_object_get_string_member(entry, JSON_SCHEMA_AP_MAC)) != 0) + { + _E("Couldn't set access point MAC address!"); + } + + if (json_object_has_member(entry, JSON_SCHEMA_AP_SSID) && + car_info_set_ap_ssid(car_info_array[index], json_object_get_string_member(entry, JSON_SCHEMA_AP_SSID)) != 0) + { + _E("Couldn't set access point SSID!"); + } +} diff --git a/src/cloud/cloud_request.c b/src/cloud/cloud_request.c new file mode 100644 index 0000000..1a46fa1 --- /dev/null +++ b/src/cloud/cloud_request.c @@ -0,0 +1,245 @@ +/* + * 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 "cloud/cloud_request.h" +#include "cloud/car_info_serializer.h" +#include "cloud/http_request.h" +#include +#include +#include +#include "log.h" + +#define BASE_URL "https://son.tizen.online" +#define PATH_API_RACING "/api/racing" + +typedef struct { + char *ap_mac; + cloud_request_car_list_data_cb cb; + void *user_data; +} car_api_get_request_context_t; + +typedef struct { + car_info_t *car; + cloud_request_car_post_finish_cb cb; + void *user_data; +} car_api_post_request_context_t; + +typedef struct { + long response_code; + char *response_msg; +} car_api_post_request_response_t; + +typedef struct { + long response_code; + car_info_t **cars; + int size; +} car_api_get_request_response_t; + +GQuark g_spawn_error_quark(); +static void car_api_post_task_thread_cb(GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable); +static void car_api_post_task_ready_cb(GObject *source_object, GAsyncResult *res, gpointer user_data); +static void car_api_post_task_context_free(car_api_post_request_context_t *context); +static void car_api_post_request_response_free(car_api_post_request_response_t *response); + +static void car_api_get_task_context_free(car_api_get_request_context_t *context); +static void car_api_get_request_response_free(car_api_get_request_response_t *response); +static void car_api_get_task_thread_cb(GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable); +static void car_api_get_task_ready_cb(GObject *source_object, GAsyncResult *res, gpointer user_data); + +#define G_ERROR_DOMAIN g_spawn_error_quark() + +GCancellable *cloud_request_api_racing_get(const char *ap_mac, cloud_request_car_list_data_cb cb, void *user_data) +{ + GCancellable *cancellable = g_cancellable_new(); + + GTask *task = g_task_new(NULL, cancellable, car_api_get_task_ready_cb, NULL); + g_task_set_source_tag(task, cloud_request_api_racing_get); + g_task_set_return_on_cancel(task, FALSE); + + car_api_get_request_context_t *context = g_new0(car_api_get_request_context_t, 1); + context->ap_mac = strndup(ap_mac, strlen(ap_mac)); + context->cb = cb; + context->user_data = user_data; + + g_task_set_task_data(task, context, (GDestroyNotify)car_api_get_task_context_free); + g_task_run_in_thread(task, car_api_get_task_thread_cb); + + g_object_unref(task); + + return cancellable; +} + +GCancellable *cloud_request_api_racing_post(const car_info_t *car_info, cloud_request_car_post_finish_cb cb, void *user_data) +{ + GCancellable *cancellable = g_cancellable_new(); + + GTask *task = g_task_new(NULL, cancellable, car_api_post_task_ready_cb, NULL); + g_task_set_source_tag(task, cloud_request_api_racing_post); + g_task_set_return_on_cancel(task, FALSE); + + car_api_post_request_context_t *context = g_new0(car_api_post_request_context_t, 1); + context->car = car_info_copy(car_info); + context->cb = cb; + context->user_data = user_data; + + g_task_set_task_data(task, context, (GDestroyNotify)car_api_post_task_context_free); + g_task_run_in_thread(task, car_api_post_task_thread_cb); + + g_object_unref(task); + + return cancellable; +} + +GQuark g_spawn_error_quark() +{ + return g_quark_from_static_string("cloud_request"); +} + +static void car_api_post_task_context_free(car_api_post_request_context_t *context) +{ + ret_if(!context); + + car_info_destroy(context->car); + g_free(context); +} + +static void car_api_post_request_response_free(car_api_post_request_response_t *response) +{ + ret_if(!response); + + g_free(response->response_msg); + g_free(response); +} + +static void car_api_post_task_thread_cb(GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +{ + car_api_post_request_context_t *context = (car_api_post_request_context_t *)task_data; + + if (g_task_return_error_if_cancelled(task)) { + return; + } + + car_api_post_request_response_t *response = g_new0(car_api_post_request_response_t, 1); + + char *json = car_info_serializer_serialize(context->car); + int retval = http_request_post(BASE_URL""PATH_API_RACING, json, &(response->response_msg), &(response->response_code)); + g_free(json); + + if (retval != 0) { + GError *err = g_error_new(G_ERROR_DOMAIN, retval, "http_request_post failed!"); + g_task_return_error(task, err); + } + + g_task_return_pointer(task, response, (GDestroyNotify)car_api_post_request_response_free); +} + +static void car_api_post_task_ready_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GTask *task = G_TASK(res); + GError *error = NULL; + + //If no error occurred g_task_propagate_pointer transfers ownership, so later response have to be freed. + car_api_post_request_response_t *response = g_task_propagate_pointer(task, &error); + if (error != NULL) { + _E("POST async task failed with msg: %s", error->message); + g_error_free(error); + return; + } + car_api_post_request_context_t *context = g_task_get_task_data(task); + + request_result_e result = (response->response_code == 200 && (strcmp(response->response_msg, "Success") == 0)) ? + SUCCESS : + FAILURE; + + if (context->cb) { + context->cb(result, context->user_data); + } + + car_api_post_request_response_free(response); +} + +static void car_api_get_task_context_free(car_api_get_request_context_t *context) +{ + ret_if(!context); + + g_free(context->ap_mac); + g_free(context); +} + +static void car_api_get_request_response_free(car_api_get_request_response_t *response) +{ + ret_if(!response); + ret_if(response->size <= 0); + + for (int i = 0; i < response->size; i++) + { + car_info_destroy(response->cars[i]); + } + g_free(response->cars); + g_free(response); +} + +static void car_api_get_task_thread_cb(GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +{ + car_api_get_request_context_t *context = (car_api_get_request_context_t *)task_data; + + if (g_task_return_error_if_cancelled(task)) { + return; + } + + car_api_get_request_response_t *response = g_new0(car_api_get_request_response_t, 1); + char *response_json = NULL; + + GString *url = g_string_new(BASE_URL""PATH_API_RACING"?apMac="); + g_string_append(url, context->ap_mac); + + int retval = http_request_get(url->str, &response_json, &(response->response_code)); + g_string_free(url, TRUE); + + if (retval != 0) { + GError *err = g_error_new(G_ERROR_DOMAIN, retval, "http_request_get failed!"); + g_task_return_error(task, err); + } + else { + response->cars = car_info_serializer_deserialize_array(response_json, &(response->size)); + } + + g_free(response_json); + g_task_return_pointer(task, response, (GDestroyNotify)car_api_get_request_response_free); +} + +static void car_api_get_task_ready_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GTask *task = G_TASK(res); + GError *error = NULL; + + //If no error occurred g_task_propagate_pointer transfers ownership, so later response have to be freed. + car_api_get_request_response_t *response = g_task_propagate_pointer(task, &error); + if (error != NULL) { + _E("GET async task failed with msg: %s", error->message); + g_error_free(error); + return; + } + car_api_get_request_context_t *context = g_task_get_task_data(task); + + request_result_e result = (response->response_code == 200) ? SUCCESS : FAILURE; + + if (context->cb) { + context->cb(result, response->cars, response->size, context->user_data); + } + + car_api_get_request_response_free(response); +} \ No newline at end of file diff --git a/src/cloud/http_request.c b/src/cloud/http_request.c new file mode 100644 index 0000000..b094227 --- /dev/null +++ b/src/cloud/http_request.c @@ -0,0 +1,130 @@ +/* + * 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 "cloud/http_request.h" +#include +#include +#include +#include +#include "log.h" + +static size_t _response_write(void *ptr, size_t size, size_t nmemb, void *data); + +int http_request_get(const char *url, char **response, long *response_code) +{ + retvm_if(!url, -1, "GET request URL is NULL!"); + retvm_if(!response, -1, "GET request response is null"); + + CURL *curl = NULL; + CURLcode res = CURLE_OK; + + curl = curl_easy_init(); + retvm_if(!curl, -1, "Failed to initialize curl!"); + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _response_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)response); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + _E("curl_easy_perform() failed: %s", curl_easy_strerror(res)); + curl_easy_cleanup(curl); + return -1; + } + + long _response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &_response_code); + if (_response_code) { + *response_code = _response_code; + } + + curl_easy_cleanup(curl); + + return 0; +} + +int http_request_post(const char *url, const char *json, char **response, long *response_code) +{ + retvm_if(!url, -1, "POST request URL is NULL!"); + retvm_if(!json, -1, "POST request JSON message is NULL!"); + + CURL *curl = curl_easy_init(); + retvm_if(!curl, -1, "Failed to initialize curl!"); + + char *_response = NULL; + struct curl_slist *headers = curl_slist_append(NULL, "Content-Type: application/json"); + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _response_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&_response); + + CURLcode res = CURLE_OK; + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + _E("curl_easy_perform() failed: %s", curl_easy_strerror(res)); + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + return -1; + } + + long _response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &_response_code); + if (response_code) { + *response_code = _response_code; + } + + if (response) { + *response = _response; + } + else { + g_free(_response); + } + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + return 0; +} + +static size_t _response_write(void *ptr, size_t size, size_t nmemb, void *data) +{ + char **received = (char **)data; + size_t real_size = size * nmemb; + const char *response_msg = (const char *)ptr; + + if (received && real_size > 0) { + if (*received) { + char *temp = strndup(response_msg, real_size); + size_t length = strlen(*received) + strlen(temp); + char *new = g_malloc(length * sizeof(char)); + snprintf(new, length, "%s%s", *received, temp); + g_free(temp); + g_free(*received); + *received = new; + } + else { + *received = g_strndup((const char *)ptr, real_size); + } + } + else { + _E("Failed to get response, response size : %lu", real_size); + } + + return real_size; +} \ No newline at end of file