From 1fb9a4bff548b7a209b6545a25cdcfd6bd035816 Mon Sep 17 00:00:00 2001 From: wangbo Date: Tue, 24 Jun 2025 09:26:35 +0000 Subject: [PATCH] =?UTF-8?q?Fisrst=20commit:=E7=94=B5=E5=8A=9B=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E5=A4=9A=E7=8A=B6=E6=80=81=E9=87=8F=E4=B8=80=E4=BD=93?= =?UTF-8?q?=E5=8C=96=E6=99=BA=E8=83=BD=E5=9C=A8=E7=BA=BF=E7=9B=91=E6=B5=8B?= =?UTF-8?q?=E8=A3=85=E7=BD=AE=E4=B8=BB=E6=8E=A7=E4=BB=A3=E7=A0=81;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/include/array.h | 75 + app/include/better_log.h | 181 ++ app/include/cJSON.h | 300 ++ app/include/cmd.h | 336 +++ app/include/common.h | 161 ++ app/include/dbg.h | 115 + app/include/fifo.h | 73 + app/include/hwgpio.h | 74 + app/include/list.h | 701 +++++ app/include/main.h | 87 + app/include/memory.h | 123 + app/include/mtimer.h | 72 + app/include/pd_csg.h | 384 +++ app/include/pd_dau.h | 213 ++ app/include/pd_dbg.h | 243 ++ app/include/pd_hf.h | 36 + app/include/pd_main.h | 393 +++ app/include/pd_upgrade.h | 104 + app/include/sockunion.h | 230 ++ app/include/thread_monitor.h | 64 + app/include/typedef.h | 154 + app/include/vty.h | 331 +++ app/lib/a_process/main.c | 339 +++ app/lib/a_process/module.mk | 3 + app/lib/a_process/pd_ae.c | 0 app/lib/a_process/pd_csg.c | 955 +++++++ app/lib/a_process/pd_dau.c | 754 +++++ app/lib/a_process/pd_hf.c | 192 ++ app/lib/a_process/pd_iron.c | 0 app/lib/a_process/pd_main.c | 632 +++++ app/lib/a_process/pd_uhf.c | 0 app/lib/a_process/pd_upgrade.c | 550 ++++ app/lib/m_management/array.c | 297 ++ app/lib/m_management/better_log.c | 535 ++++ app/lib/m_management/cJSON.c | 3119 +++++++++++++++++++++ app/lib/m_management/cmd.c | 3118 ++++++++++++++++++++ app/lib/m_management/cmd_SSH2.c | 722 +++++ app/lib/m_management/common.c | 697 +++++ app/lib/m_management/dbg.c | 248 ++ app/lib/m_management/fifo.c | 273 ++ app/lib/m_management/memory.c | 564 ++++ app/lib/m_management/module.mk | 3 + app/lib/m_management/mtimer.c | 243 ++ app/lib/m_management/sockunion.c | 605 ++++ app/lib/m_management/thread_monitor.c | 125 + app/lib/m_management/vty.c | 2763 ++++++++++++++++++ app/lib/z_hardware/hwgpio.c | 355 +++ app/lib/z_hardware/module.mk | 3 + build/Makefile | 141 + build/PDMonitor.cfg | 8 + build/config.mk | 40 + build/libother/include/libssh/callbacks.h | 989 +++++++ build/libother/include/libssh/legacy.h | 120 + build/libother/include/libssh/libssh.h | 823 ++++++ build/libother/include/libssh/server.h | 369 +++ build/libother/include/libssh/sftp.h | 1036 +++++++ build/libother/include/libssh/ssh2.h | 81 + build/libother/libssh.a | Bin 0 -> 1143086 bytes build/version.c | 23 + 59 files changed, 25175 insertions(+) create mode 100644 app/include/array.h create mode 100755 app/include/better_log.h create mode 100644 app/include/cJSON.h create mode 100755 app/include/cmd.h create mode 100755 app/include/common.h create mode 100755 app/include/dbg.h create mode 100644 app/include/fifo.h create mode 100644 app/include/hwgpio.h create mode 100644 app/include/list.h create mode 100755 app/include/main.h create mode 100644 app/include/memory.h create mode 100644 app/include/mtimer.h create mode 100755 app/include/pd_csg.h create mode 100755 app/include/pd_dau.h create mode 100755 app/include/pd_dbg.h create mode 100755 app/include/pd_hf.h create mode 100755 app/include/pd_main.h create mode 100755 app/include/pd_upgrade.h create mode 100644 app/include/sockunion.h create mode 100644 app/include/thread_monitor.h create mode 100644 app/include/typedef.h create mode 100644 app/include/vty.h create mode 100755 app/lib/a_process/main.c create mode 100644 app/lib/a_process/module.mk create mode 100644 app/lib/a_process/pd_ae.c create mode 100755 app/lib/a_process/pd_csg.c create mode 100755 app/lib/a_process/pd_dau.c create mode 100755 app/lib/a_process/pd_hf.c create mode 100644 app/lib/a_process/pd_iron.c create mode 100755 app/lib/a_process/pd_main.c create mode 100644 app/lib/a_process/pd_uhf.c create mode 100755 app/lib/a_process/pd_upgrade.c create mode 100644 app/lib/m_management/array.c create mode 100755 app/lib/m_management/better_log.c create mode 100644 app/lib/m_management/cJSON.c create mode 100755 app/lib/m_management/cmd.c create mode 100755 app/lib/m_management/cmd_SSH2.c create mode 100755 app/lib/m_management/common.c create mode 100755 app/lib/m_management/dbg.c create mode 100644 app/lib/m_management/fifo.c create mode 100644 app/lib/m_management/memory.c create mode 100644 app/lib/m_management/module.mk create mode 100644 app/lib/m_management/mtimer.c create mode 100644 app/lib/m_management/sockunion.c create mode 100644 app/lib/m_management/thread_monitor.c create mode 100755 app/lib/m_management/vty.c create mode 100644 app/lib/z_hardware/hwgpio.c create mode 100644 app/lib/z_hardware/module.mk create mode 100755 build/Makefile create mode 100644 build/PDMonitor.cfg create mode 100644 build/config.mk create mode 100755 build/libother/include/libssh/callbacks.h create mode 100755 build/libother/include/libssh/legacy.h create mode 100755 build/libother/include/libssh/libssh.h create mode 100755 build/libother/include/libssh/server.h create mode 100755 build/libother/include/libssh/sftp.h create mode 100755 build/libother/include/libssh/ssh2.h create mode 100644 build/libother/libssh.a create mode 100644 build/version.c diff --git a/app/include/array.h b/app/include/array.h new file mode 100644 index 0000000..74964d4 --- /dev/null +++ b/app/include/array.h @@ -0,0 +1,75 @@ +/***************************************************************************** + * file include/array.h + * author YuLiang + * version 1.0.0 + * date 22-Sep-2021 + * brief This file provides all the headers of the array functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _ARRAY_H_ +#define _ARRAY_H_ + +/* Includes ------------------------------------------------------------------*/ + +/* Define --------------------------------------------------------------------*/ +#define ARRAY_MIN_SIZE 1 + +/* Exported types ------------------------------------------------------------*/ +typedef struct +{ + uint32_t active; /* 当前可使用的索引位置. */ + uint32_t alloced; /* 申请的内存大小. */ + void **index; /* 数组元素. */ +} array_t; + +/* Extern global variables ---------------------------------------------------*/ + +/* Exported macro ------------------------------------------------------------*/ + +/* 获取数组A索引I的数据. */ +#define array_get(A, I) ((A)->index[(I)]) +/* 获取数组A目前激活的索引. */ +#define array_active(A) ((A)->active) + +/* Exported functions --------------------------------------------------------*/ +extern array_t *array_init(uint32_t size, int32_t mem_type); +extern void array_set(array_t *a, uint32_t i, void *val, int32_t mem_type); +extern void array_unset(array_t *v, uint32_t i); +extern uint32_t array_append(array_t *a, void *val, int32_t mem_type); +extern void array_free_wrapper(array_t *a, int32_t mem_type); +extern void array_free_index(array_t *a, int32_t mem_type); +extern void array_free(array_t *a, int32_t mem_type); +extern array_t *array_copy(array_t *a, int32_t mem_type); +extern void array_merge(array_t *a, array_t *b, int32_t mem_type); +extern void *array_lookup(array_t *a, uint32_t i); +extern uint32_t array_count(array_t *a); + +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/include/better_log.h b/app/include/better_log.h new file mode 100755 index 0000000..98b30e8 --- /dev/null +++ b/app/include/better_log.h @@ -0,0 +1,181 @@ +/****************************************************************************** + * file include/better_log.h + * author YuLiang + * version 1.0.0 + * date 14-Sep-2021 + * brief This file provides all the headers of the log functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _BETTER_LOG_H_ +#define _BETTER_LOG_H_ + +/* Includes ------------------------------------------------------------------*/ +#include + +/* Define --------------------------------------------------------------------*/ +#define LOG_FILE "PowerIoT.db" +#define LOG_CMD_LEN 512 +#define LOG_STR_LEN 256 +#define LOG_DEFAULT_SHOW_CNT 100 +#define LOG_DB_FIFO "LOG_DB_FIFO" + +#define LOG_STR "Display default most 100 logs\n" +#define LOG_LEVELS "(errors|warnings|notifications|informational|debug)" +#define LOG_LEVEL_DESC \ + "Error messages\n" \ + "Warning messages\n" \ + "Normal but significant messages\n" \ + "Informational messages\n" \ + "Debug messages\n" + +/* Exported types ------------------------------------------------------------*/ +/* 日志等级 */ +typedef enum +{ + LOG_LVL_ERR, + LOG_LVL_WARN, + LOG_LVL_NOTIF, + LOG_LVL_INFO, + LOG_LVL_DBG, + LOG_LVL_MAX +} LOG_LVL_E; + +/* 日志接口 */ +typedef enum +{ + LOG_MODE_STDOUT, + LOG_MODE_FILE, + LOG_MODE_MONITOR, + LOG_MODE_MAX +} LOG_MODE_E; + +/* 日志模块 */ +typedef enum +{ + LOG_DEFAULT, + LOG_CLI, + LOG_MEMORY, + LOG_FIFO, + LOG_GPIO, + LOG_PD, + LOG_DAU, + LOG_CSG, + LOG_NET, + LOG_STORAGE, + LOG_DEBUG, + LOG_UPGRADE, + LOG_MAX +} LOG_MODULE_E; + +/* 日志显示方式 */ +typedef enum +{ + LOG_SHOW_CNT, + LOG_SHOW_LVL, + LOG_SHOW_KEYWORD, + LOG_SHOW_MAX +} LOG_SHOW_E; + +typedef struct _log_lvl +{ + int32_t lvl; + char *describe; +} log_lvl_t; + +typedef struct _log_module +{ + int32_t module; + char *describe; +} log_module_t; + +/* log全局数据结构体. */ +typedef struct _log_sys +{ + int32_t enable_lvl[LOG_MODE_MAX]; /* log模块使能等级,位图表示. */ + int32_t default_lvl; /* 默认log模块使能等级,位图表示. */ + sqlite3 *db; /* log数据库指针. */ + int32_t log_fifo_id; /* log fifo id. */ + char *filename; /* log数据库名字. */ + int32_t timestamp_precision; /* 默认log时间戳精度. */ + pthread_mutex_t log_db_mutex; /* 数据库读写互斥锁 */ +} log_sys_t; + +typedef struct +{ + LOG_SHOW_E type; + int32_t param; + char log_show_str[LOG_STR_LEN]; +} _log_show_t; + +typedef struct +{ + LOG_MODULE_E module; + LOG_LVL_E lvl; + char log_out_str[LOG_STR_LEN]; +} _log_out_t; + +typedef struct +{ + uint32_t type; + void *data; +} _log_msg_t; + +/* Exported macro ------------------------------------------------------------*/ + +/* Extern global variables ---------------------------------------------------*/ +extern log_sys_t *log_sys; + +/* Extern functions ----------------------------------------------------------*/ +/* 配置函数属性,让gcc按printf一样检查函数参数,第b个参数为可变参数. */ +#define BTLOG_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) + +extern void log_out(LOG_MODULE_E module, LOG_LVL_E priority, const char *format, ...) + BTLOG_ATTRIBUTE(3, 4); +extern void log_err(LOG_MODULE_E module, const char *format, ...) + BTLOG_ATTRIBUTE(2, 3); +extern void log_warn(LOG_MODULE_E module, const char *format, ...) + BTLOG_ATTRIBUTE(2, 3); +extern void log_info(LOG_MODULE_E module, const char *format, ...) + BTLOG_ATTRIBUTE(2, 3); +extern void log_notice(LOG_MODULE_E module, const char *format, ...) + BTLOG_ATTRIBUTE(2, 3); +extern void log_debug(LOG_MODULE_E module, const char *format, ...) + BTLOG_ATTRIBUTE(2, 3); + +extern int32_t log_open(); +extern void log_set_level(LOG_MODE_E mode, LOG_LVL_E log_level); +extern void log_unset_level(LOG_MODE_E mode, LOG_LVL_E log_level); +extern int32_t log_level_get_by_name(const char *lvl_name); +extern int32_t log_module_get_by_name( const char *module_name ); +extern void log_backtrace(int32_t priority); +extern void log_show( int32_t cnt, LOG_LVL_E priority, const char *key_word ); +extern int32_t log_handle_init(void); +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/include/cJSON.h b/app/include/cJSON.h new file mode 100644 index 0000000..95a9cf6 --- /dev/null +++ b/app/include/cJSON.h @@ -0,0 +1,300 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 15 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/include/cmd.h b/app/include/cmd.h new file mode 100755 index 0000000..7474121 --- /dev/null +++ b/app/include/cmd.h @@ -0,0 +1,336 @@ +/****************************************************************************** + * file include/cmd.h + * author YuLiang + * version 1.0.0 + * date 09-Oct-2021 + * brief This file provides all the headers of the command functions. + + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _CMD_H_ +#define _CMD_H_ + +/* Includes ------------------------------------------------------------------*/ +#include "array.h" +#include "vty.h" + +/* Define --------------------------------------------------------------------*/ +/* 命令行最大参数个数. */ +#define CMD_ARGC_MAX 16 + +/* rang检查数字最大位数. */ +#define DECIMAL_STRLEN_MAX 10 +#define INIT_MATCHARR_SIZE 10 +#define COMPLETE_BUF_SIZE 256 + +#define IPV6_ADDR_STR "0123456789abcdefABCDEF:.%" +#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:.%/" +#define STATE_START 1 +#define STATE_COLON 2 +#define STATE_DOUBLE 3 +#define STATE_ADDR 4 +#define STATE_DOT 5 +#define STATE_SLASH 6 +#define STATE_MASK 7 + +/* 命令行返回值. */ +#define CMD_SUCCESS 0 +#define CMD_WARNING 1 +#define CMD_ERR_NO_MATCH 2 +#define CMD_ERR_AMBIGUOUS 3 +#define CMD_ERR_INCOMPLETE 4 +#define CMD_ERR_EXEED_ARGC_MAX 5 +#define CMD_ERR_NOTHING_TODO 6 +#define CMD_COMPLETE_FULL_MATCH 7 +#define CMD_COMPLETE_MATCH 8 +#define CMD_COMPLETE_LIST_MATCH 9 +#define CMD_SUCCESS_DAEMON 10 + +#define CONF_BACKUP_EXT ".sav" + +#define CL_COPYRIGHT "Copyright 2023-2025 LandPower." + +/* Common descriptions. */ +#define SHOW_STR "Show running system information\n" +#define IP_STR "IP information\n" +#define IPV6_STR "IPv6 information\n" +#define NO_STR "Negate a command or set its defaults\n" +#define REDIST_STR "Redistribute information from another routing protocol\n" +#define CLEAR_STR "Reset functions\n" +#define RIP_STR "RIP information\n" +#define BGP_STR "BGP information\n" +#define OSPF_STR "OSPF information\n" +#define NEIGHBOR_STR "Specify neighbor router\n" +#define DEBUG_STR "Debugging functions (see also 'undebug')\n" +#define UNDEBUG_STR "Disable debugging functions (see also 'debug')\n" +#define ROUTER_STR "Enable a routing process\n" +#define AS_STR "AS number\n" +#define MBGP_STR "MBGP information\n" +#define MATCH_STR "Match values from routing table\n" +#define SET_STR "Set values in destination routing protocol\n" +#define OUT_STR "Filter outgoing routing updates\n" +#define IN_STR "Filter incoming routing updates\n" +#define V4NOTATION_STR "specify by IPv4 address notation(e.g. 0.0.0.0)\n" +#define OSPF6_NUMBER_STR "Specify by number\n" +#define INTERFACE_STR "Interface infomation\n" +#define IFNAME_STR "Interface name(e.g. ep0)\n" +#define IP6_STR "IPv6 Information\n" +#define OSPF6_STR "Open Shortest Path First (OSPF) for IPv6\n" +#define OSPF6_ROUTER_STR "Enable a routing process\n" +#define OSPF6_INSTANCE_STR "<1-65535> Instance ID\n" +#define SECONDS_STR "<1-65535> Seconds\n" +#define ROUTE_STR "Routing Table\n" +#define PREFIX_LIST_STR "Build a prefix list\n" + +/* Some macroes */ +#define CMD_OPTION(S) ('[' == (S[0])) +#define CMD_VARIABLE(S) (((S[0]) >= 'A' && (S[0]) <= 'Z') || ('<' == (S[0]))) +#define CMD_VARARG(S) ('.' ==(S[0])) +#define CMD_RANGE(S) ('<' == (S[0])) +#define CMD_IPV4(S) (0 == strcmp((S), "A.B.C.D")) +#define CMD_IPV4_PREFIX(S) (0 == strcmp((S), "A.B.C.D/M")) +#define CMD_IPV6(S) (0 == strcmp((S), "X:X::X:X")) +#define CMD_IPV6_PREFIX(S) (0 == strcmp((S), "X:X::X:X/M")) + +#define DEV_INFO_DEFAULT_IP "192.168.1.110" +#define DEV_INFO_DEFAULT_MASK "255.255.255.0" +#define DEV_INFO_DEFAULT_GW "192.168.1.1" + +#define SAVE_STA_CFG_BAK_FILE 1 +#define SAVE_DEV_CFG_BAK_FILE 2 + +/* Exported types ------------------------------------------------------------*/ +enum +{ + CMD_ATTR_DEPRECATED = 1, + CMD_ATTR_HIDDEN, +}; + +/* 匹配结果类型. */ +typedef enum +{ + no_match, + extend_match, + ipv4_prefix_match, + ipv4_match, + ipv6_prefix_match, + ipv6_match, + range_match, + vararg_match, + partly_match, + exact_match +} MATCH_TYPE_E; + +/* 用于定义命令的宏. */ +#define CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \ + cmd_element_t cmdname = \ + { \ + .string = cmdstr, \ + .func = funcname, \ + .doc = helpstr, \ + .attr = attrs, \ + .daemon = dnum, \ + }; + +#define CMD_FUNC_DECL(funcname) \ + static int funcname(cmd_element_t *, vty_t *, int, const char *[]); + +#define CMD_FUNC_TEXT(funcname) \ + static int funcname \ + (cmd_element_t *self __attribute__ ((unused)), \ + vty_t *vty __attribute__ ((unused)), \ + int argc __attribute__ ((unused)), \ + const char *argv[] __attribute__ ((unused))) + +/* CMD for vty command interafce. Little bit hacky ;-). */ +#define CMD(funcname, cmdname, cmdstr, helpstr) \ + CMD_FUNC_DECL(funcname) \ + CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \ + CMD_FUNC_TEXT(funcname) + +#define CMD_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ + CMD_FUNC_DECL(funcname) \ + CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \ + CMD_FUNC_TEXT(funcname) + +#define CMD_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ + CMD_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) + +#define CMD_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \ + CMD_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED) \ + +/* ALIAS macro which define existing command's alias. */ +#define ALIAS(funcname, cmdname, cmdstr, helpstr) \ + CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) + +#define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ + CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) + +#define ALIAS_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ + CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, 0) + +#define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \ + CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, 0) + +/* 命令行模式节点宏 */ +typedef enum +{ + USERNAME_NODE, /* Username node. Default mode of vty interface. */ + PASSWORD_NODE, /* Password node. */ + ENABLE_NODE, /* Enable node. */ + CONFIG_NODE, /* Config node. Default mode of config file. */ + PORT_NODE, /* Port node. */ + COMMON_NODE, /* Common node. Cmd is common to other node. */ + NODES_COUNT +} NODE_TYPE_E; + +/* config节点配置优先级 */ +typedef enum +{ + CONFIG_PRI_CSG = 0, + CONFIG_PRI_DAU, + CONFIG_PRI_PD, + CONFIG_PRI_NET, + CONFIG_PRI_COUNT +} CONFIG_PRI_E; + +/* Host configuration variable */ +typedef struct{ + uint8_t type_m; // 主设备号 + uint8_t type_s; // 次设备号 + uint8_t reserved1[2]; // 保留 + uint32_t dev_id; // 设备ID + char hostname[FILE_NAME_LEN]; // 设备名 128byte + uint32_t factory_date; // 出厂日期. + uint32_t deployment_date; // 部署日期. + uint8_t app_version[32]; // 软件版本 + uint8_t app_compile_time[32]; // 软件编译时间 + uint8_t hardware_version[32]; // 硬件版本 + uint8_t FPGA_version[32]; // fpga版本 + uint32_t ip; // 本机 IP. + uint32_t mask; // 本机 MASK. + uint32_t gw; // 本机网关 + uint8_t mac[6]; // MAC地址. + uint16_t server_port; // 服务器端口号. + uint32_t server_ipv4; // 服务器 IP. +} device_info_t; + +/* Host configuration variable */ +typedef struct +{ + char name[FILE_NAME_LEN]; /* Host name of this router. */ + int lines; /* System wide terminal lines. */ + char *version; /* 版本号. */ + char *compile; /* 编译时间. */ + char *hardversion; /* 硬件版本号. */ + char *FPGAversion; /* PFGA版本号. */ + char configfile[FILE_NAME_LEN]; /* config file name of this host */ + char bannerfile[FILE_NAME_LEN]; /* Banner configuration file name. */ +} host_t; + +/* Structure of command element. */ +typedef struct _cmd_element +{ + const char *string; /* Command specification by string */ + const char *doc; /* Documentation of this command */ + array_t *strs; /* Pointing out each description array */ + unsigned int str_size; /* Command index count */ + int (*func)(struct _cmd_element*, vty_t*, int, const char *[]); + int daemon; /* Daemon to which this command belong */ + char *config; /* Configuration string */ + array_t *sub_cfg; /* Sub configuration string */ + unsigned char attr; /* Command attributes */ +} cmd_element_t; + +/* Command description structure. */ +typedef struct +{ + char *cmd; /* Command string. */ + char *str; /* Command's description. */ +} desc_t; + +/* 用于命令行模式节点注册配置保存函数 */ +typedef int cmd_save_config_f(vty_t*); + +/* 命令行模式节点结构体 */ +typedef struct +{ + uint32_t node; /* Node index. */ + uint32_t up_node; /* Upper level node index. */ + uint8_t param_num; /* Prompt param num. */ + char *prompt; /* Prompt character at vty interface. */ + cmd_save_config_f *func; /* Node's configuration write function */ + array_t *cmds; /* Array of this node's command list. */ + array_t *configs; /* 配置函数数组. */ +} cmd_node_t; + +/* Exported macro ------------------------------------------------------------*/ + +/* Extern global variables ---------------------------------------------------*/ +extern host_t host; +extern device_info_t device_info; +extern array_t *cmd_nodes; +extern int8_t is_system_init; + +/* Extern functions ----------------------------------------------------------*/ +static inline cmd_node_t *cmd_node_get(NODE_TYPE_E ntype) +{ + return (cmd_node_t *)array_lookup(cmd_nodes, ntype); +} + +extern void cmd_init(void); +extern int32_t cmd_execute(vty_t *vty); +extern const char *cmd_prompt(NODE_TYPE_E ntype); +extern array_t *cmd_strs_create(const char *string); +extern void cmd_strs_free(array_t *a); +extern void cmd_sort_node(void); +extern void cmd_install_node(cmd_node_t *node, cmd_save_config_f *func); +extern array_t *cmd_describe_command (array_t *cmd_line, vty_t *vty, int32_t *status); +extern char **cmd_complete_command(array_t *vline, vty_t *vty, int32_t *status); +extern void cmd_install_element(NODE_TYPE_E ntype, cmd_element_t *cmd); +extern int32_t cmd_config_node_config_register(int32_t pri, cmd_save_config_f *func); +extern void vtysh_init(void); +extern char *vtysh_prompt (void); +extern int vtysh_rl_question(void); +extern int vtysh_rl_completion(void); +extern char *vtysh_completion_entry_function(const char *ignore, int invoking_key); +extern void vtysh_config_recovery(void); +extern void vtysh_device_save(void); +extern void vtysh_eth0_save(void); +extern int32_t vtysh_config_save(void); +extern int32_t vtysh_host_addr_set(char *addr, char *mask); +extern int32_t vtysh_gateway_set(char *gateway); +extern void vtysh_shell_init(void); +extern void vtycmd_init(void); +extern void vtycmd_cmd_init(void); +extern bool vtycmd_connect(void); +extern void vtycmd_print(const char *format, va_list va); +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/include/common.h b/app/include/common.h new file mode 100755 index 0000000..6ffc6bb --- /dev/null +++ b/app/include/common.h @@ -0,0 +1,161 @@ +/****************************************************************************** + * file include/common.h + * author YuLiang + * version 1.0.0 + * date 10-Sep-2021 + * brief This file provides all the headers of the common functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _COMMON_H_ +#define _COMMON_H_ + +/* Includes ------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include +#include + +#include "array.h" +#include "better_log.h" +#include "dbg.h" +#include "memory.h" +#include "thread_monitor.h" + +/* Define --------------------------------------------------------------------*/ +#define FALSE 0 +#define TRUE 1 + +#define E_NONE 0 +#define E_ERROR -1 +#define E_BAD_PARAM -2 +#define E_MEM -3 +#define E_SYS_CALL -4 +#define E_NULL -5 +#define E_NOT_FOUND -6 +#define E_NOT_IDENTIFY -7 +#define E_TIMEOUT -8 +#define E_SAME -9 + +#define OUT + +#define BACKTRACE_SIZE 20 +#define TIME_STR_LEN 64 +#define RECV_MSG_LEN 1536 +#define DEVICE_INVALID 255 +#define FILE_NAME_LEN 128 +#define DIR_PATH_LEN 512 +#define INET_ADDRSTRLEN 16 +#define INET6_ADDRSTRLEN 46 + +#define ETH_GIS_DEV_ID_LEN 16 +#define ETH_GIS_CMD_PARAM_LEN 16 +#define DEVICE_ID_LEN 6 +#define MAC_ADDR_LEN 6 +#define IP_ADDR_LEN 4 +#define DEV_DATA_LEN 1000 +#define DEV_DATA_BITMAP_LEN 8 +#define DEV_NAME_STR_LEN 32 + +/* Does the I/O error indicate that the operation should be retried later? */ +#define ERRNO_IO_RETRY(EN) \ + (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) + +/* Exported types ------------------------------------------------------------*/ +typedef int8_t bool; +typedef void* (*thread_func_t)(void*); + +typedef struct +{ + void *arg; + int priority; + int log_module; + char *thread_name; +} thread_param_t; + + +/* Exported macro ------------------------------------------------------------*/ +#define BITMAP_SET(t, b) ((t) |= 1 << (b)) +#define BITMAP_RESET(t, b) ((t) &= ~(1 << (b))) +#define IS_BITMAP_SET(t, b) ((t) & (1 << b)) + +#define LD_E_RETURN(_module_, _f_) \ + do { \ + int _rv_ = E_ERROR; \ + if ((_rv_ = _f_) != E_NONE) \ + { \ + DBG(_module_, "ERROR return %d!\r\n", _rv_); \ + return _rv_; \ + } \ + } while(0) + +#define LD_NULL_RETURN(_module_, _f_) \ + do { \ + if (NULL == (_f_)) \ + { \ + DBG(_module_, "ERROR return %d!\r\n", _rv_); \ + return E_NULL; \ + } \ + } while(0) + +/* Extern global variables ---------------------------------------------------*/ + +/* Extern functions ----------------------------------------------------------*/ +extern char *str_to_lower(char *str); +extern char *str_omit_space(char *str, bool omit_end); +extern size_t time_string(int32_t timestamp_precision, char *buf, size_t buflen); +extern const char *safe_strerror(int errnum); +extern void buf_print(char *buf, int32_t len); +extern void buf_print_16(uint16_t *buf, int32_t len); +extern int32_t mac_generate_from_ip(uint32_t ip, uint8_t *mac); +extern uint16_t crc16(uint8_t *data, uint16_t size); +extern uint16_t crc16_modbus(uint8_t *data, uint16_t size); +extern unsigned int crc32(unsigned int crc, char *buf, unsigned long len); +extern void speed_detection_stat(void); +extern void speed_detection_end(void); +extern int printh(const char *format, ...); +extern char* softversion_get(); +extern char* softversion_date_get(); +extern char* hardware_version_get(); +extern char* fpga_version_date_get(); +extern uint16_t sofrware_version_get(void); +extern int32_t str_to_mac(char *mac_str, OUT uint8_t *mac); +extern int32_t str_to_id(char *id_str, OUT uint32_t *id); +extern int32_t bitmap_set(uint8_t *buf, int32_t nbit, int32_t buf_len); +extern int32_t bitmap_reset(uint8_t *buf, int32_t nbit, int32_t buf_len); +extern int32_t is_bitmap_set(uint8_t *buf, int32_t nbit, int32_t buf_len); +extern int32_t time_str_to_long(char *date, char *time, uint32_t *t); +extern uint16_t version_str_to_int(void); +extern void time_set(time_t timestamp); +extern int32_t create_thread(thread_func_t func, thread_param_t *pParam); +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/include/dbg.h b/app/include/dbg.h new file mode 100755 index 0000000..e96b458 --- /dev/null +++ b/app/include/dbg.h @@ -0,0 +1,115 @@ +/****************************************************************************** + * file include/common.h + * author YuLiang + * version 1.0.0 + * date 10-Sep-2021 + * brief This file provides all the headers of the debug functions. + + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _DBG_H_ +#define _DBG_H_ + +/* Includes ------------------------------------------------------------------*/ + +/* Define --------------------------------------------------------------------*/ +#define DBG_HELP_STR_MAX 32 +#define DBG_INVALID_MODULE 0xffffffff + +/* Exported types ------------------------------------------------------------*/ +/* debug模块. */ +typedef enum +{ + DBG_M_DBG = 0, + DBG_M_CLI, + DBG_M_MTIMER, + DBG_M_PROCESS, + DBG_M_GPIO, + DBG_M_PD, + DBG_M_PD_ERR, + DBG_M_PD_DAU, + DBG_M_PD_DAU_SEND, + DBG_M_PD_DAU_RECV, + DBG_M_PD_DAU_ERR, + DBG_M_FIFO, + DBG_M_FIFO_ERR, + DBG_M_PD_CSG, + DBG_M_PD_CSG_ERR, + DBG_M_PD_NET_ERR, + DBG_M_STORAGE_ERR, + DBG_M_DEBUG, + DBG_M_PD_UPGRADE, + DBG_M_PD_DATA, + DBG_M_COUNT +} DBG_MODULE_E; + +/* debug命令字. */ +typedef enum +{ + DBG_CMD_ON = 0, + DBG_CMD_OFF, + DBG_CMD_ALL_OFF +} DBG_CMD_E; + +/* debug结构体,每个结构体代表一个模块. */ +typedef struct +{ + int32_t num; + int32_t stat; + char desc[DBG_HELP_STR_MAX]; +} dbg_module_t; + +/* Exported macro ------------------------------------------------------------*/ +#ifdef CFG_DBG_ON + #define DBG(_MODULE_, _format_, _msg_...) \ + do { \ + if (dbg_stat_get(_MODULE_)) printh("%s(%d): " _format_, __FUNCTION__, __LINE__, ##_msg_); \ + } while(0) + #define DBG_Q(_MODULE_, _format_, _msg_...) \ + do { \ + if (_dbg_module[_MODULE_].stat) printh(_format_, ##_msg_); \ + } while(0) +#else + #define DBG(_MODULE_, _msg_...) + #define DBG(_MODULE_, _format_, _msg_...) +#endif + +/* Extern global variables ---------------------------------------------------*/ +#ifdef CFG_DBG_ON +extern dbg_module_t _dbg_module[DBG_M_COUNT]; +#endif + +/* Extern functions ----------------------------------------------------------*/ +extern int32_t dbg_stat_get(DBG_MODULE_E module); +extern int dbg_cmd_hander(DBG_CMD_E cmd, int32_t module); +extern void dbg_init(void); + +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/include/fifo.h b/app/include/fifo.h new file mode 100644 index 0000000..5173569 --- /dev/null +++ b/app/include/fifo.h @@ -0,0 +1,73 @@ +/****************************************************************************** + * file include/fifo.h + * author YuLiang + * version 1.0.0 + * date 21-Feb-2023 + * brief This file provides all the headers of the fifo functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _FIFO_H_ +#define _FIFO_H_ + +/* Includes ------------------------------------------------------------------*/ +#include + +/* Define --------------------------------------------------------------------*/ +#define FIFO_NAME_LEN 32 + +/* Exported types ------------------------------------------------------------*/ + +/* . */ +typedef struct _fifo_t +{ + char name[FIFO_NAME_LEN]; // fifo 名字. + uint32_t size; // fifo 大小. + uint32_t cur; // 当前的数据位置. + uint32_t valid; // 当前读取的数据位置. + uint32_t used; // fifo 使用数量. + uint32_t max; // fifo 最大使用数量. + pthread_mutex_t mutex; // 多线程同时操作的信号量. + sem_t sem; // 读取有效互斥锁. + void **data; // 数据数组. +} fifo_t; + +/* Exported macro ------------------------------------------------------------*/ + +/* Extern global variables ---------------------------------------------------*/ + +/* Extern functions ----------------------------------------------------------*/ +extern int32_t fifo_init(void); +extern int32_t fifo_create(char* name, uint32_t size); +extern int32_t fifo_write(uint32_t id, void *data, int32_t len); +extern int32_t fifo_read(uint32_t id, void **data); +extern int32_t fifo_push(uint32_t id); +extern void fifo_show(uint32_t id); +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/include/hwgpio.h b/app/include/hwgpio.h new file mode 100644 index 0000000..97d4db9 --- /dev/null +++ b/app/include/hwgpio.h @@ -0,0 +1,74 @@ +/****************************************************************************** + * file include/common.h + * author YuLiang + * version 1.0.0 + * date 24-Nov-2021 + * brief This file provides all the headers of the gpio functions. + + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _HWGPIO_H_ +#define _HWGPIO_H_ + +/* Includes ------------------------------------------------------------------*/ + +/* Define --------------------------------------------------------------------*/ +#define GPIO_DIR_IN 1 +#define GPIO_DIR_OUT 2 + +#define GPIO_WATCHDONG 915 + +/* Exported types ------------------------------------------------------------*/ +/* 记录每个打开的gpio信息 */ +typedef struct +{ + uint16_t index; /* gpio在数组中的索引 */ + uint16_t gpio; /* gpio号 gpio2-15 = (2 - 1) * 32 + 15 = 47 */ + uint8_t is_export; /* 是否已导出 */ + uint8_t curr_dir; /* 方向当前值 1-in 2-out */ + uint8_t curr_val; /* 电平当前值 针对dir==out时 ,当dir为in时,每次都需要读文件 */ +} gpio_node_t; + +/* Exported macro ------------------------------------------------------------*/ +#define GPIO_WATCHDOG(_v_) gpio_val_set(gpio_watcdog_idx, _v_) + +/* Extern global variables ---------------------------------------------------*/ +extern int32_t gpio_watcdog_idx; + +/* Extern functions ----------------------------------------------------------*/ +extern int32_t gpio_val_set(uint16_t gpio, uint8_t value); +extern int32_t gpio_val_get(uint16_t gpio, uint8_t *value); +extern int32_t gpio_dir_set(uint16_t gpio, uint8_t dir); +extern int32_t gpio_export(uint16_t gpio); +extern int32_t gpio_init(void); +extern void feed_dog(void); + +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/include/list.h b/app/include/list.h new file mode 100644 index 0000000..762353d --- /dev/null +++ b/app/include/list.h @@ -0,0 +1,701 @@ +#ifndef _LIST_H_ +#define _LIST_H_ + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +#define l_offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +/** + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - l_offsetof(type,member) );}) + + +static inline void prefetch(const void *x) {;} +static inline void prefetchw(const void *x) {;} + +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +static inline void INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty() on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + +/** + * list_replace - replace old entry by new one + * @old : the element to be replaced + * @new : the new element to insert + * + * If @old was empty, it will be overwritten. + */ +static inline void list_replace(struct list_head *old, + struct list_head *new) +{ + new->next = old->next; + new->next->prev = new; + new->prev = old->prev; + new->prev->next = new; +} + +static inline void list_replace_init(struct list_head *old, + struct list_head *new) +{ + list_replace(old, new); + INIT_LIST_HEAD(old); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_is_last - tests whether @list is the last entry in list @head + * @list: the entry to test + * @head: the head of the list + */ +static inline int list_is_last(const struct list_head *list, + const struct list_head *head) +{ + return list->next == head; +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_empty_careful - tests whether a list is empty and not being modified + * @head: the list to test + * + * Description: + * tests whether a list is empty _and_ checks that no other CPU might be + * in the process of modifying either member (next or prev) + * + * NOTE: using list_empty_careful() without synchronization + * can only be safe if the only activity that can happen + * to the list entry is list_del_init(). Eg. it cannot be used + * if another CPU could re-list_add() it. + */ +static inline int list_empty_careful(const struct list_head *head) +{ + struct list_head *next = head->next; + return (next == head) && (next == head->prev); +} + +/** + * list_is_singular - tests whether a list has just one entry. + * @head: the list to test. + */ +static inline int list_is_singular(const struct list_head *head) +{ + return !list_empty(head) && (head->next == head->prev); +} + +static inline void __list_cut_position(struct list_head *list, + struct list_head *head, struct list_head *entry) +{ + struct list_head *new_first = entry->next; + list->next = head->next; + list->next->prev = list; + list->prev = entry; + entry->next = list; + head->next = new_first; + new_first->prev = head; +} + +/** + * list_cut_position - cut a list into two + * @list: a new list to add all removed entries + * @head: a list with entries + * @entry: an entry within head, could be the head itself + * and if so we won't cut the list + * + * This helper moves the initial part of @head, up to and + * including @entry, from @head to @list. You should + * pass on @entry an element you know is on @head. @list + * should be an empty list or a list you do not care about + * losing its data. + * + */ +static inline void list_cut_position(struct list_head *list, + struct list_head *head, struct list_head *entry) +{ + if (list_empty(head)) + return; + if (list_is_singular(head) && + (head->next != entry && head != entry)) + return; + if (entry == head) + INIT_LIST_HEAD(list); + else + __list_cut_position(list, head, entry); +} + +static inline void __list_splice(const struct list_head *list, + struct list_head *prev, + struct list_head *next) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + + first->prev = prev; + prev->next = first; + + last->next = next; + next->prev = last; +} + +/** + * list_splice - join two lists, this is designed for stacks + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(const struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head, head->next); +} + +/** + * list_splice_tail - join two lists, each list being a queue + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice_tail(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head->prev, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head, head->next); + INIT_LIST_HEAD(list); + } +} + +/** + * list_splice_tail_init - join two lists and reinitialise the emptied list + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * Each of the lists is a queue. + * The list at @list is reinitialised + */ +static inline void list_splice_tail_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head->prev, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_first_entry - get the first element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; prefetch(pos->next), pos != (head); \ + pos = pos->next) + +/** + * __list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + * + * This variant differs from list_for_each() in that it's the + * simplest possible list iteration code, no prefetching is done. + * Use this for code that knows the list to be very short (empty + * or 1 entry) most of the time. + */ +#define __list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ + pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_prev_safe(pos, n, head) \ + for (pos = (head)->prev, n = pos->prev; \ + prefetch(pos->prev), pos != (head); \ + pos = n, n = pos->prev) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + prefetch(pos->member.next), &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member); \ + prefetch(pos->member.prev), &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() + * @pos: the type * to use as a start point + * @head: the head of the list + * @member: the name of the list_struct within the struct. + * + * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). + */ +#define list_prepare_entry(pos, head, member) \ + ((pos) ? : list_entry(head, typeof(*pos), member)) + +/** + * list_for_each_entry_continue - continue iteration over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Continue to iterate over list of given type, continuing after + * the current position. + */ +#define list_for_each_entry_continue(pos, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member); \ + prefetch(pos->member.next), &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_continue_reverse - iterate backwards from the given point + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Start to iterate over list of given type backwards, continuing after + * the current position. + */ +#define list_for_each_entry_continue_reverse(pos, head, member) \ + for (pos = list_entry(pos->member.prev, typeof(*pos), member); \ + prefetch(pos->member.prev), &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_for_each_entry_from - iterate over list of given type from the current point + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type, continuing from current position. + */ +#define list_for_each_entry_from(pos, head, member) \ + for (; prefetch(pos->member.next), &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_continue + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type, continuing after current point, + * safe against removal of list entry. + */ +#define list_for_each_entry_safe_continue(pos, n, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_from + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type from current point, safe against + * removal of list entry. + */ +#define list_for_each_entry_safe_from(pos, n, head, member) \ + for (n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_reverse + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate backwards over list of given type, safe against removal + * of list entry. + */ +#define list_for_each_entry_safe_reverse(pos, n, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member), \ + n = list_entry(pos->member.prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.prev, typeof(*n), member)) + +/* + * Double linked lists with a single pointer list head. + * Mostly useful for hash tables where the two pointer list head is + * too wasteful. + * You lose the ability to access the tail in O(1). + */ + +struct hlist_head { + struct hlist_node *first; +}; + +struct hlist_node { + struct hlist_node *next, **pprev; +}; + +#define HLIST_HEAD_INIT { .first = NULL } +#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } +#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) +static inline void INIT_HLIST_NODE(struct hlist_node *h) +{ + h->next = NULL; + h->pprev = NULL; +} + +static inline int hlist_unhashed(const struct hlist_node *h) +{ + return !h->pprev; +} + +static inline int hlist_empty(const struct hlist_head *h) +{ + return !h->first; +} + +static inline void __hlist_del(struct hlist_node *n) +{ + struct hlist_node *next = n->next; + struct hlist_node **pprev = n->pprev; + *pprev = next; + if (next) + next->pprev = pprev; +} + +static inline void hlist_del(struct hlist_node *n) +{ + __hlist_del(n); + n->next = LIST_POISON1; + n->pprev = LIST_POISON2; +} + +static inline void hlist_del_init(struct hlist_node *n) +{ + if (!hlist_unhashed(n)) { + __hlist_del(n); + INIT_HLIST_NODE(n); + } +} + +static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) +{ + struct hlist_node *first = h->first; + n->next = first; + if (first) + first->pprev = &n->next; + h->first = n; + n->pprev = &h->first; +} + +/* next must be != NULL */ +static inline void hlist_add_before(struct hlist_node *n, + struct hlist_node *next) +{ + n->pprev = next->pprev; + n->next = next; + next->pprev = &n->next; + *(n->pprev) = n; +} + +static inline void hlist_add_after(struct hlist_node *n, + struct hlist_node *next) +{ + next->next = n->next; + n->next = next; + next->pprev = &n->next; + + if(next->next) + next->next->pprev = &next->next; +} + +/* + * Move a list from one list head to another. Fixup the pprev + * reference of the first entry if it exists. + */ +static inline void hlist_move_list(struct hlist_head *old, + struct hlist_head *new) +{ + new->first = old->first; + if (new->first) + new->first->pprev = &new->first; + old->first = NULL; +} + +#define hlist_entry(ptr, type, member) container_of(ptr,type,member) + +#define hlist_for_each(pos, head) \ + for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \ + pos = pos->next) + +#define hlist_for_each_safe(pos, n, head) \ + for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ + pos = n) + +/** + * hlist_for_each_entry - iterate over list of given type + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry(tpos, pos, head, member) \ + for (pos = (head)->first; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_continue - iterate over a hlist continuing after current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_continue(tpos, pos, member) \ + for (pos = (pos)->next; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_from - iterate over a hlist continuing from current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_from(tpos, pos, member) \ + for (; pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @n: another &struct hlist_node to use as temporary storage + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ + for (pos = (head)->first; \ + pos && ({ n = pos->next; 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = n) + +#endif + diff --git a/app/include/main.h b/app/include/main.h new file mode 100755 index 0000000..1a000b4 --- /dev/null +++ b/app/include/main.h @@ -0,0 +1,87 @@ +/***************************************************************************** + * file include/main.h + * author YuLiang + * version 1.0.0 + * date 26-Sep-2021 + * brief This file provides all the headers of the main functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 __PROCESS_H__ +#define __PROCESS_H__ + +/* Includes ------------------------------------------------------------------*/ + +/* Define --------------------------------------------------------------------*/ + +/* Exported types ------------------------------------------------------------*/ +/* 消息队列类型. */ +typedef enum +{ + MSG_TYPE_DAU_RECV = 5, /* 1-4 固定被 DAU 发送线程使用, 这里从 5 开始. */ + MSG_TYPE_COUNT, +} MSG_TYPE_E; + +/* 系统复位类型 */ +typedef enum _REBOOT_MSG +{ + REBOOT_NONE = 0, + REBOOT_LOCAL_SERVER_IP_CHANGE, + REBOOT_LOCAL_IP_CHANGE, + REBOOT_LOCAL_HOST_NAME_CHANGE, + REBOOT_LOCAL_RESET, + REBOOT_LOCAL_ARM_UPGRADE, + REBOOT_REMOTE_SERVER_IP_CHANGE, + REBOOT_REMOTE_IP_CHANGE, + REBOOT_REMOTE_HOST_NAME_CHANGE, + REBOOT_REMOTE_RESET, + REBOOT_UPGRADE_ALL, + REBOOT_SYSTEM_RESET, + REBOOT_4G_ERROR, + REBOOT_NET_ERROR, + REBOOT_MAX, +} REBOOT_MSG; + +typedef struct +{ + REBOOT_MSG type; // 重启类型 + char *msg; // 重启日志 +} reboot_msg_t; + +/* Extern global variables ---------------------------------------------------*/ +extern int32_t recv_qid; +extern uint16_t version_hex; +extern uint32_t start_time; + +/* Exported macro ------------------------------------------------------------*/ + +/* Exported functions --------------------------------------------------------*/ +extern void reboot_system(int module, REBOOT_MSG type); +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/include/memory.h b/app/include/memory.h new file mode 100644 index 0000000..be68704 --- /dev/null +++ b/app/include/memory.h @@ -0,0 +1,123 @@ +/****************************************************************************** + * file include/memory.h + * author YuLiang + * version 1.0.0 + * date 10-Sep-2021 + * brief This file provides all the headers of the memory functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _MEMORY_H_ +#define _MEMORY_H_ + +/* Includes ------------------------------------------------------------------*/ +#define MTYPE_MEMSTR_LEN 20 + +/* Define --------------------------------------------------------------------*/ +enum +{ + MTYPE_CLI = 1, + MTYPE_BUF, + MTYPE_BUF_DATA, + MTYPE_BUF_TMP, + MTYPE_VTY, + MTYPE_VTY_TMP, + MTYPE_VTY_OUT_BUF, + MTYPE_VTY_HIST, + MTYPE_LOG, + MTYPE_PREFIX, + MTYPE_THREAD_MONITOR, + MTYPE_MTIMER, + MTYPE_HASH, + MTYPE_HASH_BACKET, + MTYPE_HASH_INDEX, + MTYPE_THREAD, + MTYPE_THREAD_STATS, + MTYPE_THREAD_MASTER, + MTYPE_THREAD_FUNCNAME, + MTYPE_FIFO, + MTYPE_GPIO, + MTYPE_RECV, + MTYPE_SENT, + MTYPE_USART, + MTYPE_ETH, + MTYPE_GIS, + MTYPE_DAU, + MTYPE_CSG, + MTYPE_STORAGE, + MTYPE_DEBUG, + MTYPE_UPGRADE, + MTYPE_MAX, +}; + +/* Exported types ------------------------------------------------------------*/ +/* For pretty printing of memory allocate information. */ +typedef struct _mem_node +{ + int32_t index; + const char *format; +} mem_node_t; + +typedef struct _mem_list +{ + mem_node_t *nodes; + const char *name; +} mem_list_t; + +/* Exported macro ------------------------------------------------------------*/ +#define XMALLOC(mtype, size) \ + mtype_x_malloc (__FILE__, __LINE__, (mtype), (size)) +#define XMALLOC_Q(mtype, size) \ + mtype_x_malloc_q (__FILE__, __LINE__, (mtype), (size)) +#define XCALLOC(mtype, size) \ + mtype_x_calloc (__FILE__, __LINE__, (mtype), (size)) +#define XREALLOC(mtype, ptr, size) \ + mtype_x_realloc (__FILE__, __LINE__, (mtype), (ptr), (size)) +#define XFREE(mtype, ptr) \ + do { \ + mtype_x_free (__FILE__, __LINE__, (mtype), (ptr)); \ + ptr = NULL; } \ + while (0) +#define XSTRDUP(mtype, str) \ + mtype_x_strdup (__FILE__, __LINE__, (mtype), (str)) + +/* Extern global variables ---------------------------------------------------*/ + +/* Extern functions ----------------------------------------------------------*/ +extern void *mtype_x_malloc(const char *file, int32_t line, int32_t type, size_t size); +extern void *mtype_x_malloc_q(const char *file, int32_t line, int32_t type, size_t size); +extern void *mtype_x_calloc(const char *file, int32_t line, int32_t type, size_t size); +extern void *mtype_x_realloc(const char *file, int32_t line, int32_t type, void *ptr, size_t size); +extern void mtype_x_free(const char *file, int32_t line, int32_t type, void *ptr); +extern char *mtype_x_strdup(const char *file, int32_t line, int32_t type, const char *str); +extern void mtype_init(void); +extern void mtype_init_befor(void); + +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/include/mtimer.h b/app/include/mtimer.h new file mode 100644 index 0000000..6dd7786 --- /dev/null +++ b/app/include/mtimer.h @@ -0,0 +1,72 @@ +/***************************************************************************** + * file include/mtimer.h + * author YuLiang + * version 1.0.0 + * date 22-Sep-2021 + * brief This file provides all the headers of the timer functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 __MTIMER_H__ +#define __MTIMER_H__ + +/* Includes ------------------------------------------------------------------*/ +#include + +/* Define --------------------------------------------------------------------*/ +/* 以太网连接类型类型. */ +typedef enum +{ + MTIMER_S_STOP = 0, + MTIMER_S_RUN, +} MTIMER_STATE_E; + +/* Exported types ------------------------------------------------------------*/ +typedef void* (*MTIMER_CALLBACK)(void *arg); + +/* 定时器属性结构体. */ +typedef struct _mtimer_t +{ + MTIMER_CALLBACK handle; /* 回调函数 */ + void *arg; /* 运行参数 */ + char name[DEV_NAME_STR_LEN]; /* 定时器名字 */ + uint16_t interval; /* 间隔时间s */ + uint32_t time; /* 加入定时器的时间 */ +} mtimer_t; + +/* Extern global variables ---------------------------------------------------*/ + +/* Exported macro ------------------------------------------------------------*/ + +/* Exported functions --------------------------------------------------------*/ +extern int32_t mtimer_add(MTIMER_CALLBACK func, void *arg, uint16_t interval, char *name); +extern int32_t mtimer_init(void); + +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/include/pd_csg.h b/app/include/pd_csg.h new file mode 100755 index 0000000..c49904b --- /dev/null +++ b/app/include/pd_csg.h @@ -0,0 +1,384 @@ +/****************************************************************************** + * file include/pd_csg.h + * author YuLiang + * version 1.0.0 + * date 21-Feb-2023 + * brief This file provides all the headers of the csg server functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _PD_CSG_H_ +#define _PD_CSG_H_ + +#ifdef CFG_DEV_TYPE_LAND_PD +/* Includes ------------------------------------------------------------------*/ +#include "pd_main.h" +#include +#include +#include + + +/* Define --------------------------------------------------------------------*/ +#define CSG_HEAD_LEN (32) +#define CSG_TOTLE_LEN (26) +#define CSG_PKT_LEN (1300) + +#define MAX_FILES (128) +#define MAX_PATH_LEN (256) +#define THRESHOLD_MS 10 // 时间差阈值 +#define UDP_SEND_TIMEOUT (2) +#define CSG_SEND_ERR_CNT (3) +/* 配置文件结构体 */ +#define FILE_FIFO_PATH_LEN 256 + +/* 命令类型. */ +enum CSG_CMD_TYPE +{ + CSG_REQUEST = 1, + CSG_REPLY = 2, + CSG_PRV_REQUEST = 121, + CSG_PRV_REPLY = 122 +}; + +/* 共有命令字. */ +enum CSG_CMD +{ + CSG_C_CONTACT = 1, + CSG_C_ADD_DAU = 2, + CSG_C_RESET = 3, + CSG_C_UPDATE = 5, + CSG_C_DEV_INFO_SET = 6, /* 设备基本信息设置 */ + CSG_C_DEV_INFO_GET = 7, /* 设备基本信息获取 */ + CSG_C_UPDATE_RESULT = 9, + CSG_C_HEARTBEAT = 10, + CSG_C_MAX +}; + +/* 私有命令字. */ +enum DEBUG_CM_CMD +{ + CSG_PRV_CONFIG_GLOBAL_SET = 1, /* 设备全局参数设置 */ + CSG_PRV_CONFIG_GLOBAL_GET = 2, /* 设备全局参数获取 */ + CSG_PRV_CONFIG_PORT_SET = 3, /* 设备端口参数设置 */ + CSG_PRV_CONFIG_PORT_GET = 4, /* 设备端口参数获取 */ + CSG_PRV_CONFIG_REAL_WAVE = 5, /* 实时波形配置 */ + CSG_PRV_TREND = 10, + CSG_PRV_REAL_PRPS = 11, + CSG_PRV_EVENT = 12 +}; + +// 定义命令字常量 +typedef enum { + //CMD_CONTACT = 0x00, + CMD_INVALID +} command_type; +// 定义函数指针类型 +typedef void (*command_handler)(int slot, char *data); + +// 定义命令结构体 +typedef struct { + command_type cmd; // 命令字 + command_handler handler; // 处理函数 +} command_entry; + +/* Exported types ------------------------------------------------------------*/ +/* 设备配置的滤波类型. */ +typedef enum +{ + CSG_FILTER_TYPE_FF = 1, + CSG_FILTER_TYPE_FR, + CSG_FILTER_TYPE_LF, + CSG_FILTER_TYPE_HF +} CSG_FILTER_TYPE_E; + +typedef struct { + uint32_t port; /* 端口号 0 ~ 7 */ + uint32_t length; /* 端口数据长度 */ +}port_info_t; + +/* . */ +typedef struct +{ + int skfd; // 后台通讯使用的 socket. + uint32_t pkt_index; // 报文索引. + char buf_send[1500]; + char buf_recv[1500]; + char event_booster_buf[1500]; + char trend_booster_buf[1500]; + struct sockaddr_in server; + int32_t server_ip; // server ip. + uint16_t server_port; // server port. + uint8_t is_connect; // 是否连接上服务器. + int32_t communication_time; // 最后通讯时间. + time_t heartbeat_timeout; + time_t heartbeat_timeout_cnt; + pthread_mutex_t mutex; + pthread_mutex_t lock; +} csg_t; + +/* 报文头结构. */ +typedef struct{ + uint16_t len; + uint8_t dev_type_m; + uint8_t dev_type_s; + uint32_t dev_id; + uint8_t cmd_type; + uint8_t cmd; + uint16_t pkt_id; + uint8_t version; + uint8_t reserve1[2]; + uint8_t slot; // 槽位 + uint32_t sdev_id; // 从设备id + uint8_t reserve2[12]; +} csg_pkt_head_t; + +typedef struct { + uint8_t result; // 应答结果. 0:成功 1:失败 + uint8_t reserved[3]; // 保留 +}csg_ack_t; + +/* 心跳报文. */ +typedef struct +{ + float freq; // 实测同步源工频频率. + uint8_t dau_state[4]; // 采集模块的状态. + uint8_t out_sync; // 外接调频同步 1: 有效 0: 无效. + uint8_t pt_sync; // PT同步 1: 有效 0: 无效. + uint8_t in_sync; // 内同步 1: 有效 0: 无效. + uint8_t reserved1; // 预留 字节对齐. + uint8_t port_link_alarm[PD_PORT_SUM]; // 通道传感器连接状态 断线告警. + uint8_t dau_port_num[4]; // 采集板端口数量. +} csg_heartbeat_t; + +typedef struct{ + uint8_t type_m; // 主设备号 + uint8_t type_s; // 次设备号 + uint8_t reserved1[2]; // 保留 + uint32_t dev_id; // 设备ID + char hostname[FILE_NAME_LEN]; // 设备名 128byte + uint32_t factory_date; // 出厂日期. + uint32_t deployment_date; // 部署日期. + uint8_t app_version[32]; // 软件版本 + uint8_t app_compile_time[32]; // 软件编译时间 + uint8_t hardware_version[32]; // 硬件版本 + uint8_t FPGA_version[32]; // fpga版本 + uint32_t ip; // 本机 IP. + uint32_t mask; // 本机 MASK. + uint32_t gw; // 本机网关 + uint8_t mac[6]; // MAC地址. + uint16_t server_port; // 服务器端口号. + uint32_t server_ipv4; // 服务器 IP. +} csg_dev_info_t; + +typedef struct{ + uint16_t power_frequency; // 工频频率, 单位: 0.1Hz + uint16_t trend_period; // 趋势数据上送周期, 单位: 秒. + uint8_t sync_mode; // 同步方式 1: PT 同步 2: 内同步(默认) 3: 外接信号同步. + uint8_t heartbeat_period; // 心跳包周期, 单位: 分钟. + uint8_t pps_mode; // pps 主从模式 PD_PPS_XXX. + uint8_t protocol_type; // 0:朗德协议 1:南网协议 + uint16_t trend_storage; //趋势存储文件数量阈值 + uint16_t event_storage; //事件存储文件数量阈值 +} csg_config_global_t; + +typedef struct { + uint8_t vport; // 通道编号. + uint8_t port_type; // 采集通道类型 , 1 表示特高频局放 2 表示超声局放 3 表示 TEV 4 表示高频. + uint8_t filter; // 滤波器类型 1: 低频段 2: 全频段 3: 窄频段 4: 高频段 + uint8_t sensor_type; // 0: 无配置; 1: UHF信号传感器; 2: UHF噪声传感器 ; 3: UHF信号传感器, 关联噪声降噪. + uint8_t is_auto_noise; // 是否自动调整降噪等级. + uint8_t denoise_type; // 0-无配置 1-自动 2-手动降噪 + uint16_t denoise_variance; // 方差降噪系数, 单位: 1% + + uint32_t event_counter_h; // 事件次数阀值高. + uint16_t event_sec_h; // 事件每秒次数阀值高. + uint16_t event_thr_h; // 事件值阈值高. + uint32_t event_counter_thr_h; // 事件值阈值高的次数. + + uint32_t event_counter_l; // 事件次数阀值低. + uint16_t event_sec_l; // 事件每秒次数阀值低. + uint16_t event_thr_l; // 事件值阈值低. + uint32_t event_counter_thr_l; // 事件值阈值低的次数. + + uint8_t burst_time; // 事件突发计算时间 + uint8_t reserved1[1]; + uint16_t burst_thr; // 事件突发阈值 + + int16_t denoise_manual; // 手动底噪等级 + int16_t denoise_auto; // 自动降噪水平 +}csg_config_port_t; + +typedef struct +{ + uint8_t vport; // 通道编号 1 ~ 8 + uint8_t is_concern; // 关注 1: 关注 0: 取消关注 + uint8_t filter; // 滤波器类型 + uint8_t denoise_correlation; // 关联降噪 1: 关联噪声去噪 + uint8_t denoise_type; // 0-无配置 1-自动 2-手动降噪 + uint8_t reserved; // 保留 + uint16_t denoise_manual; // 手动降噪等级 +} csg_real_image_get_t; + +/* 实时图谱报文头 28Byte. */ +typedef struct{ + uint16_t index; // 每10包传完之后自动自加,用于区分当前包结束. + uint8_t pkt_sum; // 总包数 10. + uint8_t pkt_index; // 当前包数 0 ~ 9. + uint16_t fre_cnt; // 工频周期计数. + uint8_t vport; // 通道编号. + uint8_t reserved1[1]; + int16_t max; + int16_t avg; + uint32_t cnt; + uint32_t utc; + float freq; // 频率. + uint32_t len; +} csg_real_image_t; + +typedef struct{ + uint16_t index; // 包索引 + uint16_t sum; // 总包数 + uint8_t type; // 数据类型 0:prpd 1:原始波形 2:10秒prps 3:统计数据 + uint8_t vport; // 通道号 + uint8_t boosterpack; // 是否补包 0:否 1:是 + uint8_t reserved[1]; // 保留 + uint32_t identifier; // 数据编号 + uint32_t utc; // 同步时间 + uint32_t len; // 当前包长度 +} csg_trend_t; +typedef struct{ + uint16_t data_cnt; // 数据计数. + int16_t max; // 通道的最大值. + uint16_t reserved; // 保留 + uint16_t avg; // 通道的平均值. + uint32_t cnt; // 通道的计数值. + uint32_t phase; // 放电相位 . + uint32_t noise; // 趋势数据中的底噪值: 单位 dBm . + uint32_t event_cnt; // 趋势数据中的的事件数量记录. +} csg_trend_stat; + +typedef struct { + uint16_t index; // 包索引 + uint16_t sum; // 总包数 + uint8_t vport; // 通道号 + uint8_t boosterpack; // 是否补包 0:否 1:是 + int16_t max; // 通道的最大值. + uint32_t power_fre; // 工频周期. + uint8_t type; // 事件类型. + uint8_t reserved[3]; // 保留 + uint32_t identifier; // 数据编号: 0 - (2^32-1) 循环. + uint32_t utc; // UTC 时标. + uint32_t cnt; // 通道每秒脉冲计数值. + uint16_t avg_o; // 通道原始信号 (降噪前) 的平均值. + uint16_t avg; // 脉冲平均值. + uint32_t point_cnt; // 数据累计点数 + uint32_t len; // 当前包长度 +} csg_event_t; +/* 升级文件包结构体 */ +typedef struct { + uint8_t type; // 升级类型 + uint8_t resverd[3]; + uint16_t index; // 报文索引 + uint16_t sum; // 总包数. + uint32_t len; // 数据包长度. +} csg_upgrade_data_t; + +/* 应答升级结构体 */ +typedef struct { + uint16_t index; // 应答包序号. + uint8_t result; // 应答结果. 0:成功 1:失败 + uint8_t reserve; // 保留 +} upgrade_ack_t; + +/* 升级结果通知 */ +typedef struct +{ + uint8_t result; // 升级结果 + uint8_t reserved[3]; + char context[128]; +} upgrade_res_t; + +typedef struct +{ + uint8_t type_m; // 主设备号 + uint8_t type_s; // 次设备号 + uint8_t reserved1[2]; // 保留 + uint32_t dev_id; // 设备ID + char hostname[FILE_NAME_LEN]; // 设备名 128byte + uint32_t factory_date; // 出厂日期. + uint32_t deployment_date; // 部署日期. + uint8_t app_version[32]; // 软件版本 + uint8_t app_compile_time[32]; // 软件编译时间 + uint8_t hardware_version[32]; // 硬件版本 + uint8_t FPGA_version[32]; // fpga版本 + uint32_t ip; // 本机 IP. + uint32_t mask; // 本机 MASK. + uint32_t gw; // 本机网关 + uint8_t mac[6]; // MAC地址. + uint16_t server_port; // 服务器端口号. + uint32_t server_ipv4; // 服务器 IP. + uint8_t port[8]; + uint8_t port_type[8]; +} csg_contact_t; + +typedef struct +{ + uint32_t dev_id; + uint8_t slot; + uint8_t status; + uint8_t reserved[2]; + uint8_t port[8]; + uint8_t port_type[8]; +} csg_add_dau_t; + +typedef struct +{ + uint32_t dev_id; + uint8_t slot; + uint8_t reserved[2]; + uint8_t result; +} csg_add_dau_ack_t; + + +/* Exported macro ------------------------------------------------------------*/ + +/* Extern global variables ---------------------------------------------------*/ +extern csg_t csg; + +/* Extern functions ----------------------------------------------------------*/ +extern int32_t _csg_pkt_check(char *pkt); +extern int32_t csg_handle_init(void); +extern int32_t csg_handle_init_after(void); +extern void csg_upgrade_result_send(int32_t rv, char *buf); +extern void _print_sockaddr_in(const struct sockaddr_in *addr); +extern command_handler _csg_get_table_handle(command_entry *ptable, command_type cmd); +extern void _csg_send_data(uint8_t cmd_type, uint8_t cmd, char *pkt, int32_t len); +#endif +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/include/pd_dau.h b/app/include/pd_dau.h new file mode 100755 index 0000000..837467c --- /dev/null +++ b/app/include/pd_dau.h @@ -0,0 +1,213 @@ +/***************************************************************************** + * file include/pd_dau.h + * author YuLiang + * version 1.0.0 + * date 03-Feb-2023 + * brief This file provides all the headers of the dau functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 __PD_DAU_H__ +#define __PD_DAU_H__ + +#ifdef CFG_DEV_TYPE_LAND_PD +/* Includes ------------------------------------------------------------------*/ +#include +#include +#include + +#include "pd_main.h" +#include "common.h" + + + +/* Define --------------------------------------------------------------------*/ + +#define MAX_SLOTS 6 +#define BUFFER_SIZE 2048 +#define UDP_SLOTS 4 +#define RS485_SLOTS 2 + + +#define UHF "UDP_UHF" +#define HF "UDP_HF" +#define ULTRASONIC "UDP_ULTRASONIC" +#define IRONCORE "RS485_IRONCORE" +#define UDP_PORT 5000 + + + +/* Exported types ------------------------------------------------------------*/ + +// 板卡类型枚举 +typedef enum { + DAU_TYPE_UDP, + DAU_TYPE_RS485, + DAU_TYPE_NONE +} DauType; + +// 板卡状态枚举 +typedef enum { + DAU_STATE_DISCONNECTED, + DAU_STATE_CONNECTED, + DAU_STATE_REGISTERED +} DauState; + + +// 通信回调函数类型 +//typedef void (*data_recv_callback)(void* priv_data, const void* data, size_t len); +//typedef void (*data_send_callback)(void* priv_data, void* data, size_t len); + +// 板卡私有数据结构 +typedef struct +{ + DauType type; + union + { + struct + { + int sockfd; + struct sockaddr_in addr; + } udp; + struct + { + int fd; + char port[20]; + struct termios options; + } rs485; + } comm; + char board_id[32]; + void *rx_buffer; + size_t buffer_size; + uint8_t cmd_type; + uint8_t cmd; +} dau_private_data_t; + +// 板卡操作回调函数结构 +typedef struct +{ + int (*init)(dau_private_data_t *data, const char *config); + int (*receive)(dau_private_data_t *data, void *buf, size_t len); + int (*transmit)(dau_private_data_t *data, const void *buf, size_t len); + void (*cleanup)(dau_private_data_t *data); +} dau_operations_t; + +// UDP客户端信息 +typedef struct { + struct sockaddr_in addr; + char board_id[32]; +} udp_client_data; + + +// RS485设备信息 +typedef struct { + int fd; // 串口文件描述符 + char board_id[32]; + uint8_t address; // 从机地址 +} rs485_device_data; + + +typedef struct +{ + uint8_t type_m; // 主设备号 + uint8_t type_s; // 次设备号 + uint8_t reserved1[2]; // 保留 + uint32_t dev_id; // 设备ID + char hostname[FILE_NAME_LEN]; // 设备名 128byte + uint32_t factory_date; // 出厂日期. + uint32_t deployment_date; // 部署日期. + uint8_t app_version[32]; // 软件版本 + uint8_t app_compile_time[32]; // 软件编译时间 + uint8_t hardware_version[32]; // 硬件版本 + uint8_t FPGA_version[32]; // fpga版本 + uint32_t ip; // 本机 IP. + uint32_t mask; // 本机 MASK. + uint32_t gw; // 本机网关 + uint8_t mac[6]; // MAC地址. + uint16_t server_port; // 服务器端口号. + uint32_t server_ipv4; // 服务器 IP. + uint8_t port[8]; + uint8_t port_type[8]; +} dau_info_t; + +// 板卡管理器结构 +typedef struct +{ + //dau_private_data_t *private_data; + + dau_operations_t ops; + + // udp + int sockfd; + struct sockaddr_in addr; + // rs485 + int fd; + char port[20]; + struct termios options; + + char board_id[32]; + void *rx_buffer; + size_t buffer_size; + uint8_t cmd_type; + uint8_t cmd; + + pthread_t thread_id; + int slot; + bool occupied; // 槽位占用标志 + + dau_info_t info; +} dau_manager_t; + +typedef struct { + int slot; + DauType type; + DauState state; + void *private_data; + dau_info_t info; +} dau_t; + +/* Exported macro ------------------------------------------------------------*/ +#define SET_BIT(REG, BIT) ((REG) |= (BIT)) +#define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT)) +#define READ_BIT(REG, BIT) ((REG) & (BIT)) +#define WRITE_REG(REG, VAL) ((REG) = (VAL)) +#define MODIFY_REG(REG, CLEARMASK, SETMASK) WRITE_REG((REG), (((REG) & (~(CLEARMASK))) | (SETMASK))) + +#define DAU_REG_PORT_ADDR_GET(port) ((port + 1) << 8) + +/* Extern global variables ---------------------------------------------------*/ +extern dau_t daus[MAX_SLOTS]; + +/* Extern functions ----------------------------------------------------------*/ +extern int32_t dau_handle_init(void); +extern int32_t dau_handle_init_after(void); +extern int _dau_response(int slot, char *buf, int len); +#endif +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/include/pd_dbg.h b/app/include/pd_dbg.h new file mode 100755 index 0000000..6194dc8 --- /dev/null +++ b/app/include/pd_dbg.h @@ -0,0 +1,243 @@ +/***************************************************************************** + * file include/pd_dbg.h + * author YuLiang + * version 1.0.0 + * date 01-June-2023 + * brief This file provides all the headers of the debug server functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 __PD_DBG_H__ +#define __PD_DBG_H__ + +#ifdef CFG_DEV_TYPE_LAND_PD +/* Includes ------------------------------------------------------------------*/ +#include "pd_main.h" + +/* Define --------------------------------------------------------------------*/ +#define DEBUG_DAU_FILE_UPGRADE "/lib/firmware/system_wrapper.bin.upgrade" +#define DEBUG_DAU_FILE_BAK "./bak/system_wrapper_bak.bin.bak" +#define DEBUG_DAU_FILE "/lib/firmware/system_wrapper.bin" + + +#define DEBUG_BUG_SIZE 1512 +#define DEBUG_CJSON_BUG_SIZE 1512 + +//#define DEBUG_NOISE_CAREFOR (0x0007) // 关注对应通道的底噪原始值, 校准底噪值. +//#define DEBUG_NOISE_POST (0x0008) // 获取底噪原始值, 校准底噪值 服务端主动提交每秒刷新一次. + +enum DebugCommand +{ + DEBUG_CONFIG_FACTORY_GET = 1, // 获取配置 + DEBUG_CONFIG_FACTORY_SET = 2, // 设置配置 + DEBUG_CONFIG_GLOBAL_SET = 3, /* 设备全局参数设置 */ + DEBUG_CONFIG_GLOBAL_GET = 4, /* 设备全局参数获取 */ + DEBUG_CONFIG_PORT_SET = 5, /* 设备端口参数设置 */ + DEBUG_CONFIG_PORT_GET = 6, /* 设备端口参数获取 */ + DEBUG_REBOOT = 7, // 重启 + DEBUG_TIME_SET = 8, // 对时 + + DEBUG_ADJSUT_COEFFICIENT_GET = 9, // 获取校准系数 + DEBUG_ADJSUT_COEFFICIENT_SET = 10, // 设置校准系数 + DEBUG_NOISE_POST = 11, // 自动获取采样值 + DEBUG_NOISE_CAREFOR = 12, // 使能自动获取采样值功能 + + DEBUG_DEVICE_STATUS = 13, // 查询设备状态(后台连接、4G连接) + + + DEBUG_ARM_UPGRADE = 0x0050, // ARM 升级 + DEBUG_FPGA1_UPGRADE = 0x0051, // FPGA 板卡升级 + DEBUG_UPGRADE_ALL = 0x0056 // ARM 和 FPGA 板卡升级 +}; + +#define BITMAP_SAVE_FILE (1 << 0) +#define BITMAP_IP_CHANGE (1 << 1) + +#define DEBUG_MANAGE_TOOL_PORT (10050) +/* Exported types ------------------------------------------------------------*/ +/* 端口校准系数. */ +typedef struct { + uint16_t point[4]; // 通道分段点. + uint16_t param_a[5]; // 通道线性度校准系数 a. + uint16_t param_b[5]; // 通道线性度校准系数 b. + uint16_t alarm_ref; // 断线告警比较值. + uint16_t reserve; +} debug_pkt_adj_vport_t; + +typedef struct { + debug_pkt_adj_vport_t vport[PD_PORT_SUM]; +} debug_pkt_adj_t; + +/* 端口状态, 主要是 ADC 原始值和校准后的值.*/ +typedef struct { + uint16_t value_adc; + int16_t value_adj; +} debug_pkt_port_state_t; + +typedef struct { + debug_pkt_port_state_t vport[PD_PORT_SUM]; +} debug_pkt_port_t; + +/* 设备状态. */ +typedef struct { + uint32_t idx; // 编号. + uint32_t UTC_TimeScale; // UTC 时标 + float F50Hz_Frequency; // 同步 50Hz 频率. + uint8_t F50Hz_SynStatus; // 同步状态. + uint8_t dau_status; // 插件状态. + uint16_t sensor_status; // 传感器断线状态. + uint16_t is_server_link; // 是否连接上后台. + uint16_t version; // 软件版本 + uint32_t communication_time; // 上一次通讯的时间 + uint32_t run_time; // 设备运行时间 +} debug_pkt_status_t; + +/* 报文头. */ +typedef struct{ + uint16_t head; // 0x55aa. + uint16_t cmd; // 命令. + uint32_t len; // 包长. +} debug_pkt_head_t; + +/* 调试工具全局结构 */ +typedef struct{ + int fd; // TCP server 监听使用的 socket. + int fd_client; // TCP client 通讯使用的 socket. + char buf[DEBUG_BUG_SIZE]; // 通讯使用收法包 buffer. + char buf_post[DEBUG_BUG_SIZE]; // 主动上传通讯使用收法包 buffer. + int is_manual_col; // 是否手动采集. +} debug_ctrl_t; + +/* 报文头结构. */ +typedef struct{ + uint16_t head; // 0x55aa. + + uint32_t len; + uint8_t dev_type_m; + uint8_t dev_type_s; + uint16_t dev_id; + uint8_t cmd_type; + uint8_t cmd; + uint16_t pkt_id; + uint8_t reserve[18]; +} dbg_pkt_head_t; + + +/* 基础信息 */ +typedef struct{ + uint8_t type_m; // 主设备号 + uint8_t type_s; // 次设备号 + uint8_t reserved1[2]; // 保留 + uint32_t dev_id; // 设备ID + char hostname[FILE_NAME_LEN]; // 设备名 128byte + uint32_t factory_date; // 出厂日期. + uint32_t deployment_date; // 部署日期. + uint8_t app_version[32]; // 软件版本 + uint8_t app_compile_time[32]; // 软件编译时间 + uint8_t hardware_version[32]; // 硬件版本 + uint8_t FPGA_version[32]; // fpga版本 + uint32_t ip; // 本机(服务器) IP. + uint32_t mask; // 本机(服务器) MASK. + uint32_t gw; // 本机(服务器)网关 + uint8_t mac[6]; // MAC地址. + uint16_t csg_port; // 后台端口号. + uint32_t csg_ipv4; // 后台 IP. + uint32_t running_time; //运行时间 +} dbg_device_info_t; + +/* 全局配置 */ +#pragma pack(push, 1) +typedef struct{ + uint16_t power_frequency; // 工频频率, 单位: 0.1Hz + uint16_t trend_period; // 趋势数据上送周期, 单位: 秒. + uint8_t sync_mode; // 同步方式 1: PT 同步 2: 内同步(默认) 3: 外接信号同步. + uint8_t heartbeat_period; // 心跳包周期, 单位: 分钟. + uint8_t pps_mode; // pps 主从模式 PD_PPS_XXX. + uint8_t reserved[1]; + uint16_t trend_storage; // 趋势存储文件数量阈值. + uint16_t event_storage; // 事件存储文件数量阈值 + uint8_t is_4G_enable; // 是否使用 4G 模块 + char APN[PD_4G_APN_LEN]; // 4G 模块 APN +} dbg_global_config_t; +#pragma pack(pop) + + +/* 端口配置. */ +typedef struct { + uint8_t vport; // 通道编号. + uint8_t port_type; // 采集通道类型 , 1 表示特高频局放 2 表示超声局放 3 表示 TEV 4 表示高频. + uint8_t filter; // 滤波器类型 1: 低频段 2: 全频段 3: 窄频段 4: 高频段 + uint8_t sensor_type; // 0: 无配置; 1: UHF信号传感器; 2: UHF噪声传感器 ; 3: UHF信号传感器, 关联噪声降噪. + uint8_t is_auto_noise; // 是否自动调整降噪等级. + uint8_t denoise_type; // 0-无配置 1-自动 2-手动降噪 + uint8_t reserved0[2]; + + uint32_t event_counter_h; // 事件次数阀值高. + uint16_t event_sec_h; // 事件每秒次数阀值高. + uint16_t event_thr_h; // 事件值阈值高. + uint32_t event_counter_thr_h; // 事件值阈值高的次数. + + uint32_t event_counter_l; // 事件次数阀值低. + uint16_t event_sec_l; // 事件每秒次数阀值低. + uint16_t event_thr_l; // 事件值阈值低. + uint32_t event_counter_thr_l; // 事件值阈值低的次数. + + uint8_t burst_time; // 事件突发计算时间 + uint8_t reserved1[1]; + uint16_t burst_thr; // 事件突发阈值 + + int16_t denoise_manual; // 手动底噪等级 + int16_t denoise_auto; // 自动降噪水平 +}dbg_port_config_t; + +/* 高频通道校准系数 */ +typedef struct { + uint16_t param_a[PD_PORT_SUM]; // 通道线性度校准系数 a. + uint16_t param_b[PD_PORT_SUM]; // 通道线性度校准系数 b. +} debug_hf_pkt_adj_vport_t; + +/* 设备状态. */ +typedef struct { + uint8_t csg_connect_status; // 后台连接状态. + uint8_t ec20_connect_status; // 4g模块连接状态 +} debug_pkt_dev_connect_status_t; + +/* Exported macro ------------------------------------------------------------*/ + +/* Extern global variables ---------------------------------------------------*/ +extern debug_ctrl_t debug_ctrl; + +/* Extern functions ----------------------------------------------------------*/ +extern int32_t debug_handle_init(void); +extern int32_t debug_handle_init_after(void); +extern int32_t debug_pkt_port_state_post(void); +extern void debug_upgrade_result_send(int32_t rv, char *buf); +#endif +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/include/pd_hf.h b/app/include/pd_hf.h new file mode 100755 index 0000000..5863976 --- /dev/null +++ b/app/include/pd_hf.h @@ -0,0 +1,36 @@ +#ifndef __PD_HF_H__ +#define __PD_HF_H__ + +#ifdef CFG_DEV_TYPE_LAND_PD +/* Includes ------------------------------------------------------------------*/ + +typedef struct +{ + uint8_t slot; // 槽位号 + uint8_t type_m; // 主设备号 + uint8_t type_s; // 次设备号 + uint8_t reserved1[1]; // 保留 + uint32_t dev_id; // 设备ID + char hostname[FILE_NAME_LEN]; // 设备名 128byte + uint32_t factory_date; // 出厂日期. + uint32_t deployment_date; // 部署日期. + uint8_t app_version[32]; // 软件版本 + uint8_t app_compile_time[32]; // 软件编译时间 + uint8_t hardware_version[32]; // 硬件版本 + uint8_t FPGA_version[32]; // fpga版本 + uint32_t ip; // 本机 IP. + uint32_t mask; // 本机 MASK. + uint32_t gw; // 本机网关 + uint8_t mac[6]; // MAC地址. + uint16_t server_port; // 服务器端口号. + uint32_t server_ipv4; // 服务器 IP. + uint8_t port[8]; + uint8_t port_type[8]; +} hf_dev_info_t; + + +extern int32_t _hf_recv_process(int slot, char *pkt); +#endif + +#endif + diff --git a/app/include/pd_main.h b/app/include/pd_main.h new file mode 100755 index 0000000..db46328 --- /dev/null +++ b/app/include/pd_main.h @@ -0,0 +1,393 @@ +/***************************************************************************** + * file include/pd_main.h + * author YuLiang + * version 1.0.0 + * date 07-Feb-2023 + * brief This file provides all the headers of the partial discharge functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 __PD_MAIN_H__ +#define __PD_MAIN_H__ + +#ifdef CFG_DEV_TYPE_LAND_PD +/* Includes ------------------------------------------------------------------*/ +#include "cmd.h" + +/* Define --------------------------------------------------------------------*/ +#define PD_DAU_SUM 1 +#define PD_DAU_PORT_SUM 8 +#define PD_PORT_SUM 8 +#define PD_PORT_PROMPT_LEN 64 // DAU 端口节点前标长度. + +#define PD_DEV_NUM_LEN 16 +#define PD_DEV_TYPE_LEN 8 + +#define PD_POWER_FRE_MAX 384 +#define PD_PHASE_NUM 256 +#define PD_VAULE_NUM 256 +#define PD_PRPS_NUM 12800 +#define PD_PRPS_NUM_MAX 98304 +#define PD_EVENT_POINT_MAX 983040 +#define PD_TREND_PHASE_POINT_CNT 256 +#define PD_TREND_POINT_MAX 983040 +#define PD_TREND_ORIGINAL_NUM 65536 +#define PD_PORT_ORIGINAL_NUM 2560000 // 100M采样率, 最大40Hz + +#define PD_4G_APN_LEN 64 + +/* Exported types ------------------------------------------------------------*/ +/* 用于命令行模式节点注册配置保存函数 */ +typedef int pd_port_cmd_save_config_f(vty_t*, uint8_t, uint8_t); + +/* 向服务器发送消息的类型. */ +typedef enum +{ + PD_DEV_TYPE_HF = 1, + PD_DEV_TYPE_UHF +} PD_DEV_TYPE_E; + +/* 向服务器发送消息的类型. */ +typedef enum +{ + PD_SEND_TYPE_PRPS = 1, + PD_SEND_TYPE_TREND, + PD_SEND_TYPE_EVENT, + PD_SEND_TYPE_POINT, + PD_SEND_TYPE_COUNT +} PD_SEND_TYPE_E; + +/* 端口类型. */ +typedef enum +{ + PD_PORT_TYPE_UHF = 1, + PD_PORT_TYPE_AE, + PD_PORT_TYPE_TEV, + PD_PORT_TYPE_HF, + PD_PORT_TYPE_COUNT +} PD_PORT_TYPE_E; + +/* 设备配置的主备类型. */ +typedef enum +{ + PD_DAU_MODE_AUTO = 0, + PD_DAU_MODE_MASTER, + PD_DAU_MODE_SLAVE, + PD_DAU_MODE_COUNT +} PD_DAU_MODE_E; + +/* 设备配置的滤波类型. */ +typedef enum +{ + PD_FILTER_TYPE_HF = 3, + PD_FILTER_TYPE_LF = 6, + PD_FILTER_TYPE_FR = 9, + PD_FILTER_TYPE_FF = 12 +} PD_FILTER_TYPE_E; + +/* 端口类型. */ +typedef enum +{ + PD_SEN_TYPE_NONE = 0, + PD_SEN_TYPE_SIG, + PD_SEN_TYPE_NOISE, + PD_SEN_TYPE_SIG_NOISE, + PD_SEN_TYPE_COUNT +} PD_SEN_TYPE_E; + +/* 端口类型. */ +typedef enum +{ + PD_DENOISE_TYPE_NONE = 0, + PD_DENOISE_TYPE_AOTU, + PD_DENOISE_TYPE_MANUAL, + PD_DENOISE_TYPE_VARIANCE, + PD_DENOISE_TYPE_COUNT +} PD_NOISE_TYPE_E; + +/* 设备配置的滤波类型. */ +typedef enum +{ + PD_SYNC_PT = 1, + PD_SYNC_INSIDE, + PD_SYNC_OUTSIDE +} PD_SYNC_MODE_E; + +/* 设备配置的 PPS 同步模式. */ +typedef enum +{ + PD_PPS_AUTO = 0, + PD_PPS_MASTER, + PD_PPS_SLAVE, +} PD_PPS_MODE_E; + +/* 协议类型. */ +typedef enum +{ + PD_PROTOCOL_LAND = 0, + PD_PROTOCOL_CSG = 1 +} PD_PROTOCOL_TYPE; + +/* 事件类型. */ +typedef enum +{ + PD_EVENT_TYPE_NONE = 0, + PD_EVENT_TYPE_CNTH, // 计数高 + PD_EVENT_TYPE_THRH, // 阈值要 + PD_EVENT_TYPE_THRL, // 计数阈值低 + PD_EVENT_TYPE_BURST, // 突发 + PD_EVENT_TYPE_COUNT +} PD_EVENT_TYPE_E; + +/* 端口节点配置优先级 */ +typedef enum +{ + PD_PORT_CMD_PRI_DAU = 0, + PD_PORT_CMD_PRI_COUNT +} PD_PORT_CMD_PRI_E; + +typedef struct { + uint16_t index; // 点位 + int16_t data; // 数据 +} pd_data_point_t; + +/* 1s prps 数据结构体. */ +typedef struct { + uint16_t fre_cnt; // 工频周期计数. + uint16_t max; // 通道的最大值. + uint16_t avg_o; // 通道原始信号 (降噪前) 的平均值. + uint16_t avg; // 通道脉冲的平均值. + uint32_t cnt; // 通道脉冲的计数值. + uint32_t phase_sum[PD_PHASE_NUM]; // 通道的周波放电相位累加值. + uint16_t phase_max[PD_PHASE_NUM]; // 通道的周波相位最大值. + uint16_t phase_avg[PD_PHASE_NUM]; // 通道的周波相位平均值. + uint16_t phase_cnt[PD_PHASE_NUM]; // 通道的周波相位计数值 + int16_t prps[PD_PRPS_NUM_MAX]; // 1s prps 降噪数据. + pd_data_point_t point[PD_PRPS_NUM_MAX]; // 通道数据. + uint32_t point_cnt; // 通道点数. + uint8_t is_event; // 是否产生事件. + uint8_t is_timing; // 是否产生定时数据. + uint16_t cnt_h; // 高于指定高阈值的脉冲个数. + uint16_t cnt_l; // 高于指定低阈值的脉冲个数. +} pd_prps_data_point_t; + +typedef struct { + uint32_t index; // 数据编号. + uint32_t utc; // UTC 时标. + uint32_t ms; // UTC 时标. + pd_prps_data_point_t data[PD_DAU_SUM][PD_DAU_PORT_SUM]; // 16 个通道的数据. +} pd_prps_point_t; + +typedef struct { + uint8_t vport; // 通道编号. + uint8_t reserved[3]; // 预留. + uint32_t index; // 数据编号: 0 - (2^32-1) 循环. + uint32_t utc; // UTC 时标. + uint16_t max; // 通道的最大值. + int16_t avg_o; // 通道原始信号 (降噪前) 的平均值. + int16_t avg; // 通道的平均值. + int16_t cnt; // 通道的计数值. +} pd_event_old_t; + +typedef struct { + uint32_t power_fre; // 工频周期. + uint8_t data_cnt; // 数据计数 + uint8_t event_cnt; // 上次处理的事件计数 + uint8_t is_sec_h; // 每秒脉冲高触发. + uint8_t is_sec_l; // 每秒脉冲低触发. + uint8_t is_burst; // 是否产生突变. + uint8_t type; // 事件类型. + uint16_t max; // 通道的最大值. + uint32_t index; // 数据编号: 0 - (2^32-1) 循环. + uint32_t utc; // UTC 时标. + uint32_t cnt; // 通道每秒脉冲计数值. + uint32_t cnt_h; // 高于指定高阈值的脉冲个数. + uint32_t cnt_l; // 高于指定低阈值的脉冲个数. + uint32_t point_cnt; // 数据累计点数. + uint64_t avg_o; // 通道原始信号 (降噪前) 的平均值. + uint64_t avg; // 脉冲平均值. + pd_data_point_t point[PD_EVENT_POINT_MAX]; // 事件累计数据. +} pd_event_port_t; + +typedef struct { + pd_event_port_t port[PD_DAU_SUM][PD_DAU_PORT_SUM]; +} pd_event_t; + +typedef struct { + uint32_t trend_sec; // 上次处理的趋势计数 + uint32_t utc; // UTC 时标. + uint16_t data_cnt; // 数据计数. + uint16_t max; // 通道的最大值. + uint64_t avg; // 通道的平均值. + uint32_t cnt; // 通道的计数值. + float phase; // 放电相位 . + float noise; // 趋势数据中的底噪值: 单位 dBm . + uint32_t event_cnt; // 趋势数据中的的事件数量记录. + uint64_t phase_sum[PD_PHASE_NUM]; // 通道的周波放电相位累加值. + uint16_t phase_max[PD_PHASE_NUM]; // 通道的周波相位最大值. + uint16_t phase_avg[PD_PHASE_NUM]; // 通道的周波相位平均值. + uint32_t phase_cnt[PD_PHASE_NUM]; // 通道的周波相位计数值. +} pd_trend_data_t; + +typedef struct { + uint32_t index; // 数据编号: 0 - (2^32-1) 循环. + uint32_t utc; // UTC 时标. + pd_trend_data_t data[PD_DAU_SUM][PD_DAU_PORT_SUM]; // 通道 0 - 15 的计算及测量数据. +} pd_trend_col_t; + +typedef struct { + uint16_t data[PD_PHASE_NUM][PD_VAULE_NUM]; +} pd_trend_prpd_port_t; + +typedef struct { + pd_trend_prpd_port_t port[PD_DAU_SUM][PD_DAU_PORT_SUM]; +} pd_trend_prpd_t; + +typedef struct { + uint32_t point_cnt; // 数据累计点数. + pd_data_point_t point[PD_TREND_POINT_MAX]; +} pd_trend_prps_port_t; + +typedef struct { + pd_trend_prps_port_t port[PD_DAU_SUM][PD_DAU_PORT_SUM]; +} pd_trend_prps_t; + +typedef struct { + uint32_t point[PD_PHASE_NUM]; // 最大值出现的位置 + int16_t data[PD_PHASE_NUM][PD_TREND_PHASE_POINT_CNT]; // 每个相位中有 256 个点 +} pd_trend_original_port_t; + +typedef struct { + pd_trend_original_port_t port[PD_DAU_SUM][PD_DAU_PORT_SUM]; +} pd_trend_original_t; + +typedef struct { + pd_trend_col_t col; + pd_trend_prpd_t *prpd; + pd_trend_prps_t prps; + pd_trend_original_t original; +} pd_trend_t; + +typedef struct { + pd_prps_point_t *real; // 实时数据指针 + pd_event_t *event; // 事件 + pd_trend_col_t trend_col; // 趋势统计数据 + pd_trend_prpd_t *trend_prpd; // 趋势 PRPD + pd_trend_t trend; // 趋势完整数据 +} pd_data_t; + +typedef struct { + uint32_t type; + void *data; +} pd_csg_msg_t; + +typedef struct{ + uint16_t power_frequency; // 工频频率, 单位: 0.1Hz + uint16_t trend_period; // 趋势数据上送周期, 单位: 秒 + uint8_t sync_mode; // 同步方式 1: PT 同步 2: 内同步(默认) 3: 外接信号同步 + uint8_t heartbeat_period; // 心跳包周期, 单位: 分钟 + uint8_t pps_mode; // pps 主从模式 PD_PPS_XXX + uint8_t protocol_type; // 0:朗德协议 1:南网协议 + uint16_t trend_storage; // 趋势存储文件数量阈值 + uint16_t event_storage; // 事件存储文件数量阈值 + uint8_t is_4G_enable; // 是否使用 4G 模块 + char APN[PD_4G_APN_LEN]; // 4G 模块 APN +} pd_config_global_t; + +/* 端口配置. */ +typedef struct { + uint8_t vport; // 通道编号. + uint8_t port_type; // 采集通道类型 , 1 表示特高频局放 2 表示超声局放 3 表示 TEV 4 表示高频. + uint8_t filter; // 滤波器类型 1: 低频段 2: 全频段 3: 窄频段 4: 高频段 + uint8_t sensor_type; // 0: 无配置; 1: UHF信号传感器; 2: UHF噪声传感器 ; 3: UHF信号传感器, 关联噪声降噪. + uint8_t is_auto_noise; // 是否自动调整降噪等级. + uint8_t denoise_type; // 0-无配置 1-自动 2-手动降噪 + uint16_t denoise_variance; // 方差降噪系数, 单位: 1% + + uint32_t event_counter_h; // 事件次数阀值高. + uint16_t event_sec_h; // 事件每秒次数阀值高. + uint16_t event_thr_h; // 事件值阈值高. + uint32_t event_counter_thr_h; // 事件值阈值高的次数. + + uint32_t event_counter_l; // 事件次数阀值低. + uint16_t event_sec_l; // 事件每秒次数阀值低. + uint16_t event_thr_l; // 事件值阈值低. + uint32_t event_counter_thr_l; // 事件值阈值低的次数. + + uint8_t burst_time; // 事件突发计算时间 + uint8_t reserved0[1]; + uint16_t burst_thr; // 事件突发阈值 + + int16_t denoise_manual; // 手动底噪等级 + int16_t denoise_auto; // 自动降噪水平 + uint8_t reserved1[2]; + uint16_t auto_noise_cnt; // 自动调整降噪等级时脉冲计数阈值. +} pd_config_port_t; + +/* 实时波形配置. */ +typedef struct { + uint8_t is_concern; // 是否被关注, 在实时波形中使用. + uint8_t filter_cfg; // 端口配置的滤波类型, 在实时波形中使用. + uint8_t denoise_correlation; // 是否启动关联降噪, 在实时波形中使用. + uint8_t denoise_type; // 0-无配置 1-自动 2-手动降噪 + uint16_t denoise_manual; // 手动降噪等级 +} pd_config_real_t; + +typedef struct { + pd_config_global_t config; // 全局配置 + pd_config_port_t config_port[PD_DAU_SUM][PD_DAU_PORT_SUM]; // 端口配置 + pd_config_real_t config_real[PD_DAU_SUM][PD_DAU_PORT_SUM]; // 实时波形配置 +} pd_config_t; + +typedef struct +{ + uint8_t state; + uint8_t sync; + uint8_t is_4G_connect; +} pd_state_t; + +/* Exported macro ------------------------------------------------------------*/ + +/* Extern global variables ---------------------------------------------------*/ +extern pd_data_t pd_data; +extern pd_config_t pd_config; +extern pd_state_t pd_state; +extern cmd_node_t pd_port_node; + +/* Extern functions ----------------------------------------------------------*/ +extern int32_t pd_main(void); +extern int32_t pd_main_after(void); +extern int32_t pd_port_cmd_config_register(int32_t pri, pd_port_cmd_save_config_f *func); +extern void pd_sync_state_get(void); +extern void pd_pps_mode_set(void); +extern void pd_prps_show(void); +extern void pd_show(void); +#endif +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/include/pd_upgrade.h b/app/include/pd_upgrade.h new file mode 100755 index 0000000..8175c35 --- /dev/null +++ b/app/include/pd_upgrade.h @@ -0,0 +1,104 @@ +/***************************************************************************** + * file include/pd_upgrade.h + * author Wangbo + * version 1.0.0 + * date 07-Feb-2023 + * brief This file provides all the headers of the softwave upgrade functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2024 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 __PD_UPGRADE_H__ +#define __PD_UPGRADE_H__ + +#ifdef CFG_DEV_TYPE_LAND_PD +/* Includes ------------------------------------------------------------------*/ + +/* Define --------------------------------------------------------------------*/ +#define PD_UPG_MAGICNUM 0xEACFA6A6 +#define PD_UPG_AREA_MAX 2 // 最多升级区域 +#define PD_UPG_VERSION_LEN 16 +#define PD_UPG_BLOCK_SIZE (128*1024) +#define PD_UPG_SOFTWARE "upgrade.sw" +#define PD_UPG_CMU_FILE "PDMonitor" +#define PD_UPG_CMU_FILE_BAK "PDMonitor.bak" + +#define PD_UPG_SEL_ALL 0 +#define PD_UPG_SEL_CMU 1 +#define PD_UPG_SEL_DAU 2 +#define PD_UPG_SEL_MAX 3 + +/* Exported types ------------------------------------------------------------*/ +enum PD_UPG_TYPE +{ + PD_UPG_TYPE_CMU, + PD_UPG_TYPE_DAU, + PD_UPG_TYPE_MAX, +}; + +enum PD_UPG_FROM +{ + PD_UPG_FROM_CSG, + PD_UPG_FROM_DBG, + PD_UPG_FROM_MAX, +}; + +/*升级文件头*/ +typedef struct +{ + uint32_t magic; // 结构体幻数 PD_UPG_MAGICNUM + uint32_t crc; // 这里的 CRC 值只包括程序数据的 CRC 校验值 (不包含头) + uint32_t version; // 结构体版本 + char hard_ver[PD_UPG_VERSION_LEN]; // hardware version + struct + { + char version[PD_UPG_VERSION_LEN]; + uint32_t type; // PD_UPG_TYPE_XXX + uint32_t start; // 文件内的偏移量 + uint32_t len; // 升级数据的长度 + } arr_info[PD_UPG_AREA_MAX]; // 分段信息 +} pd_upg_head_t; + +typedef struct +{ + uint8_t is_start; // 是否开始升级 + uint8_t upg_from; // 是谁在升级 PD_UPG_FROM_xxx + uint8_t upg_type; // 升级类型 PD_UPG_TYPE_xxx + pd_upg_head_t head; // 报文头信息 + void (*upgrade_result)(int32_t rv, char *msg); // 升级结果回调函数 + char msg[128]; // 返回结果字符串 +} pd_upg_ctrl_t; + +/* Exported macro ------------------------------------------------------------*/ + +/* Extern global variables ---------------------------------------------------*/ + +/* Extern functions ----------------------------------------------------------*/ +extern int pd_upg_start(uint8_t from, uint8_t type); +#endif +#endif diff --git a/app/include/sockunion.h b/app/include/sockunion.h new file mode 100644 index 0000000..f9aebbf --- /dev/null +++ b/app/include/sockunion.h @@ -0,0 +1,230 @@ +/****************************************************************************** + * file include/sockunion.h + * author YuLiang + * version 1.0.0 + * date 09-Oct-2021 + * brief This file provides all the headers of the sockunion functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _SOCKUNION_H_ +#define _SOCKUNION_H_ + +/* Includes ------------------------------------------------------------------*/ + +/* Define --------------------------------------------------------------------*/ +#define IPV4_MAX_BITLEN 32 +#define IPV6_MAX_BITLEN 128 + +#define PNBBY 8 +#define AFI_IP 1 +#define AFI_IP6 2 +#define AFI_MAX 3 + +#define SU_ADDRSTRLEN 46 +/* Default address family. */ +#ifdef HAVE_IPV6 +#define AF_INET_UNION AF_INET6 +#else +#define AF_INET_UNION AF_INET +#endif + +/* Exported types ------------------------------------------------------------*/ +/* IPv4 and IPv6 unified prefix structure. */ +typedef struct _prefix +{ + uint8_t family; + uint8_t prefixlen; + union + { + uint8_t prefix; + struct in_addr prefix4; +#ifdef HAVE_IPV6 + struct in6_addr prefix6; +#endif /* HAVE_IPV6 */ + struct + { + struct in_addr id; + struct in_addr adv_router; + } lp; + uint8_t val[8]; + } u __attribute__ ((aligned (8))); +} prefix_t; + +/* IPv4 prefix structure. */ +typedef struct _prefix_ipv4 +{ + uint8_t family; + uint8_t prefixlen; + struct in_addr prefix __attribute__ ((aligned (8))); +} prefix_ipv4_t; + +/* IPv6 prefix structure. */ +#ifdef HAVE_IPV6 +typedef struct _prefix_ipv6 +{ + uint8_t family; + uint8_t prefixlen; + struct in6_addr prefix __attribute__ ((aligned (8))); +} prefix_ipv6_t; +#endif /* HAVE_IPV6 */ + +typedef enum +{ + ACCESS_TYPE_STRING, + ACCESS_TYPE_NUMBER +} ACCESS_TYPE_E; + +/* Filter type is made by `permit', `deny' and `dynamic'. */ +typedef enum +{ + FILTER_DENY, + FILTER_PERMIT, + FILTER_DYNAMIC +} FILTER_TYPE_E; + +/* List of access_list. */ +typedef struct +{ + struct _access_list *head; + struct _access_list *tail; +} access_list_list_t; + +/* Master structure of access_list. */ +typedef struct +{ + /* List of access_list which name is number. */ + access_list_list_t num; + + /* List of access_list which name is string. */ + access_list_list_t str; + + /* Hook function which is executed when new access_list is added. */ + void (*add_hook)(struct _access_list *); + + /* Hook function which is executed when access_list is deleted. */ + void (*delete_hook)(struct _access_list *); +} access_master_t; + +typedef struct +{ + /* Cisco access-list */ + int extended; + struct in_addr addr; + struct in_addr addr_mask; + struct in_addr mask; + struct in_addr mask_mask; +} filter_cisco_t; + +typedef struct +{ + /* If this filter is "exact" match then this flag is set. */ + int exact; + + /* Prefix information. */ + prefix_t prefix; +} filter_zebra_t; + +/* Filter element of access list */ +typedef struct _filter +{ + /* For doubly linked list. */ + struct _filter *next; + struct _filter *prev; + + /* Filter type information. */ + FILTER_TYPE_E type; + + /* Cisco access-list */ + int cisco; + + union + { + filter_cisco_t cfilter; + filter_zebra_t zfilter; + } u; +} filter_t; + +/* Access list */ +typedef struct _access_list +{ + char *name; + char *remark; + + access_master_t *master; + + ACCESS_TYPE_E type; + + struct _access_list *next; + struct _access_list *prev; + + filter_t *head; + filter_t *tail; +} access_list_t; + +typedef union +{ + struct sockaddr sa; + struct sockaddr_in sin; +#ifdef HAVE_IPV6 + struct sockaddr_in6 sin6; +#endif /* HAVE_IPV6 */ +} SOCKUNION_U; + +/* Exported macro ------------------------------------------------------------*/ + +/* Extern global variables ---------------------------------------------------*/ + +/* Extern functions ----------------------------------------------------------*/ +extern void prefix_free(prefix_t *p); +extern prefix_ipv4_t *prefix_ipv4_new(void); +#ifdef HAVE_IPV6 +extern prefix_ipv6_t *prefix_ipv6_new(void); +#endif /* HAVE_IPV6 */ + +extern access_list_t *access_list_lookup(uint16_t afi, const char *name); +extern FILTER_TYPE_E access_list_apply(access_list_t *access, void *object); +extern void masklen2ip(int masklen, struct in_addr *netmask); +extern int prefix_match(const prefix_t *n, const prefix_t *p); + +extern int str2sockunion(const char *str, SOCKUNION_U *su); +extern const char *sockunion2str(SOCKUNION_U *su, char *buf, size_t len); +extern int sockunion_accept(int sock, SOCKUNION_U *su); +extern int set_nonblocking(int fd); +extern prefix_t *sockunion2hostprefix(const SOCKUNION_U *su); +extern char *sockunion_su2str(SOCKUNION_U *su); +extern int sockunion_stream_socket(SOCKUNION_U *su); +extern int sockunion_reuseaddr(int sock); +extern int sockunion_bind(int sock, SOCKUNION_U *su, unsigned short port, SOCKUNION_U *su_addr); +extern int sockunion_ip_set(char *name, unsigned int addr); +extern int sockunion_mask_set(char *name, unsigned int mask); +extern int sockunion_gw_set(char *name, unsigned int gateway, unsigned int gateway_old); + +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/include/thread_monitor.h b/app/include/thread_monitor.h new file mode 100644 index 0000000..d12c949 --- /dev/null +++ b/app/include/thread_monitor.h @@ -0,0 +1,64 @@ +/***************************************************************************** + * file include/thread_monitor.h + * author YuLiang + * version 1.0.0 + * date 08-Oct-2021 + * brief This file provides all the headers of the thread monitor functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 __THREAD_M_H__ +#define __THREAD_M_H__ + +/* Includes ------------------------------------------------------------------*/ +#include + +/* Define --------------------------------------------------------------------*/ +#define THREAD_M_NAME_LEN 32 + +/* Exported types ------------------------------------------------------------*/ +/* 线程监控结构体. */ +typedef struct _thread_m_t +{ + char name[THREAD_M_NAME_LEN]; /* 线程名字 */ + pthread_t pid; /* 线程pid */ + int8_t alive; /* 是否活着 */ +} thread_m_t; + +/* Extern global variables ---------------------------------------------------*/ + +/* Exported macro ------------------------------------------------------------*/ + +/* Exported functions --------------------------------------------------------*/ +extern int32_t thread_m_add(char *str, pthread_t pid); +extern int32_t thread_m_init(void); + +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ + diff --git a/app/include/typedef.h b/app/include/typedef.h new file mode 100644 index 0000000..74aa491 --- /dev/null +++ b/app/include/typedef.h @@ -0,0 +1,154 @@ +/************************************************* + Copyright (C), 2023-- + 文件名: typedef.h + 作 者: wangbo + 日 期: 2023-08-24 + + 描 叙: 本文件主要提供基本的 类型申明 + 函数列表: 无 + 历史记录: + 1: + 日期 : 2023-08-24 + 作者 : wangbo + 描叙 : 新创建文件 + + +*************************************************/ +#ifndef _TPYEDEF_H +#define _TPYEDEF_H +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif +#endif /* __cplusplus */ + +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include // flock函数 + +/************************************************************************ +网络相关 +************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // tcp keepalive +#include +#include +#include +#include +#include +#include + +#define ZERO_INIT {} +#define NFDS_FOR_SELECT(sock) ((sock) + 1) + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef SUCCESS +#define SUCCESS 0 +#endif + +#ifndef FAILURE +#define FAILURE -1 +#endif + +// 整型环境, 内核使用的类型 +typedef char s8; +typedef unsigned char u8; +typedef short s16; +typedef unsigned short u16; +typedef int s32; +typedef unsigned int u32; +#if defined WIN32 +typedef __int64 s64; +typedef unsigned __int64 u64; +#else +typedef long long s64; +typedef unsigned long long u64; +#endif + +typedef int TIMER_T; +typedef int socket_t; +typedef pid_t ThreadId_T; +typedef pid_t ProcessId_T; /// < 进程id +typedef pthread_t Thread_T; +typedef pid_t Process_T; +typedef void * ThreadResult_T; +typedef void * ThreadParam_T; +typedef unsigned int TimeTick_T; +typedef int DevHandle_T; +typedef int Semaphore_T; + +#define ThreadProcSpec +#define INVALID_THREAD (Thread_T)(~0) +#define INVALID_SOCKET (~0) +#define INVALID_TIMER (-1) + +typedef pthread_mutex_t Mutex_T; + + +/// @brief 线程函数指针类型 +typedef ThreadResult_T (ThreadProcSpec* ThreadProc_T)(ThreadParam_T param); + +// max / min +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a) > (b) ? (b) : (a)) +#endif + + +#ifndef setbitEx +#ifndef NBBYTE //the BSD family defines NBBY +#define NBBYTE 8 //8 bits per byte +#endif +#define setbit(a, i) (((unsigned char *)a)[(i)/NBBYTE] |= 1<<((i)%NBBYTE)) +#define clrbit(a, i) (((unsigned char *)a)[(i)/NBBYTE] &= ~(1<<((i)%NBBYTE))) +#define isset(a, i) (((const unsigned char *)a)[(i)/NBBYTE] & (1<<((i)%NBBYTE))) +#define isclr(a, i) ((((const unsigned char *)a)[(i)/NBBYTE] & (1<<((i)%NBBYTE))) == 0) +#endif + +#ifdef __cplusplus +#if __cplusplus +} +#endif +#endif /* __cplusplus */ + +#endif /*_TPYEDEF_H*/ + + diff --git a/app/include/vty.h b/app/include/vty.h new file mode 100644 index 0000000..661ae73 --- /dev/null +++ b/app/include/vty.h @@ -0,0 +1,331 @@ +/****************************************************************************** + * file include/vty.h + * author YuLiang + * version 1.0.0 + * date 10-Sep-2021 + * brief This file provides all the headers of the vty functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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 _VTY_H_ +#define _VTY_H_ + +/* Includes ------------------------------------------------------------------*/ +#include + +#include "array.h" + +/* Define --------------------------------------------------------------------*/ +/* Thread types. */ +#define THREAD_READ 0 +#define THREAD_WRITE 1 +#define THREAD_TIMER 2 +#define THREAD_EVENT 3 +#define THREAD_READY 4 +#define THREAD_BACKGROUND 5 +#define THREAD_UNUSED 6 +#define THREAD_PERFORM 7 + +/* Struct timeval's tv_usec one second value. */ +#define TIMER_SECOND_MICRO 1000000L + +#define RUSAGE_T rusage_t +#define GETRUSAGE(X) thread_getrusage(X) + +#define THREAD_ADD_READ(m,f,a,v) thread_add_read(m,f,a,v,#f) +#define THREAD_ADD_WRITE(m,f,a,v) thread_add_write(m,f,a,v,#f) +#define THREAD_ADD_TIMER(m,f,a,v) thread_add_timer(m,f,a,v,#f) +#define THREAD_ADD_TIMER_MSEC(m,f,a,v) thread_add_timer_msec(m,f,a,v,#f) +#define THREAD_ADD_EVENT(m,f,a,v) thread_add_event(m,f,a,v,#f) +#define THREAD_EXECUTE(m,f,a,v) thread_execute(m,f,a,v,#f) + +/* Macros. */ +#define THREAD_ARG(X) ((X)->arg) +#define THREAD_FD(X) ((X)->u.fd) +#define THREAD_VAL(X) ((X)->u.val) + +#define VTY_BUFSIZ 512 +#define VTY_TMP_BUFSIZ 1024 +#define VTY_MAXHIST 20 +#define TELNET_NAWS_SB_LEN 5 + +#define VTY_TIMEOUT_VAL 600 + +/* Vty read buffer size. */ +#define VTY_READ_BUFSIZ 512 + +/* Small macro to determine newline is newline only or linefeed needed. */ +#define VTY_NEWLINE ((vty->type == VTY_TERM || vty->type == VTY_CMD) ? "\r\n" : "\n") + +#define CONTROL(X) ((X) - '@') +#define VTY_NO_ESCAPE 0 +#define VTY_PRE_ESCAPE 1 +#define VTY_ESCAPE 2 + +#define VTY_USERNAME_LEN 32 +#define VTY_USERNAME_DEFAULT "ld" +#define VTY_PASSWORD_DEFAULT "1" + +/* Exported types ------------------------------------------------------------*/ +/* buffer数据结构体. 必须保证: 0 <= sp <= cp <= size. */ +typedef struct _buf_data +{ + struct _buf_data *next; /* 数据链表. */ + size_t cp; /* 当前可以添加的数据索引. */ + size_t sp; /* 等待数据发送的数据索引. */ + unsigned char data[]; /* 数据空间. */ +} buf_data_t; + +/* Buffer结构体. */ +typedef struct _buf +{ + buf_data_t *head; /* 数据块头. */ + buf_data_t *tail; /* 数据块尾. */ + size_t size; /* 每个数据块的大小. */ +} buf_t; + +typedef int hash_key_f(void*); +typedef void* hash_alloc_f(void *); +typedef int hash_cmp_f(const void*, const void*); + +typedef struct _hash_backet +{ + struct _hash_backet *next; /* Linked list. */ + unsigned int key; /* Hash key. */ + void *data; /* Data. */ +} hash_backet_t; + +typedef struct _hash +{ + hash_backet_t **index; /* Hash backet. */ + unsigned int size; /* Hash table size. */ + hash_key_f *hash_key; /* Key make function. */ + hash_cmp_f *hash_cmp; /* Data compare function. */ + unsigned long count; /* Backet alloc. */ +} hash_t; + +typedef unsigned char thread_type; + +typedef struct _rusage +{ + struct rusage cpu; + struct timeval real; +} rusage_t; + +typedef struct _time_stats +{ + unsigned long total, max; +} time_stats_t; + +typedef struct _cpu_thread_history +{ + int (*func)(void*); + char *funcname; + unsigned int total_calls; + time_stats_t real; + time_stats_t cpu; + thread_type types; +} cpu_thread_history_t; + + + +/* Linked list of thread. */ +typedef struct _thread_list +{ + struct _thread *head; + struct _thread *tail; + int count; +} thread_list_t; + +/* Master of the theads. */ +typedef struct _thread_master +{ + thread_list_t read; + thread_list_t write; + thread_list_t timer; + thread_list_t event; + thread_list_t ready; + thread_list_t unuse; + thread_list_t background; + fd_set readfd; + fd_set writefd; + fd_set exceptfd; + unsigned long alloc; +} thread_master_t; + +/* Thread itself. */ +typedef struct _thread +{ + thread_type type; /* thread type */ + thread_type add_type; /* thread type */ + struct _thread *next; /* next pointer of the thread */ + struct _thread *prev; /* previous pointer of the thread */ + thread_master_t *master; /* pointer to the struct thread_master. */ + int (*func)(struct _thread*); /* event function */ + void *arg; /* event argument */ + union + { + int val; /* second argument of the event. */ + int fd; /* file descriptor in case of read/write. */ + struct timeval sands; /* rest of time sands value. */ + } u; + RUSAGE_T ru; /* Indepth usage info. */ + cpu_thread_history_t *hist; /* cache pointer to cpu_history */ + char* funcname; +} thread_t; + +typedef int thread_func_f(thread_t*); + +typedef enum {VTY_TERM, VTY_FILE, VTY_SHELL, VTY_SHELL_SERV, VTY_CMD} VTY_TYPE_E; +typedef enum {VTY_NORMAL, VTY_CLOSE, VTY_MORE, VTY_MORELINE} VTY_STAT_E; + +/* Vty events */ +typedef enum +{ + VTY_SERV, + VTY_READ, + VTY_WRITE, + VTY_TIMEOUT_RESET, +#if 0 + VTYSH_SERV, + VTYSH_READ, + VTYSH_WRITE +#endif /* VTYSH */ +} VTY_EVENT_E; + +/* 用户结构体 */ +typedef struct +{ + char username[VTY_USERNAME_LEN]; + char password[VTY_USERNAME_LEN]; + uint8_t level; /* 用户等级 */ +} vty_user_t; + +/* VTY struct. */ +typedef struct +{ + int fd; /* File descripter of this vty. */ + VTY_TYPE_E type; /* Is this vty connect to file or not */ + uint32_t node; /* Node status of this vty */ + char *address; /* What address is this vty comming from. */ + uint32_t fail_count; /* Failure count */ + + buf_t *out_buf; /* Output buffer. */ + char *buf; /* Command input buffer */ + uint32_t cp; /* Command cursor point */ + uint32_t length; /* Command length */ + uint32_t max; /* Command max length. */ + + char *hist[VTY_MAXHIST]; /* Histry of command */ + uint32_t hp; /* History lookup current point */ + uint32_t hindex; /* History insert end point */ + + void *index; /* For current referencing point of interface, route-map, access-list etc... */ + void *index_sub; /* For multiple level index treatment such as key chain and key. */ + unsigned char escape; /* For escape character. */ + VTY_STAT_E status; /* Current vty status. */ + + /* IAC handling: was the last character received the + IAC (interpret-as-command) escape character (and therefore the next + character will be the command code)? Refer to Telnet RFC 854. */ + unsigned char iac; + unsigned char iac_sb_in_progress; /* IAC SB (option subnegotiation) handling */ + + /* At the moment, we care only about the NAWS (window size) negotiation, + and that requires just a 5-character buffer (RFC 1073): + <16-bit width> <16-bit height> */ + unsigned char sb_buf[TELNET_NAWS_SB_LEN]; + /* How many subnegotiation characters have we received? We just drop + those that do not fit in the buffer. */ + size_t sb_len; + + uint32_t width; /* Window width/height. */ + uint32_t height; + + int32_t lines; /* Configure lines. */ + int32_t monitor; /* Terminal monitor. */ + + int config; /* In configure mode. */ + + /* Read and write thread. */ + thread_t *t_read; + thread_t *t_write; + + /* Timeout seconds and thread. */ + unsigned long v_timeout; + thread_t *t_timeout; + + /* Timeout seconds and thread. */ + vty_user_t user; +} vty_t; + +/* Exported macro ------------------------------------------------------------*/ + +/* Extern global variables ---------------------------------------------------*/ + +/* Extern functions ----------------------------------------------------------*/ +extern void *hash_get(hash_t *hash, void *data, hash_alloc_f alloc_func); + +extern unsigned long thread_consumed_time(RUSAGE_T *now,RUSAGE_T *start, unsigned long *cputime); +extern thread_master_t *thread_master_create(); +extern void thread_call(thread_t *thread); +extern thread_t *thread_add_read(thread_master_t *m, thread_func_f *func, void *arg, int fd, const char* funcname); +extern thread_t *thread_add_write(thread_master_t *m, thread_func_f *func, void *arg, int fd, const char* funcname); +extern thread_t *thread_add_timer(thread_master_t *m, thread_func_f *func, void *arg, long timer, const char* funcname); +extern thread_t *thread_add_timer_msec(thread_master_t *m, thread_func_f *func, void *arg, long timer, const char* funcname); +extern thread_t *thread_add_background(thread_master_t *m, thread_func_f *func, void *arg, long delay, const char *funcname); +extern thread_t *thread_add_event(thread_master_t *m, thread_func_f *func, void *arg, int val, const char* funcname); +extern thread_t *thread_execute(thread_master_t *m, thread_func_f *func, void *arg, int val, const char* funcname); +extern void thread_cancel(thread_t *thread); +extern thread_t *thread_fetch(thread_master_t *m, thread_t *fetch); + +extern int vty_out(vty_t *vty, const char *format, ...); +extern vty_t *vty_create(); +extern int vty_execute(vty_t *vty); +extern int vty_config_lock(vty_t *vty); +extern int vty_config_unlock(vty_t *vty); +extern void vty_question(vty_t *vty, array_t *cmd_line); +extern void vty_print_word(vty_t *vty, char *strs[]); +extern void vty_free_match_strs(char *match_strs[]); +extern void vty_will_echo(vty_t *vty); +extern void vty_will_suppress_go_ahead(vty_t *vty); +extern void vty_dont_linemode(vty_t *vty); +extern void vty_do_window_size(vty_t *vty); +extern void vty_prompt(vty_t *vty); +extern void vty_close(vty_t *vty); +extern void vty_init(void); +extern void vty_event(VTY_EVENT_E event, int sock, vty_t *vty); +extern void vty_log(const char *level, const char *proto_str, const char *format, char *time_str, va_list va); +extern void vty_print(const char *format, va_list va); +extern void vty_serv_sock_family(const char* addr, unsigned short port, int family); +extern void vty_reset(void); +extern void vty_version_print(vty_t *vty); + +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/a_process/main.c b/app/lib/a_process/main.c new file mode 100755 index 0000000..2e46ced --- /dev/null +++ b/app/lib/a_process/main.c @@ -0,0 +1,339 @@ +/***************************************************************************** + * file lib/process/main.c + * author YuLiang + * version 1.0.0 + * date 26-Sep-2021 + * brief This file provides all the main related operation functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* 标准C库头文件. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cmd.h" +#include "mtimer.h" +#include "main.h" +#include "hwgpio.h" +#include "fifo.h" + +#include "pd_main.h" +#include "hwgpio.h" + +/* Private typedef -----------------------------------------------------------*/ + +/* Private define ------------------------------------------------------------*/ + +/* Private macro -------------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +int32_t recv_qid; +uint16_t version_hex; +uint32_t start_time; + +static int8_t is_system_reboot; + +static reboot_msg_t _reboot_msg[] = +{ + {REBOOT_NONE, "Reboot none."}, + {REBOOT_LOCAL_SERVER_IP_CHANGE, "Reboot by debug tool has changed server."}, + {REBOOT_LOCAL_IP_CHANGE, "Reboot by debug tool has changed device ip."}, + {REBOOT_LOCAL_HOST_NAME_CHANGE, "Reboot by debug tool has changed device id."}, + {REBOOT_LOCAL_RESET, "Reboot by debug tool reset."}, + {REBOOT_LOCAL_ARM_UPGRADE, "Reboot by debug tool softwave upgrade."}, + {REBOOT_REMOTE_SERVER_IP_CHANGE, "Reboot by remote has changed server."}, + {REBOOT_REMOTE_IP_CHANGE, "Reboot by remote has changed device ip."}, + {REBOOT_REMOTE_HOST_NAME_CHANGE, "Reboot by remote has changed device id."}, + {REBOOT_REMOTE_RESET, "Reboot by remote reset."}, + {REBOOT_UPGRADE_ALL, "Reboot by softwave upgrade."}, + {REBOOT_SYSTEM_RESET, "Reboot by command."}, + {REBOOT_4G_ERROR, "Reboot by 4G error."}, + {REBOOT_NET_ERROR, "Reboot by CSG platform connection time out."}, + {REBOOT_MAX, NULL} +}; + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +/* 信号处理函数 */ +void _signal_handler(int sig) +{ + if (SIGSEGV == sig) + { + log_backtrace(LOG_LVL_ERR); + } + //else if(SIGINT == sig + // || SIGTSTP == sig) + //{ + /* 屏蔽信号 */ + // return; + //} + + exit(-1); +} + +/* description: 设备重启函数 + param: module -- 记录在 哪个log 模块 + type -- 重启原因 + return: */ +void reboot_system(int module, REBOOT_MSG type) +{ + char *pmsg = NULL; + + is_system_reboot = TRUE; + + for (REBOOT_MSG i = REBOOT_NONE; i < REBOOT_MAX; i++) + { + if (_reboot_msg[i].type == type) + { + pmsg = _reboot_msg[i].msg; + break; + } + } + + if (pmsg) + { + log_out(module, LOG_LVL_WARN, pmsg); + system("sync"); + } + + sleep(3); + system("reboot -f"); +} + +//通过RTC-pcf8563系统驱动读写寄存器 + +int32_t rtc_time_set(struct tm tm) +{ + int rtc_fd; + struct tm rtc_tm = tm; + + // 打开 RTC 设备 + rtc_fd = open("/dev/rtc0", O_RDWR); + if (rtc_fd == -1) { + DBG(DBG_M_DBG, "Unable to open RTC device\n"); + return -1; + } + if (ioctl(rtc_fd, RTC_SET_TIME, &rtc_tm) == -1) { + DBG(DBG_M_DBG, "Unable to set RTC time\n"); + close(rtc_fd); + return -1; + } + + DBG(DBG_M_DBG, "RTC time set successfully\n"); + + close(rtc_fd); + return 0; +} + +int32_t rtc_time_get(struct tm tm) +{ + time_t timestamp; + // 定义存储时间的结构体 + struct tm rtc_tm; + memset(&rtc_tm, 0, sizeof(struct rtc_time)); + + //DBG(DBG_M_DBG, "rtc_time_get start!!!!!!!!\r\n"); + int fd = open("/dev/rtc0", O_RDONLY); + if (fd == -1) { + DBG(DBG_M_DBG, "打开设备文件失败 errno: %d\r\n", errno); + char * mesg = strerror(errno); + printf("Mesg:%s\n",mesg); + return -1; + } + // 使用 ioctl 调用 RTC_RD_TIME 命令读取时间 + if (ioctl(fd, RTC_RD_TIME, &rtc_tm) == -1) { + DBG(DBG_M_DBG, "读取时间失败errno: %d\r\n", errno); + char * mesg = strerror(errno); + printf("Mesg:%s\n",mesg); + close(fd); + return -1; + } else { + memcpy(&tm, &rtc_tm, sizeof(rtc_tm)); + // 转换为 Unix 时间戳 + timestamp = mktime(&rtc_tm); + if (timestamp == -1) { + DBG(DBG_M_DBG, "make Unix time failed\n"); + } + + // 输出读取到的时间 + #if 0 + DBG(DBG_M_DBG, "当前时间: %04d-%02d-%02d %02d:%02d:%02d\n", + rtc_tm.tm_year + 1900, rtc_tm.tm_mon + 1, rtc_tm.tm_mday, + rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); + //DBG(DBG_M_DBG, "Unix timestamp: %ld\n", timestamp); + #endif + } + // 关闭设备文件 + close(fd); + + return 0; +} + +/* description: 数据处理预初始化. + param: + return: (E_NONE)成功,(其他)失败 */ +int32_t process_init(void) +{ + int32_t rv = E_NONE; + + /* 创建消息队列. */ + if ((recv_qid = msgget(0x4321, IPC_CREAT | 0666)) == -1) + { + log_err(LOG_DEFAULT, "message ERROR at msgget return %s!", safe_strerror(errno)); + } + + /* 清空消息队列. */ + msgctl(recv_qid, IPC_RMID, NULL); + if ((recv_qid = msgget(0x4321, IPC_CREAT | 0666)) == -1) + { + log_err(LOG_DEFAULT, "message ERROR at msgget return %s!", safe_strerror(errno)); + } + + /* 初始化局放应用. */ + rv |= pd_main(); + + return rv; +} + +/* description: 数据处理初始化. + param: + return: (E_NONE)成功,(其他)失败 */ +int32_t process_init_after(void) +{ + int32_t rv = E_NONE; + + rv = pd_main_after(); + + return rv; +} + +/* description: 程序入口函数. + param: + return: */ +int32_t main(int32_t argc, char **argv) +{ + struct sigaction act; + uint32_t cnt = 0; + + /* 设置本地化信息为默认值. */ + setlocale(LC_ALL, ""); + + /* 必须最前面, 初始化内存管理模块的基本参数. */ + mtype_init_befor(); + + /* log初始化 */ + log_open(); + + log_out(LOG_DEFAULT, LOG_LVL_WARN, "System start!"); +#if 0 + system("/etc/ppp/peers/quectel-ppp-kill"); + sleep(2); +#endif + + /* 设置信号处理的回调函数 */ + act.sa_handler = _signal_handler; + sigemptyset(&act.sa_mask); + sigaction(SIGINT, &act, NULL); + sigaction(SIGSEGV, &act, NULL); + sigaction(SIGTSTP, &act, NULL); + + /* 初始化cli等公共模块. */ + cmd_init(); + thread_m_init(); + mtype_init(); + dbg_init(); + mtimer_init(); + vtysh_init(); + vtycmd_init(); + gpio_init(); + fifo_init(); + log_handle_init(); + + /* 主处理函数预初始化 */ + process_init(); + + /* 配置恢复命令行启动 */ + vtysh_config_recovery(); + + /* 主处理函数初始化 */ + process_init_after(); + + /* 启动命令行 */ + vtysh_shell_init(); + vtycmd_cmd_init(); + + /* 初始化完成 */ + version_hex = version_str_to_int(); + is_system_init = TRUE; + +#if 1 + struct tm rtc_tm; + // 设置当前时间(设置为2025年2月24日 12:00:00) + rtc_tm.tm_year = 2025 - 1900; // 年份需要减去1900 + rtc_tm.tm_mon = 1; // 2月 + rtc_tm.tm_mday = 24; // 24号 + rtc_tm.tm_wday = 1; //星期一 + rtc_tm.tm_hour = 12; + rtc_tm.tm_min = 5; + rtc_tm.tm_sec = 30; + rtc_time_set(rtc_tm); +#endif + + /* 主循环, 点灯喂狗. */ + for(;;) + { + sleep(1); + start_time++; + cnt++; + /* 喂狗. */ + if (0 == (cnt & 0x1F)) + { + feed_dog(); + //rtc_time_get(rtc_tm); + } + } + + return 0; +} +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/lib/a_process/module.mk b/app/lib/a_process/module.mk new file mode 100644 index 0000000..d9f837b --- /dev/null +++ b/app/lib/a_process/module.mk @@ -0,0 +1,3 @@ +local_src := $(patsubst $(SOURCE_DIR)/%,%,$(wildcard $(SOURCE_DIR)/$(subdirectory)/*.c)) + +$(eval $(call make-library,$(subdirectory)/liba_process.a,$(local_src))) \ No newline at end of file diff --git a/app/lib/a_process/pd_ae.c b/app/lib/a_process/pd_ae.c new file mode 100644 index 0000000..e69de29 diff --git a/app/lib/a_process/pd_csg.c b/app/lib/a_process/pd_csg.c new file mode 100755 index 0000000..8d4e770 --- /dev/null +++ b/app/lib/a_process/pd_csg.c @@ -0,0 +1,955 @@ +/****************************************************************************** + * file lib/process/pd_csg.c + * author YuLiang + * version 1.0.0 + * date 27-Feb-2023 + * brief This file provides all the csg server operation functions. + * + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef CFG_DEV_TYPE_LAND_PD +/* 标准C库头文件. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 用户代码头文件. */ +#include "main.h" +#include "cmd.h" +#include "fifo.h" +#include "pd_main.h" +#include "pd_csg.h" +#include "pd_upgrade.h" +#include "pd_dau.h" + +/* Private define ------------------------------------------------------------*/ + +/* Private macro -------------------------------------------------------------*/ + +/* Private typedef -----------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +csg_t csg; + +/* Private function prototypes -----------------------------------------------*/ +extern void _csg_server_set(int32_t ip, uint16_t port); +void _csg_show(); + +/* Internal functions --------------------------------------------------------*/ +/* 服务器地址设置 */ +CMD(csg_server_set, + csg_server_set_cmd, + "csg server A.B.C.D <1-65535>", + "Csg\n" + "Server\n" + "IPv4 address\n" + "UDP port\n") +{ + _csg_server_set(inet_addr((char*)argv[0]), strtol((char*)argv[1], NULL, 10)); + + return CMD_SUCCESS; +} + +/* 显示模块状态 */ +CMD(csg_show, + csg_show_cmd, + "show csg", + "Show\n" + "CSG\n") +{ + _csg_show(); + + return CMD_SUCCESS; +} + +void _print_sockaddr_in(const struct sockaddr_in *addr) +{ + // 将IP地址从网络字节序转换为点分十进制格式 + char ip_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &addr->sin_addr, ip_str, sizeof(ip_str)); + + // 将端口号从网络字节序转换为主机字节序并打印 + unsigned short port = ntohs(addr->sin_port); + + DBG(DBG_M_PD_CSG, "IP Address: %s, Port: %u\r\n", ip_str, port); +} + +void _csg_show() +{ + printh("CSG connect: %s \n", (csg.is_connect == 1)? "OK" : "FAIL"); +} + +void _csg_server_set(int32_t ip, uint16_t port) +{ + /* 比较配置 */ + if (csg.server_ip != ip + || csg.server_port != port) + { + csg.server_ip = ip; + csg.server_port = port; + + bzero(&csg.server, sizeof(csg.server)); + csg.server.sin_family = AF_INET; + csg.server.sin_addr.s_addr = csg.server_ip; + csg.server.sin_port = htons(csg.server_port); + } +} + +/* 校验收到包的包头, 长度, 校验码. */ +int32_t _csg_pkt_check(char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + + /* 对主次设备号进行识别, 次设备号可以是广播. */ + if ((head->dev_type_m != device_info.type_m)) + { + DBG(DBG_M_PD_CSG_ERR, "@1 type_m=%d %d \r\n", head->dev_type_m, device_info.type_m); + return E_ERROR; + } + + if (head->len > 1500) + { + DBG(DBG_M_PD_CSG_ERR, "@2 receive packet len(%d) is out of range\r\n", head->len); + return E_ERROR; + } + + return E_NONE; +} + +/* 包头填充. */ +void _csg_head_init(char *buf, uint16_t len, uint8_t cmdType, uint8_t cmd) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)buf; + + /* 封装报文头. */ + head->len = len; + head->dev_type_m = device_info.type_m; + head->dev_type_s= device_info.type_s; + head->dev_id = device_info.dev_id; + head->cmd_type = cmdType; + head->cmd = cmd; + head->version = 1; + head->pkt_id = csg.pkt_index++; +} + +/* 数据发送 */ +void _csg_send_data(uint8_t cmd_type, uint8_t cmd, char *pkt, int32_t len) +{ + int32_t rv = 0; + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + + /* 封装报文头. */ + _csg_head_init(pkt, sizeof(csg_pkt_head_t) + len, cmd_type, cmd); + + rv = sendto(csg.skfd, pkt, head->len, 0, (struct sockaddr*)&csg.server, sizeof(csg.server)); + if (rv < 0) + { + DBG(DBG_M_PD_CSG_ERR, "Sendto return %s!\r\n", safe_strerror(errno)); + } +} + +/* 与后台连接断开 */ +void _csg_disconnect_set(const char *message) +{ + if (csg.is_connect) + { + csg.is_connect = FALSE; + log_warn(LOG_CSG, "[%s]CSG Connection lost!!!\n", message); + } +} + +/* 主动连接请求. */ +int32_t _csg_connect_send(void) +{ + char *pkt = csg.buf_send; + csg_contact_t *pinfo = (csg_contact_t *)(pkt + sizeof(csg_pkt_head_t)); + uint8_t unit = 0; + uint8_t port = 0; + + pinfo->type_m = device_info.type_m; + pinfo->type_s = device_info.type_s; + pinfo->dev_id = device_info.dev_id; + strncpy(pinfo->hostname, host.name, sizeof(pinfo->hostname)-1); + pinfo->factory_date = device_info.factory_date; + pinfo->deployment_date = device_info.deployment_date; + strncpy((char *)pinfo->app_version, host.version, sizeof(pinfo->app_version)-1); + strncpy((char *)pinfo->app_compile_time, host.compile, sizeof(pinfo->app_compile_time)-1); + strncpy((char *)pinfo->hardware_version, host.hardversion, 31); + strncpy((char *)pinfo->FPGA_version, host.FPGAversion, 31); + pinfo->ip = device_info.ip; + pinfo->mask = device_info.mask; + pinfo->gw = device_info.gw; + memcpy(pinfo->mac, device_info.mac, sizeof(pinfo->mac)); + pinfo->server_port = csg.server_port; + pinfo->server_ipv4 = csg.server_ip; + + memset(pinfo->port, 0, sizeof(pinfo->port)); + memset(pinfo->port, 0, sizeof(pinfo->port_type)); + for(unit = 0; unit < PD_DAU_SUM; unit++) + { + //if (!dau_is_valid(dau[unit])) + { + continue; + } + + //for(port = 0; port < dau[unit]->port_num; port++) + { + pinfo->port[port] = pd_config.config_port[unit][port].vport; + pinfo->port_type[port] = pd_config.config_port[unit][port].port_type;; + } + } + + _csg_send_data(CSG_REPLY, CSG_C_CONTACT, pkt, sizeof(csg_contact_t)); + + return E_NONE; +} + +/* 心跳包 */ +int32_t _csg_heartbeat_send(void) +{ + char *pkt = csg.buf_send; + csg_heartbeat_t *pinfo = (csg_heartbeat_t *)(pkt + sizeof(csg_pkt_head_t)); + uint16_t i = 0; + + for(i = 0; i < PD_DAU_SUM; i++) + { + //if (dau_is_valid(dau[i])) + { + //pinfo->dau_state[i] = dau[i]->is_connect; + //pinfo->dau_port_num[i] = dau[i]->port_num; + } + //else + { + pinfo->dau_state[i] = 0; + pinfo->dau_port_num[i] = 0; + } + } + + pinfo->freq = 50; + pinfo->out_sync = 0; + pinfo->pt_sync = 0; + pinfo->in_sync = 0; + if (pd_state.sync) + { + if (PD_SYNC_PT == pd_config.config.sync_mode) + { + pinfo->pt_sync = 1; + } + else if (PD_SYNC_INSIDE == pd_config.config.sync_mode) + { + pinfo->in_sync = 1; + } + else if (PD_SYNC_OUTSIDE == pd_config.config.sync_mode) + { + pinfo->out_sync = 1; + } + } + + for(i = 0; i < PD_PORT_SUM; i++) + { + pinfo->port_link_alarm[i] = 0; + } + + _csg_send_data(CSG_REPLY, CSG_C_HEARTBEAT, pkt, sizeof(csg_heartbeat_t)); + return E_NONE; +} + +/* 解析连接报文 */ +void _csg_connect_recv(void) +{ + csg.is_connect = TRUE; + log_warn(LOG_CSG, "CSG connection OK!"); +} + +void _csg_add_dau_recv(char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + int slot = head->slot; + + printf("_csg_add_dau_recv slot = %d\n", head->slot); + head->dev_id = head->sdev_id; + head->cmd_type = CSG_REQUEST; + head->cmd = CSG_C_CONTACT; + head->len = CSG_HEAD_LEN + 4; + //(uint32_t *)(pkt + CSG_HEAD_LEN) = time(NULL); + uint32_t *timestamp = (uint32_t *)(pkt + CSG_HEAD_LEN); + *timestamp = time(NULL); + + //_dau_response(slot, pkt, head->len); + _dau_response(slot, pkt, head->len); +} + +/* 解析心跳报文. */ +void _csg_heartbeat_recv(char *pkt) +{ + uint32_t server_time = *(uint32_t*)(pkt + sizeof(csg_pkt_head_t)); + + //printf("server_time:%d now:%ld\n", server_time, time(NULL)); + if (abs(server_time - time(NULL)) > 3) + { + time_set(server_time); //北京时间 + } +} + +/* 设备重启报文. */ +void _csg_reboot_recv(char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + csg_ack_t ack = {0}; + + ack.result = TRUE; + memcpy(pkt + sizeof(csg_pkt_head_t), (char *)&ack, sizeof(csg_ack_t)); + _csg_send_data(CSG_REPLY, head->cmd, pkt, sizeof(csg_ack_t)); + + sleep(3); + reboot_system(LOG_CSG, REBOOT_REMOTE_RESET); +} + +/* 厂家参数设置报文处理. + 说明:修改本地ip 设备ID 服务器地址时需要重启。 +*/ +int32_t _csg_dev_info_set_recv(char *pkt) +{ + int change_ip = 0; + REBOOT_MSG boottype = REBOOT_NONE; + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + csg_dev_info_t *pinfo = (csg_dev_info_t *)(pkt + CSG_HEAD_LEN); + device_info.dev_id = pinfo->dev_id; + device_info.mask = pinfo->mask; + device_info.gw = pinfo->gw; + + if (strncmp((char *)(pinfo->hostname), device_info.hostname, sizeof(device_info.hostname))) + { + snprintf((char*)device_info.hostname, PD_DEV_NUM_LEN, "%s", pinfo->hostname); + boottype = REBOOT_REMOTE_HOST_NAME_CHANGE; + } + + if (device_info.ip != pinfo->ip) + { + device_info.ip = pinfo->ip; + change_ip++; + boottype = REBOOT_REMOTE_IP_CHANGE; + } + + if (csg.server_ip != pinfo->server_ipv4) + { + csg.server_ip = pinfo->server_ipv4; + boottype = REBOOT_REMOTE_SERVER_IP_CHANGE; + } + + if (csg.server_port != pinfo->server_port) + { + csg.server_port = pinfo->server_port; + boottype = REBOOT_REMOTE_SERVER_IP_CHANGE; + } + + csg_ack_t ack = {0}; + ack.result = TRUE; + memcpy(pkt + sizeof(csg_pkt_head_t), (char *)&ack, sizeof(csg_ack_t)); + _csg_send_data(CSG_REPLY, head->cmd, pkt, sizeof(csg_ack_t)); + + vtysh_config_save(); + if (change_ip) + { + uint8_t mac[MAC_ADDR_LEN] = {0}; + mac_generate_from_ip(device_info.ip, mac); + memcpy(device_info.mac, mac, MAC_ADDR_LEN); + vtysh_eth0_save(); + } + vtysh_device_save(); + + if (boottype) + { + reboot_system(LOG_CSG, boottype); + } + + return 0; +} + +/* 厂家参数查询报文处理. */ +int32_t _csg_dev_info_get_recv(char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + csg_dev_info_t *pinfo = (csg_dev_info_t *)(pkt + sizeof(csg_pkt_head_t)); + pinfo->type_m = device_info.type_m; + pinfo->type_s = device_info.type_s; + pinfo->dev_id = device_info.dev_id; + strcpy(pinfo->hostname, device_info.hostname); + pinfo->factory_date = device_info.factory_date; + pinfo->deployment_date = device_info.deployment_date; + strncpy((char *)pinfo->app_compile_time, host.compile, 31); + strncpy((char *)pinfo->app_version, host.version, 31); + strncpy((char *)pinfo->hardware_version, host.hardversion, 31); + strncpy((char *)pinfo->FPGA_version, host.FPGAversion, 31); + pinfo->ip = device_info.ip; + pinfo->mask = device_info.mask; + pinfo->gw = device_info.gw; + memcpy(pinfo->mac, device_info.mac, sizeof(pinfo->mac)); + //info.server_port = device_info.server_port; + //info.server_ipv4 = device_info.server_ipv4; + pinfo->server_port = csg.server_port; + pinfo->server_ipv4 = csg.server_ip; + + _csg_send_data(CSG_REPLY, head->cmd, pkt, sizeof(csg_dev_info_t)); + + return E_NONE; +} + +/* 配置用户参数报文报文处理. */ +int32_t _csg_config_set_recv(char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + csg_config_global_t *pnet = (csg_config_global_t *)(pkt + CSG_HEAD_LEN); + + pd_config.config.power_frequency = pnet->power_frequency; + pd_config.config.trend_period = pnet->trend_period * 60; + pd_config.config.sync_mode = pnet->sync_mode; + pd_config.config.heartbeat_period = pnet->heartbeat_period; + pd_config.config.pps_mode = pnet->pps_mode; + pd_config.config.protocol_type = pnet->protocol_type; + pd_config.config.event_storage = pnet->event_storage; + pd_config.config.trend_storage = pnet->trend_storage; + + vtysh_config_save(); + + csg_ack_t ack = {0}; + ack.result = TRUE; + memcpy(pkt + sizeof(csg_pkt_head_t), (char *)&ack, sizeof(csg_ack_t)); + + _csg_send_data(CSG_PRV_REPLY, head->cmd, pkt, sizeof(csg_ack_t)); + return E_NONE; +} + +/* 查询用户参数查询报文处理. */ +int32_t _csg_config_get_recv(char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + csg_config_global_t config = {0}; + config.power_frequency = pd_config.config.power_frequency; + config.sync_mode = pd_config.config.sync_mode; + config.heartbeat_period = pd_config.config.heartbeat_period; + config.pps_mode = pd_config.config.pps_mode; + config.protocol_type = pd_config.config.protocol_type; + config.trend_period = pd_config.config.trend_period / 60; + config.trend_storage = pd_config.config.trend_storage; + config.event_storage = pd_config.config.event_storage; + memcpy(pkt + sizeof(csg_pkt_head_t), (char *)&config, sizeof(csg_config_global_t)); + _csg_send_data(CSG_PRV_REPLY, head->cmd, pkt, sizeof(csg_config_global_t)); + + return E_NONE; +} + +/* 通道提交端口参数设置. */ +int32_t _csg_port_config_set_recv(char *pkt) +{ + uint8_t vport = *(uint8_t*)(pkt + CSG_HEAD_LEN); + uint8_t unit = 0; + uint8_t port = 0; + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + csg_config_port_t *pnet = (csg_config_port_t *)(pkt + CSG_HEAD_LEN); + + pd_config.config_port[unit][port].vport = pnet->vport; + pd_config.config_port[unit][port].port_type = pnet->port_type; + pd_config.config_port[unit][port].filter = pnet->filter; + pd_config.config_port[unit][port].sensor_type = pnet->sensor_type; + pd_config.config_port[unit][port].is_auto_noise = pnet->is_auto_noise; + pd_config.config_port[unit][port].denoise_type = pnet->denoise_type; + pd_config.config_port[unit][port].denoise_variance = pnet->denoise_variance; + + pd_config.config_port[unit][port].event_counter_h = pnet->event_counter_h; + pd_config.config_port[unit][port].event_sec_h = pnet->event_sec_h; + pd_config.config_port[unit][port].event_thr_h = pnet->event_thr_h; + pd_config.config_port[unit][port].event_counter_thr_h = pnet->event_counter_thr_h; + + pd_config.config_port[unit][port].event_counter_l = pnet->event_counter_l; + pd_config.config_port[unit][port].event_sec_l = pnet->event_sec_l; + pd_config.config_port[unit][port].event_thr_l = pnet->event_thr_l; + pd_config.config_port[unit][port].event_counter_thr_l = pnet->event_counter_thr_l; + + pd_config.config_port[unit][port].burst_time = pnet->burst_time; + pd_config.config_port[unit][port].burst_thr = pnet->burst_thr; + pd_config.config_port[unit][port].denoise_manual = pnet->denoise_manual; + pd_config.config_port[unit][port].denoise_auto = pnet->denoise_auto; + + pd_config.config_real[unit][port].filter_cfg = pd_config.config_port[unit][port].filter; + //dau_port_filter_set(unit, port); + + vtysh_config_save(); + + //csg_config_port_ack_t ack = {0}; + //ack.vport = pnet->vport; + //ack.result = TRUE; + //memcpy(pkt + sizeof(csg_pkt_head_t), (char *)&ack, sizeof(csg_config_port_ack_t)); + //_csg_send_data(CSG_PRV_REPLY, head->cmd, pkt, sizeof(csg_config_port_ack_t)); + return E_NONE; +} + +/* 按通道提交端口参数查询结果. */ +int32_t _csg_port_config_get_recv(char *pkt) +{ + uint8_t vport = *(uint8_t*)(pkt + CSG_HEAD_LEN); + uint8_t unit = 0; + uint8_t port = 0; + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + csg_config_port_t *pnet = (csg_config_port_t *)(pkt + CSG_HEAD_LEN); + + //if (dau_vport_to_port(pnet->vport, &unit, &port) != E_NONE) + { + DBG(DBG_M_PD_CSG_ERR, "Pkt port %d error!\r\n", vport); + return E_ERROR; + } + csg_config_port_t config = {0}; + config.vport = pd_config.config_port[unit][port].vport; + config.port_type = pd_config.config_port[unit][port].port_type; + config.filter = pd_config.config_port[unit][port].filter; + config.sensor_type = pd_config.config_port[unit][port].sensor_type; + config.is_auto_noise = pd_config.config_port[unit][port].is_auto_noise; + config.denoise_type = pd_config.config_port[unit][port].denoise_type; + config.denoise_variance = pd_config.config_port[unit][port].denoise_variance; + + config.event_counter_h = pd_config.config_port[unit][port].event_counter_h; + config.event_sec_h = pd_config.config_port[unit][port].event_sec_h; + config.event_thr_h = pd_config.config_port[unit][port].event_thr_h; + config.event_counter_thr_h = pd_config.config_port[unit][port].event_counter_thr_h; + + config.event_counter_l = pd_config.config_port[unit][port].event_counter_l; + config.event_sec_l = pd_config.config_port[unit][port].event_sec_l; + config.event_thr_l = pd_config.config_port[unit][port].event_thr_l; + config.event_counter_thr_l = pd_config.config_port[unit][port].event_counter_thr_l; + + config.burst_time = pd_config.config_port[unit][port].burst_time; + config.burst_thr = pd_config.config_port[unit][port].burst_thr; + config.denoise_manual = pd_config.config_port[unit][port].denoise_manual; + config.denoise_auto = pd_config.config_port[unit][port].denoise_auto; + + memcpy(pkt + sizeof(csg_pkt_head_t), (char *)&config, sizeof(csg_config_port_t)); + + _csg_send_data(CSG_PRV_REPLY, head->cmd, pkt, sizeof(pd_config_port_t)); + + return E_NONE; +} + +/* 升级文件接收 */ +int32_t _csg_upgrade_recv(char *pkt) +{ + static int fd = -1; + static uint32_t fix_len = 0; + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + csg_upgrade_data_t *head_msg = (csg_upgrade_data_t*)(pkt + CSG_HEAD_LEN); + char *pdata = pkt + CSG_HEAD_LEN + sizeof(csg_upgrade_data_t); + upgrade_ack_t ack = {0}; + int32_t size = 0; + int32_t len_wr = 0; + int32_t rv = E_NONE; + uint32_t offset = 0; + + /* 首保处理, 打开文件描述符, 初始化变量 */ + if (head_msg->index == 0) + { + if (fd > 0) + { + close(fd); + fd = -1; + } + + fd = open(PD_UPG_SOFTWARE, O_WRONLY | O_CREAT | O_TRUNC, 0777); + if (fd < 0) + { + DBG(DBG_M_PD_CSG_ERR, "Open file " PD_UPG_SOFTWARE " error!\n"); + return E_SYS_CALL; + } + fix_len = head_msg->len; + + DBG(DBG_M_PD_CSG, "Receive upgrade file start.\n"); + } + DBG(DBG_M_PD_CSG_ERR,"type=%d,sum=%d,index=%d,len=%d,fix_len=%d\n", head_msg->type, head_msg->sum, head_msg->index, head_msg->len, fix_len); + + /* 收包流程 */ + size = head_msg->len; + offset = head_msg->index * fix_len; + if (lseek(fd, offset, SEEK_SET) < 0) + { + DBG(DBG_M_PD_CSG_ERR, "lseek file " PD_UPG_SOFTWARE " error!\n"); + return E_SYS_CALL; + } + len_wr = write(fd, pdata, size); + if (len_wr != size) + { + DBG(DBG_M_PD_CSG_ERR, "Write file " PD_UPG_SOFTWARE " error!\n"); + return E_SYS_CALL; + } + + /* 最后一个报文处理 */ + if (head_msg->sum - 1 == head_msg->index) + { + close(fd); + fd = -1; + DBG(DBG_M_PD_CSG, "Receive upgrade file end.\n"); + rv = pd_upg_start(PD_UPG_FROM_CSG, head_msg->type); + } + ack.index = head_msg->index; + ack.result = TRUE; + + DBG(DBG_M_PD_CSG_ERR," send ack\n"); + + /* 发送应答 */ + memcpy(pkt + sizeof(csg_pkt_head_t), (char *)&ack, sizeof(csg_ack_t)); + _csg_send_data(CSG_REPLY, head->cmd, pkt, sizeof(ack)); + + /* 如果升级线程开启失败返回错误信息. */ + if (rv != E_NONE) + { + printf("Upgrade start failed.\n"); + csg_upgrade_result_send(0, "Upgrade start failed."); + } + + return E_NONE; +} + +int32_t _csg_event_recv(char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + int slot = head->slot; + + printf("_csg_event_recv slot = %d\n", head->slot); + head->dev_id = head->sdev_id; + head->cmd = CSG_PRV_EVENT; + + _dau_response(slot, pkt, head->len); + return E_NONE; +} + + +int32_t _csg_recv_process(char *pkt, uint32_t len) +{ + csg_pkt_head_t *head = (csg_pkt_head_t *)pkt; + + /* 报文头和 CRC 校验. */ + LD_E_RETURN(DBG_M_PD_CSG_ERR, _csg_pkt_check(pkt)); + csg.heartbeat_timeout_cnt = 0; + + if (CSG_REQUEST == head->cmd_type) + { + switch (head->cmd) + { + case CSG_C_CONTACT: + _csg_connect_recv(); + break; + case CSG_C_ADD_DAU: + _csg_add_dau_recv(pkt); + break; + case CSG_C_RESET: + _csg_reboot_recv(pkt); + break; + case CSG_C_UPDATE: + _csg_upgrade_recv(pkt); + break; + case CSG_C_HEARTBEAT: + _csg_heartbeat_recv(pkt); + break; + case CSG_C_DEV_INFO_SET: + _csg_dev_info_set_recv(pkt); + break; + case CSG_C_DEV_INFO_GET: + _csg_dev_info_get_recv(pkt); + break; + default: + break; + } + } + else if (CSG_PRV_REQUEST == head->cmd_type) + { + switch (head->cmd) + { + case CSG_PRV_CONFIG_GLOBAL_SET: + _csg_config_set_recv(pkt); + break; + case CSG_PRV_CONFIG_GLOBAL_GET: + _csg_config_get_recv(pkt); + break; + case CSG_PRV_CONFIG_PORT_SET: + _csg_port_config_set_recv(pkt); + break; + case CSG_PRV_CONFIG_PORT_GET: + _csg_port_config_get_recv(pkt); + break; + case CSG_PRV_CONFIG_REAL_WAVE: + break; + case CSG_PRV_TREND: + break; + case CSG_PRV_REAL_PRPS: + break; + case CSG_PRV_EVENT: + _csg_event_recv(pkt); + break; + default: + break; + } + } + return E_NONE; +} + + +/* 心跳和连接处理函数. */ +void *_csg_recv_handle(void *arg) +{ + struct sockaddr_in server; + socklen_t server_len; + int32_t addr = 0; + uint16_t data_len = 0; + + /* 等待初始化完成 */ + while(!is_system_init) + { + sleep(1); + } + + prctl(PR_SET_NAME, "CSG_RCVE", 0, 0, 0); + + while(1) + { + /* 读取数据. */ + memset(csg.buf_recv, 0, sizeof(csg.buf_recv)); + data_len = recvfrom(csg.skfd, csg.buf_recv, CSG_PKT_LEN, 0, (struct sockaddr*)&server, &server_len); + if (data_len <= 0) + { + DBG(DBG_M_PD_CSG_ERR, "Recvfrom return ERROR %s!\r\n", safe_strerror(errno)); + continue; + } + + addr = server.sin_addr.s_addr; + if (addr != csg.server_ip) + { + continue; + } + + _csg_recv_process(csg.buf_recv, data_len); + } + + return NULL; +} + +/* 心跳和连接处理函数. */ +void *_csg_heartbeat_handle(void *arg) +{ + time_t now = 0; + time_t t_connect = 0; + time_t t_heartbeat = 0; + + /* 等待初始化完成 */ + while(!is_system_init) + { + sleep(1); + } + + while(1) + { + sleep(1); + now = time(NULL); + + /* 发送连接报文. */ + if (!csg.is_connect) + { + if (abs(now - t_connect) >= 3) + { + _csg_connect_send(); + t_connect = now; + } + + continue; + } + + /* 发送心跳包. */ + if (abs(now - t_heartbeat) >= pd_config.config.heartbeat_period * 60) + { + _csg_heartbeat_send(); + t_heartbeat = now; + csg.heartbeat_timeout_cnt++; + /* 等待回复报文后再进行连接判断 */ + sleep(3); + if (csg.heartbeat_timeout_cnt > 3) + { + csg.heartbeat_timeout_cnt = 0; + _csg_disconnect_set(__FUNCTION__); + } + } + } + + return NULL; +} + +/* 配置保存函数. */ +int _csg_config_save(vty_t* vty) +{ + int16_t i = 0; + struct in_addr addr; + + addr.s_addr = csg.server_ip; + vty_out(vty, "csg server %s %d%s", inet_ntoa(addr), csg.server_port, VTY_NEWLINE); + i++; + + return i; +} + +/* Interface functions -------------------------------------------------------*/ +/* 后台通讯模块预初始化. */ +int32_t csg_handle_init(void) +{ + int32_t rv = 0; + + memset(&csg, 0, sizeof(csg_t)); + + /* 发送数据. */ + csg.server_ip = inet_addr("192.168.1.161"); + csg.server_port = 1885; + bzero(&csg.server, sizeof(csg.server)); + csg.server.sin_family = AF_INET; + csg.server.sin_addr.s_addr = csg.server_ip; + csg.server.sin_port = htons(csg.server_port); + + cmd_install_element(CONFIG_NODE, &csg_server_set_cmd); + cmd_install_element(COMMON_NODE, &csg_show_cmd); + + /* 注册配置保存函数 */ + rv = cmd_config_node_config_register(CONFIG_PRI_CSG, _csg_config_save); + if (rv != E_NONE) + { + log_err(LOG_CSG, "Command save register ERROR %d!", rv); + return rv; + } + + return E_NONE; +} + +/* 后台通讯模块初始化. */ +int32_t csg_handle_init_after(void) +{ + struct sockaddr_in server; + int fd = 0; + thread_param_t param = {0}; + + if (pd_config.config.protocol_type != PD_PROTOCOL_LAND) + { + return E_NONE; + } + + /* 创建协议 socket. */ + if (0 == csg.skfd) + { + /* 创建socket */ + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + { + log_err(LOG_CSG, "ERROR at socket create return %s!", safe_strerror(errno)); + return E_SYS_CALL; + } + + /* 绑定端口 */ + bzero(&server, sizeof(server)); + server.sin_family = AF_INET; + server.sin_addr.s_addr = htonl(INADDR_ANY); + server.sin_port = htons(7777); + if(bind(fd, (struct sockaddr*)&server, sizeof(server)) < 0) + { + log_err(LOG_CSG, "ERROR at socket bind return %s!", safe_strerror(errno)); + close(fd); + return E_SYS_CALL; + } + + /* 保存数据. */ + csg.skfd = fd; + } + + param.arg = NULL; + param.log_module = LOG_CSG; + + param.priority = 45; + param.thread_name = "CSG_RCVE"; + create_thread(_csg_recv_handle, ¶m); + + param.priority = 45; + param.thread_name = "CSG_HEARTBEAT"; + create_thread(_csg_heartbeat_handle, ¶m); + + return E_NONE; +} + +/* description: 远程升级结果返回回调函数 + param: rv -- 返回结果 + buf -- 描述字符串 + return: */ +void csg_upgrade_result_send(int32_t rv, char *buf) +{ + upgrade_res_t ack = {0}; + char *pkt = csg.buf_send; + + ack.result = rv; + strcpy(ack.context, buf); + + memcpy(pkt + sizeof(csg_pkt_head_t), (char *)&ack, sizeof(csg_ack_t)); + _csg_send_data(CSG_REPLY, CSG_C_UPDATE_RESULT, pkt, sizeof(upgrade_res_t)); +} + +command_handler _csg_get_table_handle(command_entry *ptable, command_type cmd) +{ + if (NULL == ptable) + { + return NULL; + } + + for (;CMD_INVALID != ptable->cmd; ptable++) + { + if (cmd == ptable->cmd) + { + return ptable->handler; + } + } + return ptable->handler; +} + +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/a_process/pd_dau.c b/app/lib/a_process/pd_dau.c new file mode 100755 index 0000000..ca5fa04 --- /dev/null +++ b/app/lib/a_process/pd_dau.c @@ -0,0 +1,754 @@ +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef CFG_DEV_TYPE_LAND_PD +/* 标准C库头文件. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 用户代码头文件. */ +#include "main.h" +#include "cmd.h" +#include "pd_dau.h" +#include "pd_hf.h" +#include "pd_csg.h" + +/* Private variables ---------------------------------------------------------*/ + +//dau_t dau; +pthread_mutex_t board_mutex = PTHREAD_MUTEX_INITIALIZER; +int udp_socket; +dau_t daus[MAX_SLOTS]; + + +// 上传平台回调函数类型 +typedef void (*UploadCallback)(int slot, const void *data, size_t len); + +/* Private function prototypes -----------------------------------------------*/ +int _dau_insert(int slot, DauType type); +int _dau_remove(int slot); +extern void _print_sockaddr_in(const struct sockaddr_in *addr); + +/* Internal functions --------------------------------------------------------*/ +CMD(dau_add, + dau_add_cmd, + "dau <1-6>", + "DAU\n" + "Dau number\n") +{ + uint8_t unit = 0; + + unit = strtol(argv[0], NULL, 10) - 1; + + _dau_insert(unit, DAU_TYPE_UDP); + + return CMD_SUCCESS; +} + +CMD(no_dau_add, + no_dau_add_cmd, + "no dau <1-6>", + "DAU\n" + "Dau number\n") +{ + uint8_t unit = 0; + + unit = strtol(argv[0], NULL, 10) - 1; + + _dau_remove(unit); + + return CMD_SUCCESS; +} + +#if 0 +// ================== UDP 操作函数 ================== +static int _dau_udp_init(dau_private_data_t *data, const char *config) +{ + // 解析配置: "ip:port:board_id" + char ip[32], board_id[32]; + int port; + struct sockaddr_in server_addr; + int sockfd; + if (sscanf(config, "%[^:]:%d:%s", ip, &port, board_id) != 3) + { + return -1; + } + + // 创建socket + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + { + log_err(LOG_DAU, "ERROR at socket create return %s!", safe_strerror(errno)); + return E_SYS_CALL; + } + + // 设置地址 + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr.s_addr = htonl(INADDR_ANY); + //inet_pton(AF_INET, ip, &server_addr.sin_addr); + + // 绑定套接字 + if (bind(sockfd, (const struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) + { + log_err(LOG_DAU, "ERROR at socket create return %s!", safe_strerror(errno)); + close(sockfd); + return E_SYS_CALL; + } + + strncpy(data->board_id, board_id, sizeof(data->board_id)); + + // 配置缓冲区 (示例) + data->buffer_size = 4096; + data->rx_buffer = malloc(data->buffer_size); + if (!data->rx_buffer) + { + close(sockfd); + return E_SYS_CALL;; + } + + //data->comm.udp.addr = server_addr; + data->comm.udp.sockfd = sockfd; + printf("UDP board %s initialized at %s:%d\n", + board_id, ip, port); + return 0; +} + + + +static int _dau_udp_receive(dau_private_data_t *data, void *buf, size_t len) +{ + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + int data_len = 0; + do + { + data_len = recvfrom(data->comm.udp.sockfd, buf, len, 0, + (struct sockaddr*)&client_addr, &addr_len); + if (data_len <= 0) + { + DBG(DBG_M_PD_DAU_ERR, "Recvfrom return ERROR %s!\r\n", safe_strerror(errno)); + continue; + } + + // 获取客户端IP和端口 + char client_ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN); + int client_port = ntohs(client_addr.sin_port); + + //memcpy(&data->comm.add, &client_addr, sizeof(client_addr)); + data->comm.udp.addr = client_addr; + + printf("Received from %s:%d:\n", client_ip, client_port); + } while(0); + return data_len; +} + +static int _dau_udp_transmit(dau_private_data_t *data, const void *buf, size_t len) +{ + printf("sockfd=%d\n", data->comm.udp.sockfd); + _print_sockaddr_in(&data->comm.udp.addr); + return sendto(data->comm.udp.sockfd, buf, len, 0, + (struct sockaddr*)&data->comm.udp.addr, + sizeof(data->comm.udp.addr)); +} + +static void _dau_udp_cleanup(dau_private_data_t *data) +{ + if (data->comm.udp.sockfd >= 0) + { + close(data->comm.udp.sockfd); + data->comm.udp.sockfd = -1; + } + if (data->rx_buffer) + { + free(data->rx_buffer); + data->rx_buffer = NULL; + } + printf("UDP board %s cleaned up\n", data->board_id); +} +#endif + +#if 0 +// ================== RS485 操作函数 ================== +static int _dau_rs485_init(dau_private_data_t *data, const char *config) +{ + // 解析配置: "port:baudrate:board_id" + char port[32], board_id[32]; + int baudrate; + if (sscanf(config, "%[^:]:%d:%s", port, &baudrate, board_id) != 3) { + return -1; + } + + // 打开串口 + data->comm.rs485.fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); + if (data->comm.rs485.fd < 0) + { + perror("RS485 open failed"); + return -1; + } + + // 获取当前串口设置 + tcgetattr(data->comm.rs485.fd, &data->comm.rs485.options); + + // 设置波特率 + cfsetispeed(&data->comm.rs485.options, baudrate); + cfsetospeed(&data->comm.rs485.options, baudrate); + + // 设置8N1 + data->comm.rs485.options.c_cflag &= ~PARENB; + data->comm.rs485.options.c_cflag &= ~CSTOPB; + data->comm.rs485.options.c_cflag &= ~CSIZE; + data->comm.rs485.options.c_cflag |= CS8; + + // 应用设置 + tcsetattr(data->comm.rs485.fd, TCSANOW, &data->comm.rs485.options); + + strncpy(data->board_id, board_id, sizeof(data->board_id)); + strncpy(data->comm.rs485.port, port, sizeof(data->comm.rs485.port)); + + // 配置缓冲区 + data->buffer_size = 1024; + data->rx_buffer = malloc(data->buffer_size); + if (!data->rx_buffer) { + close(data->comm.rs485.fd); + return -1; + } + + printf("RS485 board %s initialized on %s@%d\n", + board_id, port, baudrate); + return 0; +} + +static int _dau_rs485_receive(dau_private_data_t *data, void *buf, size_t len) +{ + return read(data->comm.rs485.fd, buf, len); +} + +static int _dau_rs485_transmit(dau_private_data_t *data, const void *buf, size_t len) +{ + return write(data->comm.rs485.fd, buf, len); +} + +static void _dau_rs485_cleanup(dau_private_data_t *data) +{ + if (data->comm.rs485.fd >= 0) + { + close(data->comm.rs485.fd); + data->comm.rs485.fd = -1; + } + if (data->rx_buffer) + { + free(data->rx_buffer); + data->rx_buffer = NULL; + } + printf("RS485 board %s cleaned up\n", data->board_id); +} +// ================== 板卡管理函数 ================== +static void _dau_seek_proper_function(int slot, dau_private_data_t *data) +{ + if (strncmp(data->board_id, UHF, strlen(UHF)) == 0) + { + } + else if (strncmp(data->board_id, HF, strlen(HF)) == 0) + { + _hf_recv_process(slot, data->rx_buffer); + } + else if (strncmp(data->board_id, ULTRASONIC, strlen(ULTRASONIC)) == 0) + { + } + else if (strncmp(data->board_id, IRONCORE, strlen(IRONCORE)) == 0) + { + } +} +#endif + +static int32_t _dau_find_proper_function(char *pkt) +{ + int flag = 0; + csg_pkt_head_t *head = (csg_pkt_head_t *)pkt; + for (int i = 0; i < MAX_SLOTS; i++) + { + if (daus[i].slot != head->slot) + continue; + + if (daus[i].state == DAU_STATE_REGISTERED) + { + flag = 1; + } + } + if (!flag) + return E_NONE; + if (head->dev_type_m == 0x03) + { + if (head->dev_type_s == 0x01) + { + _hf_recv_process(head->slot, pkt); + } + else if (head->dev_type_s == 0x02) + {} + else if (head->dev_type_s == 0x03) + {} + } + return E_NONE; +} + +// 申请板卡私有数据 +void* _dau_alloc_private_data(DauType type, int slot) +{ + if (type == DAU_TYPE_UDP) + { + udp_client_data *data = malloc(sizeof(udp_client_data)); + memset(data, 0, sizeof(udp_client_data)); + return data; + } + else if (type == DAU_TYPE_RS485) + { + rs485_device_data *data = malloc(sizeof(rs485_device_data)); + memset(data, 0, sizeof(rs485_device_data)); + + // 根据槽位分配串口设备 + const char *device = (slot == 4) ? "/dev/ttyS0" : "/dev/ttyS1"; + //data->fd = init_rs485(device); + data->address = (slot == 4) ? 0x01 : 0x02; + + if (data->fd < 0) + { + free(data); + return NULL; + } + return data; + } + return NULL; +} + +// 释放板卡私有数据 +void _dau_free_private_data(DauType type, void *data) +{ + if (!data) + { + return; + } + + if (type == DAU_TYPE_UDP) + { + free(data); + } + else if (type == DAU_TYPE_RS485) + { + rs485_device_data *dev = (rs485_device_data*)data; + if (dev->fd >= 0) + { + close(dev->fd); + } + free(data); + } +} + + +// ================== 板卡管理函数 ================== +#if 0 +static void *_dau_thread_func(void *arg) +{ + dau_manager_t *manager = (dau_manager_t *)arg; + dau_private_data_t *data = manager->private_data; + + prctl(PR_SET_NAME, (unsigned long)data->board_id, 0, 0, 0); + + printf("Board thread started for slot %d (%s)\n", + manager->slot, data->board_id); + + + while (manager->occupied) + { + // 接收数据 + ssize_t bytes = manager->ops.receive(data, data->rx_buffer, data->buffer_size); + + if (bytes > 0) + { + // 处理数据 + printf("Slot %d received %zd bytes\n", manager->slot, bytes); + _dau_seek_proper_function(manager->slot, data); + } + else if (bytes < 0) + { + perror("Receive error"); + usleep(100000); // 100ms 延迟后重试 + } + } + + printf("Board thread exiting for slot %d\n", manager->slot); + return NULL; +} + +int _dau_insert(int slot, DauType type, const char *config) +{ + if (slot < 0 || slot >= MAX_SLOTS) return -1; + + pthread_mutex_lock(&dau.mutex); + + if (dau.mgr[slot].occupied) + { + pthread_mutex_unlock(&dau.mutex); + return -2; // 槽位已被占用 + } + + // 创建私有数据 + dau_private_data_t *data = malloc(sizeof(dau_private_data_t)); + if (!data) + { + pthread_mutex_unlock(&dau.mutex); + return -3; + } + memset(data, 0, sizeof(dau_private_data_t)); + data->type = type; + + // 设置操作函数 + dau_operations_t ops; + switch (type) + { + case DAU_UDP: + ops.init = _dau_udp_init; + ops.receive = _dau_udp_receive; + ops.transmit = _dau_udp_transmit; + ops.cleanup = _dau_udp_cleanup; + break; + case DAU_RS485: + ops.init = _dau_rs485_init; + ops.receive = _dau_rs485_receive; + ops.transmit = _dau_rs485_transmit; + ops.cleanup = _dau_rs485_cleanup; + break; + default: + free(data); + pthread_mutex_unlock(&dau.mutex); + return -4; + } + + // 初始化板卡 + if (ops.init(data, config) != 0) + { + free(data); + pthread_mutex_unlock(&dau.mutex); + return -5; + } + + // 配置管理器 + dau.mgr[slot].private_data = data; + dau.mgr[slot].ops = ops; + dau.mgr[slot].slot = slot + 1; + dau.mgr[slot].occupied = TRUE; + + // 创建线程 + if (pthread_create(&dau.mgr[slot].thread_id, NULL, + _dau_thread_func, &dau.mgr[slot]) != 0) + { + ops.cleanup(data); + free(data); + memset(&dau.mgr[slot], 0, sizeof(dau_manager_t)); + pthread_mutex_unlock(&dau.mutex); + return -6; + } + + pthread_mutex_unlock(&dau.mutex); + return 0; +} + +int _dau_remove(int slot) +{ + if (slot < 0 || slot >= MAX_SLOTS) return -1; + + pthread_mutex_lock(&dau.mutex); + + if (!dau.mgr[slot].occupied) + { + pthread_mutex_unlock(&dau.mutex); + return -2; // 槽位空闲 + } + + // 设置停止标志 + dau.mgr[slot].occupied = FALSE; + + // 等待线程结束 + pthread_join(dau.mgr[slot].thread_id, NULL); + + // 清理资源 + dau.mgr[slot].ops.cleanup(dau.mgr[slot].private_data); + free(dau.mgr[slot].private_data); + + memset(&dau.mgr[slot], 0, sizeof(dau_manager_t)); + + pthread_mutex_unlock(&dau.mutex); + return 0; +} + +#endif +int _dau_insert(int slot, DauType type) +{ + if (slot < 0 || slot >= MAX_SLOTS) + { + return E_BAD_PARAM; + } + + pthread_mutex_lock(&board_mutex); + + if (daus[slot].state != DAU_STATE_DISCONNECTED) + { + pthread_mutex_unlock(&board_mutex); + return E_ERROR; + } + + // 分配私有数据 + void *priv_data = _dau_alloc_private_data(type, slot); + if (!priv_data) + { + pthread_mutex_unlock(&board_mutex); + return E_ERROR; + } + + // 更新板卡信息 + daus[slot].type = type; + daus[slot].state = DAU_STATE_CONNECTED; + daus[slot].private_data = priv_data; + daus[slot].slot = slot; + + pthread_mutex_unlock(&board_mutex); + + printf("Board inserted in slot %d (Type: %s)\n", + slot, (type == DAU_TYPE_UDP) ? "UDP" : "RS485"); + return E_NONE; +} + +int _dau_remove(int slot) +{ + pthread_mutex_lock(&board_mutex); + + if (daus[slot].state == DAU_STATE_DISCONNECTED) + { + pthread_mutex_unlock(&board_mutex); + return; + } + + // 释放资源 + _dau_free_private_data(daus[slot].type, daus[slot].private_data); + + // 重置板卡信息 + daus[slot].type = DAU_TYPE_NONE; + daus[slot].state = DAU_STATE_DISCONNECTED; + daus[slot].private_data = NULL; + + pthread_mutex_unlock(&board_mutex); + + printf("Board removed from slot %d\n", slot); +} + +int _dau_response(int slot, char *buf, int len) +{ + printf("_dau_response: slot=%d len=%d\n", slot, len); + + + if (slot >= 0 && slot < MAX_SLOTS) + { + if (daus[slot].type == DAU_TYPE_UDP) + { + udp_client_data *client = (udp_client_data*)daus[slot].private_data; + sendto(udp_socket, buf, len, 0, + (struct sockaddr*)&client->addr, sizeof(client->addr)); + } + else if (daus[slot].type == DAU_TYPE_RS485) + { + rs485_device_data *dev = (rs485_device_data*)daus[slot].private_data; + write(dev->fd, buf, len); + } + } +} + + +#if 0 +int main() { + _board_init_system(); + register_upload_callback(example_upload_callback); + + // 插入UDP板卡 + _board_insert(0, BOARD_UDP, "192.168.1.100:5000:UDP_CARD_1"); + _board_insert(1, BOARD_UDP, "192.168.1.101:5000:UDP_CARD_2"); + _board_insert(2, BOARD_UDP, "192.168.1.102:5000:UDP_CARD_3"); + _board_insert(3, BOARD_UDP, "192.168.1.103:5000:UDP_CARD_4"); + + // 插入RS485板卡 + _board_insert(4, BOARD_RS485, "/dev/ttyS0:115200:RS485_CARD_1"); + _board_insert(5, BOARD_RS485, "/dev/ttyS1:115200:RS485_CARD_2"); + + // 模拟运行 + sleep(5); + + // 拔出板卡示例 + _board_remove(2); + + sleep(2); + + // 重新插入板卡 + _board_insert(2, BOARD_UDP, "192.168.1.104:5000:UDP_CARD_NEW"); + + sleep(5); + + _board_shutdown_system(); + return 0; +} +#endif + +// 初始化UDP服务器 +int _dau_init_udp_server() +{ + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) + { + perror("UDP socket creation failed"); + return -1; + } + + struct sockaddr_in server_addr = + { + .sin_family = AF_INET, + .sin_port = htons(UDP_PORT), + .sin_addr.s_addr = INADDR_ANY + }; + + if (bind(sock, (struct sockaddr*)&server_addr, sizeof(server_addr))) + { + perror("UDP bind failed"); + close(sock); + return -1; + } + return sock; +} + + +void *_dau_manager_handle(void *arg) +{ + prctl(PR_SET_NAME, "CSG_RCVE", 0, 0, 0); + + while(1) + { + sleep(1); + + } + + return NULL; +} + +void *_dau_udp_receive_handle(void *arg) +{ + prctl(PR_SET_NAME, "DAU_RCVE", 0, 0, 0); + + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + char buffer[2048]; + + while(1) + { + ssize_t len = recvfrom(udp_socket, buffer, sizeof(buffer), 0, + (struct sockaddr*)&client_addr, &addr_len); + if (len <= 0) continue; + + buffer[len] = '\0'; + _print_sockaddr_in(&client_addr); + + // 查找匹配的UDP板卡 + pthread_mutex_lock(&board_mutex); + for (int i = 0; i < UDP_SLOTS; i++) + { + //printf("state=%d\n", daus[i].state); + if (daus[i].state == DAU_STATE_DISCONNECTED) + continue; + + udp_client_data *client = (udp_client_data *)daus[i].private_data; + if (memcmp(&client->addr, &client_addr, sizeof(client_addr)) == 0) + { + break; + } + + // 如果是新连接 + if (daus[i].state == DAU_STATE_CONNECTED && + client->addr.sin_port == 0) + { + memcpy(&client->addr, &client_addr, sizeof(client_addr)); + daus[i].state = DAU_STATE_REGISTERED; + break; + } + } + pthread_mutex_unlock(&board_mutex); + + // 处理数据 + if (!csg.is_connect) + continue; + _dau_find_proper_function(buffer); + + usleep(1000); + + } + + return NULL; +} + + +int32_t dau_handle_init(void) +{ + int32_t rv = 0; + + memset(&daus, 0, sizeof(dau_t)*MAX_SLOTS); + + + //cmd_install_element(CONFIG_NODE, &csg_server_set_cmd); + cmd_install_element(COMMON_NODE, &dau_add_cmd); + cmd_install_element(COMMON_NODE, &no_dau_add_cmd); + //cmd_install_element(COMMON_NODE, &csg_file_cmd); + + /* 注册配置保存函数 */ + //rv = cmd_config_node_config_register(CONFIG_PRI_CSG, _csg_config_save); + if (rv != E_NONE) + { + log_err(LOG_CSG, "Command save register ERROR %d!", rv); + return rv; + } + + return E_NONE; +} + +/* 后台通讯模块初始化. */ +int32_t dau_handle_init_after(void) +{ + if (0 == udp_socket) + { + udp_socket = _dau_init_udp_server(); + } + printf("udp_socket=%d\n", udp_socket); + /* 初始化模块. */ + thread_param_t param = {0}; + + param.priority = 45; + param.thread_name = "DAU_MANAGER"; + create_thread(_dau_manager_handle, ¶m); + + param.priority = 45; + param.thread_name = "DAU_RECV"; + create_thread(_dau_udp_receive_handle, ¶m); + + return E_NONE; +} + +#endif diff --git a/app/lib/a_process/pd_hf.c b/app/lib/a_process/pd_hf.c new file mode 100755 index 0000000..2429998 --- /dev/null +++ b/app/lib/a_process/pd_hf.c @@ -0,0 +1,192 @@ +/****************************************************************************** + * file lib/process/pd_hf.c + * author YuLiang + * version 1.0.0 + * date 05-March-2025 + * brief This file provides all the HF operation functions. + * + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2025 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef CFG_DEV_TYPE_LAND_PD +/* 标准C库头文件. */ +#include + +/* 用户代码头文件. */ +#include "pd_main.h" +#include "pd_csg.h" +#include "pd_dau.h" + +/* Private define ------------------------------------------------------------*/ + +/* Private macro -------------------------------------------------------------*/ + +/* Private typedef -----------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ + + +/* Private function prototypes -----------------------------------------------*/ +void _hf_contact_recv(int slot, char *pkt); +void _hf_heartbeat_recv(int slot, char *pkt); +void _hf_real_image_recv(int slot, char *pkt); +void _hf_trend_recv(int slot, char *pkt); +void _hf_event_recv(int slot, char *pkt); + +// 命令映射表 +static command_entry hf_request_command_table[] = +{ + {CSG_C_CONTACT, _hf_contact_recv}, + {CSG_C_HEARTBEAT, _hf_heartbeat_recv}, + {CMD_INVALID, NULL} +}; + +// 命令映射表 +static command_entry hf_prv_request_command_table[] = +{ + {CSG_PRV_TREND, _hf_event_recv}, + {CSG_PRV_REAL_PRPS, _hf_event_recv}, + {CSG_PRV_EVENT, _hf_event_recv}, + {CMD_INVALID, NULL} +}; + +/* Interface functions -------------------------------------------------------*/ + +void _hf_contact_recv(int slot, char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + csg_contact_t *pnet = (csg_contact_t *)(pkt + sizeof(csg_pkt_head_t)); + csg_add_dau_t snd = {0} ; + dau_info_t *pinfo = &daus[slot].info; + + memset(pinfo, 0, sizeof(dau_info_t)); + pinfo->type_m = pnet->type_m; + pinfo->type_s = pnet->type_s; + pinfo->dev_id = pnet->dev_id; + strncpy(pinfo->hostname, pnet->hostname, sizeof(pinfo->hostname) - 1); + pinfo->factory_date = pnet->factory_date; + pinfo->deployment_date = pnet->deployment_date; + strncpy((char *)pinfo->app_version, (char *)pnet->app_version, sizeof(pinfo->app_version) -1); + strncpy((char *)pinfo->app_compile_time, (char *)pnet->app_compile_time, sizeof(pinfo->app_compile_time) -1); + strncpy((char *)pinfo->hardware_version, (char *)pnet->hardware_version, sizeof(pinfo->hardware_version) -1); + strncpy((char *)pinfo->FPGA_version, (char *)pnet->FPGA_version, sizeof(pinfo->FPGA_version) -1); + pinfo->ip = pnet->ip; + pinfo->mask = pnet->mask; + pinfo->gw = pnet->gw; + memcpy(pinfo->mac, pnet->mac, sizeof(pinfo->mac)); + pinfo->server_port = pnet->server_port; + pinfo->server_ipv4 = pnet->server_ipv4; + memcpy(pinfo->port, pnet->port, sizeof(pinfo->port)); + memcpy(pinfo->port_type, pnet->port_type, sizeof(pinfo->port_type)); + + snd.dev_id = pnet->dev_id; + snd.slot = slot; + snd.status = 1; + memcpy(snd.port, pnet->port, sizeof(pnet->port)); + memcpy(snd.port_type, pnet->port_type, sizeof(pnet->port_type)); + memcpy(pkt + sizeof(csg_pkt_head_t), (char *)&snd, sizeof(csg_add_dau_t)); + head->slot = slot; + head->sdev_id = head->dev_id; + printf("recv contact dev_id = 0x%x slot = %d\n", snd.dev_id, snd.slot); + + _csg_send_data(CSG_REPLY, CSG_C_ADD_DAU, pkt, sizeof(csg_add_dau_t)); +} + +void _hf_heartbeat_recv(int slot, char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + + printf("_hf_heartbeat_recv slot = %d\n", slot); + head->cmd_type = CSG_REQUEST; + head->cmd = CSG_C_HEARTBEAT; + head->len = CSG_HEAD_LEN + 4; + uint32_t *timestamp = (uint32_t *)(pkt + CSG_HEAD_LEN); + *timestamp = time(NULL); + _dau_response(slot, pkt, head->len); +} + +void _hf_real_image_recv(int slot, char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + head->slot = slot; + head->sdev_id = head->dev_id; + _csg_send_data(CSG_PRV_REPLY, CSG_PRV_REAL_PRPS, pkt, head->len - sizeof(csg_pkt_head_t)); +} + +void _hf_trend_recv(int slot, char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + head->slot = slot; + head->sdev_id = head->dev_id; + _csg_send_data(CSG_PRV_REPLY, CSG_PRV_TREND, pkt, head->len - sizeof(csg_pkt_head_t)); +} + +void _hf_event_recv(int slot, char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t*)pkt; + head->slot = slot; + head->sdev_id = head->dev_id; + _csg_send_data(CSG_PRV_REPLY, CSG_PRV_EVENT, pkt, head->len - sizeof(csg_pkt_head_t)); +} + +int32_t _hf_recv_process(int slot, char *pkt) +{ + csg_pkt_head_t *head = (csg_pkt_head_t *)pkt; + command_handler handle = NULL; + + /* 报文头和 CRC 校验. */ + LD_E_RETURN(DBG_M_PD_CSG_ERR, _csg_pkt_check(pkt)); + + if (CSG_REPLY == head->cmd_type) + { + handle = _csg_get_table_handle(hf_request_command_table, head->cmd); + if (handle) + { + handle(slot, pkt); + } + } + else if (CSG_PRV_REPLY == head->cmd_type) + { + handle = _csg_get_table_handle(hf_prv_request_command_table, head->cmd); + if (handle) + { + handle(slot, pkt); + } + } + return E_NONE; +} + + +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/a_process/pd_iron.c b/app/lib/a_process/pd_iron.c new file mode 100644 index 0000000..e69de29 diff --git a/app/lib/a_process/pd_main.c b/app/lib/a_process/pd_main.c new file mode 100755 index 0000000..a6deddf --- /dev/null +++ b/app/lib/a_process/pd_main.c @@ -0,0 +1,632 @@ +/***************************************************************************** + * file lib/process/pd_main.c + * author YuLiang + * version 1.0.0 + * date 07-Feb-2023 + * brief This file provides all the partial discharge related operation functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef CFG_DEV_TYPE_LAND_PD +/* 标准C库头文件. */ +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "pd_csg.h" +#include "pd_main.h" +#include "pd_dau.h" + + +/* Private typedef -----------------------------------------------------------*/ + +/* Private define ------------------------------------------------------------*/ +#define PD_BOARD_PORT 12345 + +/* Private macro -------------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +pd_data_t pd_data; +pd_config_t pd_config; +pd_state_t pd_state; +cmd_node_t pd_port_node = +{ + PORT_NODE, + CONFIG_NODE, + 1, + NULL, +}; + +/* DAU 端口类型分类. */ +static const char *pd_sen_type_str[PD_SEN_TYPE_COUNT] = +{ + "", + "sig", + "noise", + "sig-noise" +}; + +/* DAU 端口降噪类型分类. */ +static const char *pd_noise_type_str[PD_DENOISE_TYPE_COUNT] = +{ + "none", + "auto", + "manual", + "variance" +}; + +/* Private function prototypes -----------------------------------------------*/ +extern int32_t _pd_port_str_to_unit_port(const char *port_str, uint8_t *unit, uint8_t *port); + +/* Internal functions --------------------------------------------------------*/ + +/* 4G 模块是否使能. */ +CMD(pd_4G_enable, + pd_4G_enable_cmd, + "wireless (enable|disable)", + "Wireless\n" + "Enable\n" + "Disable\n") +{ + uint8_t enable = FALSE; + + if (0 == strncmp(argv[0], "e", 1)) + { + enable = TRUE; + } + else + { + enable = FALSE; + } + + pd_config.config.is_4G_enable = enable; + + return CMD_SUCCESS; +} + +/* 4G 模块 APN. */ +CMD(pd_4G_APN, + pd_4G_APN_cmd, + "wireless apn WORD", + "Wireless\n" + "APN\n" + "Enable\n" + "Disable\n") +{ + snprintf(pd_config.config.APN, PD_4G_APN_LEN, "%s", argv[0]); + + return CMD_SUCCESS; +} + +/* 通信协议选择. */ +CMD(pd_protocol_type, + pd_protocol_type_cmd, + "protocol-type (land|csg)", + "Protocol type\n" + "Land\n" + "Csg\n") +{ + uint8_t type = 0; + + if (0 == strncmp(argv[0], "land", 4)) + { + type = PD_PROTOCOL_LAND; + } + else if (0 == strncmp(argv[0], "csg", 3)) + { + type = PD_PROTOCOL_CSG; + } + else + { + type = PD_PROTOCOL_LAND; + } + + pd_config.config.protocol_type = type; + + return CMD_SUCCESS; +} + +/* 显示 DAU 状态. */ +CMD(show_pd, + show_pd_cmd, + "show pd", + "Show\n" + "Partial discharge\n") +{ + pd_show(); + + return CMD_SUCCESS; +} + +/* 配置保存函数. */ +int _pd_config_save(vty_t* vty) +{ + int16_t i = 0; + + switch (pd_config.config.protocol_type) + { + case PD_PROTOCOL_LAND: + vty_out(vty, "protocol-type land%s", VTY_NEWLINE); + i++; + break; + case PD_PROTOCOL_CSG: + vty_out(vty, "protocol-type csg%s", VTY_NEWLINE); + i++; + break; + default: + break; + } + + vty_out(vty, "evnet-amount %d%s", pd_config.config.event_storage, VTY_NEWLINE); + i++; + vty_out(vty, "trend-interval %d%s", pd_config.config.trend_period / 60, VTY_NEWLINE); + i++; + vty_out(vty, "trend-amount %d%s", pd_config.config.trend_storage, VTY_NEWLINE); + i++; + + vty_out(vty, "heartbeat-interval %d%s", pd_config.config.heartbeat_period, VTY_NEWLINE); + i++; + + switch(pd_config.config.sync_mode) + { + case PD_SYNC_PT: + vty_out(vty, "sync-mode pt%s", VTY_NEWLINE); + i++; + break; + case PD_SYNC_INSIDE: + vty_out(vty, "sync-mode inside%s", VTY_NEWLINE); + i++; + break; + case PD_SYNC_OUTSIDE: + vty_out(vty, "sync-mode outside%s", VTY_NEWLINE); + i++; + break; + default: + break; + } + + vty_out(vty, "sync-inside %d%s", pd_config.config.power_frequency, VTY_NEWLINE); + i++; + + switch(pd_config.config.pps_mode) + { + case PD_PPS_AUTO: + vty_out(vty, "pps-mode auto%s", VTY_NEWLINE); + i++; + break; + case PD_PPS_MASTER: + vty_out(vty, "pps-mode master%s", VTY_NEWLINE); + i++; + break; + case PD_PPS_SLAVE: + vty_out(vty, "pps-mode slave%s", VTY_NEWLINE); + i++; + break; + default: + break; + } + + vty_out(vty, "wireless %s%s", pd_config.config.is_4G_enable ? "enable" : "disable", VTY_NEWLINE); + i++; + vty_out(vty, "wireless apn %s%s", pd_config.config.APN, VTY_NEWLINE); + i++; + + return i; +} + +/* 将端口字符串, 转换成 unit port 格式. */ +int32_t _pd_port_str_to_unit_port(const char *port_str, uint8_t *unit, uint8_t *port) +{ + char *str = NULL; + char *p = NULL; + char temp[8]; + uint8_t len = 0; + uint8_t i = 0; + + snprintf(temp, 8, "%s", port_str); + str = strtok_r(temp, "/", &p); + while(str != NULL) + { + /* 检查长度. */ + if (len >= 2) + { + return E_BAD_PARAM; + } + + /* 检查字符串 */ + for(i = 0; str[i] && str[i] != '\0'; i++) + { + if (!(str[i] >= '0' && str[i] <= '9')) + { + return E_BAD_PARAM; + } + } + + /* 检查数据长度 */ + if (i != 1) + { + return E_BAD_PARAM; + } + + /* 读取板卡和端口号. */ + if (0 == len) + { + *unit = strtol(str, NULL, 10) - 1; + } + else + { + *port = strtol(str, NULL, 10) - 1; + } + len++; + + /* 获取下个数据 */ + str = strtok_r(NULL, "/", &p); + } + + return E_NONE; +} + +/* 广播接收程序 */ +void *_pd_broadcast_handle(void *arg) +{ + struct sockaddr_in broad_addr; + struct sockaddr_in peer_addr; + socklen_t addr_len = 0; + char packet[1024]; + int fd = 0; + int len = 0; + int rv = -1; + + + /* 创建报文套接字 */ + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (-1 == fd) + { + log_err(LOG_PD, "Fail to open broadcast socket."); + return NULL; + } + + bzero(&broad_addr, sizeof(broad_addr)); + broad_addr.sin_family = AF_INET; + broad_addr.sin_port = htons(PD_BOARD_PORT); + broad_addr.sin_addr.s_addr = INADDR_ANY; + + rv = bind(fd, (struct sockaddr *)&broad_addr, sizeof(broad_addr)); + if (-1 == rv) + { + log_err(LOG_PD, "Fail to bind broad socket."); + return NULL; + } + + while (1) + { + /* 接收广播 */ + len = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr *)&peer_addr, &addr_len); + if (-1 == len) + { + continue; + } + + packet[len] = '\0'; + if (strncmp(packet, "hello", 5) == 0) + { + len = snprintf(packet, 1024, "world,GIS7.0_V%s,", softversion_get()); + snprintf(packet + len, 1024 - len, "%s", device_info.hostname); + sendto(fd, packet, strlen(packet) + 1, 0, (struct sockaddr *)&peer_addr, sizeof(peer_addr)); + } + } + + /* 关闭套接字 */ + close(fd); + return NULL; +} + +int32_t _pd_broadcast_init(void) +{ + struct sched_param param; + pthread_attr_t attr; + pthread_t pid; + int32_t rv = E_NONE; + + /* 初始化广播报文处理线程. */ + /* 配置线程RR调度, 优先级25 */ + pthread_attr_init(&attr); + param.sched_priority = 25; + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + rv = pthread_create(&pid, &attr, _pd_broadcast_handle, NULL); + if (rv != 0) + { + log_err(LOG_PD, "PD can't create broadcast pthread %d!", rv); + return E_SYS_CALL; + } + else + { + thread_m_add("PD_BROADCAST", pid); + } + pthread_attr_destroy(&attr); + + return E_NONE; +} + +/* 4G 模块服务程序 */ +void *_pd_4G_handle(void *arg) +{ + struct ifaddrs *ifa = NULL, *ifList = NULL; + int8_t err_cnt = 0; + bool is_found = FALSE; + char cmd[256]; + + /* 上电拨号 */ + sleep(30); + snprintf(cmd, 256, "/etc/ppp/peers/quectel-pppd.sh /dev/ttyUSB3 %s", pd_config.config.APN); + system(cmd); + + while(1) + { + sleep(40); + + /* 查询 4G 是否连接 */ + is_found = FALSE; + if (getifaddrs(&ifList) < 0) + { + //err_cnt++; + continue; + } + + for(ifa = ifList; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == NULL) + { + continue; + } + + if (ifa->ifa_addr->sa_family != AF_INET) + { + continue; + } + + if (strncmp(ifa->ifa_name, "ppp0", 4)) + { + continue; + } + + is_found = TRUE; + break; + } + + freeifaddrs(ifList); + + /* 没有连接, 重新拨号 */ + if (is_found) + { + pd_state.is_4G_connect = TRUE; + err_cnt = 0; + continue; + } + else + { + pd_state.is_4G_connect = FALSE; + DBG(DBG_M_PD, "4G connect start...\r\n"); + err_cnt++; + if(err_cnt > 64) + { + /* 每 64 分钟没有拨号成功则重启 */ + reboot_system(LOG_PD, REBOOT_4G_ERROR); + continue; + } + + /* 每 8 分钟拨号一次 */ + if (0 == (err_cnt & 0x7)) + { + /* 重新拨号 */ + log_warn(LOG_PD, "Wireless dialing restart!"); + system("/etc/ppp/peers/quectel-ppp-kill"); + sleep(20); + snprintf(cmd, 256, "/etc/ppp/peers/quectel-pppd.sh /dev/ttyUSB3 %s", pd_config.config.APN); + system(cmd); + } + } + } + + return NULL; +} + +int32_t _pd_main_init(void) +{ + uint8_t i = 0; + uint8_t j = 0; + int32_t rv = 0; + + /* 初始化基本参数. */ + for(i = 0; i < PD_DAU_SUM; i++) + { + for(j = 0; j < PD_DAU_PORT_SUM; j++) + { + pd_config.config_port[i][j].port_type = PD_PORT_TYPE_HF; + pd_config.config_port[i][j].sensor_type = PD_SEN_TYPE_SIG; + pd_config.config_port[i][j].is_auto_noise = TRUE; + pd_config.config_port[i][j].filter = CSG_FILTER_TYPE_FR; + + pd_config.config_real[i][j].filter_cfg = CSG_FILTER_TYPE_FR; + pd_config.config_real[i][j].denoise_type = PD_DENOISE_TYPE_AOTU; + pd_config.config_real[i][j].denoise_manual = 1000; + + pd_config.config_port[i][j].denoise_type = PD_DENOISE_TYPE_AOTU; + pd_config.config_port[i][j].denoise_variance = 100; + pd_config.config_port[i][j].burst_time = 5; + pd_config.config_port[i][j].burst_thr = 800; + pd_config.config_port[i][j].event_counter_h = 8000; + pd_config.config_port[i][j].event_sec_h = 800; + pd_config.config_port[i][j].event_counter_thr_h = 8000; + pd_config.config_port[i][j].event_thr_h = 1000; + pd_config.config_port[i][j].event_counter_l = 8000; + pd_config.config_port[i][j].event_sec_l = 800; + pd_config.config_port[i][j].event_counter_thr_l = 8000; + pd_config.config_port[i][j].event_thr_l = 1000; + + pd_config.config_port[i][j].denoise_manual = 100; + pd_config.config_port[i][j].denoise_auto = 100; + pd_config.config_port[i][j].auto_noise_cnt = 1600; + } + } + + pd_config.config.protocol_type = PD_PROTOCOL_LAND; + pd_config.config.power_frequency = 500; + pd_config.config.sync_mode = PD_SYNC_PT; + pd_config.config.is_4G_enable = FALSE; + snprintf(pd_config.config.APN, PD_4G_APN_LEN, "3gnet"); + + pd_config.config.trend_period = 900; + pd_config.config.trend_storage = 10; + pd_config.config.event_storage = 500; + pd_config.config.heartbeat_period = 5; + + /* 注册配置保存函数 */ + rv = cmd_config_node_config_register(CONFIG_PRI_PD, _pd_config_save); + if (rv != E_NONE) + { + log_err(LOG_PD, "Command save register ERROR %d!", rv); + return rv; + } + + /* 注册端口节点. */ + pd_port_node.prompt = XMALLOC(MTYPE_DAU, PD_PORT_PROMPT_LEN); + pd_port_node.configs = array_init(PD_PORT_CMD_PRI_COUNT, MTYPE_DAU); + + cmd_install_element(CONFIG_NODE, &pd_4G_enable_cmd); + cmd_install_element(CONFIG_NODE, &pd_4G_APN_cmd); + cmd_install_element(CONFIG_NODE, &pd_protocol_type_cmd); + + + + cmd_install_element(COMMON_NODE, &show_pd_cmd); + + return E_NONE; +} + +int32_t _pd_main_init_after(void) +{ + struct sockaddr_in addr; + thread_param_t param = {0}; + uint8_t i = 0; + uint8_t j = 0; + char cmd[256]; + + /* 初始化基本参数 */ + if (pd_config.config.is_4G_enable) + { + param.arg = NULL; + param.priority = 20; + param.thread_name = "4G"; + param.log_module = LOG_PD; + create_thread(_pd_4G_handle, ¶m); + } + else + { + addr.sin_addr.s_addr = device_info.gw; + snprintf(cmd, 256, "route add default gw %s", inet_ntoa(addr.sin_addr)); + printf("%s\r\n", cmd); + system(cmd); + } + + return E_NONE; +} + +/* Interface functions -------------------------------------------------------*/ +/* description: 局放程序入口函数. + param: + return: */ +int32_t pd_main(void) +{ + int32_t rv = 0; + + rv |= _pd_main_init(); + rv |= dau_handle_init(); + rv |= csg_handle_init(); + //rv |= debug_handle_init(); + + return E_NONE; +} + +int32_t pd_main_after(void) +{ + int32_t rv = E_NONE; + + rv |= _pd_main_init_after(); + rv |= dau_handle_init_after(); + rv |= csg_handle_init_after(); + //rv |= debug_handle_init_after(); + rv |= _pd_broadcast_init(); + + return rv; +} + +int32_t pd_port_cmd_config_register(int32_t pri, pd_port_cmd_save_config_f *func) +{ + cmd_node_t *node = &pd_port_node; + + /* 参数检查 */ + if (pri >= PD_PORT_CMD_PRI_COUNT || !func) + { + return E_BAD_PARAM; + } + + /* 加入列表 */ + array_set(node->configs, pri, func, MTYPE_DAU); + + return 0; +} + +void pd_show(void) +{ + printh("Connect: %s\r\n", csg.is_connect ? "yes" : "no"); + printh("Synchronization: %s\r\n", pd_state.sync ? "valid" : "invalid"); + printh("PT: %s\r\n", (pd_state.state & 0x2) ? "valid" : "invalid"); + printh("PPS: %s\r\n", (pd_state.state & 0x4) ? "valid" : "invalid"); + printh("PPS mode: %s\r\n", (pd_state.state & 0x8) ? "slave" : "master"); +} +#else +int32_t PD_main(void) +{ + return 0; +} +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/lib/a_process/pd_uhf.c b/app/lib/a_process/pd_uhf.c new file mode 100644 index 0000000..e69de29 diff --git a/app/lib/a_process/pd_upgrade.c b/app/lib/a_process/pd_upgrade.c new file mode 100755 index 0000000..a70d2cf --- /dev/null +++ b/app/lib/a_process/pd_upgrade.c @@ -0,0 +1,550 @@ +/***************************************************************************** + * file lib/process/pd_upgrade.c + * author WangBo + * version 1.0.0 + * date 07-Feb-2023 + * brief This file provides all the softwave upgrade related operation functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2024 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef CFG_DEV_TYPE_LAND_PD +/* 标准 C 库头文件 */ +#include +#include +#include +#include +#include +#include +#include + +/* 第三方头文件 */ + +/* 私有头文件 */ +#include "main.h" +#include "pd_upgrade.h" +#include "pd_dau.h" +#include "pd_csg.h" +#include "pd_dbg.h" + +/* Private define ------------------------------------------------------------*/ +#define PD_UPG_READ_SIZE (1024*1024) +#define PD_UPG_HARDWARE_VERSON "GIS6.0" + +/* Private macro -------------------------------------------------------------*/ + +/* Private typedef -----------------------------------------------------------*/ +typedef void (*pd_upg_sighandler)(int); + +/* Private variables ---------------------------------------------------------*/ +static pd_upg_ctrl_t pd_upg_ctrl; +static const char *pd_upg_type_str[PD_UPG_SEL_MAX] = +{ + "all", + "cmu", + "dau" +}; + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +/* description: 调用 system 下发 shell 命令 + param: cmd_line -- 需要执行的命令 + return: */ +int _pd_upg_system(const char *cmd_line) +{ + int ret = 0; + pd_upg_sighandler old_handler; + + old_handler = signal(SIGCHLD, SIG_DFL); + ret = system(cmd_line); + signal(SIGCHLD, old_handler); + + return ret; +} + +/* description: 提取升级数据写成文件 + param: addr -- 起始地址 + len -- 大小 + name -- 文件名字 + return: E_NONE - 成功, E_XXXX - 失败 */ +int32_t _pd_upg_write_file(int32_t addr, int32_t size, const char *name) +{ + int fp_wr = -1; + int fp_rd = -1; + char *buf = NULL; + int32_t len = 0; + int32_t len_wr = 0; + int32_t len_rd = 0; + int32_t rv = E_NONE; + + /* 打开文件 */ + fp_wr = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0777); + if (fp_wr <= 0) + { + DBG(DBG_M_PD_UPGRADE, "Open %s failed!\n", name); + return E_SYS_CALL; + } + + fp_rd = open(PD_UPG_SOFTWARE, O_RDONLY); + if (fp_rd <= 0) + { + close(fp_wr); + DBG(DBG_M_PD_UPGRADE, "Open " PD_UPG_SOFTWARE " failed!\n"); + return E_SYS_CALL; + } + + /* 偏移到读取位置 */ + if (lseek(fp_rd, addr, SEEK_SET) < 0) + { + close(fp_wr); + close(fp_rd); + rv = E_SYS_CALL; + DBG(DBG_M_PD_UPGRADE, "Fseek %d failed!\n", addr); + return E_SYS_CALL; + } + + /* 申请 buf */ + buf = (char*)XMALLOC_Q(MTYPE_UPGRADE, PD_UPG_READ_SIZE); + if (!buf) + { + close(fp_wr); + close(fp_rd); + DBG(DBG_M_PD_UPGRADE, "Malloc failed!\n"); + return E_MEM; + } + + while (len < size) + { + len_rd = read(fp_rd, buf, PD_UPG_READ_SIZE); + if (len_rd > 0) + { + len_wr = write(fp_wr, buf, len_rd); + if(len_wr != len_rd) + { + DBG(DBG_M_PD_UPGRADE, "Write %s error!\n", name); + rv = E_SYS_CALL; + break; + } + } + else if(0 == len_rd) + { + break; + } + else + { + DBG(DBG_M_PD_UPGRADE, "Read " PD_UPG_SOFTWARE " error!\n"); + rv = E_SYS_CALL; + break; + } + + len += len_rd; + } + + close(fp_rd); + close(fp_wr); + XFREE(MTYPE_UPGRADE, buf); + _pd_upg_system("sync"); + sleep(1); + + return rv; +} + +/* description: 升级 CMU + param: + return: E_NONE - 成功, E_XXXX - 失败 */ +int32_t _pd_upg_upgrade_dau(void) +{ + uint8_t i = 0; + int32_t offset = 0; + int32_t lenth = 0; + int32_t rv = E_ERROR; + + /* 查找升级文件 */ + for (i = 0; i < PD_UPG_AREA_MAX; i++) + { + if (0 == pd_upg_ctrl.head.arr_info[i].len) + { + continue; + } + + if (PD_UPG_TYPE_DAU == pd_upg_ctrl.head.arr_info[i].type) + { + offset = pd_upg_ctrl.head.arr_info[i].start; + lenth = pd_upg_ctrl.head.arr_info[i].len; + break; + } + } + + /* 没有找到 */ + if (0 == lenth) + { + snprintf(pd_upg_ctrl.msg, 128, "DAU file is not found."); + DBG(DBG_M_PD_UPGRADE, "DAU file is not found.\n"); + return E_NOT_FOUND; + } + + /* 读取并写升级文件 */ + rv = _pd_upg_write_file(offset, lenth, DEBUG_DAU_FILE); + if (rv != E_NONE) + { + snprintf(pd_upg_ctrl.msg, 128, "DAU write file error."); + return E_NOT_FOUND; + } + + return E_NONE; +} + +/* description: 升级 CMU + param: + return: E_NONE - 成功, E_XXXX - 失败 */ +int32_t _pd_upg_upgrade_cmu(void) +{ + char cmd[128] = {0}; + uint8_t i = 0; + int32_t offset = 0; + int32_t lenth = 0; + int32_t rv = E_ERROR; + + /* 查找升级文件 */ + for (i = 0; i < PD_UPG_AREA_MAX; i++) + { + if (0 == pd_upg_ctrl.head.arr_info[i].len) + { + continue; + } + + if (PD_UPG_TYPE_CMU == pd_upg_ctrl.head.arr_info[i].type) + { + offset = pd_upg_ctrl.head.arr_info[i].start; + lenth = pd_upg_ctrl.head.arr_info[i].len; + break; + } + } + + /* 没有找到 */ + if (0 == lenth) + { + snprintf(pd_upg_ctrl.msg, 128, "CMU file is not found."); + DBG(DBG_M_PD_UPGRADE, "CMU file is not found.\n"); + return E_NOT_FOUND; + } + + /* 先备份原始文件 */ + snprintf(cmd, 128, "cp -rf %s bak", PD_UPG_CMU_FILE); + _pd_upg_system(cmd); + _pd_upg_system("sync"); + sleep(1); + + /* 读取并写升级文件 */ + rv = _pd_upg_write_file(offset, lenth, PD_UPG_CMU_FILE_BAK); + if (rv != E_NONE) + { + snprintf(pd_upg_ctrl.msg, 128, "CMU write file error."); + return E_NOT_FOUND; + } + + if (rename(PD_UPG_CMU_FILE_BAK, PD_UPG_CMU_FILE) != 0) + { + snprintf(pd_upg_ctrl.msg, 128, "CMU file rename error."); + DBG(DBG_M_PD_UPGRADE, "Can't rename file %s!\n", PD_UPG_CMU_FILE_BAK); + return E_SYS_CALL; + } + DBG(DBG_M_PD_UPGRADE, "Upgrade CMU Ok.\n"); + + return E_NONE; +} + +/* description: 升级系统 + param: + return: E_NONE - 成功, E_XXXX - 失败 */ +int32_t _pd_upg_upgrade(void) +{ + if (PD_UPG_SEL_ALL == pd_upg_ctrl.upg_type || PD_UPG_SEL_CMU == pd_upg_ctrl.upg_type) + { + LD_E_RETURN(DBG_M_PD_UPGRADE, _pd_upg_upgrade_cmu()); + } + + if (PD_UPG_SEL_ALL == pd_upg_ctrl.upg_type || PD_UPG_SEL_DAU == pd_upg_ctrl.upg_type) + { + LD_E_RETURN(DBG_M_PD_UPGRADE, _pd_upg_upgrade_dau()); + } + + return E_NONE; +} + +/* description: 检测硬件版本号是否匹配 + param: + return: E_NONE - 成功, E_XXXX - 失败 */ +int32_t _pd_upg_hardware_check(void) +{ + if (strncmp(pd_upg_ctrl.head.hard_ver, PD_UPG_HARDWARE_VERSON, strlen(PD_UPG_HARDWARE_VERSON))) + { + DBG(DBG_M_PD_UPGRADE, "Hardware version(%s) not match\n", pd_upg_ctrl.head.hard_ver); + return E_ERROR; + } + + return E_NONE; +} + + +/* description: 验证升级文件, CRC 校验升级文件并对比每段分区确定是否需要升级 + param: + return: E_NONE - 成功, E_XXXX - 失败 */ +int32_t _pd_upg_verify_file(void) +{ + int32_t rv = 0; + int fd = 0; + unsigned int crc = 0; + char *buf = NULL; + + /*读取升级文件头信息*/ + fd = open(PD_UPG_SOFTWARE, O_RDONLY); + if (fd < 0) + { + DBG(DBG_M_PD_UPGRADE, "Open file "PD_UPG_SOFTWARE" failed!\n"); + return E_SYS_CALL; + } + + rv = read(fd, &pd_upg_ctrl.head, sizeof(pd_upg_head_t)); + if (rv != sizeof(pd_upg_head_t) || pd_upg_ctrl.head.magic != PD_UPG_MAGICNUM) + { + close(fd); + DBG(DBG_M_PD_UPGRADE, "Read len %d(%d), magic %x(%x)\n", + rv, sizeof(pd_upg_head_t), pd_upg_ctrl.head.magic, PD_UPG_MAGICNUM); + return E_NOT_IDENTIFY; + } + + /*crc校验*/ + buf = (char*)XMALLOC_Q(MTYPE_UPGRADE, PD_UPG_BLOCK_SIZE); + if (!buf) + { + close(fd); + DBG(DBG_M_PD_UPGRADE, "Malloc error!\n"); + return E_MEM; + } + + while (1) + { + rv = read(fd, buf, PD_UPG_BLOCK_SIZE); + if (rv < 0) + { + DBG(DBG_M_PD_UPGRADE, "Read failed!\n"); + rv = E_SYS_CALL; + break; + } + else if(rv == 0) + { + rv = E_NONE; + break; + } + + crc = crc32(crc, buf, (unsigned long)rv); + } + + if (E_NONE == rv) + { + if (crc != pd_upg_ctrl.head.crc) + { + DBG(DBG_M_PD_UPGRADE, "CRC error!\n"); + rv = E_ERROR;; + } + } + + /* 释放资源 */ + XFREE(MTYPE_UPGRADE, buf); + close(fd); + DBG(DBG_M_PD_UPGRADE, "Verfiy upgrade success.\n"); + return rv; +} + +/* description: 检测是否存在满足升级的条件 + param: + return: E_NONE - 成功, E_XXXX - 失败 */ +int32_t _pd_upg_file_check(void) +{ + int32_t rv = 0; + + /* 读取升级文件并校验 */ + rv = _pd_upg_verify_file(); + if (rv != E_NONE) + { + snprintf(pd_upg_ctrl.msg, 128, "App CRC verify error."); + return rv; + } + + /* 硬件版本号检查 */ + rv = _pd_upg_hardware_check(); + if (rv != E_NONE) + { + snprintf(pd_upg_ctrl.msg, 128, "Hardware is not match."); + return rv; + } + + return rv; +} + +/* description: 软件升级处理线程 + param: parm -- 控制结构体 + return: */ +void *_pd_upg_handle(void *parm) +{ + char cmd[128] = {0}; + int32_t rv = E_ERROR; + + while(1) + { + /* 文件检查 */ + rv = _pd_upg_file_check(); + if (rv != E_NONE) + { + break; + } + + /* 升级系统 */ + rv = _pd_upg_upgrade(); + if (rv != E_NONE) + { + break; + } + + break; + } + + /* 保存升级原始文件 */ + snprintf(cmd, 128, "mv " PD_UPG_SOFTWARE " bak"); + _pd_upg_system(cmd); + _pd_upg_system("sync"); + sleep(1); + + /* 处理升级结果 */ + if (E_NONE == rv) + { + DBG(DBG_M_PD_UPGRADE, "Upgrade ok.\n"); + if (pd_upg_ctrl.upgrade_result) + { + pd_upg_ctrl.upgrade_result(1, "App and dua update successful."); + } + + /* 升级成功, 主动重启 */ + log_notice(LOG_UPGRADE, "Upgrade %s ok.", pd_upg_type_str[pd_upg_ctrl.upg_type]); + reboot_system(LOG_UPGRADE, REBOOT_UPGRADE_ALL); + } + else + { + DBG(DBG_M_PD_UPGRADE, "Upgrade failed.\n"); + if (pd_upg_ctrl.upgrade_result) + { + pd_upg_ctrl.upgrade_result(0, pd_upg_ctrl.msg); + } + } + + pd_upg_ctrl.is_start = FALSE; + pthread_exit(NULL); + return NULL; +} + +/* description: 启动升级线程 + param: + return: E_NONE - 成功, E_XXXX - 失败 */ +int32_t _pd_upg_start(void) +{ + struct sched_param param; + pthread_attr_t attr; + pthread_t pid; + int32_t rv = 0; + + /* 创建线程 配置线程 RR 调度, 优先级 50 */ + pthread_attr_init(&attr); + param.sched_priority = 50; + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + rv = pthread_create(&pid, &attr, _pd_upg_handle, NULL); + if (rv != 0) + { + log_err(LOG_UPGRADE, "PD can't create pthread %d!", rv); + return E_SYS_CALL; + } + else + { + thread_m_add("UPGRADE", pid); + } + pthread_attr_destroy(&attr); + + return E_NONE; +} + +/* Interface functions -------------------------------------------------------*/ +/* description: 软件升级开始 + param: from -- 来自谁的升级 PD_UPG_FROM_xxx + type -- 升级类型 PD_UPG_SEL_xxx + return: E_NONE - 成功, E_XXXX - 失败 */ +int32_t pd_upg_start(uint8_t from, uint8_t type) +{ + int32_t rv = E_NONE; + + if (pd_upg_ctrl.is_start == FALSE) + { + DBG(DBG_M_PD_UPGRADE, "Start upgrade system.\n"); + + pd_upg_ctrl.is_start = TRUE; + pd_upg_ctrl.upg_from = from; + pd_upg_ctrl.upg_type = type; + if (PD_UPG_FROM_CSG == from) + { + pd_upg_ctrl.upgrade_result = csg_upgrade_result_send; + } + else if(PD_UPG_FROM_DBG == from) + { + //pd_upg_ctrl.upgrade_result = debug_upgrade_result_send; + } + else + { + pd_upg_ctrl.upgrade_result = NULL; + } + + /* 初始化模块. */ + LD_E_RETURN(DBG_M_PD_UPGRADE, _pd_upg_start()); + } + else + { + DBG(DBG_M_PD_UPGRADE, "Upgrade is busy!\n"); + rv = E_TIMEOUT; + } + + return rv; +} +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/lib/m_management/array.c b/app/lib/m_management/array.c new file mode 100644 index 0000000..502f1ee --- /dev/null +++ b/app/lib/m_management/array.c @@ -0,0 +1,297 @@ +/***************************************************************************** + * file lib/management/array.c + * author YuLiang + * version 1.0.0 + * date 22-Sep-2021 + * brief This file provides all the array related operation functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* 标准C库头文件. */ + +/* 用户代码头文件. */ +#include "array.h" +/* Private typedef -----------------------------------------------------------*/ + +/* Private define ------------------------------------------------------------*/ + +/* Private macro -------------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +/* 确保设置的数组成员没有内存越界,如果超出数组长度,则重新realloc: + a -- 数组结构 + len -- 需要申请的长度 + mem_type -- 使用的内存模块 + + return: void */ +static void _array_len_ensure(array_t *a, uint32_t len, int32_t mem_type) +{ + if (a->alloced > len) + { + return; + } + + a->index = XREALLOC(mem_type, a->index, sizeof(void *) * (a->alloced * 2)); + memset(&a->index[a->alloced], 0, sizeof(void *) * a->alloced); + a->alloced *= 2; + + if (a->alloced <= len) + { + _array_len_ensure(a, len, mem_type); + } +} + +/* 查找数组a的第一个空位置. + a -- 数组结构 + + return: 第一个空位置索引 */ +uint32_t _array_empty_slot(array_t *a) +{ + uint32_t i = 0; + + for(i = 0; i < a->active; i++) + { + if (NULL == a->index[i]) + { + return i; + } + } + + return i; +} + +/* Interface functions -------------------------------------------------------*/ +/* 初始化数组: + size -- 数组初始大小 + mem_type -- 使用的内存模块 + + return: 数组指针 */ +array_t *array_init(uint32_t size, int32_t mem_type) +{ + array_t *a = XMALLOC(mem_type, sizeof(array_t)); + + /* 如果大小为0,设置默认大小. */ + if (0 == size) + { + size = ARRAY_MIN_SIZE; + } + + a->alloced = size; + a->active = 0; + a->index = XMALLOC(mem_type, sizeof(void *) * size); + + return a; +} + +/* 设置数组相应索引位置的值: + a -- 数组 + i -- 索引 + val -- 值 + mem_type -- 使用的内存模块 + + return: void */ +void array_set(array_t *a, uint32_t i, void *val, int32_t mem_type) +{ + _array_len_ensure(a, i, mem_type); + + a->index[i] = val; + + if (a->active <= i) + { + a->active = i + 1; + } +} + +/* 删除数组相应索引位置的值: + a -- 数组 + i -- 索引 + + return: void */ +void array_unset(array_t *a, uint32_t i) +{ + if (i >= a->alloced) + { + return; + } + + a->index[i] = NULL; + + /* 不是最后一个元素直接返回. */ + if (i + 1 != a->active) + { + return; + } + + /* 如果是最后一个元素,则将当前可使用指针前移. */ + while(--a->active) + { + if (a->index[a->active - 1] != NULL) + { + break; + } + } +} + +/* 将数据放入数组的第一个空位置: + a -- 数组 + val -- 值 + mem_type -- 使用的内存模块 + + return: 数据放入位置的索引值 */ +uint32_t array_append(array_t *a, void *val, int32_t mem_type) +{ + uint32_t i = 0; + + i = _array_empty_slot(a); + _array_len_ensure(a, i, mem_type); + + a->index[i] = val; + + if (a->active <= i) + { + a->active = i + 1; + } + + return i; +} + +/* 复制数组a: + a -- 数组 + mem_type -- 使用的内存模块 + + return: 新数组指针 */ +array_t *array_copy(array_t *a, int32_t mem_type) +{ + unsigned int size = 0; + array_t *array = XMALLOC(mem_type, sizeof(array_t)); + + array->active = a->active; + array->alloced = a->alloced; + + size = sizeof(void *) * (a->alloced); + array->index = XMALLOC(mem_type, size); + memcpy(array->index, a->index, size); + + return array; +} + +/* 将数组a和数组b合并成数组a: + a -- 数组1 + b -- 数组2 + mem_type -- 使用的内存模块 + + return: void */ +void array_merge(array_t *a, array_t *b, int32_t mem_type) +{ + uint32_t size = 0; + + if (0 == b->alloced) + { + return; + } + + size = sizeof(void *) * (a->alloced + b->alloced); + a->index = XREALLOC(mem_type, a->index, size); + memcpy(&a->index[a->active], b->index, sizeof(void *) * b->active); + + a->active = a->active + b->active; + a->alloced = a->alloced + b->alloced; +} + +/* 释放array_t: + a -- 数组 + mem_type -- 使用的内存模块 + + return: void */ +void array_free_wrapper(array_t *a, int32_t mem_type) +{ + XFREE(mem_type, a); +} + +/* 释放array_t->index: + a -- 数组 + mem_type -- 使用的内存模块 + + return: void */ +void array_free_index(array_t *a, int32_t mem_type) +{ + XFREE(mem_type, a->index); +} + +/* 释放array_t和array_t->index: + a -- 数组 + mem_type -- 使用的内存模块 + + return: void */ +void array_free(array_t *a, int32_t mem_type) +{ + XFREE(mem_type, a->index); + XFREE(mem_type, a); +} + +/* 查询数组a的数据i: + a -- 数组 + i -- 元素索引 + + return: 元素指针 */ +void *array_lookup(array_t *a, uint32_t i) +{ + return (i >= a->active) ? NULL : a->index[i]; +} + +/* 获取数组a中有效数据的个数: + a -- 数组 + + return: void */ +uint32_t array_count(array_t *a) +{ + uint32_t i = 0; + uint32_t count = 0; + + for (i = 0; i < a->active; i++) + { + if (a->index[i] != NULL) + { + count++; + } + } + + return count; +} +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/lib/m_management/better_log.c b/app/lib/m_management/better_log.c new file mode 100755 index 0000000..4962480 --- /dev/null +++ b/app/lib/m_management/better_log.c @@ -0,0 +1,535 @@ +/****************************************************************************** + * file lib/management/better_log.c + * author YuLiang + * version 1.0.0 + * date 14-Sep-2021 + * brief This file provides all the log operation functions. + * + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* 标准C库头文件. */ +#include +#include +#include +#include + +/* 用户代码头文件. */ +#include + +/* Private define ------------------------------------------------------------*/ +#define LOG_DB_LOCK pthread_mutex_lock(&log_sys->log_db_mutex) +#define LOG_DB_UNLOCK pthread_mutex_unlock(&log_sys->log_db_mutex) + +#define LOG_OUT 0 +#define LOG_SHOW 1 +/* Private macro -------------------------------------------------------------*/ + +/* Private typedef -----------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +log_sys_t *log_sys; +static char *log_str = NULL; + +/* log等级. */ +static const log_lvl_t log_priority[] = +{ + {LOG_LVL_ERR, "err"}, + {LOG_LVL_WARN, "warn"}, + {LOG_LVL_NOTIF, "notif"}, + {LOG_LVL_INFO, "info"}, + {LOG_LVL_DBG, "debug"}, + {-1, NULL} +}; + +/* log模块. */ +static const log_module_t log_module_names[] = +{ + {LOG_DEFAULT, "DEFAULT"}, + {LOG_CLI, "CLI"}, + {LOG_MEMORY, "MEMORY"}, + {LOG_FIFO, "FIFO"}, + {LOG_GPIO, "GPIO"}, + {LOG_PD, "PD"}, + {LOG_DAU, "DAU"}, + {LOG_CSG, "CSG"}, + {LOG_NET, "NET"}, + {LOG_STORAGE, "STORAGE"}, + {LOG_DEBUG, "DEBUG"}, + {LOG_UPGRADE, "UPGRADE"}, + {-1, NULL} +}; + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +/* 删除多余的 log 保留 5000 条. */ +void _log_clean(void) +{ + log_sys_t *log = log_sys; + char *zErrMsg = NULL; + const char *sql = "delete from better_log where id<(select Max(id) from better_log)-5000;"; + + if (sqlite3_exec(log->db, sql, 0, 0, &zErrMsg) != SQLITE_OK) + { + printh("SQL delete error: %s\r\n", zErrMsg); + sqlite3_free(zErrMsg); + } +} + +/* 文件log输出底层函数. */ +void _log_out_db(LOG_MODULE_E module, LOG_LVL_E priority, const char *va_str) +{ + static uint16_t cnt = 0; + log_sys_t *log = log_sys; + char time_str[TIME_STR_LEN] = {0}; + char *zErrMsg = NULL; + + time_string(log->timestamp_precision, time_str, sizeof(time_str)); + + LOG_DB_LOCK; + + snprintf(log_str, LOG_CMD_LEN, + "INSERT INTO better_log (LEVEL,LOG) VALUES (%d, \"%s %s-%s: %s\"); ", + priority, time_str, log_priority[priority].describe, log_module_names[module].describe, va_str ); //id 已设为自增 + + if (sqlite3_exec( log->db, log_str, 0, 0, &zErrMsg ) != SQLITE_OK) + { + printh("SQL INSERT error: %s\r\n", zErrMsg); + sqlite3_free(zErrMsg); + } + + /*定量清除log*/ + cnt++; + if (cnt >= 500) + { + clock_t begin,end; + begin = clock(); + _log_clean(); + end = clock(); + printh("Log clean runtimer : %lf\r\n",(double)(end-begin)/CLOCKS_PER_SEC); + cnt = 0; + } + + LOG_DB_UNLOCK; +} + +/* 解日志输出fifo数据封装 */ +static void _log_out(_log_out_t *data) +{ + _log_out_db(data->module, data->lvl, data->log_out_str); +} + +/* 串口日志输出 */ +static void _log_out_std( LOG_MODULE_E module, LOG_LVL_E priority, const char *va_str ) +{ + log_sys_t *log = log_sys; + + char time_str[TIME_STR_LEN] = {0}; + time_string(log->timestamp_precision, time_str, sizeof(time_str)); + + printh("%s %s-%s: %s\n", time_str, log_priority[priority].describe, log_module_names[module].describe, va_str); +} + +/* 将日志按照格式串口打印 */ +static int _log_show_print(void *data, int argc, char **argv, char **azColName) +{ + printh("LOG id %s: %s\r\n", argv[0], argv[2]); + + return 0; +} + +/* log打印底层函数 */ +static void _log_show(_log_show_t *data) +{ + log_sys_t *log = log_sys; + char *zErrMsg = NULL; + + LOG_DB_LOCK; + + memset(log_str, 0, LOG_CMD_LEN); + switch (data->type) + { + case LOG_SHOW_CNT: //打印指定数量的日志 + snprintf(log_str, LOG_CMD_LEN, "SELECT * FROM better_log ORDER BY id DESC LIMIT %d;", data->param); + break; + + case LOG_SHOW_LVL: //打印指定等级的日志 + snprintf(log_str, LOG_CMD_LEN, "SELECT * FROM better_log WHERE LEVEL = %d ORDER BY id DESC LIMIT 100;", data->param); + break; + + case LOG_SHOW_KEYWORD: //打印含关键词的日志 + if (data->param <= 0) + { + snprintf(log_str, LOG_CMD_LEN, "SELECT * FROM better_log WHERE LOG GLOB \'*%s*\' ORDER BY id DESC;", + data->log_show_str); + } + else + { + snprintf(log_str, LOG_CMD_LEN, "SELECT * FROM better_log WHERE LOG GLOB \'*%s*\' ORDER BY id DESC LIMIT %d;", + data->log_show_str, data->param); + } + break; + + default: //打印所有日志 + snprintf(log_str, LOG_CMD_LEN, "SELECT * FROM better_log ORDER BY id DESC;"); + break; + } + + if (sqlite3_exec(log->db, log_str, _log_show_print, 0, &zErrMsg) != SQLITE_OK) + { + printh("SQL show error: %s\r\n", zErrMsg); + sqlite3_free(zErrMsg); + } + + LOG_DB_UNLOCK; +} + +/* 封装数据并写fifo */ +int32_t _log_msg_send(uint32_t type, void *data) +{ + _log_msg_t log_msg; + + /* 封装消息. */ + log_msg.type = type; + log_msg.data = data; + + /* 发送消息 */ + if (fifo_write(log_sys->log_fifo_id, (void*)(&log_msg), sizeof(_log_msg_t)) != sizeof(_log_msg_t)) + { + DBG(DBG_M_FIFO, "LOG write ERROR!\r\n"); + return E_ERROR; + } + + return E_NONE; +} + +/* 经由fifo输出日志 */ +void _log_fifo_out(LOG_MODULE_E module, LOG_LVL_E priority, const char *va_str) +{ + _log_out_t *log_data = NULL; + + log_data = XMALLOC_Q(MTYPE_LOG, sizeof(_log_out_t)); + if (!log_data) + { + return; + } + + log_data->module = module; + log_data->lvl = priority; + snprintf(log_data->log_out_str, LOG_STR_LEN, va_str); + + _log_msg_send(LOG_OUT, log_data); +} + +/* 读取log fifo数据循环线程 */ +void *_log_handle(void *arg) +{ + _log_msg_t *recv_msg = NULL; + + while(1) + { + if (fifo_read(log_sys->log_fifo_id, (void **)&recv_msg) != 0) + { + DBG(DBG_M_FIFO, "ERROR at fifo %d read!\r\n", log_sys->log_fifo_id); + continue; + } + + if (recv_msg->type == LOG_OUT) //输出日志,写数据库 + { + _log_out((_log_out_t*) recv_msg->data); + } + else if (recv_msg->type == LOG_SHOW) //打印日志,读数据库 + { + _log_show((_log_show_t*) recv_msg->data); + } + + XFREE(MTYPE_LOG, recv_msg->data); + fifo_push(log_sys->log_fifo_id); + } + return NULL; +} + +/* Interface functions -------------------------------------------------------*/ +/* log打印输出函数. */ +#define BLOG_FUNC(FUNCNAME,PRIORITY) \ +void FUNCNAME(LOG_MODULE_E module, const char *format, ...) \ +{ \ + char str[LOG_STR_LEN] = {0}; \ + log_sys_t *log = log_sys; \ + if (NULL == log) return; \ + va_list args; \ + va_start(args, format); \ + vsnprintf(str, LOG_STR_LEN, format, args); \ + va_end(args); \ + if ((1 << PRIORITY) & log->enable_lvl[LOG_MODE_STDOUT]) \ + _log_out_std(module, PRIORITY, str); \ + if (((1 << PRIORITY) & log->enable_lvl[LOG_MODE_FILE]) && log->db){ \ + if (log_sys->log_fifo_id) \ + _log_fifo_out(module, PRIORITY, str); \ + else \ + _log_out_db(module, PRIORITY, str);} \ +} + +BLOG_FUNC(log_err, LOG_LVL_ERR) +BLOG_FUNC(log_warn, LOG_LVL_WARN) +BLOG_FUNC(log_info, LOG_LVL_INFO) +BLOG_FUNC(log_notice, LOG_LVL_NOTIF) +BLOG_FUNC(log_debug, LOG_LVL_DBG) +#undef BLOG_FUNC + +/* description: log输出. + param: module -- 指定模块 + priority -- 指定优先级 + return: */ +void log_out(LOG_MODULE_E module, LOG_LVL_E priority, const char *format, ...) +{ + char str[LOG_STR_LEN] = {0}; \ + log_sys_t *log = log_sys; + + if (NULL == log) return; + + va_list args; + va_start(args, format); + vsnprintf(str, LOG_STR_LEN, format, args); + va_end (args); + + /* 串口log. */ + if ((1 << priority) & log->enable_lvl[LOG_MODE_STDOUT]) + _log_out_std(module, priority, str); + + /* 判断是否是文件输出 */ + if (((1 << priority) & log->enable_lvl[LOG_MODE_FILE]) && log->db) + _log_out_db(module, priority, str); +} + +/* 打印日志函数 */ +void log_show(int32_t show_cnt, LOG_LVL_E priority, const char *key_word) +{ + _log_show_t *log_data = NULL; + + log_data = XMALLOC_Q(MTYPE_LOG, sizeof(_log_show_t)); + if (!log_data) + { + return; + } + + if (priority != LOG_LVL_MAX) // 按等级打印日志 + { + log_data->type = LOG_SHOW_LVL; + log_data->param = priority; + } + else if (key_word != NULL) // 高级打印功能 + { + log_data->type = LOG_SHOW_KEYWORD; + log_data->param = show_cnt; + snprintf(log_data->log_show_str, LOG_STR_LEN, key_word); + } + else if (show_cnt > 0) // 按数量打印日志 + { + log_data->type = LOG_SHOW_CNT; + log_data->param = show_cnt; + } + else + { + log_data->type = LOG_SHOW_MAX; + } + + _log_msg_send(LOG_SHOW, log_data); +} + +/* description: log系统初始化. + param: + return: (0)完成,(-1)失败 */ +int32_t log_open() +{ + log_sys_t *log = NULL; + int32_t i = 0; + int32_t rv = 0; + char *sql = NULL; + char *zErrMsg = NULL; + + /* 申请内存. */ + log_str = XMALLOC(MTYPE_LOG, LOG_CMD_LEN); + log_sys = XMALLOC(MTYPE_LOG, sizeof(log_sys_t)); + + if (NULL == log_sys) + { + return E_MEM; + } + + log = log_sys; + + pthread_mutex_init(&log->log_db_mutex, NULL); + + /* 打开 log 数据库. */ + log->filename = XSTRDUP(MTYPE_LOG, LOG_FILE); + rv = sqlite3_open(log->filename, &log->db); + if (rv) + { + log->db = NULL; + printf("Can't open database: %s\r\n", sqlite3_errmsg(log->db)); + return E_SYS_CALL; + } + + /* 创建表. */ + sql = "CREATE TABLE IF NOT EXISTS better_log(" \ + "ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," \ + "LEVEL INT8 NOT NULL," \ + "LOG TEXT);"; + if (sqlite3_exec(log->db, sql, 0, 0, &zErrMsg) != SQLITE_OK) + { + printf("SQL create error: %s\r\n", zErrMsg); + sqlite3_free(zErrMsg); + } + + /* 设置默认log级别输出方式 */ + for (i = 0; i < LOG_MODE_MAX; i++) + { + log->enable_lvl[i] = 0; + } + log->enable_lvl[LOG_MODE_FILE] = (1 << LOG_LVL_ERR) | (1 << LOG_LVL_WARN) | (1 << LOG_LVL_NOTIF); + log->enable_lvl[LOG_MODE_STDOUT] = (1 << LOG_LVL_ERR) | (1 << LOG_LVL_WARN) | (1 << LOG_LVL_NOTIF); + log->enable_lvl[LOG_MODE_MONITOR] = (1 << LOG_LVL_ERR) | (1 << LOG_LVL_WARN) | (1 << LOG_LVL_NOTIF); + log->default_lvl = 0; + + /* 清除多余的日志 */ + _log_clean(); + + return E_NONE; +} + +/* description: 配置对应等级的log的输出方式. + param: mode -- 输出方式 + log_level -- 输出等级 + return: */ +void log_set_level(LOG_MODE_E mode, LOG_LVL_E log_level) +{ + log_sys->enable_lvl[mode] |= (1 << log_level); +} + +/* description: 取消对应等级的log的输出方式. + param: mode -- 输出方式 + log_level -- 输出等级 + return: */ +void log_unset_level(LOG_MODE_E mode, LOG_LVL_E log_level) +{ + log_sys->enable_lvl[mode] &= ~(1 << log_level); +} + +/* description: 根据传入的字符串返回相应的log优先级. + param: lvl_name -- 输出等级的字符串 + return: (LOG_LVL_E)输出等级 */ +int32_t log_level_get_by_name(const char *lvl_name) +{ + int32_t level = LOG_LVL_MAX; + + for (level = 0 ; log_priority[level].describe != NULL ; level++) + if (!strncmp(lvl_name, log_priority[level].describe, 2)) + return log_priority[level].lvl; + + return LOG_LVL_MAX; +} + +/* 打印堆栈使用情况: */ +/* description: 根据传入的字符串返回相应的log优先级. + param: priority -- log优先级 + return: */ +void log_backtrace(int32_t priority) +{ + void *array[BACKTRACE_SIZE] = {NULL}; + int32_t size = 0; + int32_t i = 0; + char **strings = NULL; + + size = backtrace(array, BACKTRACE_SIZE); + if ((size <= 0) || ((size_t)size > BACKTRACE_SIZE)) + { + log_err(LOG_DEFAULT, "Cannot get backtrace, returned invalid # of frames %d " + "(valid range is between 1 and %d)", size, BACKTRACE_SIZE); + return; + } + + log_out(LOG_DEFAULT, priority, "Backtrace for %d stack frames:", size); + strings = backtrace_symbols(array, size); + if (!strings) + { + log_out(LOG_DEFAULT, priority, "Cannot get backtrace symbols (out of memory?)"); + for (i = 0; i < size; i++) + log_out(LOG_DEFAULT, priority, "[bt %d] %p", i, array[i]); + } + else + { + for (i = 0; i < size; i++) + log_out(LOG_DEFAULT, priority, "[bt %d] %s",i,strings[i]); + free(strings); + } +} + +int32_t log_handle_init(void) +{ + struct sched_param param; + pthread_attr_t attr; + pthread_t pid; + int rv = 0; + + /* 初始化log fifo */ + log_sys->log_fifo_id = fifo_create(LOG_DB_FIFO, 32); + if (log_sys->log_fifo_id < 0) + { + log_out(LOG_DEFAULT, LOG_LVL_ERR, "Open fifo " LOG_DB_FIFO " error."); + return E_ERROR; + } + + /* 配置线程RR调度,优先级25 */ + pthread_attr_init(&attr); + param.sched_priority = 25; + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + rv = pthread_create(&pid, &attr, _log_handle, NULL); + if (rv != 0) + { + log_out(LOG_DEFAULT, LOG_LVL_ERR, "PD can't create log db pthread %d.", rv); + return E_SYS_CALL; + } + else + { + thread_m_add("LOG_DB_THREAD", pid); + } + pthread_attr_destroy(&attr); + + return E_NONE; +} +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/m_management/cJSON.c b/app/lib/m_management/cJSON.c new file mode 100644 index 0000000..524ba46 --- /dev/null +++ b/app/lib/m_management/cJSON.c @@ -0,0 +1,3119 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 15) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + if (strlen(valuestring) <= strlen(object->valuestring)) + { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} diff --git a/app/lib/m_management/cmd.c b/app/lib/m_management/cmd.c new file mode 100755 index 0000000..3c99e36 --- /dev/null +++ b/app/lib/m_management/cmd.c @@ -0,0 +1,3118 @@ +/****************************************************************************** + * file lib/management/cmd.c + * author YuLiang + * version 1.0.0 + * date 09-Oct-2021 + * brief This file provides all the command operation functions. + * + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* 标准C库头文件. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 外部程序库头文件. */ +#include +#include + +/* 用户代码头文件. */ +#include "main.h" +#include "vty.h" +#include "cmd.h" +#include "mtimer.h" +#include "sockunion.h" + +/* Private define ------------------------------------------------------------*/ +#define VTYSH_CONFIG_R_BUF 512 + +/* Private macro -------------------------------------------------------------*/ + +/* Private typedef -----------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +/* 主机信息结构. */ +host_t host; +device_info_t device_info; +int8_t is_system_init; + +/* 用于补齐回车,比如使用'?'或者'TAB'. */ +static desc_t cmd_desc_enter; +static char *cmd_enter = NULL; + +/* 多线程同时操作配置文件的互斥锁. */ +static pthread_mutex_t m_config_mutex; + +/* 命令行所有节点列表. */ +array_t *cmd_nodes = NULL; + +static cmd_node_t common_node = +{ + COMMON_NODE, + USERNAME_NODE, + 1, + "%s(common)# ", +}; + +static cmd_node_t username_node = +{ + USERNAME_NODE, + USERNAME_NODE, + 0, + "Username: ", +}; + +static cmd_node_t password_node = +{ + PASSWORD_NODE, + USERNAME_NODE, + 0, + "Password: ", +}; + +static cmd_node_t enable_node = +{ + ENABLE_NODE, + USERNAME_NODE, + 1, + "%s# ", +}; + +static cmd_node_t config_node = +{ + CONFIG_NODE, + ENABLE_NODE, + 1, + "%s(config)# ", +}; + +/* shell vty结构体. */ +vty_t *vtysh; + +/* 保存readline命令行内容的buf. */ +static char *line_read; + +/* 保存readline命令行历史的文件. */ +const char history_file[] = "./command_line_history"; + +/* cli线程pid. */ +static pthread_t _cmd_pid; + +extern cmd_node_t pd_port_node; + +extern int32_t vtysh_config_save_bak(int idx); +extern int32_t _config_write_host(vty_t *vty); + +/* 根据传入的字符串,返回相应log模块. */ +uint32_t _mode_match(const char *s) +{ + if (0 == strncmp(s, "f", 1)) + return LOG_MODE_FILE; + else if(0 == strncmp(s, "s", 1)) + return LOG_MODE_STDOUT; + else if(0 == strncmp(s, "m", 1)) + return LOG_MODE_MONITOR; + + return LOG_MODE_MAX; +} + +/* MAC 设置 */ +CMD(mac_set, + mac_set_cmd, + "mac eth0 WORD", + "Set mac\n" + "HH:HH:HH:HH:HH:HH\n") +{ + uint8_t mac[MAC_ADDR_LEN]; + + if (str_to_mac((char*)argv[0], mac) != E_NONE) + { + vty_out(vty, "Mac addr is not valid!%s", VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + + memcpy(device_info.mac, mac, MAC_ADDR_LEN); + vtysh_device_save(); + vtysh_eth0_save(); + + return CMD_SUCCESS; +} + +/* 设置设备类型. */ +CMD(type_id_set_cli, + type_id_set_cmd, + "device type id WORD", + "Device\n" + "Type\n" + "Id\n" + "Ex: DDD.DDD\n") +{ + uint32_t id[2] = {0}; + + if (str_to_id((char*)argv[0], id) != E_NONE) + { + vty_out(vty, "ID is not valid!%s", VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + device_info.type_m = id[0]; + device_info.type_s = id[1]; + vtysh_device_save(); + + return CMD_SUCCESS; +} + +/* 设置设备 ID. */ +CMD(device_id_set_cli, + device_id_set_cmd, + "device id WORD", + "Device\n" + "Id\n" + "Ex: 60100110\n") +{ + device_info.dev_id = strtol(argv[0], NULL, 16); + vtysh_device_save(); + + return CMD_SUCCESS; +} + +/* 设置设备名称. */ +CMD(hostname_set, + hostname_set_cmd, + "hostname WORD", + "Set hostname\n" + "hostname\n") +{ + snprintf(device_info.hostname, FILE_NAME_LEN, "%s", argv[0]); + vtysh_device_save(); + + return CMD_SUCCESS; +} + +/* 基本命令: 进入enable模式. */ +CMD(config_enable, + config_enable_cmd, + "enable", + "Turn on privileged mode command\n") +{ + vty->node = ENABLE_NODE; + + return CMD_SUCCESS; +} + +/* 基本命令:进入shell模式. */ +CMD(bash_open, + bash_open_cmd, + "bash", + "Open a shell terminal\n") +{ + if (vty->type == VTY_TERM || vty->type == VTY_CMD) + { + vty_out(vty, "Entry bash error!%s", VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + + if (system("bash") <= 0) + { + vty_out(vty, "Entry bash error!%s", VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + + return CMD_SUCCESS; +} + +/* 设置终端的显示模式. */ +CMD(terminal_monitor, + terminal_monitor_cmd, + "terminal monitor", + "Set terminal line parameters\n" + "Copy debug output to the current terminal line\n") +{ + vty->monitor = 1; + return CMD_SUCCESS; +} + +/* 设置终端的显示模式. */ +CMD(no_terminal_monitor, + no_terminal_monitor_cmd, + "no terminal monitor", + NO_STR + "Set terminal line parameters\n" + "Copy debug output to the current terminal line\n") +{ + vty->monitor = 0; + return CMD_SUCCESS; +} + +/* 基本命令:进入config模式. */ +CMD(config_terminal, + config_terminal_cmd, + "configure", + "Configuration from vty interface\n") +{ + if (vty_config_lock(vty)) + vty->node = CONFIG_NODE; + else + { + vty_out(vty, "VTY configuration is locked by other VTY%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* 基本命令:显示版本信息. */ +CMD(show_version, + show_version_cmd, + "show version", + SHOW_STR + "Displays zebra version\n") +{ + vty_version_print(vty); + vty_out(vty, "%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +/* 基本命令:退出当前模式. */ +CMD(exit_node, + exit_node_cmd, + "exit", + "Exit current mode\n") +{ + cmd_node_t *node = cmd_node_get(vty->node); + if (NULL == node) + return CMD_WARNING; + + vty->node = node->up_node; + + if (CONFIG_NODE == node->node) + vty_config_unlock(vty); + + return CMD_SUCCESS; +} +ALIAS(exit_node, + quit_node_cmd, + "quit", + "Exit current mode\n") + +CMD(logoff_cli, + logoff_cmd, + "logoff", + "Exit app\n") +{ + log_out(LOG_CLI, LOG_LVL_WARN, "System logout..."); + sleep(1); + exit(-1); + + return CMD_SUCCESS; +} + +CMD(reboot_node, + reboot_cmd, + "reboot", + "reset system\n") +{ + reboot_system(LOG_CLI, REBOOT_SYSTEM_RESET); + + return CMD_SUCCESS; +} + +/* 配置log模块的输出级别. */ +CMD(config_log_level, + config_log_level_cmd, + "log (stdout|file|monitor) "LOG_LEVELS, + "Logging control\n" + "Set stdout logging level\n" + "Set file logging level\n" + "Set terminal line (monitor) logging level\n" + LOG_LEVEL_DESC) +{ + LOG_MODE_E log_mode = _mode_match(argv[0]); + LOG_LVL_E level = log_level_get_by_name(argv[1]); + + if (LOG_LVL_MAX == level) + return CMD_ERR_NO_MATCH; + + if (log_mode >= LOG_MODE_MAX) + return CMD_ERR_NO_MATCH; + + log_set_level(log_mode, level); + + return CMD_SUCCESS; +} + +/* 配置log模块的输出级别. */ +CMD(no_config_log_level, + no_config_log_level_cmd, + "no log (stdout|file|monitor) "LOG_LEVELS, + NO_STR + "Logging control\n" + "Set stdout logging level\n" + "Set file logging level\n" + "Set terminal line (monitor) logging level\n" + LOG_LEVEL_DESC) +{ + uint32_t log_mode = _mode_match(argv[0]); + int32_t level = log_level_get_by_name(argv[1]); + + if (LOG_LVL_MAX == level) + return CMD_ERR_NO_MATCH; + + if (log_mode >= LOG_MODE_MAX) + return CMD_ERR_NO_MATCH; + + log_unset_level(log_mode, level); + + return CMD_SUCCESS; +} + +/* 默认串口输出日志 */ +CMD(show_log_default, + show_log_default_cmd, + "show log", + SHOW_STR + LOG_STR) +{ + log_show( LOG_DEFAULT_SHOW_CNT, LOG_LVL_MAX, NULL ); + return CMD_SUCCESS; +} + +CMD(show_log_level, + show_log_level_cmd, + "show log "LOG_LEVELS, + SHOW_STR + LOG_STR + LOG_LEVEL_DESC) +{ + int32_t level = log_level_get_by_name( argv[0] ); + log_show( LOG_DEFAULT_SHOW_CNT, level, NULL ); + return CMD_SUCCESS; +} + +CMD(show_log_cnt, + show_log_cnt_cmd, + "show log <0-5000>", + SHOW_STR + LOG_STR + "Count of logs, will show all as it == 0, can enter the keyword of logs afterword\n") +{ + int32_t cnt = atoi( argv[0] ); + + log_show( cnt, LOG_LVL_MAX, NULL ); + return CMD_SUCCESS; +} + +CMD(show_log, + show_log_cmd, + "show log <0-5000> WORD", + SHOW_STR + LOG_STR + "Count of logs, will show all as it == 0, can enter the keyword of logs afterword\n" + "Keyword of logs\n") +{ + int32_t cnt = atoi( argv[0] ); + + log_show( cnt, LOG_LVL_MAX, argv[1] ); + return CMD_SUCCESS; +} + +CMD(host_ip_set, + host_ip_set_cmd, + "host ip A.B.C.D A.B.C.D", + "Host ip for produce\n" + "IPv4 type address\n" + "Ip address of host\n" + "Ip mask of host\n") +{ + vtysh_host_addr_set((char*)argv[0], (char*)argv[1]); + + return CMD_SUCCESS; +} + +CMD(default_route_set, + default_route_set_cmd, + "default route A.B.C.D", + "Default route set\n" + "Default route\n" + "Address of default route\n") +{ + vtysh_gateway_set((char*)argv[0]); + + return CMD_SUCCESS; +} + +CMD(factory_date_set, + factory_date_set_cmd, + "factory date WORD WORD", + "factory\n" + "factory date\n" + "date\n" + "time\n") +{ + uint32_t t = 0; + + if (time_str_to_long((char*)argv[0], (char*)argv[1], &t) != E_NONE) + { + vty_out(vty, "Factory date set error!%s", VTY_NEWLINE); + return CMD_SUCCESS; + } + + device_info.factory_date = t; + vtysh_device_save(); + + return CMD_SUCCESS; +} + +CMD(deployment_date_set, + deployment_date_set_cmd, + "deployment date WORD WORD", + "deployment\n" + "deployment date\n" + "date dddd-dd-dd\n" + "time dd:dd:dd\n") +{ + uint32_t t = 0; + + if (time_str_to_long((char*)argv[0], (char*)argv[1], &t) != E_NONE) + { + vty_out(vty, "Factory date set error!%s", VTY_NEWLINE); + return CMD_SUCCESS; + } + + device_info.deployment_date = t; + vtysh_device_save(); + + return CMD_SUCCESS; +} + +/* 把当前配置写入配置文件. */ +CMD(config_write_file, + config_write_file_cmd, + "write file", + "Write running configuration to memory, network, or terminal\n" + "Write to configuration file\n") +{ + if (vtysh_config_save() != E_NONE) + { + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* 在终端上显示当前配置信息. */ +CMD(show_running_config, + show_running_config_cmd, + "show running-config", + SHOW_STR + "running configuration\n") +{ + uint32_t i = 0; + cmd_node_t *node = NULL; + + vty_out(vty, "Current configuration:%s", VTY_NEWLINE); + vty_out(vty, "!%s", VTY_NEWLINE); + + for(i = 0; i < array_active(cmd_nodes); i++) + if ((node = array_get(cmd_nodes, i)) && node->func) + { + if ((*node->func)(vty)) + vty_out(vty, "!%s", VTY_NEWLINE); + } + + vty_out(vty, "end%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +/* 在终端上显示当前配置信息. */ +CMD(show_running_globel, + show_running_globel_cmd, + "show running-config globel", + SHOW_STR + "Running configuration\n" + "Globel\n") +{ + vty_out(vty, "Current configuration:%s", VTY_NEWLINE); + vty_out(vty, "!%s", VTY_NEWLINE); + + _config_write_host(vty); + + vty_out(vty, "end%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +/* 用于自动补齐命令行关键字. */ +void _vtysh_append_word(array_t *cmd_line, char *word, int32_t status) +{ + uint32_t index = array_active(cmd_line) - 1; + uint32_t len = 0; + uint32_t start = 0; + + if (NULL == array_get(cmd_line, index)) + rl_insert_text(word); + else + { + len = strlen(array_get(cmd_line, index)); + start = rl_end - len; + + rl_begin_undo_group(); + rl_delete_text(start, rl_end); + rl_point = start; + rl_insert_text(word); + rl_end_undo_group(); + } + + if (CMD_COMPLETE_FULL_MATCH == status) + rl_insert_text(" "); +} + +/* config模式配置保存函数: vty -- 相应的终端 */ +int32_t _config_write_host(vty_t *vty) +{ + array_t *configs = config_node.configs; + cmd_save_config_f *func = NULL; + uint16_t i = 0; + + /* 其他配置保存 */ + for(i = 0; i < array_active(configs); i++) + { + func = array_lookup(configs, i); + if (!func) + { + continue; + } + + if (func(vty)) + { + vty_out(vty, "!%s", VTY_NEWLINE); + } + } + + return E_NONE; +} + +/* 获取命令串的描述: + string -- 描述字符串(OUT) + + return: 命令行描述字符串,失败返回NULL */ +static char *_cmd_desc_str(const char **string) +{ + const char *cp = NULL; + const char *start = NULL; + char *str = NULL; + int32_t strlen = 0; + + cp = *string; + + if (NULL == cp) + { + return NULL; + } + + /* 跳过空格 */ + while (isspace ((int) *cp) && *cp != '\0') + { + cp++; + } + + if ('\0' == *cp) + { + return NULL; + } + + /* 获取字符串 */ + start = cp; + + while (!(*cp == '\r' || *cp == '\n') && *cp != '\0') + { + cp++; + } + + strlen = cp - start; + str = XMALLOC(MTYPE_CLI ,strlen + 1); + memcpy(str, start, strlen); + *(str + strlen) = '\0'; + + /* 更新描述字符串的指针. */ + *string = cp; + + return str; +} + +/* 解析命令行字符串,将其每个单词拆分存入数组中: + string -- 命令行字符串 + descstr -- 命令行帮助字符串 + + return: 命令行字符串结构数组,失败返回NULL */ +array_t *_cmd_strs_create(const char *string, const char *descstr) +{ + uint32_t multiple = 0; + const char *sp = NULL; + char *word = NULL; + uint32_t len = 0; + const char *cp = NULL; + const char *dp = NULL; + array_t *strs = NULL; + array_t *descs = NULL; + desc_t *desc = NULL; + + cp = string; + dp = descstr; + + if (NULL == cp) + { + return NULL; + } + + strs = array_init(ARRAY_MIN_SIZE, MTYPE_CLI); + + while (1) + { + /* 跳过多余空格 */ + while (isspace((int)(*cp)) && *cp != '\0') + { + cp++; + } + + if ('(' == *cp) + { + multiple = 1; + cp++; + } + + if (')' == *cp) + { + multiple = 0; + cp++; + } + + if ('|' == *cp) + { + if (!multiple) { + log_err(LOG_CLI, "Command parse error!: %s", string); + exit(1); + } + cp++; + } + + while (isspace((int)(*cp)) && *cp != '\0') + { + cp++; + } + + if ('(' == *cp) + { + multiple = 1; + cp++; + } + + if ('\0' == *cp) + { + return strs; + } + + sp = cp; + + while (!(isspace((int) *cp) || '\r' == *cp || '\n' == *cp || ')' == *cp || '|' == *cp ) + && *cp != '\0') + { + cp++; + } + + /* 获取字符串 */ + len = cp - sp; + word = XMALLOC(MTYPE_CLI, len + 1); + memcpy(word, sp, len); + *(word + len) = '\0'; + + /* 创建命令行当个单词结构 */ + desc = XMALLOC(MTYPE_CLI, sizeof(desc_t)); + desc->cmd = word; + desc->str = _cmd_desc_str(&dp); + + if (multiple) + { + /* 多选的命令放在同一个数组下 */ + if (1 == multiple) + { + descs = array_init(ARRAY_MIN_SIZE, MTYPE_CLI); + array_append(strs, descs, MTYPE_CLI); + } + multiple++; + } + else + { + /* 单个命令放在一个独立的数组下 */ + descs = array_init(ARRAY_MIN_SIZE, MTYPE_CLI); + array_append(strs, descs, MTYPE_CLI); + } + array_append(descs, desc, MTYPE_CLI); + } +} + +/* 统计命令有效单词个数: + strs -- 命令行字符串数组 + + return: 命令行包含的字符串的个数 */ +static uint32_t _cmd_descs_size(array_t *strs) +{ + uint32_t i = 0; + uint32_t size = 0; + array_t *descs = NULL; + desc_t *desc = NULL; + + /* 遍历所有字符串 */ + for (i = 0; i < array_active(strs); i++) + { + if (NULL == (descs = array_get(strs, i))) + { + continue; + } + + /* 多选项直接++, 单选项如果不是NULL或者可选项直接++ */ + if (1 == (array_active(descs)) + && (desc = array_get (descs, 0)) != NULL) + { + if (NULL == desc->cmd || CMD_OPTION(desc->cmd)) + { + return size; + } + else + { + size++; + } + } + else + { + size++; + } + } + + return size; +} + +/* 命令行字符串比较函数, 用于cmd_sort_node(): + p -- 命令行节点1 + q -- 命令行节点2 + + return: 等于返回0,大于返回正数,小于返回负数 */ +static int _cmp_node (const void *p, const void *q) +{ + const cmd_element_t *a = *(cmd_element_t * const *)p; + const cmd_element_t *b = *(cmd_element_t * const *)q; + + return strcmp(a->string, b->string); +} + +/* 命令字符串排序: + p -- 命令字符串1 + q -- 命令字符串2 + + return: 等于返回0,大于返回正数,小于返回负数 */ +static int _cmp_desc (const void *p, const void *q) +{ + const desc_t *a = *(desc_t * const *)p; + const desc_t *b = *(desc_t * const *)q; + + return strcmp(a->cmd, b->cmd); +} + +/* 获取命令行节点下所有命令数组: + ntype -- 命令节点 + + return: 节点上所有命令行数组 */ +static array_t *_cmd_node_cmds_get(NODE_TYPE_E ntype) +{ + cmd_node_t *node = array_get(cmd_nodes, ntype); + return node->cmds; +} + +/* 检查字符串str是否符合range: + range -- 范围字符串 + str -- 需要检测字符串 + + return: 匹配返回1 */ +static int _cmd_range_match(const char *range, const char *str) +{ + char *p = NULL; + char buf[DECIMAL_STRLEN_MAX + 1] = {0}; + char *endptr = NULL; + unsigned long min = 0; + unsigned long max = 0; + unsigned long val = 0; + + if (NULL == str) + return 1; + + /* 将字符串转为十进制数. */ + val = strtoul(str, &endptr, 10); + if (*endptr != '\0') + return 0; + + /* 是否符合格式 */ + range++; + p = strchr(range, '-'); + if (p == NULL) + return 0; + if (p - range > DECIMAL_STRLEN_MAX) + return 0; + + strncpy(buf, range, p - range); + buf[p - range] = '\0'; + min = strtoul(buf, &endptr, 10); + if (*endptr != '\0') + return 0; + + /* 是否符合格式 */ + range = p + 1; + p = strchr (range, '>'); + if (p == NULL) + return 0; + if (p - range > DECIMAL_STRLEN_MAX) + return 0; + + strncpy (buf, range, p - range); + buf[p - range] = '\0'; + max = strtoul (buf, &endptr, 10); + if (*endptr != '\0') + return 0; + + if (val < min || val > max) + return 0; + + return 1; +} + +/* 检查字符串是不是ipv6地址: + str -- ipv6字符串 + + return: 部分匹配, 完全匹配, 不匹配 */ +static MATCH_TYPE_E _cmd_ipv6_match(const char *str) +{ + uint32_t state = STATE_START; + uint32_t colons = 0; + uint32_t nums = 0; + uint32_t double_colon = 0; + const char *sp = NULL; + struct sockaddr_in6 sin6_dummy; + int ret; + + if (NULL == str) + return partly_match; + + if (strspn(str, IPV6_ADDR_STR) != strlen (str)) + return no_match; + + /* use inet_pton that has a better support, + * for example inet_pton can support the automatic addresses: + * ::1.2.3.4 + */ + ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr); + if (1 == ret) + return exact_match; + + while(*str != '\0') + { + switch (state) + { + case STATE_START: + if (*str == ':') + { + if (*(str + 1) != ':' && *(str + 1) != '\0') + return no_match; + colons--; + state = STATE_COLON; + } + else + { + sp = str; + state = STATE_ADDR; + } + + continue; + + case STATE_COLON: + colons++; + if (*(str + 1) == ':') + state = STATE_DOUBLE; + else + { + sp = str + 1; + state = STATE_ADDR; + } + break; + + case STATE_DOUBLE: + if (double_colon) + return no_match; + + if (*(str + 1) == ':') + return no_match; + else + { + if (*(str + 1) != '\0') + colons++; + sp = str + 1; + state = STATE_ADDR; + } + + double_colon++; + nums++; + break; + + case STATE_ADDR: + if (*(str + 1) == ':' || *(str + 1) == '\0') + { + if (str - sp > 3) + return no_match; + + nums++; + state = STATE_COLON; + } + if (*(str + 1) == '.') + state = STATE_DOT; + break; + + case STATE_DOT: + state = STATE_ADDR; + break; + + default: + break; + } + + if (nums > 8) + return no_match; + + if (colons > 7) + return no_match; + + str++; + } + + return exact_match; +} + +/* 检查字符串是不是带掩码的ipv6地址: + str -- ipv6字符串 + + return: 部分匹配, 完全匹配, 不匹配 */ +static MATCH_TYPE_E _cmd_ipv6_prefix_match(const char *str) +{ + uint32_t state = STATE_START; + int colons = 0; + uint32_t nums = 0; + uint32_t double_colon = 0; + uint32_t mask; + const char *sp = NULL; + char *endptr = NULL; + + if (NULL == str) + return partly_match; + + if (strspn(str, IPV6_PREFIX_STR) != strlen(str)) + return no_match; + + while (*str != '\0' && state != STATE_MASK) + { + switch (state) + { + case STATE_START: + if (':' == *str) + { + if (*(str + 1) != ':' && *(str + 1) != '\0') + return no_match; + colons--; + state = STATE_COLON; + } + else + { + sp = str; + state = STATE_ADDR; + } + continue; + + case STATE_COLON: + colons++; + if (*(str + 1) == '/') + return no_match; + else if (*(str + 1) == ':') + state = STATE_DOUBLE; + else + { + sp = str + 1; + state = STATE_ADDR; + } + break; + + case STATE_DOUBLE: + if (double_colon) + return no_match; + + if (*(str + 1) == ':') + return no_match; + else + { + if (*(str + 1) != '\0' && *(str + 1) != '/') + colons++; + sp = str + 1; + + if (*(str + 1) == '/') + state = STATE_SLASH; + else + state = STATE_ADDR; + } + + double_colon++; + nums += 1; + break; + + case STATE_ADDR: + if (*(str + 1) == ':' || + *(str + 1) == '.' || + *(str + 1) == '\0' || + *(str + 1) == '/') + { + if (str - sp > 3) + return no_match; + + for (; sp <= str; sp++) + if (*sp == '/') + return no_match; + + nums++; + + if (*(str + 1) == ':') + state = STATE_COLON; + else if (*(str + 1) == '.') + state = STATE_DOT; + else if (*(str + 1) == '/') + state = STATE_SLASH; + } + break; + + case STATE_DOT: + state = STATE_ADDR; + break; + + case STATE_SLASH: + if (*(str + 1) == '\0') + return partly_match; + + state = STATE_MASK; + break; + + default: + break; + } + + if (nums > 11) + return no_match; + + if (colons > 7) + return no_match; + + str++; + } + + if (state < STATE_MASK) + return partly_match; + + mask = strtol (str, &endptr, 10); + if (*endptr != '\0') + return no_match; + + if (mask < 0 || mask > 128) + return no_match; + + return exact_match; +} + +/* 检查字符串是不是ipv4地址: + str -- ipv4字符串 + + return: 部分匹配, 完全匹配, 不匹配 */ +static MATCH_TYPE_E _cmd_ipv4_match(const char *str) +{ + const char *sp; + uint32_t dots = 0; + uint32_t nums = 0; + char buf[4] = {0}; + + if (NULL == str) + return partly_match; + + while(1) + { + memset(buf, 0, sizeof (buf)); + sp = str; + while(*str != '\0') + { + if (*str == '.') + { + if (dots >= 3) + return no_match; + + if (*(str + 1) == '.') + return no_match; + + if (*(str + 1) == '\0') + return partly_match; + + dots++; + break; + } + if (!isdigit((int)*str)) + return no_match; + + str++; + } + + if (str - sp > 3) + return no_match; + + strncpy(buf, sp, str - sp); + if (atoi(buf) > 255) + return no_match; + + nums++; + + if (*str == '\0') + break; + + str++; + } + + if (nums < 4) + return partly_match; + + return exact_match; +} + +/* 检查字符串是不是带掩码的ipv4地址: + str -- ipv4字符串 + + return: 部分匹配, 完全匹配, 不匹配 */ +static MATCH_TYPE_E _cmd_ipv4_prefix_match(const char *str) +{ + const char *sp = NULL; + int dots = 0; + char buf[4] = {0}; + + if (NULL == str) + return partly_match; + + while(1) + { + memset(buf, 0, sizeof (buf)); + sp = str; + while (*str != '\0' && *str != '/') + { + if (*str == '.') + { + if (dots == 3) + return no_match; + + if (*(str + 1) == '.' || *(str + 1) == '/') + return no_match; + + if (*(str + 1) == '\0') + return partly_match; + + dots++; + break; + } + + if (!isdigit((int)(*str))) + return no_match; + + str++; + } + + if (str - sp > 3) + return no_match; + + strncpy (buf, sp, str - sp); + if (atoi(buf) > 255) + return no_match; + + if (3 == dots) + { + if ('/' == *str) + { + if (*(str + 1) == '\0') + return partly_match; + + str++; + break; + } + else if (*str == '\0') + return partly_match; + } + + if (*str == '\0') + return partly_match; + + str++; + } + + sp = str; + while(*str != '\0') + { + if (!isdigit((int)(*str))) + return no_match; + + str++; + } + + if (atoi(sp) > 32) + return no_match; + + return exact_match; +} + +/* cl_str与所有命令匹配,将没有匹配的cmd在cmds中置为NULL: + cl_str -- 实际输入的命令 + cmds -- 所有命令, + index -- cl_str在命令行中的位置 + + return: 返回匹配结果 */ +static MATCH_TYPE_E _cmd_filter_by_completion(char *cl_str,array_t *cmds,uint32_t index) +{ + uint32_t i = 0, j = 0; + int matched = 0; + const char *str = NULL; + cmd_element_t *cmd = NULL; + MATCH_TYPE_E match_type = no_match; + array_t *strs = NULL; + desc_t *desc = NULL; + + /* 如果cmds中的命令在index位置上不能配置cl_str,在cmds中将会被设置为NULL. */ + for (i = 0; i < array_active(cmds); i++) + { + /* 跳过空位. */ + if (NULL == (cmd = array_get(cmds, i))) + { + continue; + } + + /* 长度不够的命令直接排除. */ + if (index >= array_active(cmd->strs)) + { + array_get(cmds, i) = NULL; + continue; + } + + matched = 0; + + /* 如果strs是命令中的参数,有可能有多个可选项,所以要做循环. */ + strs = array_get(cmd->strs, index); + for (j = 0; j < array_active(strs); j++) + { + if (NULL == (desc = array_get(strs, j))) + { + continue; + } + + /* 比较是有优先级的,如果在index位置有多种类型的匹配, + 这里只会返回优先级最高的类型. */ + str = desc->cmd; + if (CMD_VARARG(str)) + { + if (match_type < vararg_match) + match_type = vararg_match; + matched++; + } + else if (CMD_RANGE(str)) + { + if (_cmd_range_match(str, cl_str)) + { + if (match_type < range_match) + { + match_type = range_match; + } + matched++; + } + } + else if (CMD_IPV6(str)) + { + if (_cmd_ipv6_match(cl_str)) + { + if (match_type < ipv6_match) + { + match_type = ipv6_match; + } + matched++; + } + } + else if (CMD_IPV6_PREFIX(str)) + { + if (_cmd_ipv6_prefix_match(cl_str)) + { + if (match_type < ipv6_prefix_match) + { + match_type = ipv6_prefix_match; + } + matched++; + } + } + else if (CMD_IPV4(str)) + { + if (_cmd_ipv4_match(cl_str)) + { + if (match_type < ipv4_match) + { + match_type = ipv4_match; + } + matched++; + } + } + else if (CMD_IPV4_PREFIX(str)) + { + if (_cmd_ipv4_prefix_match(cl_str)) + { + if (match_type < ipv4_prefix_match) + { + match_type = ipv4_prefix_match; + } + matched++; + } + } + else + { + /* Check is this point's argument optional ? */ + if (CMD_OPTION(str) || CMD_VARIABLE(str)) + { + if (match_type < extend_match) + { + match_type = extend_match; + } + matched++; + } + else if (0 == strncmp(cl_str, str, strlen(cl_str))) + { + if (0 == strcmp(cl_str, str)) + { + match_type = exact_match; + } + else + { + if (match_type < partly_match) + { + match_type = partly_match; + } + } + matched++; + } + } + } + + /* 将不匹配的命令排除. */ + if (!matched) + { + array_get(cmds, i) = NULL; + } + } + + return match_type; +} + +/* 根据配置类型type,进一步剔除cmds中不符合的cmd: + cl_str -- 命令行字符串 + cmds -- 所有可用命令数组 + index -- 命令行字符串索引 + type -- 上一次匹配类型 + + return: 有多个模糊匹配返回1, ipv4 or ipv6匹配不完整返回2, 成功返回0 */ +static int32_t _cmd_is_ambiguous(char *cl_str, array_t *cmds, uint32_t index, MATCH_TYPE_E type) +{ + uint32_t i = 0, j = 0; + int32_t match = 0; + const char *str = NULL; + cmd_element_t *cmd = NULL; + const char *matched = NULL; + array_t *strs = NULL; + desc_t *desc = NULL; + MATCH_TYPE_E ret = no_match; + + /* 遍历所有命令 */ + for (i = 0; i < array_active(cmds); i++) + { + if (NULL == (cmd = array_get(cmds, i))) + { + continue; + } + + match = 0; + /* 获取命令行第index字符串 */ + strs = array_get(cmd->strs, index); + + /* 遍历该字符串下所有可能的关键字 */ + for (j = 0; j < array_active(strs); j++) + { + if (NULL == (desc = array_get(strs, j))) + { + continue; + } + + ret = no_match; + str = desc->cmd; + + switch (type) + { + case exact_match: + if (!(CMD_OPTION(str) || CMD_VARIABLE(str)) + && 0 == strcmp(cl_str, str)) + { + match++; + } + break; + + case partly_match: + if (!(CMD_OPTION(str) || CMD_VARIABLE(str)) + && 0 == strncmp(cl_str, str, strlen(cl_str))) + { + if (matched && strcmp(matched, str) != 0) + { + return 1; /* There is ambiguous match. */ + } + else + { + matched = str; + } + match++; + } + break; + + case range_match: + if (_cmd_range_match(str, cl_str)) + { + if (matched && strcmp(matched, str) != 0) + { + return 1; + } + else + { + matched = str; + } + match++; + } + break; + + case ipv6_match: + if (CMD_IPV6(str)) + { + match++; + } + break; + + case ipv6_prefix_match: + if ((ret = _cmd_ipv6_prefix_match(cl_str)) != no_match) + { + if (ret == partly_match) + { + return 2; /* There is incomplete match. */ + } + match++; + } + break; + + case ipv4_match: + if (CMD_IPV4(str)) + { + match++; + } + break; + + case ipv4_prefix_match: + if ((ret = _cmd_ipv4_prefix_match(cl_str)) != no_match) + { + if (ret == partly_match) { + return 2; /* There is incomplete match. */ + } + match++; + } + break; + + case extend_match: + if (CMD_OPTION(str) || CMD_VARIABLE(str)) + { + match++; + } + break; + + case no_match: + default: + break; + } + } + + /* 将类型不匹配的命令排除. */ + if (!match) + { + array_get(cmds, i) = NULL; + } + } + + return 0; +} + +/* 获取命令节点下所有命令数组和公共命令节点上的所有命令数组合并: + ntype -- 命令节点 + + return: 返回复制的命令行数组 + node: 在非VIEW_NODE模式下总会复制公共节点上的命令行 */ +static array_t *_cmd_cmds_create(NODE_TYPE_E ntype) +{ + array_t *cmds = array_copy(_cmd_node_cmds_get(ntype), MTYPE_CLI); + + if (ntype != PASSWORD_NODE && ntype != USERNAME_NODE) + { + array_merge(cmds, _cmd_node_cmds_get(COMMON_NODE), MTYPE_CLI); + } + + return cmds; +} + +/* 从vty执行命令cmd_line. */ +static int32_t _cmd_execute_command_real(array_t *cmd_line, vty_t *vty, cmd_element_t **cmd) +{ + uint32_t i = 0; + uint32_t index = 0; + array_t *cmds = NULL; + cmd_element_t *cmd_element = NULL; + cmd_element_t *matched_element = NULL; + uint32_t matched_count = 0; + uint32_t incomplete_count = 0; + int32_t argc = 0; + const char *argv[CMD_ARGC_MAX] = {NULL}; + MATCH_TYPE_E match = no_match; + int32_t varflag = 0; + char *cl_str = NULL; + + /* 将该节点下的所有命令复制一份. */ + cmds = _cmd_cmds_create(vty->node); + + /* 查找符合的cmd. */ + for(index = 0; index < array_active(cmd_line); index++) + { + if (NULL == (cl_str = array_get(cmd_line, index))) + { + continue; + } + + int ret = 0; + + /* 匹配cl_str和cmds中index位置字符串并返回配置类型. */ + match = _cmd_filter_by_completion(cl_str, cmds, index); + if (vararg_match == match) + { + break; + } + + /* 根据配置类型type,进一步剔除cmds中不符合的cmd. */ + ret = _cmd_is_ambiguous(cl_str, cmds, index, match); + if (1 == ret) + { + array_free(cmds, MTYPE_CLI); + return CMD_ERR_AMBIGUOUS; + } + else if (2 == ret) + { + array_free(cmds, MTYPE_CLI); + return CMD_ERR_NO_MATCH; + } + } + + /* 检查是否只有唯一匹配并取出该命令成员. */ + matched_element = NULL; + matched_count = 0; + incomplete_count = 0; + + for (i = 0; i < array_active(cmds); i++) + { + if (NULL == (cmd_element = array_get(cmds, i))) + { + continue; + } + + if (vararg_match == match || index >= cmd_element->str_size) + { + matched_element = cmd_element; + matched_count++; + } + else + { + incomplete_count++; + } + } + + array_free(cmds, MTYPE_CLI); + + if (0 == matched_count) + { + if (incomplete_count) + { + return CMD_ERR_INCOMPLETE; + } + else + { + return CMD_ERR_NO_MATCH; + } + } + + if (matched_count > 1) + { + return CMD_ERR_AMBIGUOUS; + } + + /* 将命令的参数解析出来. */ + varflag = 0; + argc = 0; + + for (i = 0; i < array_active(cmd_line); i++) + { + if (varflag) + { + argv[argc++] = array_get(cmd_line, i); + } + else + { + array_t *strs = array_get(matched_element->strs, i); + + if (1 == array_active(strs)) + { + desc_t *desc = array_get(strs, 0); + + if (CMD_VARARG(desc->cmd)) + { + varflag = 1; + } + + if (varflag || CMD_VARIABLE(desc->cmd) || CMD_OPTION(desc->cmd)) + { + argv[argc++] = array_get(cmd_line, i); + } + } + else + { + argv[argc++] = array_get(cmd_line, i); + } + } + + if (argc >= CMD_ARGC_MAX) + { + return CMD_ERR_EXEED_ARGC_MAX; + } + } + + /* 执行命令. */ + if (cmd) + { + *cmd = matched_element; + } + + if (matched_element->daemon) + { + return CMD_SUCCESS_DAEMON; + } + + return (*matched_element->func)(matched_element, vty, argc, argv); +} + +/* 字符串str与数组v中的cmd一一比较,用于保证数组v中只有1个字符串str: + v -- 字符串数组 + str -- 参与比较的字符串 + + return: 相同返回1,其他返回0.*/ +static int32_t _cmd_desc_unique_string(array_t *v, const char *str) +{ + uint32_t i = 0; + desc_t *desc = NULL; + + for(i = 0; i < array_active(v); i++) + { + if (NULL == (desc = array_get(v, i))) + { + continue; + } + + if (0 == strcmp(desc->cmd, str)) + { + return 1; + } + } + + return 0; +} + +/* 比较src是否符合dst的格式,只要部分匹配即可: + src -- 需要检测的字符串 + dst -- 命令中的字符串 + + return: src匹配dst返回dst,否则返回NULL */ +static const char *_cmd_entry_function_desc(const char *src, const char *dst) +{ + if (CMD_VARARG(dst)) + { + return dst; + } + + if (CMD_RANGE(dst)) + { + if (_cmd_range_match (dst, src)) + { + return dst; + } + else + { + return NULL; + } + } + + if (CMD_IPV6(dst)) + { + if (_cmd_ipv6_match(src)) + { + return dst; + } + else + { + return NULL; + } + } + + if (CMD_IPV6_PREFIX(dst)) + { + if (_cmd_ipv6_prefix_match(src)) + { + return dst; + } + else + { + return NULL; + } + } + + if (CMD_IPV4(dst)) + { + if (_cmd_ipv4_match(src)) + { + return dst; + } + else + { + return NULL; + } + } + + if (CMD_IPV4_PREFIX(dst)) + { + if (_cmd_ipv4_prefix_match(src)) + { + return dst; + } + else + { + return NULL; + } + } + + /* Optional or variable commands always match on '?' */ + if (CMD_OPTION(dst) || CMD_VARIABLE(dst)) + { + return dst; + } + + /* In case of 'command \t', given src is NULL string. */ + if (src == NULL) + { + return dst; + } + + if (0 == strncmp(src, dst, strlen(src))) + { + return dst; + } + else + { + return NULL; + } +} + +/* 返回可供补齐的命令字数组: + cmd_line -- 用户输入的命令 + vty -- 输入命令的vty + status -- 匹配结果 + + return: 匹配的字符串数组 */ +static array_t* _cmd_describe_command_real(array_t *cmd_line, vty_t *vty, int32_t *status) +{ + uint32_t i = 0, j = 0; + array_t *cmds = NULL; + array_t *matchs = NULL; + array_t *strs = NULL; + array_t *descs = NULL; + desc_t *desc = NULL; + cmd_element_t *cmd = NULL; + uint32_t index = array_active(cmd_line) - 1; + int32_t ret = 0; + MATCH_TYPE_E match = no_match; + char *cl_str = NULL; + const char *string = NULL; + + /* 将该节点下的所有命令复制一份. */ + cmds = _cmd_cmds_create(vty->node); + + /* 初始化匹配数组. */ + matchs = array_init(INIT_MATCHARR_SIZE, MTYPE_CLI); + + /* 先对倒数第2个关键字之前的命令进行匹配. */ + for (i = 0; i < index; i++) + { + if (NULL == (cl_str = array_get(cmd_line, i))) + { + continue; + } + + /* 比较所有cmds中第i个字符串,不匹配会将cmd从cmds中删除 */ + match = _cmd_filter_by_completion(cl_str, cmds, i); + /* 这里基本进不去,不用深究 */ + if (vararg_match == match) + { + array_t *descs = NULL; + uint32_t j = 0; + uint32_t k = 0; + + for (j = 0; j < array_active(cmds); j++) + { + if ((cmd = array_get(cmds, j)) != NULL && (array_active(cmd->strs))) + { + descs = array_get(cmd->strs, array_active(cmd->strs) - 1); + for (k = 0; k < array_active(descs); k++) + { + desc_t *desc = array_get(descs, k); + array_append(matchs, desc, MTYPE_CLI); + } + } + } + + array_append(matchs, &cmd_desc_enter, MTYPE_CLI); + array_free(cmds, MTYPE_CLI); + + qsort(matchs->index, array_active(matchs), sizeof (void *), _cmp_desc); + *status = CMD_SUCCESS; + return matchs; + } + + /* 根据前一轮的比较,把cmds中不是match匹配类型的cmd去掉. */ + ret = _cmd_is_ambiguous(cl_str, cmds, i, match); + if (1 == ret) + { + array_free(cmds, MTYPE_CLI); + array_free(matchs, MTYPE_CLI); + *status = CMD_ERR_AMBIGUOUS; + return NULL; + } + else if(2 == ret) + { + array_free(cmds, MTYPE_CLI); + array_free(matchs, MTYPE_CLI); + *status = CMD_ERR_NO_MATCH; + return NULL; + } + } + + /* 检查最后一个关键字 */ + cl_str = array_get(cmd_line, index); + if (cl_str) + { + match = _cmd_filter_by_completion(cl_str, cmds, index); + } + + /* 得到匹配的关键字列表. */ + for (i = 0; i < array_active(cmds); i++) + { + if (NULL == (cmd = array_get(cmds, i))) + { + continue; + } + + strs = cmd->strs; + + if (cl_str && index >= array_active(strs)) + { + array_get(cmds, i) = NULL; + continue; + } + + /* 如果命令正好相同,直接加入desc_enter字符串. */ + if (NULL == cl_str && index == array_active(strs)) + { + if (!_cmd_desc_unique_string(matchs, cmd_enter)) + { + array_append(matchs, &cmd_desc_enter, MTYPE_CLI); + } + + continue; + } + + descs = array_get(strs, index); + desc = NULL; + + for (j = 0; j < array_active(descs); j++) + { + if (NULL == (desc = array_get(descs, j))) + { + continue; + } + + if (NULL == (string = _cmd_entry_function_desc(cl_str, desc->cmd))) + { + continue; + } + + if (!_cmd_desc_unique_string(matchs, string)) + { + array_append(matchs, desc, MTYPE_CLI); + } + } + } + array_free(cmds, MTYPE_CLI); + + if (NULL == array_get(matchs, 0)) + { + array_free(matchs, MTYPE_CLI); + *status = CMD_ERR_NO_MATCH; + return NULL; + } + + /* 将得到的数组按字符串排序. */ + qsort(matchs->index, array_active(matchs), sizeof(void*), _cmp_desc); + *status = CMD_SUCCESS; + return matchs; +} + +/* 计算公共字符串的长度: + matchs -- 字符串数组 + + return: 公共字符串长度 */ +static uint32_t _cmd_common_str(array_t *matchs) +{ + uint32_t i = 0; + uint32_t j = 0; + desc_t *desc = NULL; + uint32_t common_len = 0xffffffff; + char *s1, *s2; + + if (NULL == array_get(matchs, 0) || NULL == array_get(matchs, 1)) + { + return 0; + } + + for (i = 1; i < array_active(matchs); i++) + { + desc = array_get(matchs, i - 1); + s1 = desc->cmd; + desc = array_get(matchs, i); + s2 = desc->cmd; + + for (j = 0; s1[j] && s2[j]; j++) + { + if (s1[j] != s2[j]) + { + break; + } + } + + if (j < common_len) + { + common_len = j; + } + if (0 == common_len) + { + break; + } + } + + return common_len; +} + +/* Interface functions -------------------------------------------------------*/ +/* 命令行相应'?'输入: + cmd_line -- 当前命令行字符串数组 + vty -- 当前终端结构体 + status -- 补齐状态*/ +array_t *cmd_describe_command(array_t *cmd_line, vty_t *vty, int32_t *status) +{ + return _cmd_describe_command_real(cmd_line, vty, status); +} + +/* 命令行相应'TAB'输入: + cmd_line -- 命令行字符串数组 + vty -- 终端结构体 + status -- 补齐状态 + + return: */ +char **cmd_complete_command(array_t *cmd_line, vty_t *vty, int32_t *status) +{ + uint32_t i = 0; + array_t *matchs = NULL; + array_t *temps = NULL; + desc_t *desc = NULL; + bool is_include_arg = FALSE; + char **match_str = NULL; + char buf[COMPLETE_BUF_SIZE] = {0}; + uint32_t common_len = 0; + uint32_t width = 0; + uint32_t index = array_active(cmd_line) - 1; + + /* 获取可用关键字队列. */ + matchs = _cmd_describe_command_real(cmd_line, vty, status); + switch(*status) + { + case CMD_ERR_AMBIGUOUS: + return NULL; + case CMD_ERR_NO_MATCH: + return NULL; + } + + /* 判断匹配是不是有参数 */ + for (i = 0; i < array_active(matchs); i++) + { + if (NULL == (desc = array_get(matchs, i))) + { + continue; + } + + if (CMD_OPTION(desc->cmd) || CMD_VARIABLE(desc->cmd) + || CMD_VARARG(desc->cmd) || CMD_RANGE(desc->cmd) + || CMD_IPV4(desc->cmd) || CMD_IPV4_PREFIX(desc->cmd) + || CMD_IPV6(desc->cmd) || CMD_IPV6_PREFIX(desc->cmd)) + { + is_include_arg = TRUE; + } + } + + /* 只有1个匹配,且不是参数的情况下,直接返回完整字符串. */ + if (NULL == array_get(matchs, 1) && FALSE == is_include_arg) + { + /* Make new matchs. */ + desc = array_get(matchs, 0); + array_free(matchs, MTYPE_CLI); + + matchs = array_init(INIT_MATCHARR_SIZE, MTYPE_CLI); + array_append(matchs, XSTRDUP(MTYPE_CLI, desc->cmd), MTYPE_CLI); + match_str = (char **)matchs->index; + array_free_wrapper(matchs, MTYPE_CLI); + + *status = CMD_COMPLETE_FULL_MATCH; + return match_str; + } + + /* 有多个匹配,且不是参数,且存在公共字符串的情况下,返回公共字符串. */ + if (FALSE == is_include_arg && (common_len = _cmd_common_str(matchs))) + { + uint32_t len = 0; + if (NULL == array_get(cmd_line, index)) + { + len = 0; + } + else + { + len = strlen(array_get(cmd_line, index)); + } + + if (len < common_len) + { + char *common_str = NULL; + desc = array_get(matchs, 0); + + common_str = XMALLOC(MTYPE_CLI, common_len + 1); + memcpy(common_str, desc->cmd, common_len); + common_str[common_len] = '\0'; + + array_free(matchs, MTYPE_CLI); + matchs = array_init(INIT_MATCHARR_SIZE, MTYPE_CLI); + array_append(matchs, common_str, MTYPE_CLI); + match_str = (char **)matchs->index; + array_free_wrapper(matchs, MTYPE_CLI); + + *status = CMD_COMPLETE_MATCH; + return match_str; + } + } + + /* 计算所有命令字中最长的长度用于打印对其. */ + for (i = 0; i < array_active(matchs); i++) + { + if (NULL == (desc = array_get(matchs, i))) + { + continue; + } + + if (desc->cmd[0] == '\0') + { + continue; + } + + uint32_t len = 0; + + len = strlen(desc->cmd); + if (desc->cmd[0] == '.') + { + len--; + } + + if (width < len) + { + width = len; + } + } + + /* 打印可用命令字. */ + temps = array_init(INIT_MATCHARR_SIZE, MTYPE_CLI); + for (i = 0; i < array_active(matchs); i++) + { + if (NULL == (desc = array_get(matchs, i))) + { + continue; + } + + if (desc->cmd[0] == '\0') + { + continue; + } + + if (!desc->str) + { + snprintf(buf, COMPLETE_BUF_SIZE, " %-s%s", desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, VTY_NEWLINE); + } + else + { + snprintf(buf, COMPLETE_BUF_SIZE, " %-*s %s%s", width, desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, desc->str, VTY_NEWLINE); + } + + array_append(temps, XSTRDUP(MTYPE_CLI, buf), MTYPE_CLI); + } + + array_append(temps, NULL, MTYPE_CLI); + match_str = (char **)temps->index; + array_free_wrapper(temps, MTYPE_CLI); + + array_free(matchs, MTYPE_CLI); + *status = CMD_COMPLETE_LIST_MATCH; + return match_str; +} + +/* 将命令节点中的命令按字符串排序: + + return: void */ +void cmd_sort_node(void) +{ + uint32_t i = 0; + cmd_node_t *node = NULL; + + for (i = 0; i < array_active(cmd_nodes); i++) + { + if (NULL == (node = array_get(cmd_nodes, i))) + { + continue; + } + + array_t *cmds = node->cmds; + qsort(cmds->index, array_active(cmds), sizeof (void *), _cmp_node); + } +} + +/* 将命令行模式加入总模式节点中: + node -- 命令行节点结构体 + func -- 该节点下的保存配置函数 + + return: void */ +void cmd_install_node(cmd_node_t *node, cmd_save_config_f *func) +{ + array_set(cmd_nodes, node->node, node, MTYPE_CLI); + node->func = func; + node->cmds = array_init(ARRAY_MIN_SIZE, MTYPE_CLI); +} + +/* 安装命令到相应的命令模式节点: + ntype -- 命令行节点 + cmd -- 命令. + + return: void + node: 命令没有检查入参数的有效性,请调用者一定保证入参的正确性.*/ +void cmd_install_element(NODE_TYPE_E ntype, cmd_element_t *cmd) +{ + cmd_node_t *cnode = NULL; + + if (!cmd_nodes) + { + log_err(LOG_CLI, "Command isn't initialied, please check it."); + return; + } + + cnode = array_get(cmd_nodes, ntype); + if (NULL == cnode) + { + log_err(LOG_CLI, "Command node %d doesn't exist, please check it.", ntype); + return; + } + + array_append(cnode->cmds, cmd, MTYPE_CLI); + + if (NULL == cmd->strs) + { + cmd->strs = _cmd_strs_create(cmd->string, cmd->doc); + } + + cmd->str_size = _cmd_descs_size(cmd->strs); +} + +/* 命令行核心初始化: + + return: void */ +void cmd_init(void) +{ + cmd_enter = strdup(""); + cmd_desc_enter.cmd = cmd_enter; + cmd_desc_enter.str = strdup(""); + + /* 初始化所有命令的总节点. */ + cmd_nodes = array_init(ARRAY_MIN_SIZE, MTYPE_CLI); + + /* 默认主机信息. */ + strncpy(host.name, "601200179", FILE_NAME_LEN); + strncpy(host.configfile, "startup-config", FILE_NAME_LEN); + host.version = softversion_get(); + host.compile = softversion_date_get(); + host.hardversion = hardware_version_get(); + host.FPGAversion = fpga_version_date_get(); + host.lines = -1; + + /* 安装命令行模式节点. */ + cmd_install_node(&common_node, NULL); + cmd_install_node(&username_node, NULL); + cmd_install_node(&password_node, NULL); + cmd_install_node(&enable_node, NULL); + cmd_install_node(&config_node, _config_write_host); + /* config node是全局配置,需要注册其他的配置保存函数 */ + config_node.configs = array_init(CONFIG_PRI_COUNT, MTYPE_CLI); + + /* 安转基本命令. */ + cmd_install_element(COMMON_NODE, &show_version_cmd); + cmd_install_element(COMMON_NODE, &exit_node_cmd); + cmd_install_element(COMMON_NODE, &quit_node_cmd); + cmd_install_element(COMMON_NODE, &show_running_config_cmd); + cmd_install_element(COMMON_NODE, &show_running_globel_cmd); + cmd_install_element(COMMON_NODE, &show_log_default_cmd); + cmd_install_element(COMMON_NODE, &show_log_level_cmd); + cmd_install_element(COMMON_NODE, &show_log_cnt_cmd); + cmd_install_element(COMMON_NODE, &show_log_cmd); + + cmd_install_element(ENABLE_NODE, &config_enable_cmd); + cmd_install_element(ENABLE_NODE, &config_terminal_cmd); + cmd_install_element(ENABLE_NODE, &bash_open_cmd); + cmd_install_element(ENABLE_NODE, &terminal_monitor_cmd); + cmd_install_element(ENABLE_NODE, &no_terminal_monitor_cmd); + cmd_install_element(ENABLE_NODE, &config_write_file_cmd); + cmd_install_element(ENABLE_NODE, &type_id_set_cmd); + cmd_install_element(ENABLE_NODE, &device_id_set_cmd); + cmd_install_element(ENABLE_NODE, &host_ip_set_cmd); + cmd_install_element(ENABLE_NODE, &default_route_set_cmd); + cmd_install_element(ENABLE_NODE, &factory_date_set_cmd); + cmd_install_element(ENABLE_NODE, &deployment_date_set_cmd); + cmd_install_element(ENABLE_NODE, &logoff_cmd); + cmd_install_element(ENABLE_NODE, &reboot_cmd); + + cmd_install_element(CONFIG_NODE, &hostname_set_cmd); + cmd_install_element(CONFIG_NODE, &config_log_level_cmd); + cmd_install_element(CONFIG_NODE, &no_config_log_level_cmd); +} + +/* 分解命令每个单词并填入列表,便于于系统命令进行比较: + string -- 命令行字符串 + + return: 命令行每个单词组成的数组,失败返回NULL */ +array_t *cmd_strs_create(const char *string) +{ + const char *cp = NULL; + const char *start = NULL; + char *word = NULL; + int32_t strlen = 0; + array_t *strs = NULL; + + if (NULL == string) + { + return NULL; + } + + cp = string; + + /* 跳过空格. */ + while (isspace((int)*cp) && *cp != '\0') + { + cp++; + } + + /* 如果只有空格则直接返回. */ + if ('\0' == *cp || '!' == *cp || '#' == *cp) + { + return NULL; + } + + strs = array_init(ARRAY_MIN_SIZE, MTYPE_CLI); + + /* 产生单词列表. */ + while (1) + { + start = cp; + + /* 找到word的结束位置. */ + while (!(isspace((int)*cp) || '\r' == *cp || '\n' == *cp) && *cp != '\0') + { + cp++; + } + + /* 产生word. */ + strlen = cp - start; + word = XMALLOC(MTYPE_CLI, strlen + 1); + memcpy(word, start, strlen); + *(word + strlen) = '\0'; + array_append(strs, word, MTYPE_CLI); + + /* 跳过空格. */ + while ((isspace((int)*cp) || '\r' == *cp || '\n' == *cp) && *cp != '\0') + { + cp++; + } + + /* 遇到结束符返回. */ + if ('\0' == *cp) + { + return strs; + } + } +} + +/* 于函数cmd_strs_create对应,释放其分配的空间: + a -- 命令行每个字符串组成的数组 + + return: void */ +void cmd_strs_free(array_t *a) +{ + uint32_t i = 0; + char *cp = NULL; + + if (!a) + { + return; + } + + for (i = 0; i < array_active(a); i++) + { + if ((cp = array_get(a, i)) != NULL) + { + XFREE(MTYPE_CLI, cp); + } + } + + array_free(a, MTYPE_CLI); +} + +/* 从vty执行命令cmd_line: + cmd_line -- 命令字符串 + vty -- 终端结构体 + cmd -- 被执行命令行结构体(OUT) + + return: 执行结果(CMD_xxxx) */ +int32_t cmd_execute_command(array_t *cmd_line, vty_t *vty, cmd_element_t **cmd) +{ + return _cmd_execute_command_real(cmd_line, vty, cmd); +} + +/* 从vty执行命令cmd_line: + vty -- 终端结构体 + + return: 执行结果(CMD_xxxx) */ +int32_t cmd_execute(vty_t *vty) +{ + int ret = CMD_ERR_NO_MATCH; + array_t *cmd_line = NULL; + + /* 分解命令的每个单词到队列. */ + cmd_line = cmd_strs_create(vty->buf); + if (NULL == cmd_line) + { + return CMD_SUCCESS; + } + + ret = cmd_execute_command(cmd_line, vty, NULL); + if (ret != CMD_SUCCESS) + { + switch (ret) + { + case CMD_WARNING: + if (vty->type == VTY_FILE) + vty_out(vty, "Warning...%s", VTY_NEWLINE); + break; + case CMD_ERR_AMBIGUOUS: + vty_out(vty, "%% Ambiguous command.%s", VTY_NEWLINE); + break; + case CMD_ERR_NO_MATCH: + vty_out(vty, "%% Unknown command: %s %s", vty->buf, VTY_NEWLINE); + break; + case CMD_ERR_INCOMPLETE: + vty_out(vty, "%% Command incomplete.%s", VTY_NEWLINE); + break; + } + } + + cmd_strs_free(cmd_line); + + return ret; +} + +/* 注册命令行配置保存函数: + ntype -- 命令行节点类型 + pri -- 优先级 + func -- 配置保存回调函数 + + return: E_xxx */ +int32_t cmd_config_node_config_register(int32_t pri, cmd_save_config_f *func) +{ + cmd_node_t *node = &config_node; + + /* 参数检查 */ + if (pri >= CONFIG_PRI_COUNT || !func) + { + return E_BAD_PARAM; + } + + /* 加入列表 */ + array_set(node->configs, pri, func, MTYPE_CLI); + + return 0; +} + +/* 从shell终端读取一行命. */ +char *_vtysh_gets(void) +{ + HIST_ENTRY *last; + struct termios new_setting,init_setting; + + /* readline要求自己释放其返回的buf. */ + if (line_read) + { + free(line_read); + line_read = NULL; + } + + /* 输入密码时不要回显 */ + if (PASSWORD_NODE == vtysh->node) + { + tcgetattr(0, &init_setting); + new_setting = init_setting; + new_setting.c_lflag &= ~ECHO; + tcsetattr(0, TCSANOW, &new_setting); + } + + /* 获取一行命令. */ + line_read = readline(vtysh_prompt()); + + /* 打开回显 */ + if (PASSWORD_NODE == vtysh->node) + { + tcsetattr(0,TCSANOW, &init_setting); + printf("\r\n"); + } + + /* 没有字符直接返回 */ + if (NULL == line_read) + { + return NULL; + } + + /* 如果命令有效记录历史. */ + if (*line_read && vtysh->node != PASSWORD_NODE && vtysh->node != USERNAME_NODE) + { + using_history(); + last = previous_history(); + if (!last || strcmp (last->line, line_read) != 0) + { + add_history(line_read); + append_history(1, history_file); + } + } + + return line_read; +} + +void *_vtysh_handle(void *arg) +{ + /* CLI主循环 */ + while(1) + { + if (NULL == _vtysh_gets()) + { + continue; + } + + if (strlen(line_read) >= vtysh->max) + { + printh("ERROR: The command is too long\n"); + continue; + } + + strncpy(vtysh->buf, line_read, vtysh->max - 1); + vty_execute(vtysh); + } + + return NULL; +} + +/* commandLine '?'响应函数. */ +int vtysh_rl_question(void) +{ + array_t *cmd_line = NULL; + + cmd_line = cmd_strs_create(rl_line_buffer); + if (NULL == cmd_line) + { + cmd_line = array_init(1, MTYPE_CLI); + array_append(cmd_line, '\0', MTYPE_CLI); + } + else + if (rl_end && isspace((int)rl_line_buffer[rl_end - 1])) + array_append(cmd_line, '\0', MTYPE_CLI); + + vty_question(vtysh, cmd_line); + + cmd_strs_free(cmd_line); + rl_on_new_line(); + return 0; +} + +/* commandLine 'TAB'响应函数. */ +int vtysh_rl_completion(void) +{ + array_t *cmd_line = NULL; + char **match_strs = NULL; + int32_t complete_status = CMD_ERR_NO_MATCH; + + cmd_line = cmd_strs_create(rl_line_buffer); + if (NULL == cmd_line) + { + cmd_line = array_init(1, MTYPE_CLI); + array_append(cmd_line, '\0', MTYPE_CLI); + } + else + if (rl_end && isspace ((int) rl_line_buffer[rl_end - 1])) + array_append(cmd_line, '\0', MTYPE_CLI); + + match_strs = cmd_complete_command(cmd_line, vtysh, &complete_status); + if (NULL == match_strs) + { + cmd_strs_free(cmd_line); + return 0; + } + + if (CMD_COMPLETE_MATCH == complete_status || + CMD_COMPLETE_FULL_MATCH == complete_status) + _vtysh_append_word(cmd_line, match_strs[0], complete_status); + else + { + vty_print_word(vtysh, match_strs); + rl_on_new_line(); + } + + vty_free_match_strs(match_strs); + cmd_strs_free(cmd_line); + + return 0; +} + +/* shell前缀 */ +char *vtysh_prompt(void) +{ + static char buf[100] = {0}; + cmd_node_t *node = NULL; + + node = cmd_node_get(vtysh->node); + + /* 用户密码输入 */ + if (USERNAME_NODE == node->node + || PASSWORD_NODE == node->node) + { + snprintf(buf, sizeof(buf), "%s", node->prompt); + } + else + { + snprintf(buf, sizeof(buf), node->prompt, device_info.hostname); + } + + return buf; +} + +/* 初始化readline. */ +void vtysh_readline_init(void) +{ + /* 修改自己的按键操作. */ + rl_bind_key('?', (rl_command_func_t *)vtysh_rl_question); + rl_bind_key('\t', (rl_command_func_t *)vtysh_rl_completion); + //printf("2 %d\r\n", rl_bind_key(CONTROL('D'), (rl_command_func_t *)vtysh_rl_question)); + //printf("2 %d\r\n", rl_bind_key(CONTROL('Z'), (rl_command_func_t *)vtysh_rl_question)); + /* do not append space after completion. It will be appended + * in new_completion() function explicitly. */ + rl_completion_append_character = '\0'; + + read_history(history_file); + clear_history(); +} + +void vtysh_device_save(void) +{ + int32_t fd = 0; + int32_t rv = 0; + + /* 将原始文件备份 */ + vtysh_config_save_bak(SAVE_DEV_CFG_BAK_FILE); + + /* 获取文件名. */ + fd = open("device-config", O_RDWR | O_CREAT | O_TRUNC, 0777); + if (-1 == fd) + { + DBG(DBG_M_CLI, "Can't open config file device-config\r\n"); + return; + } + chmod("device-config", 0666); + + rv = write(fd, &device_info, sizeof(device_info)); + if (rv != sizeof(device_info)) + { + DBG(DBG_M_CLI, "Can't write config file device-config\r\n"); + } + + /* 回收资源 */ + close(fd); + return; +} + +void vtysh_eth0_save(void) +{ + struct sockaddr_in server; + int32_t fd = 0; + int32_t rv = 0; + uint16_t len = 0; + char *str = NULL; + + /* 获取文件名. */ + fd = open("./script/Eth0Setting", O_RDWR | O_CREAT | O_TRUNC, 0777); + if (-1 == fd) + { + DBG(DBG_M_CLI, "Can't open config file Eth0Setting\r\n"); + return; + } + chmod("./script/Eth0Setting", 0666); + + str = XMALLOC(MTYPE_VTY_TMP, 512); + server.sin_addr.s_addr = device_info.ip; + len += snprintf(str, 512, "ip=%s\n", inet_ntoa(server.sin_addr)); + server.sin_addr.s_addr = device_info.mask; + len += snprintf(str + len, 512 - len, "netmask=%s\n", inet_ntoa(server.sin_addr)); + len += snprintf(str + len, 512 - len, "mac=%02x:%02x:%02x:%02x:%02x:%02x\n", device_info.mac[0], device_info.mac[1], + device_info.mac[2], device_info.mac[3], device_info.mac[4], device_info.mac[5]); + server.sin_addr.s_addr = device_info.gw; + len += snprintf(str + len, 512 - len, "gateway=%s\n", inet_ntoa(server.sin_addr)); + rv = write(fd, str, len); + if (rv != sizeof(device_info)) + { + DBG(DBG_M_CLI, "Can't write config file Eth0Setting\r\n"); + } + + /* 回收资源 */ + XFREE(MTYPE_VTY_TMP, str); + close(fd); + return; +} + +/* 打印 device_info 结构体的函数 */ +void print_device_info(const device_info_t *info) +{ + struct in_addr ip_addr, mask_addr, gw_addr, server_ip_addr; + char ip_str[INET_ADDRSTRLEN], mask_str[INET_ADDRSTRLEN], gw_str[INET_ADDRSTRLEN], server_ip_str[INET_ADDRSTRLEN]; + char mac_str[18]; // 格式如 "00:11:22:33:44:55" + + /* 转换 IP 地址为字符串 */ + ip_addr.s_addr = info->ip; + inet_ntop(AF_INET, &ip_addr, ip_str, INET_ADDRSTRLEN); + mask_addr.s_addr = info->mask; + inet_ntop(AF_INET, &mask_addr, mask_str, INET_ADDRSTRLEN); + gw_addr.s_addr = info->gw; + inet_ntop(AF_INET, &gw_addr, gw_str, INET_ADDRSTRLEN); + server_ip_addr.s_addr = info->server_ipv4; + inet_ntop(AF_INET, &server_ip_addr, server_ip_str, INET_ADDRSTRLEN); + + /* 格式化 MAC 地址 */ + snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X", + info->mac[0], info->mac[1], info->mac[2], info->mac[3], info->mac[4], info->mac[5]); + + /* 打印所有字段 */ + DBG(DBG_M_CLI, "Device Info:\r\n"); + DBG(DBG_M_CLI, " 主设备号 (type_m): %u\r\n", info->type_m); + DBG(DBG_M_CLI, " 次设备号 (type_s): %u\r\n", info->type_s); + DBG(DBG_M_CLI, " 保留字段 (reserved1): %02X %02X\r\n", info->reserved1[0], info->reserved1[1]); + DBG(DBG_M_CLI, " 设备ID (dev_id): %08x\r\n", info->dev_id); + DBG(DBG_M_CLI, " 设备名 (hostname): %s\r\n", info->hostname); + DBG(DBG_M_CLI, " 出厂日期 (factory_date): %u (%s)\r\n", info->factory_date, ctime((const time_t *)&info->factory_date)); + DBG(DBG_M_CLI, " 部署日期 (deployment_date): %u (%s)\r\n", info->deployment_date, ctime((const time_t *)&info->deployment_date)); + DBG(DBG_M_CLI, " 软件版本 (app_version): %.*s\r\n", 32, info->app_version); + DBG(DBG_M_CLI, " 编译时间 (app_compile_time): %.*s\r\n", 32, info->app_compile_time); + DBG(DBG_M_CLI, " 硬件版本 (hardware_version): %.*s\r\n", 32, info->hardware_version); + DBG(DBG_M_CLI, " FPGA版本 (FPGA_version): %.*s\r\n", 32, info->FPGA_version); + DBG(DBG_M_CLI, " IP地址 (ip): %s\r\n", ip_str); + DBG(DBG_M_CLI, " 子网掩码 (mask): %s\r\n", mask_str); + DBG(DBG_M_CLI, " 网关 (gw): %s\r\n", gw_str); + DBG(DBG_M_CLI, " MAC地址 (mac): %s\r\n", mac_str); + DBG(DBG_M_CLI, " 服务器端口 (server_port): %u\r\n", info->server_port); + DBG(DBG_M_CLI, " 服务器IP (server_ipv4): %s\r\n", server_ip_str); +} + +/* 设备基本信息初始化 */ +void vtysh_device_init(void) +{ + struct sockaddr_in server; + int32_t fd = 0; + int32_t rv = 0; + + /* 获取文件名. */ + fd = open("device-config", O_RDONLY); + if (fd != -1) + { + /* 有文件的情况下直接读取配置 */ + rv = read(fd, &device_info, sizeof(device_info)); + if (rv <= 0) + { + log_err(LOG_DEFAULT, "Can't read config file device-config."); + } + close(fd); + } + else + { + device_info.type_m = 3; + device_info.type_s = 1; + inet_aton(DEV_INFO_DEFAULT_IP, &server.sin_addr); + device_info.ip = server.sin_addr.s_addr; + inet_aton(DEV_INFO_DEFAULT_MASK, &server.sin_addr); + device_info.mask = server.sin_addr.s_addr; + inet_aton(DEV_INFO_DEFAULT_GW, &server.sin_addr); + device_info.gw = server.sin_addr.s_addr; + device_info.factory_date = 1745317827; + device_info.deployment_date = 1745317827; + strncpy((char *)device_info.app_version, host.version, 31); + strncpy((char *)device_info.app_compile_time, host.compile, 31); + strncpy((char *)device_info.hardware_version, host.hardversion, 31); + strncpy((char *)device_info.FPGA_version, host.FPGAversion, 31); + device_info.dev_id = 0x6010FFFF; + strncpy((char *)device_info.hostname, "LandPower", FILE_NAME_LEN); + + mac_generate_from_ip(device_info.ip, device_info.mac); + vtysh_device_save(); + vtysh_eth0_save(); + } + + return; +} + +/* 初始化shell. */ +void vtysh_init(void) +{ + vtysh_readline_init(); + vty_init(); + vtysh_device_init(); + + /* Make vty structure. */ + vtysh = vty_create(); + vtysh->type = VTY_SHELL; + vtysh->node = CONFIG_NODE; + + if (pthread_mutex_init(&m_config_mutex, NULL) != 0) + { + log_err(LOG_DEFAULT, "ERROR at mutex init return %s!", safe_strerror(errno)); + return; + } +} + +/* 配置恢复并开启shell线程 */ +void vtysh_config_recovery(void) +{ + char *config_file = NULL; + FILE *file = NULL; + char *buf = NULL; + + /* 获取文件名. */ + config_file = host.configfile; + file = fopen(config_file, "r"); + if (!file) + { + log_err(LOG_DEFAULT, "Can't open config file %s!", host.configfile); + vtysh->node = USERNAME_NODE; + return; + } + + /* 配置恢复 */ + buf = XMALLOC(MTYPE_CLI, VTYSH_CONFIG_R_BUF); + while (!feof(file)) + { + if (NULL == fgets(buf, VTYSH_CONFIG_R_BUF, file)) + { + break; + } + strncpy(vtysh->buf, buf, vtysh->max - 1); + vty_execute(vtysh); + } + XFREE(MTYPE_CLI, buf); + + /* 回收资源 */ + fclose(file); + vtysh->node = USERNAME_NODE; +} + +int32_t vtysh_config_save_bak(int idx) +{ + char src_file[64] = {0}; + char bak_file[64] = {0}; + char cmd[256] = {0}; + snprintf(bak_file, 64, "./bak"); + + if (idx == SAVE_DEV_CFG_BAK_FILE) + { + snprintf(src_file, 64, "device-config"); + snprintf(cmd, 256, "cp -rf %s %s", src_file, bak_file); + system(cmd); + system("sync"); + } + else if (idx == SAVE_STA_CFG_BAK_FILE) + { + snprintf(src_file, 64, "startup-config"); + snprintf(cmd, 256, "cp -rf %s %s", src_file, bak_file); + system(cmd); + system("sync"); + } + return 0; +} + +int32_t vtysh_config_save(void) +{ + uint32_t i = 0; + int fd; + cmd_node_t *node = NULL; + char *config_file = NULL; + char *config_file_tmp = NULL; + char *config_file_sav = NULL; + char buf[TIME_STR_LEN] = {0}; + vty_t *file_vty = NULL; + + /* 获取文件名. */ + config_file = host.configfile; + pthread_mutex_lock(&m_config_mutex); + + config_file_sav = XMALLOC(MTYPE_CLI, strlen(config_file) + strlen(CONF_BACKUP_EXT) + 1); + strcpy(config_file_sav, config_file); + strcat(config_file_sav, CONF_BACKUP_EXT); + + config_file_tmp = XMALLOC(MTYPE_CLI, strlen(config_file) + 8); + sprintf(config_file_tmp, "%s.XXXXXX", config_file); + + /* 打开临时文件写入配置. */ + fd = open(config_file_tmp, O_RDWR | O_CREAT ,0777); + if (fd < 0) + { + printh("Can't open configuration file %s.\r\n", config_file_tmp); + XFREE(MTYPE_CLI, config_file_tmp); + XFREE(MTYPE_CLI, config_file_sav); + pthread_mutex_unlock(&m_config_mutex); + return E_ERROR; + } + + /* Make vty for configuration file. */ + file_vty = vty_create(); + file_vty->fd = fd; + file_vty->type = VTY_FILE; + + /* Config file header print. */ + vty_out(file_vty, "!\n! Zebra configuration saved from vty\n"); + if (time_string(0, buf, sizeof(buf)) != 0) + vty_out(file_vty, "! %s\n", buf); + vty_out(file_vty, "!\n"); + + for (i = 0; i < array_active(cmd_nodes); i++) + if ((node = array_get(cmd_nodes, i)) && node->func) + { + if ((*node->func)(file_vty)) + vty_out(file_vty, "!\n"); + } + vty_close(file_vty); + + if (rename(config_file, config_file_sav) != 0) + { + printh("Can't backup configuration file %s: %s (%d).\r\n", config_file_sav, safe_strerror(errno), errno); + } + if (rename(config_file_tmp, config_file) != 0) + { + printh("Can't save configuration file %s: %s (%d).\r\n", config_file, safe_strerror(errno), errno); + } + + if (chmod(config_file, 0666) != 0) + { + printh("Can't chmod configuration file %s: %s (%d).\r\n", config_file, safe_strerror(errno), errno); + } + + if (chmod(config_file_sav, 0666) != 0) + { + printh("Can't chmod backup configuration file %s: %s (%d).\r\n", config_file, safe_strerror(errno), errno); + } + system("mv startup-config.sav bak/startup-config"); + + printh("Configuration saved to %s.\r\n", config_file); + + XFREE(MTYPE_CLI, config_file_tmp); + XFREE(MTYPE_CLI, config_file_sav); + pthread_mutex_unlock(&m_config_mutex); + + return E_NONE; +} + +int32_t vtysh_host_addr_set(char *addr, char *mask) +{ + struct sockaddr_in server; + uint8_t mac[MAC_ADDR_LEN] = {0}; + + /* 配置 ip. */ + if (inet_aton(addr, &server.sin_addr) < 0) + { + DBG(DBG_M_CLI, "inet_aton ip is error!\r\n"); + return E_BAD_PARAM; + } + device_info.ip = server.sin_addr.s_addr; + + /* 配置 mask. */ + if (inet_aton(mask, &server.sin_addr) < 0) + { + DBG(DBG_M_CLI, "inet_aton ip is error!\r\n"); + return E_BAD_PARAM; + } + device_info.mask = server.sin_addr.s_addr; + + /* 配置 mac. */ + mac_generate_from_ip(device_info.ip, mac); + memcpy(device_info.mac, mac, MAC_ADDR_LEN); + + vtysh_device_save(); + vtysh_eth0_save(); + + system("./script/S80SetEth0"); + sleep(3); + + vty_reset(); + vty_serv_sock_family(NULL, 11000, AF_INET); + + return E_NONE; +} + +int32_t vtysh_gateway_set(char *gateway) +{ + struct sockaddr_in server; + + /* 将 ip_str 转为数字. */ + if (inet_aton(gateway, &server.sin_addr) < 0) + { + DBG(DBG_M_CLI, "inet_aton ip is error!\r\n"); + return E_BAD_PARAM; + } + + device_info.gw = server.sin_addr.s_addr; + vtysh_device_save(); + vtysh_eth0_save(); + + system("./script/S80SetEth0"); + sleep(3); + + return E_NONE; +} + +void vtysh_shell_init(void) +{ + struct sched_param param; + pthread_attr_t attr; + int32_t rv = 0; + + /* 配置线程RR调度,优先级50 */ + pthread_attr_init(&attr); + param.sched_priority = 50; + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + + rv = pthread_create(&_cmd_pid, &attr, _vtysh_handle, NULL); + if (rv != 0) + { + log_err(LOG_DEFAULT, "vtysh_shell_init can't create pthread %d!", rv); + } + else + { + thread_m_add("CLI", _cmd_pid); + } + pthread_attr_destroy(&attr); + + /* YL_TEST */ + //vty_serv_sock_family(device_info.host_ip, 11000, AF_INET); + vty_serv_sock_family(NULL, 11000, AF_INET); +} +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/m_management/cmd_SSH2.c b/app/lib/m_management/cmd_SSH2.c new file mode 100755 index 0000000..93aa5fb --- /dev/null +++ b/app/lib/m_management/cmd_SSH2.c @@ -0,0 +1,722 @@ +/***************************************************************************** + * file lib/management/cmd_SSH2.c + * author YuLiang + * version 1.0.0 + * date 20-Mar-2025 + * brief This file provides all the SSH2 cmd related operation functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2025 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* 标准C库头文件. */ +#include +#include +#include +#include + +/* 用户代码头文件. */ +#include "main.h" +#include "vty.h" +#include "cmd.h" +#include "mtimer.h" + +/* Private typedef -----------------------------------------------------------*/ + +/* Private define ------------------------------------------------------------*/ +#define VTYCMD_PORT "2222" +#define VTYCMD_RSA_PATH "/home/root/ssh/ssh_host_rsa_key" +#define VTYCMD_HOSTKEY_PATH "/home/root/ssh/ssh_host_ed25519_key" + +#define VTYCMD_BUF_OUT_LEN 32768 + +typedef struct _vtycmd_ctrl +{ + vty_t *vtycmd; + ssh_bind sshbind; + ssh_session session; + ssh_channel channel; + struct ssh_server_callbacks_struct server_cb; + struct ssh_channel_callbacks_struct channel_cb; + uint8_t is_connect; + uint8_t is_recv; + uint16_t cmd_idx; + char cmd[VTY_BUFSIZ]; + uint16_t out_buf_start; + uint16_t out_buf_end; + char out_buf[VTYCMD_BUF_OUT_LEN]; +} vtycmd_ctrl_t; + +/* Private macro -------------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +/* cmd vty结构体. */ +static vtycmd_ctrl_t vtycmd_ctrl; + +/* Private function prototypes -----------------------------------------------*/ +/* 在终端上显示当前配置信息. */ +CMD(show_sshcmd, + show_sshcmd_cmd, + "show sshcmd", + SHOW_STR + "SSHCMD\n") +{ + vty_out(vty, "Connect: %s\r\n\n", vtycmd_ctrl.is_connect ? "yes" : "no"); + + return CMD_SUCCESS; +} +#if 0 + +/* Insert a word into vty interface with overwrite mode. */ +void _vtycmd_insert_word(char *str) +{ + int len = strlen(str); + + ssh_channel_write(vtycmd_ctrl.channel, str, len); + strcpy(&vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx], str); + vtycmd_ctrl.cmd_idx += len; + vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0; +} + +/* commandLine '?'响应函数. */ +int _vtycmd_question(void) +{ + array_t *cmd_line = NULL; + + cmd_line = cmd_strs_create(vtycmd_ctrl.cmd); + if (NULL == cmd_line) + { + cmd_line = array_init(1, MTYPE_CLI); + array_append(cmd_line, '\0', MTYPE_CLI); + } + else + if (vtycmd_ctrl.cmd_idx && isspace((int)vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx - 1])) + array_append(cmd_line, '\0', MTYPE_CLI); + + vty_question(vtycmd_ctrl.vtycmd, cmd_line); + + cmd_strs_free(cmd_line); + vty_prompt(vtycmd_ctrl.vtycmd); + vty_out(vtycmd_ctrl.vtycmd, "%s", vtycmd_ctrl.cmd); + return 0; +} + +/* 用于自动补齐命令行关键字. */ +void _vtycmd_completion_append(array_t *cmd_line, char *word, int32_t status) +{ + uint32_t index = array_active(cmd_line) - 1; + uint32_t len = 0; + uint32_t i = 0; + + if (NULL == array_get(cmd_line, index)) + { + _vtycmd_insert_word(word); + } + else + { + len = strlen(array_get(cmd_line, index)); + for(i = 0; i < len; i++) + { + if (vtycmd_ctrl.cmd_idx) + { + vtycmd_ctrl.cmd_idx--; + vty_out(vtycmd_ctrl.vtycmd, "%c%c%c", 8, ' ', 8); + } + } + _vtycmd_insert_word(word); + } + + if (CMD_COMPLETE_FULL_MATCH == status) + _vtycmd_insert_word(" "); +} + +/* commandLine 'TAB'响应函数. */ +int _vtycmd_completion(void) +{ + array_t *cmd_line = NULL; + char **match_strs = NULL; + int32_t complete_status = CMD_ERR_NO_MATCH; + + cmd_line = cmd_strs_create(vtycmd_ctrl.cmd); + if (NULL == cmd_line) + { + cmd_line = array_init(1, MTYPE_CLI); + array_append(cmd_line, '\0', MTYPE_CLI); + } + else + if (vtycmd_ctrl.cmd_idx && isspace((int)vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx - 1])) + array_append(cmd_line, '\0', MTYPE_CLI); + + match_strs = cmd_complete_command(cmd_line, vtycmd_ctrl.vtycmd, &complete_status); + if (NULL == match_strs) + { + cmd_strs_free(cmd_line); + return 0; + } + + if (CMD_COMPLETE_MATCH == complete_status || + CMD_COMPLETE_FULL_MATCH == complete_status) + _vtycmd_completion_append(cmd_line, match_strs[0], complete_status); + else + { + vty_print_word(vtycmd_ctrl.vtycmd, match_strs); + vty_prompt(vtycmd_ctrl.vtycmd); + vty_out(vtycmd_ctrl.vtycmd, "%s", vtycmd_ctrl.cmd); + } + + vty_free_match_strs(match_strs); + cmd_strs_free(cmd_line); + + return 0; +} + + +/* Internal functions --------------------------------------------------------*/ +/* Print command line history. This function is called from + vty_next_line and vty_previous_line. */ +static void _vtycmd_history_print(vty_t *vty) +{ + uint16_t i= 0; + + for(i = 0; i < vtycmd_ctrl.cmd_idx; i++) + { + vty_out(vtycmd_ctrl.vtycmd, "%c%c%c", 8, ' ', 8); + } + vtycmd_ctrl.cmd_idx = 0; + + /* Get previous line from history buffer */ + vtycmd_ctrl.cmd_idx = strlen(vty->hist[vty->hp]); + memcpy(vtycmd_ctrl.cmd, vty->hist[vty->hp], vtycmd_ctrl.cmd_idx); + vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0; + + /* Redraw current line */ + vty_out(vtycmd_ctrl.vtycmd, "%s", vtycmd_ctrl.cmd); +} + +/* Show previous command line history. */ +static void _vtycmd_previous_line(vty_t *vty) +{ + unsigned int try_index = 0; + + try_index = vty->hp; + if (try_index == 0) + try_index = VTY_MAXHIST - 1; + else + try_index--; + + if (vty->hist[try_index] == NULL) + return; + else + vty->hp = try_index; + + _vtycmd_history_print(vty); +} + +/* Show next command line history. */ +static void _vtycmd_next_line(vty_t *vty) +{ + unsigned int try_index = 0; + + if (vty->hp == vty->hindex) + return; + + /* Try is there history exist or not. */ + try_index = vty->hp; + if (try_index == (VTY_MAXHIST - 1)) + try_index = 0; + else + try_index++; + + /* If there is not history return. */ + if (vty->hist[try_index] == NULL) + return; + else + vty->hp = try_index; + + _vtycmd_history_print(vty); +} + +int _vtycmd_cmd(char *buf, uint32_t len) +{ + uint16_t i = 0; + vty_t *vty = vtycmd_ctrl.vtycmd; + + for (i = 0; i < len; i++) + { + switch (buf[i]) + { + case '\033': + if (i + 2 < len && buf[i + 1] == '[') + { + if (buf[i + 2] == 'A') + { + _vtycmd_previous_line(vty); + } + else if (buf[i + 2] == 'B') + { + _vtycmd_next_line(vty); + } + i += 2; + } + break; + case CONTROL('H'): + case 0x7f: + if (vtycmd_ctrl.cmd_idx) + { + vtycmd_ctrl.cmd_idx--; + vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0; + vty_out(vty, "%c%c%c", 8, ' ', 8); + } + break; + case '\n': + case '\r': + vty_out(vty, "%s", VTY_NEWLINE); + /* 执行命令 */ + vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0; + vty->length = vtycmd_ctrl.cmd_idx; + snprintf(vty->buf, VTY_BUFSIZ, "%s", vtycmd_ctrl.cmd); + vty_execute(vty); + vtycmd_ctrl.cmd_idx = 0; + vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0; + break; + case '\t': + _vtycmd_completion(); + break; + case '?': + _vtycmd_question(); + break; + default: + if (buf[i] > 31 && buf[i] < 127) + { + vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = buf[i]; + vtycmd_ctrl.cmd_idx++; + vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0; + vty_out(vty, "%c", buf[i]); + } + break; + } + } + + return E_NONE; +} + +/* 客户端通讯相关的回调 */ +int _vtycmd_channel_pty_request(ssh_session session, ssh_channel channel, + const char *term, int cols, int rows, int py, int px, + void *userdata) +{ + ssh_channel_write(vtycmd_ctrl.channel, "Username: ", 10); + ssh_channel_request_pty_size(channel, term, cols, rows); + return SSH_OK; +} + +int _vtycmd_channel_pty_resize(ssh_session session, ssh_channel channel, int cols, + int rows, int py, int px, void *userdata) +{ + return ssh_channel_change_pty_size(channel, cols, rows); +} + +int _vtycmd_channel_shell_request(ssh_session session, ssh_channel channel, + void *userdata) +{ + return SSH_OK; +} + +int _vtycmd_channel_exec_request(ssh_session session, ssh_channel channel, + const char *command, void *userdata) +{ + return ssh_channel_request_exec(channel, command); +} + +int _vtycmd_channel_subsystem_request(ssh_session session, ssh_channel channel, + const char *subsystem, void *userdata) +{ + return SSH_OK; +} + +int _vtycmd_channel_env_request_function(ssh_session session, ssh_channel channel, const char *env_name, + const char *env_value, void *userdata) +{ + return ssh_channel_request_env(channel, env_name, env_value); +} + +/* 数据处理 */ +int _vtycmd_channel_data_function(ssh_session session, ssh_channel channel, void *data, + uint32_t len, int is_stderr, void *userdata) +{ + vtycmd_ctrl.is_recv = TRUE; + _vtycmd_cmd((char*)data, len); + return len; +} + +void _vtycmd_channel_close(ssh_session session, ssh_channel channel, void *userdata) +{ + vtycmd_ctrl.is_connect = FALSE; +} + +/* 服务端认证通道相关回调 */ +int _vtycmd_server_auth_pass(ssh_session session, const char *user, const char *password, void *userdata) +{ + if (strcmp("root", user) == 0 && strcmp("123456", password) == 0) + { + return SSH_AUTH_SUCCESS; + } + else + { + return SSH_AUTH_DENIED; + } +} + +/* 空认证方法 */ +int _vtycmd_server_auth_none_callback(ssh_session session, const char *user, void *userdata) +{ + return SSH_AUTH_SUCCESS; +} + +/* pubkey 认证方法 */ +int _vtycmd_server_auth_pubkey(ssh_session session, const char *user, struct ssh_key_struct *pubkey, + char signature_state, void *userdata) +{ + return SSH_AUTH_SUCCESS; +} + +int _vtycmd_server_request_callback(ssh_session session, const char *service, void *userdata) +{ + return SSH_OK; +} + +ssh_channel _vtycmd_server_open_channel(ssh_session session, void *userdata) +{ + vtycmd_ctrl.channel = ssh_channel_new(session); + + ssh_callbacks_init(&vtycmd_ctrl.channel_cb); + ssh_set_channel_callbacks(vtycmd_ctrl.channel, &vtycmd_ctrl.channel_cb); + + return vtycmd_ctrl.channel; +} + +/* SSH2 命令行主函数 */ +void *_vtycmd_handle(void *arg) +{ + /* 初始化 libssh */ + if (ssh_init() < 0) + { + log_err(LOG_CLI, "ssh_init failed\r\n"); + return NULL; + } + + /* 初始化结构体 */ + vtycmd_ctrl.server_cb.auth_password_function = _vtycmd_server_auth_pass; + vtycmd_ctrl.server_cb.auth_none_function = _vtycmd_server_auth_none_callback; + vtycmd_ctrl.server_cb.auth_pubkey_function = _vtycmd_server_auth_pubkey; + vtycmd_ctrl.server_cb.service_request_function = _vtycmd_server_request_callback; + vtycmd_ctrl.server_cb.channel_open_request_session_function = _vtycmd_server_open_channel; + + vtycmd_ctrl.channel_cb.channel_data_function = _vtycmd_channel_data_function; + vtycmd_ctrl.channel_cb.channel_close_function = _vtycmd_channel_close; + vtycmd_ctrl.channel_cb.channel_pty_request_function = _vtycmd_channel_pty_request; + vtycmd_ctrl.channel_cb.channel_shell_request_function = _vtycmd_channel_shell_request; + vtycmd_ctrl.channel_cb.channel_pty_window_change_function = _vtycmd_channel_pty_resize; + vtycmd_ctrl.channel_cb.channel_exec_request_function = _vtycmd_channel_exec_request; + vtycmd_ctrl.channel_cb.channel_env_request_function = _vtycmd_channel_env_request_function; + vtycmd_ctrl.channel_cb.channel_subsystem_request_function = _vtycmd_channel_subsystem_request; + + /* 创建 SSH2 接口 */ + vtycmd_ctrl.sshbind = ssh_bind_new(); + ssh_bind_options_set(vtycmd_ctrl.sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, VTYCMD_PORT); + ssh_bind_options_set(vtycmd_ctrl.sshbind, SSH_BIND_OPTIONS_RSAKEY, VTYCMD_RSA_PATH); + ssh_bind_options_set(vtycmd_ctrl.sshbind, SSH_BIND_OPTIONS_HOSTKEY, VTYCMD_HOSTKEY_PATH); + if (ssh_bind_listen(vtycmd_ctrl.sshbind) < 0) + { + log_err(LOG_CLI, "Error listening to socket: %s", ssh_get_error(vtycmd_ctrl.sshbind)); + return NULL; + } + + while(1) + { + /* 等待连接 */ + ssh_session session = ssh_new(); + if (ssh_bind_accept(vtycmd_ctrl.sshbind, session) != SSH_OK) + { + DBG(DBG_M_CLI, "Error accept to socket: %s\n", ssh_get_error(vtycmd_ctrl.sshbind)); + continue; + } + + /* 只能接收一个连接 */ + if (vtycmd_ctrl.is_connect) + { + ssh_disconnect(session); + ssh_free(session); + continue; + } + + /* 初始化连接 */ + log_notice(LOG_CLI, "SSH2 command is connect.\n"); + vtycmd_ctrl.is_connect = TRUE; + ssh_callbacks_init(&vtycmd_ctrl.server_cb); + ssh_set_server_callbacks(session, &vtycmd_ctrl.server_cb); + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); + if (ssh_handle_key_exchange(session) != SSH_OK) + { + DBG(DBG_M_CLI, "Error performing key exchange: %s\n", ssh_get_error(session)); + ssh_disconnect(session); + ssh_free(session); + continue; + } + + ssh_set_blocking(session, 0); + ssh_event event = ssh_event_new(); + ssh_event_add_session(event, session); + + /* 处理连接断开 */ + while(1) + { + if (ssh_event_dopoll(event, 3000) == SSH_ERROR + || !vtycmd_ctrl.is_connect) + { + /* 终端断开处理 */ + log_notice(LOG_CLI, "SSH2 command is disconnect.\n"); + ssh_channel_close(vtycmd_ctrl.channel); + ssh_channel_free(vtycmd_ctrl.channel); + ssh_disconnect(session); + ssh_event_free(event); + ssh_free(session); + vtycmd_ctrl.is_connect = FALSE; + if (CONFIG_NODE == vtycmd_ctrl.vtycmd->node) + { + vty_config_unlock(vtycmd_ctrl.vtycmd); + } + vtycmd_ctrl.vtycmd->node = USERNAME_NODE; + break; + } + } + } + + return NULL; +} + +/* 用于判断终端是否超时 */ +void* _vtycmd_timer(void *arg) +{ + if (!vtycmd_ctrl.is_recv && vtycmd_ctrl.is_connect) + { + vtycmd_ctrl.is_connect = FALSE; + } + vtycmd_ctrl.is_recv = FALSE; + + /* 重新加入定时器. */ + mtimer_add(_vtycmd_timer, NULL, 120, "VTYCMD_TIMER"); + return NULL; +} + +/* SSH2 命令行发送数据 */ +void *_vtycmd_send_handle(void *arg) +{ + char *buf = NULL; + uint16_t end = 0; + + while(1) + { + usleep(100000); + + if (!vtycmd_ctrl.is_connect) + { + continue; + } + + end = vtycmd_ctrl.out_buf_end; + buf = vtycmd_ctrl.out_buf + vtycmd_ctrl.out_buf_start; + + if (end == vtycmd_ctrl.out_buf_start) + { + continue; + } + + if (end < vtycmd_ctrl.out_buf_start) + { + ssh_channel_write(vtycmd_ctrl.channel, buf, VTYCMD_BUF_OUT_LEN - vtycmd_ctrl.out_buf_start); + vtycmd_ctrl.out_buf_start = 0; + } + else + { + ssh_channel_write(vtycmd_ctrl.channel, buf, end - vtycmd_ctrl.out_buf_start); + vtycmd_ctrl.out_buf_start = end; + } + } + + return NULL; +} +#endif + +/* Interface functions -------------------------------------------------------*/ +/* 初始化 SSH2 命令行 */ +void vtycmd_init(void) +{ + /* Make vty structure. */ + vtycmd_ctrl.vtycmd = vty_create(); + vtycmd_ctrl.vtycmd->type = VTY_CMD; + vtycmd_ctrl.vtycmd->node = USERNAME_NODE; + memset(vtycmd_ctrl.vtycmd->hist, 0, sizeof(vtycmd_ctrl.vtycmd->hist)); + vtycmd_ctrl.vtycmd->hp = 0; + vtycmd_ctrl.vtycmd->hindex = 0; + + cmd_install_element(COMMON_NODE, &show_sshcmd_cmd); +} + +/* 启动 SSH2 命令行 */ +void vtycmd_cmd_init(void) +{ + return ; +#if 0 + pthread_t pid; + struct sched_param param; + pthread_attr_t attr; + int32_t rv = 0; + + /* 配置线程RR调度, 优先级75 */ + pthread_attr_init(&attr); + param.sched_priority = 75; + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + + rv = pthread_create(&pid, &attr, _vtycmd_handle, NULL); + if (rv != 0) + { + log_err(LOG_DEFAULT, "vtycmd_cmd_init can't create pthread %d!", rv); + } + else + { + thread_m_add("CMD", pid); + } + pthread_attr_destroy(&attr); + + mtimer_add(_vtycmd_timer, NULL, 120, "VTYCMD_TIMER"); + + /* 配置线程RR调度, 优先级75 */ + pthread_attr_init(&attr); + param.sched_priority = 75; + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + + rv = pthread_create(&pid, &attr, _vtycmd_send_handle, NULL); + if (rv != 0) + { + log_err(LOG_DEFAULT, "vtycmd_cmd_init can't create pthread %d!", rv); + } + else + { + thread_m_add("CMD_SNED", pid); + } + pthread_attr_destroy(&attr); +#endif +} + +/* cmd 是否连接 */ +bool vtycmd_connect(void) +{ + return vtycmd_ctrl.is_connect; +} + +/* 命令行输出 */ +void vtycmd_print(const char *format, va_list va) +{ + char *buf = vtycmd_ctrl.out_buf + vtycmd_ctrl.out_buf_end; + char buf_temp[VTY_BUFSIZ]; + char *buf_temp_p = buf_temp; + uint16_t start = 0;; + int len = 0; + int len_remain = 0; + int len_temp = 0; + + if (!vtycmd_ctrl.is_connect) + { + vtycmd_ctrl.out_buf_start = 0; + vtycmd_ctrl.out_buf_end = 0; + return; + } + + len = vsnprintf(buf_temp, VTY_BUFSIZ, format, va); + if (len <= 0) + { + return; + } + + /* 计算可写入的数据量 */ + start = vtycmd_ctrl.out_buf_start; + if (vtycmd_ctrl.out_buf_end >= start) + { + len_remain = VTYCMD_BUF_OUT_LEN - 1 - (vtycmd_ctrl.out_buf_end - start); + } + else + { + len_remain = start - vtycmd_ctrl.out_buf_end - 1; + } + if (len_remain <= 0) + { + return; + } + + if (len > len_remain) + { + len = len_remain; + } + + /* 超过 out_buf 需要分段存储 */ + if (len + vtycmd_ctrl.out_buf_end > VTYCMD_BUF_OUT_LEN) + { + len_temp = VTYCMD_BUF_OUT_LEN - vtycmd_ctrl.out_buf_end; + memcpy(buf, buf_temp_p, len_temp); + vtycmd_ctrl.out_buf_end = 0; + buf = vtycmd_ctrl.out_buf; + buf_temp_p = buf_temp_p + len_temp; + len -= len_temp; + + memcpy(buf, buf_temp_p, len); + vtycmd_ctrl.out_buf_end += len; + } + else + { + memcpy(buf, buf_temp_p, len); + len_temp = vtycmd_ctrl.out_buf_end + len; + if (VTYCMD_BUF_OUT_LEN == len_temp) + { + vtycmd_ctrl.out_buf_end = 0; + } + else + { + vtycmd_ctrl.out_buf_end = len_temp; + } + } +} +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/lib/m_management/common.c b/app/lib/m_management/common.c new file mode 100755 index 0000000..66e3aa0 --- /dev/null +++ b/app/lib/m_management/common.c @@ -0,0 +1,697 @@ +/****************************************************************************** + * file lib/management/common.c + * author YuLiang + * version 1.0.0 + * date 14-Sep-2021 + * brief This file provides all the common operation functions. + * + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* 标准C库头文件. */ +#include +#include +#include +#include +#include + +/* 用户代码头文件. */ +#include "vty.h" + +/* Private define ------------------------------------------------------------*/ + +/* Private macro -------------------------------------------------------------*/ + +/* Private typedef -----------------------------------------------------------*/ +/* 时间缓存结构体. */ +typedef struct _time_cache +{ + time_t last; /* 最后一次时间缓存数据. */ + size_t len; /* buf中数据的长度. */ + char buf[TIME_STR_LEN]; /* 时间数据转化的字符串. */ +} time_cache_t; + +/* Private variables ---------------------------------------------------------*/ +static struct timeval sd_start, sd_end; +static unsigned long Crc32Table[256] = +{ + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +/* 在时间后面根据precision长度,添加更加精确的时间字符串. */ +static size_t _time_string_prec_add(char *buf, size_t len, int32_t precision, struct timeval *clock) +{ + static const int divisor[] = {0, 100000, 10000, 1000, 100, 10, 1}; + char *p = NULL; + + /* 最多统计到us */ + if (precision > 6) + precision = 6; + + p = buf + len + 1 + precision; + *p-- = '\0'; + + clock->tv_usec /= divisor[precision]; + do + { + *p-- = '0' + (clock->tv_usec % 10); + clock->tv_usec /= 10; + } + while(--precision > 0); + + *p = '.'; + return len + 1 + precision; +} + +/* Interface functions -------------------------------------------------------*/ +/* description: 将字符串转为小写. + param: str -- 需要转换的字符转 + return: 转换后的字符串 */ +char *str_to_lower(char *str) +{ + uint32_t i = 0; + + if (str != NULL) + { + for(i = 0; str[i] != '\0'; i++) + { + str[i] = tolower(str[i]); + } + } + + return str; +} + +/* description: 去掉字符串中的多余空格. + param: str -- 需要转换操作的字符转 + omit_end -- omit_end(TRUE)表示会将字符串尾部的空格全部消除 + omit_end(FALSE)表示如果尾部有空格,允许保留1个空格 + return: 操作后的字符串 */ +char *str_omit_space(char *str, bool omit_end) +{ + char *front = str; + char *last = str; + + if (NULL == str) + { + return NULL; + } + + while(' ' == *front) + front++; + + while((*front) != '\0') + { + if (' ' == *front) + { + while((*++front) == ' '); + + if ('\0' == *front && omit_end) + ; + else + { + *last = ' '; + last++; + } + } + else + { + *last = *front; + front++; + last++; + } + } + + *last = '\0'; + return str; +} + +/* description: 根据传入的精度precision,将时间字符串填到buf. + param: precision -- 精度,几位小数 + buf -- 存储转换后字符串的buffer + buflen -- buf的大小 + return: 返回字符串长度,如果失败,返回0 */ +size_t time_string(int32_t precision, char *buf, size_t buflen) +{ + static time_cache_t cache; + struct timeval clock; + + /* 获取时间数据clock. */ + gettimeofday(&clock, NULL); + + /* 更新时间到字符串,cache是静态的,如果两次time_string调用在1秒之内,将沿用上次的cache. */ + if (cache.last != clock.tv_sec) + { + struct tm *tm = NULL; + cache.last = clock.tv_sec; + tm = localtime(&cache.last); + cache.len = strftime(cache.buf, sizeof(cache.buf), "%Y/%m/%d %H:%M:%S", tm); + } + + if (buflen > cache.len) + { + memcpy(buf, cache.buf, cache.len); + + /* 计算秒之后的精度. */ + if ((precision > 0) && (buflen > cache.len + 1 + precision)) + return _time_string_prec_add(buf, cache.len, precision, &clock); + + buf[cache.len] = '\0'; + return cache.len; + } + + /* buf太小,无法生成字符串. */ + if (buflen > 0) + buf[0] = '\0'; + + return 0; +} + +/* description: 获取错误代码字符串. + param: errnum -- 错误代码 + return: 获取的字符串 */ +const char *safe_strerror(int errnum) +{ + const char *s = strerror(errnum); + return (s != NULL) ? s : "Unknown error"; +} + +/* description: 按16进制打印buf数据. + param: buf -- 数据 + len -- 数据长度 + return: */ +void buf_print(char *buf, int32_t len) +{ + int32_t i = 0; + + for(i = 0; i < len;) + { + printh("%02x ", (uint8_t)buf[i++]); + if(0 == i % 32) + { + printh("\r\n"); + } + } + if(i % 32 != 0) + { + printh("\r\n"); + } + + return; +} + +/* description: 按16进制打印buf数据. + param: buf -- 数据 + len -- 数据长度 + return: */ +void buf_print_16(uint16_t *buf, int32_t len) +{ + int32_t i = 0; + + for(i = 0; i < len;) + { + printh("%04x ", buf[i++]); + if(0 == i % 32) + { + printh("\r\n"); + } + } + if(i % 32 != 0) + { + printh("\r\n"); + } + + return; +} + +/* description: 通过 ip 生成 mac 地址. + param: ip_str -- 输入 ip 地址 + mac -- 输出 mac 地址 + return: E_XXXX */ +int32_t mac_generate_from_ip(uint32_t ip, uint8_t *mac) +{ + uint32_t temp = 0; + + if (NULL == mac) + { + return E_BAD_PARAM; + } + + /* 产生 mac. */ + srand(time(NULL)); + temp = rand(); + mac[0] = 0x68; + mac[1] = temp & 0xff; + mac[2] = (temp >> 8) & 0xff; + mac[3] = (ip >> 8) & 0xff; + mac[4] = (ip >> 16) & 0xff; + mac[5] = (ip >> 24) & 0xff; + + return E_NONE; +} + +/* description: 计算CRC16/MODBUS. + param: data -- 数据 + size -- 数据长度 + return: 计算的crc值 */ +uint16_t crc16(uint8_t *data, uint16_t size) +{ + uint16_t crc = 0xFFFF; + uint8_t i = 0; + + while(size--) + { + crc = crc ^ *data++; + for(i = 0; i < 8; i++) + { + if ((crc & 0x0001) > 0) + { + crc = crc >> 1; + crc = crc ^ 0xa001; + } + else + crc = crc >> 1; + } + } + + return crc; +} + +void invert_uint8(unsigned char *dest_buf, unsigned char *src_buf) +{ + int i; + unsigned char tmp[4]; + tmp[0] = 0; + + for (i = 0; i < 8; i++) + { + if (src_buf[0] & (1 << i)) + tmp[0] |= 1 << (7 - i); + } + dest_buf[0] = tmp[0]; +} + +void invert_uint16(unsigned short *dest_buf, unsigned short *src_buf) +{ + int i; + unsigned short tmp[4]; + tmp[0] = 0; + + for (i = 0; i < 16; i++) + { + if (src_buf[0] & (1 << i)) + tmp[0] |= 1 << (15 - i); + } + dest_buf[0] = tmp[0]; +} + +uint16_t crc16_modbus(uint8_t *data, uint16_t size) +{ + unsigned short wCRCin = 0xFFFF; + unsigned short wCPoly = 0x8005; + unsigned char wChar = 0; + + while (size--) + { + wChar = *(data++); + invert_uint8(&wChar, &wChar); + wCRCin ^= (wChar << 8); + int i = 0; + for (i = 0; i < 8; i++) + { + if (wCRCin & 0x8000) + wCRCin = (wCRCin << 1) ^ wCPoly; + else + wCRCin = wCRCin << 1; + } + } + invert_uint16(&wCRCin, &wCRCin); + return (wCRCin); +} + +/* description: 计算 CRC32 + param: crc -- 前一次 CRC32 + buf -- 数据 + len -- 数据长度 + return: 计算的crc值 */ +unsigned int crc32(unsigned int crc, char *buf, unsigned long len) +{ + const char *s = (const char *)buf; + + while (len) + { + crc = Crc32Table[(crc ^ *s++) & 0xff] ^ (crc >> 8); + len --; + } + return crc; +} + +/* description: 时间统计开始. + param: + return: */ +void speed_detection_stat(void) +{ + gettimeofday(&sd_start, NULL); +} + +/* description: 时间统计结束. + param: + return: */ +void speed_detection_end(void) +{ + float timeuse; + + gettimeofday(&sd_end, NULL); + + timeuse = 1000000 * (sd_end.tv_sec - sd_start.tv_sec) + sd_end.tv_usec - sd_start.tv_usec; + timeuse /= 1000000; + printh("Used Time:%f\r\n", timeuse); +} + +/* description: 通用打印函数. + param: + return: */ +int printh(const char *format, ...) +{ + va_list args; + + va_start(args, format); + vty_print(format, args); + va_end(args); + + return 0; +} + +/* description: 获取int16_t类型的版本号. + param: + return: int16_t类型的版本号 */ +uint16_t sofrware_version_get(void) +{ + char version_str[16]; + char *str = NULL; + char *p = NULL; + uint16_t version = 0; + + snprintf(version_str, 16, "%s", softversion_get()); + str = strtok_r(version_str, ".", &p); + while(str != NULL) + { + version = version << 8; + version |= (uint8_t)(atoi(str)); + str = strtok_r(NULL, ".", &p); + } + + return version; +} + +/* description: 根据传入的字符串转换成mac地址. + param: + return: */ +int32_t str_to_mac(char *mac_str, OUT uint8_t *mac) +{ + char *str = NULL; + char *p = NULL; + uint8_t len = 0; + uint8_t i = 0; + + /* 按:分词 */ + str = strtok_r(mac_str, ":", &p); + while(str != NULL) + { + /* 检查mac长度 */ + if (len >= 6) + { + return E_BAD_PARAM; + } + + /* 检查字符串 */ + for(i = 0; str[i] && str[i] != '\0'; i++) + { + if (!((str[i] >= '0' && str[i] <= '9') + || (str[i] >= 'a' && str[i] <= 'f') + || (str[i] >= 'A' && str[i] <= 'F'))) + { + return E_BAD_PARAM; + } + } + + /* 检查数据长度 */ + if (i != 2) + { + return E_BAD_PARAM; + } + mac[len++] = strtol(str, NULL, 16); + + /* 获取下个数据 */ + str = strtok_r(NULL, ":", &p); + } + + return E_NONE; +} + +/* description: 根据传入的字符串转换成设备id. + param: + return: */ +int32_t str_to_id(char *id_str, OUT uint32_t *id) +{ + char *str = NULL; + char *p = NULL; + uint8_t len = 0; + uint8_t i = 0; + + /* 按 . 分词 */ + str = strtok_r(id_str, ".", &p); + while(str != NULL && len < 2) + { + /* 检查id长度 */ + if (len >= 2) + { + return E_BAD_PARAM; + } + + /* 检查字符串 */ + for(i = 0; str[i] && str[i] != '\0'; i++) + { + if (!(str[i] >= '0' && str[i] <= '9')) + { + return E_BAD_PARAM; + } + } + + id[len++] = strtol(str, NULL, 10); + + /* 获取下个数据 */ + str = strtok_r(NULL, ".", &p); + } + + return E_NONE; +} + +int32_t bitmap_set(uint8_t *buf, int32_t nbit, int32_t buf_len) +{ + if ((NULL == buf) || (nbit >= buf_len * 8)) + { + return E_BAD_PARAM; + } + + buf[nbit / 8] |= 1 << (nbit % 8); + + return E_NONE; +} + +int32_t bitmap_reset(uint8_t *buf, int32_t nbit, int32_t buf_len) +{ + if ((NULL == buf) || (nbit >= buf_len * 8)) + { + return E_BAD_PARAM; + } + + buf[nbit / 8] &= ~(1 << (nbit % 8)); + + return E_NONE; +} + +int32_t is_bitmap_set(uint8_t *buf, int32_t nbit, int32_t buf_len) +{ + if ((NULL == buf) || (nbit >= buf_len * 8)) + { + return FALSE; + } + + return buf[nbit / 8] & (1 << (nbit % 8)); +} + +int32_t time_str_to_long(char *date, char *time, uint32_t *t) +{ + struct tm stm; + int32_t year, month, day, hour, minute,second; + + if (sscanf(date, "%d-%d-%d", &year, &month, &day) != 3) + { + return E_BAD_PARAM; + } + if (sscanf(time, "%d:%d:%d", &hour, &minute, &second) != 3) + { + return E_BAD_PARAM; + } + + stm.tm_year = year - 1900; + stm.tm_mon = month - 1; + stm.tm_mday = day; + stm.tm_hour = hour; + stm.tm_min = minute; + stm.tm_sec = second; + stm.tm_isdst = 0; + *t = mktime(&stm); + + return E_NONE; +} + +uint16_t version_str_to_int(void) +{ + uint32_t version_m = 0; + uint32_t version_s = 0; + uint32_t version_hm = 0; + uint32_t version_hs = 0; + uint32_t n = 0; + + n = sscanf(softversion_get(), "%d.%d.%d.%d", &version_hm, &version_hs, &version_m, &version_s); + if (n != 4) + { + return 0xffff; + } + + return (version_m << 8 | (version_s & 0xff)) & 0xffff; +} + +void time_set(time_t timestamp) +{ + struct tm *p =localtime(×tamp); + struct tm tptr = {0}; + struct timeval tv = {0}; + + tptr.tm_year = p->tm_year; + tptr.tm_mon = p->tm_mon; + tptr.tm_mday = p->tm_mday; + tptr.tm_hour = p->tm_hour; + tptr.tm_min = p->tm_min; + tptr.tm_sec = p->tm_sec; + + tv.tv_sec = mktime(&tptr); + tv.tv_usec = 0; + settimeofday(&tv, NULL); +} + +int32_t create_thread(thread_func_t func, thread_param_t *pParam) +{ + pthread_attr_t attr; + struct sched_param param; + pthread_t pid; + int32_t rv = 0; + + /* 配置线程RR调度, 优先级40 */ + pthread_attr_init(&attr); + param.sched_priority = pParam->priority; + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + rv = pthread_create(&pid, &attr, func, pParam->arg); + if (rv != 0) + { + log_err(pParam->log_module, "PD can't create %s pthread %d!", pParam->thread_name, rv); + return E_SYS_CALL; + } + else + { + thread_m_add(pParam->thread_name, pid); + } + pthread_attr_destroy(&attr); + return E_NONE; +} + +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/m_management/dbg.c b/app/lib/m_management/dbg.c new file mode 100755 index 0000000..012c75c --- /dev/null +++ b/app/lib/m_management/dbg.c @@ -0,0 +1,248 @@ +/****************************************************************************** + * file lib/management/common.c + * author YuLiang + * version 1.0.0 + * date 14-Sep-2021 + * brief This file provides all the debug operation functions. + * + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "cmd.h" + +/* Private define ------------------------------------------------------------*/ + +/* Private macro -------------------------------------------------------------*/ + +/* Private typedef -----------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +#ifdef CFG_DBG_ON +dbg_module_t _dbg_module[DBG_M_COUNT] = +{ + {DBG_M_DBG, FALSE, "debug"}, + {DBG_M_CLI, FALSE, "cli"}, + {DBG_M_MTIMER, FALSE, "timer"}, + {DBG_M_PROCESS, FALSE, "process"}, + {DBG_M_GPIO, FALSE, "gpio"}, + {DBG_M_PD, FALSE, "pd"}, + {DBG_M_PD_ERR, TRUE, "pd_err"}, + {DBG_M_PD_DAU, FALSE, "pd_dau"}, + {DBG_M_PD_DAU_SEND, FALSE, "pd_dau_send"}, + {DBG_M_PD_DAU_RECV, FALSE, "pd_dau_recv"}, + {DBG_M_PD_DAU_ERR, TRUE, "pd_dau_err"}, + {DBG_M_FIFO, FALSE, "fifo"}, + {DBG_M_FIFO_ERR, FALSE, "fifo_err"}, + {DBG_M_PD_CSG, FALSE, "csg"}, + {DBG_M_PD_CSG_ERR, FALSE, "csg_err"}, + {DBG_M_PD_NET_ERR, TRUE, "net_err"}, + {DBG_M_STORAGE_ERR, FALSE, "stroage"}, + {DBG_M_DEBUG, FALSE, "debug"}, + {DBG_M_PD_UPGRADE, FALSE, "upgrade"}, + {DBG_M_PD_DATA, FALSE, "pd_data"}, +}; + +/* Private function prototypes -----------------------------------------------*/ +CMD(debug_on, + debug_on_cmd, + "debug WORD", + "Debug\n" + "Debug module\n") +{ + int32_t i = 0; + + for(i = 0; i < DBG_M_COUNT; i++) + { + if (strncmp(argv[0], _dbg_module[i].desc, strlen(_dbg_module[i].desc))) + { + continue; + } + + dbg_cmd_hander(DBG_CMD_ON, i); + } + + return CMD_SUCCESS; +} + +CMD(no_debug_on, + no_debug_on_cmd, + "no debug WORD", + NO_STR + "Debug\n" + "Debug module\n") +{ + int32_t i = 0; + + for(i = 0; i < DBG_M_COUNT; i++) + { + if (strncmp(argv[0], _dbg_module[i].desc, strlen(argv[0]))) + { + continue; + } + + dbg_cmd_hander(DBG_CMD_OFF, i); + } + + return CMD_SUCCESS; +} + +CMD(no_debug_all, + no_debug_all_cmd, + "no debug", + NO_STR + "Debug\n") +{ + dbg_cmd_hander(DBG_CMD_ALL_OFF, 0); + + return CMD_SUCCESS; +} + +CMD(show_debug_all, + show_debug_all_cmd, + "show debug", + SHOW_STR + "Debug state\n") +{ + int32_t i = 0; + + for(i = 0; i < DBG_M_COUNT; i++) + { + vty_out(vty, "%03d | %-16s %s%s", i, _dbg_module[i].desc, _dbg_module[i].stat ? "on" : "off", VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +/* Internal functions --------------------------------------------------------*/ +/* 开指定模块的debug */ +static int32_t _dbg_on(DBG_MODULE_E module) +{ + if (module >= DBG_M_COUNT) + { + return E_BAD_PARAM; + } + + _dbg_module[module].stat = TRUE; + + return E_NONE; +} + +/* 关指定模块的debug */ +static int32_t _dbg_off(DBG_MODULE_E module) +{ + if (module >= DBG_M_COUNT) + { + return E_BAD_PARAM; + } + + _dbg_module[module].stat = FALSE; + + return E_NONE; +} + +/* 关所有模块的debug */ +static int32_t _dbg_all_off(void) +{ + unsigned int i = 0; + + for(i = 0; i < DBG_M_COUNT; i++) + { + _dbg_module[i].stat = FALSE; + } + + return E_NONE; +} + +/* Interface functions -------------------------------------------------------*/ +/* description: 获取当前模块debug状态 + param: module -- 模块ID + return: (TRUE)开启 (FALSE)关闭 */ +int32_t dbg_stat_get(DBG_MODULE_E module) +{ + if (module >= DBG_M_COUNT) + { + return FALSE; + } + + return _dbg_module[module].stat; +} + +/* description: debug模块命令函数分发. + param: module -- 模块ID + return: (E_NONE)成功,(其他)失败 */ +int32_t dbg_cmd_hander(DBG_CMD_E cmd, int32_t module) +{ + switch(cmd) + { + case DBG_CMD_ON: + LD_E_RETURN(DBG_M_DBG, _dbg_on(module)); + break; + case DBG_CMD_OFF: + LD_E_RETURN(DBG_M_DBG, _dbg_off(module)); + break; + case DBG_CMD_ALL_OFF: + LD_E_RETURN(DBG_M_DBG, _dbg_all_off()); + break; + default: + break; + } + + return E_NONE; +} + +/* description: debug模块初始化 + param: + return: */ +void dbg_init(void) +{ + cmd_install_element(COMMON_NODE, &show_debug_all_cmd); + cmd_install_element(ENABLE_NODE, &debug_on_cmd); + cmd_install_element(ENABLE_NODE, &no_debug_on_cmd); + cmd_install_element(ENABLE_NODE, &no_debug_all_cmd); +} + +#else +int32_t dbg_stat_get(DBG_MODULE_E module) +{ + return FALSE; +} +int dbg_cmd_hander(DBG_CMD_E cmd, int32_t module) +{ + return E_NONE; +} +void dbg_init(void) +{ +} +#endif +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/m_management/fifo.c b/app/lib/m_management/fifo.c new file mode 100644 index 0000000..37f9d40 --- /dev/null +++ b/app/lib/m_management/fifo.c @@ -0,0 +1,273 @@ +/****************************************************************************** + * file lib/management/fifo.c + * author YuLiang + * version 1.0.0 + * date 21-Feb-2023 + * brief This file provides all the fifo operation functions. + * + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* 标准C库头文件. */ + + +/* 用户代码头文件. */ +#include "cmd.h" +#include "fifo.h" + +/* Private define ------------------------------------------------------------*/ + +/* Private macro -------------------------------------------------------------*/ + +/* Private typedef -----------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +static array_t *fifo = NULL; + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +/* 显示 fifo 的使用情况. */ +CMD(show_fifo, + show_fifo_cmd, + "show fifo", + "Show\n" + "Fifo\n") +{ + uint32_t id = 0; + fifo_t *fifo_node = NULL; + + for (id = 0; id < array_active(fifo); id++) + { + fifo_node = array_lookup(fifo, id); + if (NULL == fifo_node) + { + continue; + } + + fifo_show(id); + } + + return CMD_SUCCESS; +} + +/* Interface functions -------------------------------------------------------*/ +/* 初始化 fifo 全局结构 */ +int32_t fifo_init(void) +{ + fifo = array_init(16, MTYPE_FIFO); + if (!fifo) + { + log_err(LOG_FIFO, "ERROR at array init!"); + return E_MEM; + } + + cmd_install_element(COMMON_NODE, &show_fifo_cmd); + + return E_NONE; +} + +/* 创建 1 个 fifo. */ +int32_t fifo_create(char* name, uint32_t size) +{ + fifo_t *fifo_node = NULL; + + /* 初始化. */ + fifo_node = XMALLOC(MTYPE_FIFO, sizeof(fifo_t)); + if (!fifo_node) + { + return E_MEM; + } + + snprintf(fifo_node->name, FIFO_NAME_LEN, "%s", name); + fifo_node->size = size; + + /* 申请互斥锁, 用于通知读线程读取有效. */ + if (sem_init(&fifo_node->sem, 0, 0) != 0) + { + XFREE(MTYPE_FIFO, fifo_node); + DBG(DBG_M_FIFO_ERR, "%s ERROR at sem init return %s!\r\n", name, safe_strerror(errno)); + return E_SYS_CALL; + } + + /* 申请信号量, 防止多个线程同时操作 fifo. */ + if (pthread_mutex_init(&fifo_node->mutex, NULL) != 0) + { + XFREE(MTYPE_FIFO, fifo_node); + sem_destroy(&fifo_node->sem); + DBG(DBG_M_FIFO_ERR, "%s ERROR at sem init return %s!\r\n", name, safe_strerror(errno)); + return E_SYS_CALL; + } + + /* 申请 fifo 空间. */ + fifo_node->data = XMALLOC(MTYPE_FIFO, sizeof(void *) * size); + if (!fifo_node->data) + { + XFREE(MTYPE_FIFO, fifo_node); + sem_destroy(&fifo_node->sem); + pthread_mutex_destroy(&fifo_node->mutex); + return E_MEM; + } + + /* 添加到全局结构体. */ + return array_append(fifo, fifo_node, MTYPE_FIFO); +} + +/* 往 fifo 中写入一条数据. */ +int32_t fifo_write(uint32_t id, void *data, int32_t len) +{ + int32_t index = 0; + fifo_t *fifo_node = array_get(fifo, id); + void *temp = NULL; + + /* 参数检查. */ + if (!fifo_node) + { + DBG_Q(DBG_M_FIFO_ERR, "#7\r\n"); + return E_NOT_FOUND; + } + + /* 申请数据空间. */ + temp = XMALLOC_Q(MTYPE_FIFO, len); + if (!temp) + { + return E_MEM; + } + memcpy(temp, data, len); + + pthread_mutex_lock(&fifo_node->mutex); + + /* 判断 fifo 是否满了. */ + index = fifo_node->cur + 1; + if (index == fifo_node->size) + { + index = 0; + } + if (index == fifo_node->valid) + { + DBG_Q(DBG_M_FIFO_ERR, "#8 %d\r\n", id); + XFREE(MTYPE_FIFO, temp); + pthread_mutex_unlock(&fifo_node->mutex); + return E_MEM; + } + + /* 数据加入 fifo. */ + fifo_node->data[fifo_node->cur] = temp; + fifo_node->used++; + if (fifo_node->used > fifo_node->max) + { + fifo_node->max = fifo_node->used; + } + fifo_node->cur = index; + sem_post(&fifo_node->sem); + + pthread_mutex_unlock(&fifo_node->mutex); + + return len; +} + +/* 从 fifo 中读取数据, 注意, 只能一个进程进行读取. */ +int32_t fifo_read(uint32_t id, void **data) +{ + fifo_t *fifo_node = array_get(fifo, id); + + /* 参数检查. */ + if (!fifo_node) + { + DBG(DBG_M_FIFO_ERR, "Fifo %d is not found!\r\n", id); + return E_NOT_FOUND; + } + + /* 等待有效数据, 如果有效, 返回数据. */ + while (fifo_node->valid == fifo_node->cur) + { + sem_wait(&fifo_node->sem); + } + + *data = fifo_node->data[fifo_node->valid]; + + return E_NONE; +} + +/* 释放 fifo 数据, 因为节省了一次内存申请, 所以必须手动释放 fifo 数据. */ +int32_t fifo_push(uint32_t id) +{ + uint32_t index = 0; + fifo_t *fifo_node = array_get(fifo, id); + + /* 检查参数. */ + if (!fifo_node) + { + DBG(DBG_M_FIFO_ERR, "Fifo %d is not found!\r\n", id); + return E_NOT_FOUND; + } + + /* 释放数据. */ + if (fifo_node->valid != fifo_node->cur) + { + pthread_mutex_lock(&fifo_node->mutex); + + XFREE(MTYPE_FIFO, fifo_node->data[fifo_node->valid]); + fifo_node->data[fifo_node->valid] = NULL; + + index = fifo_node->valid + 1; + if (index == fifo_node->size) + { + index = 0; + } + + fifo_node->used--; + fifo_node->valid = index; + + pthread_mutex_unlock(&fifo_node->mutex); + } + + return E_NONE; +} + +/* 显示 fifo 的使用情况. */ +void fifo_show(uint32_t id) +{ + fifo_t *fifo_node = NULL; + + fifo_node = array_lookup(fifo, id); + if (NULL == fifo_node) + { + return; + } + + printh("%-32s %-2d %-2d %-2d %-2d %-2d %-2d\r\n", fifo_node->name, id, fifo_node->size, fifo_node->cur, + fifo_node->valid, fifo_node->used, fifo_node->max); +} +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/m_management/memory.c b/app/lib/m_management/memory.c new file mode 100644 index 0000000..e2c926e --- /dev/null +++ b/app/lib/m_management/memory.c @@ -0,0 +1,564 @@ +/****************************************************************************** + * file lib/management/memory.c + * author YuLiang + * version 1.0.0 + * date 14-Sep-2021 + * brief This file provides all the memory operation functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* 标准C库头文件. */ +#include +#include + +/* 用户代码头文件. */ +#include "cmd.h" + +/* Private define ------------------------------------------------------------*/ +#define MEM_LOCK pthread_mutex_lock(&mem_mutex) +#define MEM_UNLOCK pthread_mutex_unlock(&mem_mutex) + +/* Private macro -------------------------------------------------------------*/ + +/* Private typedef -----------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +static mem_node_t mem_list_management[] = +{ + {MTYPE_CLI, "Cli"}, + {MTYPE_BUF, "Buffer"}, + {MTYPE_BUF_DATA, "Buffer data"}, + {MTYPE_BUF_TMP, "Buffer temp"}, + {MTYPE_VTY, "Vty"}, + {MTYPE_VTY_TMP, "Vty temp"}, + {MTYPE_VTY_OUT_BUF, "Vty output buffer"}, + {MTYPE_VTY_HIST, "Vty history"}, + {MTYPE_HASH, "Hash"}, + {MTYPE_HASH_BACKET, "Hash Bucket"}, + {MTYPE_HASH_INDEX, "Hash Index"}, + {MTYPE_LOG, "Logging"}, + {MTYPE_MTIMER, "Timer"}, + {MTYPE_THREAD_MONITOR, "Thread monitor"}, + {MTYPE_PREFIX, "Prefix"}, + {MTYPE_THREAD, "Thread"}, + {MTYPE_THREAD_STATS, "Thread stats"}, + {MTYPE_THREAD_MASTER, "Thread master"}, + {MTYPE_THREAD_FUNCNAME, "Thread function name"}, + {MTYPE_FIFO, "fifo"}, + {MTYPE_GPIO, "gpio"}, + { -1, NULL }, +}; + +static mem_node_t mem_list_transceiver[] = +{ + {MTYPE_RECV, "Receive"}, + {MTYPE_SENT, "Sent"}, + {MTYPE_USART, "Usart"}, + {MTYPE_ETH, "Ethernet"}, + { -1, NULL }, +}; + +static mem_node_t mem_list_process[] = +{ + {MTYPE_GIS, "GIS"}, + {MTYPE_DAU, "DAU"}, + {MTYPE_CSG, "CSG"}, + {MTYPE_STORAGE, "STORAGE"}, + {MTYPE_DEBUG, "DEBUG"}, + {MTYPE_UPGRADE, "UPGRADE"}, + { -1, NULL }, +}; + +static mem_list_t mlists[] __attribute__ ((unused)) = +{ + {mem_list_management, "MANAGEMENT"}, + {mem_list_transceiver, "TRANSCEIVER"}, + {mem_list_process, "PROCESS"}, + {NULL, NULL}, +}; + +/* 同于统计每个模块申请内存的次数. */ +static struct +{ + const char *name; + int32_t alloc; + uint32_t t_malloc; + uint32_t c_malloc; + uint32_t t_calloc; + uint32_t c_calloc; + uint32_t t_realloc; + uint32_t t_free; + uint32_t c_strdup; +} mstat[MTYPE_MAX]; + +static pthread_mutex_t mem_mutex; + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +/* 查找模块相应的说明字符串: key -- 模块 */ +static const char *_x_desc_lookup(uint32_t key) +{ + const mem_list_t *list = NULL; + const mem_node_t *pnt = NULL; + + for(list = mlists; list->nodes != NULL; list++) + { + for(pnt = list->nodes; pnt->index >= 0; pnt++) + { + if (pnt->index == key) + { + return pnt->format; + } + } + } + + return ""; +} + +/* 打印每个模块的内存使用情况: pri -- log优先级 */ +static void _x_memstats_print(int32_t pri) +{ + mem_list_t *ml = NULL; + + for (ml = mlists; ml->nodes; ml++) + { + mem_node_t *m = NULL; + + log_out(LOG_MEMORY, pri, "Memory utilization in module %s:", ml->name); + for(m = ml->nodes; m->index >= 0; m++) + { + if (m->index && mstat[m->index].alloc) + { + log_out(LOG_MEMORY, pri, " %-30s: %10ld", m->format, (long)mstat[m->index].alloc); + } + } + } +} + +/* 打印内存申请错误信息: fname -- 调用的函数,type -- 模块, size -- 大小 */ +static void __attribute__ ((noreturn)) _x_mem_error(const char *fname, int32_t type, size_t size) +{ + log_err(LOG_MEMORY, "%s : can't allocate memory for '%s' size %d: %s!", + fname, _x_desc_lookup(type), (int)size, safe_strerror(errno)); + + /* 打印每个模块的内存使用请款 */ + _x_memstats_print(LOG_LVL_WARN); + + /* 打印堆栈信息 */ + log_backtrace(LOG_LVL_WARN); + + abort(); +} + +/* 增加内存alloc计数 */ +static void _x_alloc_inc(int32_t type) +{ + MEM_LOCK; + mstat[type].alloc++; + MEM_UNLOCK; +} + +/* 减少内存alloc计数 */ +static void _x_alloc_dec(int32_t type) +{ + MEM_LOCK; + mstat[type].alloc--; + MEM_UNLOCK; +} + +/* 打印内存申请释放的debug信息. */ +static void _x_mtype_log(char *func, void *memory, const char *file, int32_t line, uint32_t type) +{ + //log_debug(LOG_MEMORY, "%s: %s %p %s %d", func, _x_desc_lookup(type), memory, file, line); + log_out(LOG_MEMORY, LOG_LVL_DBG,"%s: %s %p %s %d", func, _x_desc_lookup(type), memory, file, line); +} + +/* Stats querying from users */ +/* Return a pointer to a human friendly string describing + * the byte count passed in. E.g: + * "0 bytes", "2048 bytes", "110kB", "500MiB", "11GiB", etc. + * Up to 4 significant figures will be given. + * The pointer returned may be NULL (indicating an error) + * or point to the given buffer, or point to static storage. */ +static const char *_x_mtype_memstr(char *buf, size_t len, uint32_t bytes) +{ + int32_t t = 0; + int32_t g = 0; + int32_t m = 0; + int32_t k = 0; + + /* easy cases */ + if (!bytes) + return "0 bytes"; + if (1 == bytes) + return "1 byte"; + +#if 0 + if (sizeof(unsigned long) >= 8) + /* Hacked to make it not warn on ILP32 machines + * Shift will always be 40 at runtime. See below too */ + t = bytes >> (sizeof (unsigned long) >= 8 ? 40 : 0); + else + t = 0; +#endif + + g = bytes >> 30; + m = bytes >> 20; + k = bytes >> 10; + + if (t > 10) + { + /* The shift will always be 39 at runtime. + * Just hacked to make it not warn on 'smaller' machines. + * Static compiler analysis should mean no extra code */ + if (bytes & (1UL << (sizeof(unsigned long) >= 8 ? 39 : 0))) + t++; + snprintf (buf, len, "%4d TiB", t); + } + else if (g > 10) + { + if (bytes & (1 << 29)) + g++; + snprintf (buf, len, "%d GiB", g); + } + else if (m > 10) + { + if (bytes & (1 << 19)) + m++; + snprintf (buf, len, "%d MiB", m); + } + else if (k > 10) + { + if (bytes & (1 << 9)) + k++; + snprintf (buf, len, "%d KiB", k); + } + else + snprintf (buf, len, "%d bytes", bytes); + + return buf; +} + +/* 打印内存使用情况根据系统调用. */ +static int32_t _x_show_memory_mallinfo(vty_t *vty) +{ + struct mallinfo minfo = mallinfo(); + char buf[MTYPE_MEMSTR_LEN] = {0}; + + vty_out(vty, "System allocator statistics:%s", VTY_NEWLINE); + vty_out(vty, " Total heap allocated: %s%s", + _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.arena), VTY_NEWLINE); + vty_out(vty, " Holding block headers: %s%s", + _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.hblkhd), VTY_NEWLINE); + vty_out(vty, " Used small blocks: %s%s", + _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.usmblks), VTY_NEWLINE); + vty_out(vty, " Used ordinary blocks: %s%s", + _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.uordblks), VTY_NEWLINE); + vty_out(vty, " Free small blocks: %s%s", + _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.fsmblks), VTY_NEWLINE); + vty_out(vty, " Free ordinary blocks: %s%s", + _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.fordblks), VTY_NEWLINE); + vty_out(vty, " Ordinary blocks: %ld%s", (unsigned long)minfo.ordblks, VTY_NEWLINE); + vty_out(vty, " Small blocks: %ld%s", (unsigned long)minfo.smblks, VTY_NEWLINE); + vty_out(vty, " Holding blocks: %ld%s", (unsigned long)minfo.hblks, VTY_NEWLINE); + vty_out(vty, "(see system documentation for 'mallinfo' for meaning)%s", VTY_NEWLINE); + + return 1; +} + +static void _x_show_separator(vty_t *vty) +{ + vty_out(vty, "-----------------------------%s", VTY_NEWLINE); +} + +int32_t _x_show_memory_vty(vty_t *vty, mem_node_t *list) +{ + mem_node_t *m = NULL; + int32_t needsep = 0; + + for (m = list; m->index >= 0; m++) + if (0 == m->index) + { + if (needsep) + { + _x_show_separator(vty); + needsep = 0; + } + } + else + { + vty_out(vty, "%-30s: %10d%s", m->format, mstat[m->index].alloc, VTY_NEWLINE); + needsep = 1; + } + + return needsep; +} + +/* 申请内存,并增加相应的模块的申请数: type -- 模块,size -- 大小 */ +static void *_x_malloc (int32_t type, size_t size) +{ + void *memory = NULL; + + memory = malloc(size); + + if (NULL == memory) + _x_mem_error("malloc", type, size); + + memset(memory, 0, size); + + _x_alloc_inc(type); + + return memory; +} + +/* 申请内存,并增加相应的模块的申请数: type -- 模块,size -- 大小 */ +static void *_x_malloc_q (int32_t type, size_t size) +{ + void *memory = NULL; + + memory = malloc(size); + + if (NULL == memory) + _x_mem_error("malloc", type, size); + + _x_alloc_inc(type); + + return memory; +} + +/* Allocate memory as in zmalloc, and also clear the memory. */ +static void *_x_calloc(int32_t type, size_t size) +{ + void *memory = NULL; + + memory = calloc(1, size); + + if (NULL == memory) + _x_mem_error("calloc", type, size); + + memset(memory, 0, size); + + _x_alloc_inc(type); + + return memory; +} + +/* Given a pointer returned by zmalloc or zcalloc, free it and + * return a pointer to a new size, basically acting like realloc(). + * Requires: ptr was returned by zmalloc, zcalloc, or zrealloc with the + * same type. + * Effects: Returns a pointer to the new memory, or aborts. */ +static void *_x_realloc(int32_t type, void *ptr, size_t size) +{ + void *memory = NULL; + + memory = realloc(ptr, size); + if (NULL == memory) + _x_mem_error("realloc", type, size); + + if (NULL == ptr) + _x_alloc_inc(type); + + return memory; +} + +/* Free memory allocated by z*alloc or zstrdup. + * Requires: ptr was returned by zmalloc, zcalloc, or zrealloc with the + * same type. + * Effects: The memory is freed and may no longer be referenced. */ +static void _x_free(int32_t type, void *ptr) +{ + if (ptr != NULL) + { + _x_alloc_dec(type); + free(ptr); + } +} + +/* Duplicate a string, counting memory usage by type. + * Effects: The string is duplicated, and the return value must + * eventually be passed to zfree with the same type. The function will + * succeed or abort. */ +static char *_x_strdup(int32_t type, const char *str) +{ + void *dup = NULL; + + dup = strdup(str); + if (dup == NULL) + _x_mem_error("strdup", type, strlen(str)); + + _x_alloc_inc(type); + + return dup; +} + +/* Interface functions -------------------------------------------------------*/ +/* description: 申请内存. + param: file -- 调用的文件 + line -- 调用的行数 + type -- 哪个模块调用 + size -- 分配的大小 + return: 分配的内存地址 */ +void *mtype_x_malloc(const char *file, int32_t line, int32_t type, size_t size) +{ + void *memory = NULL; + + mstat[type].c_malloc++; + mstat[type].t_malloc++; + + memory = _x_malloc(type, size); + _x_mtype_log("x_malloc", memory, file, line, type); + + return memory; +} + +void *mtype_x_malloc_q(const char *file, int32_t line, int32_t type, size_t size) +{ + void *memory = NULL; + + mstat[type].c_malloc++; + mstat[type].t_malloc++; + + memory = _x_malloc_q(type, size); + _x_mtype_log("x_malloc_q", memory, file, line, type); + + return memory; +} + +/* 见mtype_x_malloc. */ +void *mtype_x_calloc(const char *file, int32_t line, int32_t type, size_t size) +{ + void *memory = NULL; + + mstat[type].c_calloc++; + mstat[type].t_calloc++; + + memory = _x_calloc(type, size); + _x_mtype_log("x_calloc", memory, file, line, type); + + return memory; +} + +/* 见mtype_x_malloc. */ +void *mtype_x_realloc(const char *file, int32_t line, int32_t type, void *ptr, size_t size) +{ + void *memory = NULL; + + /* Realloc need before allocated pointer. */ + mstat[type].t_realloc++; + + memory = _x_realloc(type, ptr, size); + + _x_mtype_log("x_realloc", memory, file, line, type); + + return memory; +} + +/* 见mtype_x_malloc. */ +void mtype_x_free(const char *file, int32_t line, int32_t type, void *ptr) +{ + mstat[type].t_free++; + + _x_free(type, ptr); + + _x_mtype_log("x_free", ptr, file, line, type); +} + +/* 见mtype_x_malloc. */ +char *mtype_x_strdup(const char *file, int32_t line, int32_t type, const char *str) +{ + char *memory = NULL; + + mstat[type].c_strdup++; + + memory = _x_strdup(type, str); + + _x_mtype_log("x_strdup", memory, file, line, type); + + return memory; +} + +CMD(show_memory_all, + show_memory_all_cmd, + "show memory", + SHOW_STR + "Memory statistics\n") +{ + mem_list_t *ml = NULL; + int needsep = 0; + + needsep = _x_show_memory_mallinfo(vty); + + for (ml = mlists; ml->nodes; ml++) + { + if (needsep) + _x_show_separator(vty); + needsep = _x_show_memory_vty(vty, ml->nodes); + } + + return CMD_SUCCESS; +} + +void mem_show(vty_t *vty) +{ + mem_list_t *ml = NULL; + int needsep = 0; + + for (ml = mlists; ml->nodes; ml++) + { + if (needsep) + _x_show_separator(vty); + needsep = _x_show_memory_vty(vty, ml->nodes); + } +} + +/* description: memory模块初始化. + param: + return: */ +void mtype_init(void) +{ + cmd_install_element(COMMON_NODE, &show_memory_all_cmd); +} + +/* description: memory模块初始化. + param: + return: */ +void mtype_init_befor(void) +{ + /* 初始化线程锁. */ + pthread_mutex_init(&mem_mutex, NULL); +} + +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/m_management/module.mk b/app/lib/m_management/module.mk new file mode 100644 index 0000000..cc46863 --- /dev/null +++ b/app/lib/m_management/module.mk @@ -0,0 +1,3 @@ +local_src := $(patsubst $(SOURCE_DIR)/%,%,$(wildcard $(SOURCE_DIR)/$(subdirectory)/*.c)) + +$(eval $(call make-library,$(subdirectory)/libm_management.a,$(local_src))) \ No newline at end of file diff --git a/app/lib/m_management/mtimer.c b/app/lib/m_management/mtimer.c new file mode 100644 index 0000000..5d1999b --- /dev/null +++ b/app/lib/m_management/mtimer.c @@ -0,0 +1,243 @@ +/***************************************************************************** + * file lib/management/mtimer.c + * author Yuliang + * version 1.0.0 + * date 22-Sep-2021 + * brief This file provides all the timer related operation functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "array.h" +#include "mtimer.h" +#include "cmd.h" + +/* Private typedef -----------------------------------------------------------*/ + +/* Private define ------------------------------------------------------------*/ + +/* Private macro -------------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +static pthread_t mtimer_pid; +static array_t *mtimer_array; +static pthread_mutex_t mtimer_mutex; + +/* Private function prototypes -----------------------------------------------*/ +extern void _mtimer_lock(void); +extern void _mtimer_unlock(void); + +/* 485设备显示. */ +CMD(mtime_show, + mtime_show_cmd, + "show mtime", + SHOW_STR + "mtime\n") +{ + mtimer_t *entry = NULL; + uint32_t i = 0; + + /* 遍历所有外设 */ + _mtimer_lock(); + vty_out(vty, "ID %-32s INR%s", "NAME", VTY_NEWLINE); + for (i = 0; i < array_active(mtimer_array); i++) + { + entry = array_lookup(mtimer_array, i); + if (NULL == entry) + { + continue; + } + + vty_out(vty, "%-02d %-32s %d%s", i, entry->name, entry->interval, VTY_NEWLINE); + } + + _mtimer_unlock(); + return CMD_SUCCESS; +} + +/* Internal functions --------------------------------------------------------*/ +void _mtimer_lock(void) +{ + pthread_mutex_lock(&mtimer_mutex); +} + +void _mtimer_unlock(void) +{ + pthread_mutex_unlock(&mtimer_mutex); +} + +/* 定时器线程. */ +void *_mtimer_process(void *arg) +{ + uint32_t i = 0; + uint32_t t = 0; + mtimer_t *entry = NULL; + + while(1) + { + /* 最小粒度1s */ + sleep(1); + + /* 遍历所有定时器 */ + _mtimer_lock(); + t = time(NULL); + for (i = 0; i < array_active(mtimer_array); i++) + { + entry = array_lookup(mtimer_array, i); + if (NULL == entry) + { + continue; + } + + /* 判断时间有没有到 */ + if (abs(t - entry->time) < entry->interval) + { + continue; + } + + /* 调用回调函数 */ + entry->handle(entry->arg); + + /* 删除定时器 */ + array_unset(mtimer_array, i); + XFREE(MTYPE_MTIMER, entry); + } + _mtimer_unlock(); + } + + return NULL; +} + +/* Interface functions -------------------------------------------------------*/ +/* description: 添加定时器. + param: + return: (E_NONE)成功,(其他)失败 */ +int32_t mtimer_add(MTIMER_CALLBACK func, void *arg, uint16_t interval, char *name) +{ + mtimer_t *timer = NULL; + + _mtimer_lock(); + timer = XMALLOC(MTYPE_MTIMER, sizeof(mtimer_t)); + if (NULL == timer) + { + DBG(DBG_M_MTIMER, "Malloc error!\r\n"); + _mtimer_unlock(); + return E_MEM; + } + + timer->handle = func; + timer->arg = arg; + snprintf(timer->name, DEV_NAME_STR_LEN, "%s", name); + timer->interval = interval; + timer->time = time(NULL); + array_append(mtimer_array, timer, MTYPE_MTIMER); + + _mtimer_unlock(); + return E_NONE; +} + +/* description: 删除定时器. + param: + return: (E_NONE)成功,(其他)失败 */ +int32_t mtimer_del(MTIMER_CALLBACK func, void *arg) +{ + uint32_t i = 0; + mtimer_t *entry = NULL; + + _mtimer_lock(); + /* 遍历数组 */ + for (i = 0; i < array_active(mtimer_array); i++) + { + entry = array_lookup(mtimer_array, i); + if (NULL == entry) + { + continue; + } + + /* 比较数据 */ + if (entry->handle == func && entry->arg == arg) + { + /* 删除定时器 */ + array_unset(mtimer_array, i); + XFREE(MTYPE_MTIMER, entry); + } + } + + _mtimer_unlock(); + return E_NONE; +} + +/* description: 定时器初始化. + param: + return: (E_NONE)成功,(其他)失败 */ +int32_t mtimer_init(void) +{ + struct sched_param param; + pthread_attr_t attr; + pthread_mutexattr_t attr_m; + + /* 初始化用户列表 */ + mtimer_array = array_init(16, MTYPE_MTIMER); + + /* 递归锁 */ + pthread_mutexattr_init(&attr_m); + pthread_mutexattr_settype(&attr_m, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mtimer_mutex, &attr_m); + + /* 配置线程RR调度,优先级50 */ + pthread_attr_init(&attr); + param.sched_priority = 50; + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + + /* 创建接收数据线程. */ + if (pthread_create(&mtimer_pid, &attr, _mtimer_process, NULL) != 0) + { + log_err(LOG_DEFAULT, "mtimer_init can't create pthread!"); + } + else + { + thread_m_add("TIMER", mtimer_pid); + } + pthread_attr_destroy(&attr); + + cmd_install_element(COMMON_NODE, &mtime_show_cmd); + + return E_NONE; +} +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/lib/m_management/sockunion.c b/app/lib/m_management/sockunion.c new file mode 100644 index 0000000..687a0b8 --- /dev/null +++ b/app/lib/m_management/sockunion.c @@ -0,0 +1,605 @@ +/****************************************************************************** + * file lib/management/sockunion.c + * author YuLiang + * version 1.0.0 + * date 14-Sep-2021 + * brief This file provides all the sockunion operation functions. + * + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "memory.h" +#include "sockunion.h" + +/* Private define ------------------------------------------------------------*/ + +/* Private macro -------------------------------------------------------------*/ + +/* Private typedef -----------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +/* Maskbit. */ +static const u_char maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, + 0xf8, 0xfc, 0xfe, 0xff}; + +/* Static structure for IPv4 access_list's master. */ +static access_master_t access_master_ipv4 = +{ + {NULL, NULL}, + {NULL, NULL}, + NULL, + NULL, +}; + +#ifdef HAVE_IPV6 +/* Static structure for IPv6 access_list's master. */ +static access_master_t access_master_ipv6 = +{ + {NULL, NULL}, + {NULL, NULL}, + NULL, + NULL, +}; +#endif /* HAVE_IPV6 */ + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +/* Malloc prefix structure. */ +static prefix_t *_prefix_new(void) +{ + prefix_t *p = NULL; + + p = XMALLOC(MTYPE_PREFIX, sizeof(prefix_t)); + + return p; +} + +/* Interface functions -------------------------------------------------------*/ +/* Free prefix structure. */ +void prefix_free(prefix_t *p) +{ + XFREE(MTYPE_PREFIX, p); +} + +/* Allocate new prefix_ipv4 structure. */ +prefix_ipv4_t *prefix_ipv4_new(void) +{ + prefix_ipv4_t *p = NULL; + + /* Call prefix_new to allocate a full-size struct prefix to avoid problems + * where the prefix_ipv4_t is cast to struct prefix and unallocated + * bytes were being referenced (e.g. in structure assignments). */ + p = (prefix_ipv4_t *)_prefix_new(); + p->family = AF_INET; + + return p; +} + +#ifdef HAVE_IPV6 +/* Allocate a new ip version 6 route */ +prefix_ipv6_t *prefix_ipv6_new (void) +{ + prefix_ipv6_t *p = NULL; + + /* Allocate a full-size struct prefix to avoid problems with structure + * 8 size mismatches. */ + p = (prefix_ipv6_t *)_prefix_new(); + p->family = AF_INET6; + return p; +} +#endif + +static access_master_t *_access_master_get(uint16_t afi) +{ + if (AFI_IP == afi) + return &access_master_ipv4; +#ifdef HAVE_IPV6 + else if (AFI_IP6 == afi) + return &access_master_ipv6; +#endif /* HAVE_IPV6 */ + + return NULL; +} + +/* If filter match to the prefix then return 1. */ +static int _filter_match_cisco(filter_t *mfilter, prefix_t *p) +{ + filter_cisco_t *filter = NULL; + struct in_addr mask; + uint32_t check_addr = 0; + uint32_t check_mask = 0; + + filter = &mfilter->u.cfilter; + check_addr = p->u.prefix4.s_addr & ~filter->addr_mask.s_addr; + + if (filter->extended) + { + masklen2ip(p->prefixlen, &mask); + check_mask = mask.s_addr & ~filter->mask_mask.s_addr; + + if (0 == memcmp(&check_addr, &filter->addr.s_addr, 4) && + 0 == memcmp(&check_mask, &filter->mask.s_addr, 4)) + return 1; + } + else if (0 == memcmp(&check_addr, &filter->addr.s_addr, 4)) + return 1; + + return 0; +} + +/* If filter match to the prefix then return 1. */ +static int _filter_match_zebra(filter_t *mfilter, prefix_t *p) +{ + filter_zebra_t *filter= NULL; + + filter = &mfilter->u.zfilter; + + if (filter->prefix.family == p->family) + { + if (filter->exact) + { + if (filter->prefix.prefixlen == p->prefixlen) + return prefix_match(&filter->prefix, p); + else + return 0; + } + else + return prefix_match(&filter->prefix, p); + } + else + return 0; +} + +/* Lookup access_list from list of access_list by name. */ +access_list_t *access_list_lookup(uint16_t afi, const char *name) +{ + access_list_t *access = NULL; + access_master_t *master = NULL; + + if (NULL == name) + return NULL; + + master = _access_master_get(afi); + if (NULL== master) + return NULL; + + for(access = master->num.head; access; access = access->next) + if (0 == strcmp(access->name, name)) + return access; + + for(access = master->str.head; access; access = access->next) + if (0 == strcmp(access->name, name)) + return access; + + return NULL; +} + +/* Apply access list to object (which should be prefix_t *). */ +FILTER_TYPE_E access_list_apply(access_list_t *access, void *object) +{ + filter_t *filter = NULL; + prefix_t *p = NULL; + + p = (prefix_t *)object; + + if (NULL == access) + return FILTER_DENY; + + for (filter = access->head; filter; filter = filter->next) + { + if (filter->cisco) + { + if (_filter_match_cisco(filter, p)) + return filter->type; + } + else + { + if (_filter_match_zebra(filter, p)) + return filter->type; + } + } + + return FILTER_DENY; +} + +/* Convert masklen into IP address's netmask. */ +void masklen2ip(int masklen, struct in_addr *netmask) +{ + uint8_t *pnt = NULL; + int32_t bit = 0; + int32_t offset = 0; + + memset(netmask, 0, sizeof(struct in_addr)); + pnt = (unsigned char *)netmask; + + offset = masklen / 8; + bit = masklen % 8; + + while(offset--) + *pnt++ = 0xff; + + if (bit) + *pnt = maskbit[bit]; +} + +/* If n includes p prefix then return 1 else return 0. */ +int prefix_match(const prefix_t *n, const prefix_t *p) +{ + int32_t offset = 0; + int32_t shift = 0; + const uint8_t *np = NULL; + const uint8_t *pp = NULL; + + /* If n's prefix is longer than p's one return 0. */ + if (n->prefixlen > p->prefixlen) + return 0; + + /* Set both prefix's head pointer. */ + np = (const u_char *)&n->u.prefix; + pp = (const u_char *)&p->u.prefix; + + offset = n->prefixlen / PNBBY; + shift = n->prefixlen % PNBBY; + + if (shift) + if (maskbit[shift] & (np[offset] ^ pp[offset])) + return 0; + + while (offset--) + if (np[offset] != pp[offset]) + return 0; + + return 1; +} + +/* Convert IPv4 compatible IPv6 address to IPv4 address. */ +static void _sockunion_normalise_mapped(SOCKUNION_U *su) +{ +#ifdef HAVE_IPV6 + struct sockaddr_in sin; + + if (AF_INET6 == su->sa.sa_family && + IN6_IS_ADDR_V4MAPPED(&su->sin6.sin6_addr)) + { + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_port = su->sin6.sin6_port; + memcpy(&sin.sin_addr, ((char *)&su->sin6.sin6_addr) + 12, 4); + memcpy(su, &sin, sizeof(struct sockaddr_in)); + } +#endif /* HAVE_IPV6 */ +} + +int str2sockunion(const char *str, SOCKUNION_U *su) +{ + int ret = 0; + + memset(su, 0, sizeof(SOCKUNION_U)); + + ret = inet_pton(AF_INET, str, &su->sin.sin_addr); + /* Valid IPv4 address format. */ + if (ret > 0) + { + su->sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + su->sin.sin_len = sizeof(struct sockaddr_in); +#endif + return 0; + } + +#ifdef HAVE_IPV6 + ret = inet_pton(AF_INET6, str, &su->sin6.sin6_addr); + /* Valid IPv6 address format. */ + if (ret > 0) + { + su->sin6.sin6_family = AF_INET6; +#ifdef SIN6_LEN + su->sin6.sin6_len = sizeof(struct sockaddr_in6); +#endif + return 0; + } +#endif + + return -1; +} + +const char *sockunion2str(SOCKUNION_U *su, char *buf, size_t len) +{ + if (AF_INET == su->sa.sa_family) + return inet_ntop(AF_INET, &su->sin.sin_addr, buf, len); +#ifdef HAVE_IPV6 + else if(AF_INET6 == su->sa.sa_family) + return inet_ntop(AF_INET6, &su->sin6.sin6_addr, buf, len); +#endif + + return NULL; +} + +/* Return accepted new socket file descriptor. */ +int sockunion_accept(int sock, SOCKUNION_U *su) +{ + socklen_t len; + int client_sock; + + len = sizeof(SOCKUNION_U); + client_sock = accept(sock, (struct sockaddr*)su, &len); + + _sockunion_normalise_mapped(su); + return client_sock; +} + +int set_nonblocking(int fd) +{ + int flags = 0; + + /* According to the Single UNIX Spec, the return value for F_GETFL should + * never be negative. */ + if ((flags = fcntl(fd, F_GETFL)) < 0) + { + printh("fcntl(F_GETFL) failed for fd %d: %s", fd, safe_strerror(errno)); + return -1; + } + if (fcntl(fd, F_SETFL, (flags | O_NONBLOCK)) < 0) + { + printh("fcntl failed setting fd %d non-blocking: %s", fd, safe_strerror(errno)); + return -1; + } + + return 0; +} + +/* Utility function of convert between struct prefix <=> union sockunion. */ +prefix_t *sockunion2hostprefix(const SOCKUNION_U *su) +{ + if (su->sa.sa_family == AF_INET) + { + prefix_ipv4_t *p = NULL; + + p = prefix_ipv4_new(); + p->family = AF_INET; + p->prefix = su->sin.sin_addr; + p->prefixlen = IPV4_MAX_BITLEN; + return (prefix_t*)p; + } + +#ifdef HAVE_IPV6 + if (su->sa.sa_family == AF_INET6) + { + prefix_ipv6_t *p = NULL; + + p = prefix_ipv6_new(); + p->family = AF_INET6; + p->prefixlen = IPV6_MAX_BITLEN; + memcpy(&p->prefix, &su->sin6.sin6_addr, sizeof(struct in6_addr)); + return (prefix_t*)p; + } +#endif /* HAVE_IPV6 */ + + return NULL; +} + +char *sockunion_su2str(SOCKUNION_U *su) +{ + char str[SU_ADDRSTRLEN] = {0}; + + switch (su->sa.sa_family) + { + case AF_INET: + inet_ntop(AF_INET, &su->sin.sin_addr, str, sizeof(str)); + break; +#ifdef HAVE_IPV6 + case AF_INET6: + inet_ntop(AF_INET6, &su->sin6.sin6_addr, str, sizeof(str)); + break; +#endif /* HAVE_IPV6 */ + } + + return XSTRDUP(MTYPE_PREFIX, str); +} + +/* Make socket from sockunion union. */ +int sockunion_stream_socket(SOCKUNION_U *su) +{ + int32_t sock = 0; + + if (0 == su->sa.sa_family) + su->sa.sa_family = AF_INET_UNION; + + sock = socket(su->sa.sa_family, SOCK_STREAM, 0); + if (sock < 0) + printh("can't make socket sockunion_stream_socket"); + + return sock; +} + +int sockunion_reuseaddr(int sock) +{ + int ret; + int on = 1; + + ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); + if (ret < 0) + { + printh("can't set sockopt SO_REUSEADDR to socket %d", sock); + return -1; + } + + return 0; +} + +/* Bind socket to specified address. */ +int sockunion_bind(int sock, SOCKUNION_U *su, unsigned short port, SOCKUNION_U *su_addr) +{ + int size = 0; + int ret = 0; + + if (AF_INET == su->sa.sa_family) + { + size = sizeof(struct sockaddr_in); + su->sin.sin_port = htons(port); + if (NULL == su_addr) + su->sin.sin_addr.s_addr = htonl(INADDR_ANY); + } +#ifdef HAVE_IPV6 + else if(AF_INET6 == su->sa.sa_family) + { + size = sizeof(struct sockaddr_in6); + su->sin6.sin6_port = htons(port); + if (NULL == su_addr) + memset (&su->sin6.sin6_addr, 0, sizeof(struct in6_addr)); + } +#endif /* HAVE_IPV6 */ + + + ret = bind(sock, (struct sockaddr *)su, size); + if (ret < 0) + printh("can't bind socket : %s\n", safe_strerror(errno)); + + return ret; +} + +int sockunion_ip_set(char *name, unsigned int addr) +{ + int fd = 0; + struct ifreq ifr; + struct sockaddr_in *sin = NULL; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + { + printh("Ip set socket error!\r\n"); + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, name); + sin = (struct sockaddr_in*)&ifr.ifr_addr; + sin->sin_family = AF_INET; + + /* IP地址 */ + sin->sin_addr.s_addr = addr; + if (ioctl(fd, SIOCSIFADDR, &ifr) < 0) + { + close(fd); + printh("Ip set ioctl SIOCSIFADDR error!\r\n"); + return -2; + } + + close(fd); + return 0; +} + +int sockunion_mask_set(char *name, unsigned int mask) +{ + int fd = 0; + struct ifreq ifr; + struct sockaddr_in *sin = NULL; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + { + printh("socket error\r\n"); + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, name); + sin = (struct sockaddr_in*)&ifr.ifr_addr; + sin->sin_family = AF_INET; + + /* 子网掩码 */ + sin->sin_addr.s_addr = mask; + if (ioctl(fd, SIOCSIFNETMASK, &ifr) < 0) + { + close(fd); + printh("ioctl\r\n"); + return -3; + } + + close(fd); + return 0; +} + +int sockunion_gw_set(char *name, unsigned int gateway, unsigned int gateway_old) +{ + int fd = 0; + struct rtentry rt; + int rv = 0; + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd < 0) + { + printh("Gateway set socket error!\r\n"); + return -1; + } + + /* Delete existing defalt gateway */ + memset(&rt, 0, sizeof(rt)); + rt.rt_dst.sa_family = AF_INET; + ((struct sockaddr_in *)&rt.rt_dst)->sin_addr.s_addr = 0; + rt.rt_gateway.sa_family = AF_INET; + ((struct sockaddr_in *)&rt.rt_gateway)->sin_addr.s_addr = gateway_old; + rt.rt_genmask.sa_family = AF_INET; + ((struct sockaddr_in *)&rt.rt_genmask)->sin_addr.s_addr = 0; + rt.rt_flags = RTF_UP; + rv = ioctl(fd, SIOCDELRT, &rt); + + /* Set default gateway */ + if ((rv == 0 || errno == ESRCH) && gateway) + { + memset(&rt, 0, sizeof(rt)); + rt.rt_dst.sa_family = AF_INET; + ((struct sockaddr_in *)&rt.rt_dst)->sin_addr.s_addr = 0; + rt.rt_gateway.sa_family = AF_INET; + ((struct sockaddr_in *)&rt.rt_gateway)->sin_addr.s_addr = gateway; + rt.rt_genmask.sa_family = AF_INET; + ((struct sockaddr_in *)&rt.rt_genmask)->sin_addr.s_addr = 0; + rt.rt_flags = RTF_UP | RTF_GATEWAY; + rv = ioctl(fd, SIOCADDRT, &rt); + } + + close(fd); + return rv; +} +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/m_management/thread_monitor.c b/app/lib/m_management/thread_monitor.c new file mode 100644 index 0000000..91f4b39 --- /dev/null +++ b/app/lib/m_management/thread_monitor.c @@ -0,0 +1,125 @@ +/***************************************************************************** + * file lib/management/thread_monitor.c + * author Yuliang + * version 1.0.0 + * date 08-Oct-2021 + * brief This file provides all the thread monitor related operation functions. + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "array.h" +#include "cmd.h" +#include "thread_monitor.h" + +/* Private typedef -----------------------------------------------------------*/ + +/* Private define ------------------------------------------------------------*/ +#define THREAD_M_SIZE 32 + +#define THREAD_LOCK pthread_mutex_lock(&thread_mutex) +#define THREAD_UNLOCK pthread_mutex_unlock(&thread_mutex) + +/* Private macro -------------------------------------------------------------*/ + +/* Private variables ---------------------------------------------------------*/ +static pthread_mutex_t thread_mutex; +static array_t *thread_a; + +/* Private function prototypes -----------------------------------------------*/ +extern int pthread_tryjoin_np(pthread_t thread, void **retval); + +/* Internal functions --------------------------------------------------------*/ +CMD(thread_show, + thread_show_cmd, + "show thread", + SHOW_STR + "thread\n") +{ + thread_m_t *node = NULL; + int32_t i = 0; + + for (i = 0; i < thread_a->active; i++) + { + if (thread_a->index[i] != NULL) + { + node = (thread_m_t*)thread_a->index[i]; + /* 当返回不为EBUSY时说明线程退出或者出错了,但是再次查询又会变为EBUSY, + 所以这里一旦设置为FALSE,就无法再变为TRUE了. */ + if (pthread_tryjoin_np(node->pid, NULL) != EBUSY) + { + node->alive = FALSE; + } + vty_out(vty, "%-32s %-2d%s", node->name, node->alive, VTY_NEWLINE); + } + } + vty_out(vty, "%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +/* Interface functions -------------------------------------------------------*/ +/* description: 添加需要监控的线程. + param: + return: (E_NONE)成功,(其他)失败 */ +int32_t thread_m_add(char *str, pthread_t pid) +{ + thread_m_t *node = NULL; + + node = XMALLOC(MTYPE_THREAD_MONITOR, sizeof(thread_m_t)); + snprintf(node->name, THREAD_M_NAME_LEN, "%s", str); + node->pid = pid; + node->alive = TRUE; + + THREAD_LOCK; + array_append(thread_a, node, MTYPE_THREAD_MONITOR); + THREAD_UNLOCK; + + return E_NONE; +} + +/* description: 线程监控初始化. + param: + return: (E_NONE)成功,(其他)失败 */ +int32_t thread_m_init(void) +{ + /* 初始化线程锁. */ + pthread_mutex_init(&thread_mutex, NULL); + + thread_a = array_init(THREAD_M_SIZE, MTYPE_THREAD_MONITOR); + cmd_install_element(COMMON_NODE, &thread_show_cmd); + + return E_NONE; +} + +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/ diff --git a/app/lib/m_management/vty.c b/app/lib/m_management/vty.c new file mode 100755 index 0000000..3c794c6 --- /dev/null +++ b/app/lib/m_management/vty.c @@ -0,0 +1,2763 @@ +/****************************************************************************** + * file lib/management/vty.c + * author YuLiang + * version 1.0.0 + * date 09-Oct-2021 + * brief This file provides all the vty operation functions. + * + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list.h" +#include "sockunion.h" +#include "vty.h" +#include "cmd.h" + +/* Private define ------------------------------------------------------------*/ +#define BUFFER_SIZE_DEFAULT 4096 +#define BUF_MAX_CHUNKS 16 +#define BUF_MAX_FLUSH 131072 + +/* Private macro -------------------------------------------------------------*/ +#define BUFFER_DATA_FREE(D) XFREE(MTYPE_BUF_DATA, (D)) + +/* Private typedef -----------------------------------------------------------*/ +typedef enum +{ + BUFFER_ERROR = -1, /* I/O错误产生. */ + BUFFER_EMPTY = 0, /* 数据发送成功, 并且没有其他数据要发送了. */ + BUFFER_PENDING = 1 /* 数据发送成功, 但是数据并没有发送完. */ +} BUF_STATUS_E; + +/* Private variables ---------------------------------------------------------*/ +static hash_t *cpu_record = NULL; +static struct timeval last_recent_time; +static struct timeval relative_time_base; +struct timeval recent_time; +/* init flag */ +static unsigned short timers_inited; +/* Relative time, since startup */ +static struct timeval relative_time; + +/* Configure lock. */ +static int vty_config; + +static const char vty_backward_char = 0x08; +static const char vty_space_char = ' '; + +thread_master_t *vty_master; + +/* Vector which store each vty structure. */ +array_t *vtyvec; +/* VTY server thread. */ +array_t *vty_serv_thread; +/* user array. */ +array_t *vty_user; + +/* Vty access-class command */ +static char *vty_accesslist_name = NULL; +#ifdef HAVE_IPV6 +/* Vty access-calss for IPv6. */ +static char *vty_ipv6_accesslist_name = NULL; +#endif + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +/* 在buf中添加一个data空间. */ +static buf_data_t *_buf_add(buf_t *b) +{ + buf_data_t *d; + + d = XMALLOC(MTYPE_BUF_DATA, l_offsetof(buf_data_t, data[b->size])); + d->cp = d->sp = 0; + d->next = NULL; + + if (b->tail) + b->tail->next = d; + else + b->head = d; + b->tail = d; + + return d; +} + +/* 创建buf. */ +buf_t *buf_create(size_t size) +{ + buf_t *b = NULL; + + b = XMALLOC(MTYPE_BUF, sizeof(buf_t)); + + if (size) + b->size = size; + else + { + static size_t default_size; + if (!default_size) + { + long pgsz = sysconf(_SC_PAGESIZE); + default_size = (((BUFFER_SIZE_DEFAULT-1)/pgsz+1)*pgsz); + } + b->size = default_size; + } + + return b; +} + +/* 释放所有buf中的空间. */ +void buf_reset(buf_t *b) +{ + buf_data_t *data = NULL; + buf_data_t *next = NULL; + + for (data = b->head; data; data = next) + { + next = data->next; + BUFFER_DATA_FREE(data); + } + b->head = b->tail = NULL; +} + +/* 释放buf_t结构,包括buf空间. */ +void buf_free(buf_t *b) +{ + buf_reset(b); + XFREE(MTYPE_BUF, b); +} + +/* 将数据存入buf. */ +void buf_put(buf_t *b, const void *p, size_t size) +{ + buf_data_t *data = b->tail; + const char *ptr = p; + + /* We use even last one byte of data buffer. */ + while(size) + { + size_t chunk = 0; + + /* If there is no data buffer add it. */ + if (NULL == data || data->cp == b->size) + data = _buf_add(b); + + chunk = ((size <= (b->size - data->cp)) ? size : (b->size - data->cp)); + memcpy((data->data + data->cp), ptr, chunk); + size -= chunk; + ptr += chunk; + data->cp += chunk; + } +} + +/* 设计用于非阻塞socket. 如果数据太大, 该接口并不会全部发送. + 返回0如果数据全部发送, 1表示还有数据没有发送完, -1表示发送 + 产生了致命错误. */ +BUF_STATUS_E buf_flush_available(buf_t *b, int32_t fd) +{ + buf_data_t *d = NULL; + size_t written = 0; + struct iovec iov[BUF_MAX_CHUNKS]; + size_t iovcnt = 0; + size_t nbyte = 0; + + /* 将数据填入iov,最多使用BUF_MAX_CHUNKS个iov,并且不能超过 + BUF_MAX_FLUSH字节. */ + for(d = b->head, iovcnt = 0; + d && (iovcnt < BUF_MAX_CHUNKS) && (nbyte < BUF_MAX_FLUSH); + d = d->next, iovcnt++) + { + iov[iovcnt].iov_base = d->data + d->sp; + nbyte += (iov[iovcnt].iov_len = d->cp - d->sp); + } + + if (!nbyte) + return BUFFER_EMPTY; + + /* 数据输出到终端. */ + if ((ssize_t)(written = writev(fd, iov, iovcnt)) < 0) + { + if (ERRNO_IO_RETRY(errno)) + return BUFFER_PENDING; + + printf("%s: write error on fd %d: %s.\n", __FUNCTION__, fd, safe_strerror(errno)); + return BUFFER_ERROR; + } + + /* 释放被输出到终端的data. */ + while(written > 0) + { + if (!(d = b->head)) + { + printf("%s: corruption detected: buffer queue empty, but written is %lu", __func__, (u_long)written); + break; + } + + if (written < (d->cp - d->sp)) + { + d->sp += written; + return BUFFER_PENDING; + } + + written -= (d->cp-d->sp); + if (!(b->head = d->next)) + b->tail = NULL; + + BUFFER_DATA_FREE(d); + } + + return b->head ? BUFFER_PENDING : BUFFER_EMPTY; +} + +/* 发送数据到终端,仅用于telnet接口 */ +BUF_STATUS_E buf_flush_window(buf_t *b, int fd, int32_t width, int32_t height, + int32_t erase_flag, int32_t no_more_flag) +{ + int32_t nbytes = 0; + int32_t iov_alloc = 0; + int32_t iov_index = 0; + struct iovec *iov = NULL; + struct iovec small_iov[3]; + char more[] = " --More-- "; + char erase[] = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}; + buf_data_t *data = NULL; + uint32_t column = 0; + + if (!b->head) + return BUFFER_EMPTY; + + /* 获取显示的高度与宽度. */ + if (height < 1) + { + printf("%s called with non-positive window height %d, forcing to 1", __func__, height); + height = 1; + } + else if (height >= 2) + height--; + + if (width < 1) + { + printf("%s called with non-positive window width %d, forcing to 1", __func__, width); + width = 1; + } + + /* 计算发送一屏的数据要使用多少数据块. */ + if (NULL == b->head->next) + { + iov_alloc = sizeof(small_iov)/sizeof(small_iov[0]); + iov = small_iov; + } + else + { + iov_alloc = ((height * (width + 2)) / b->size) + 10; + iov = XMALLOC(MTYPE_BUF_TMP, iov_alloc * sizeof(*iov)); + } + iov_index = 0; + + /* 处理清屏标志. */ + if (erase_flag) + { + iov[iov_index].iov_base = erase; + iov[iov_index].iov_len = sizeof(erase); + iov_index++; + } + + /* 整理发送数据. */ + column = 1; /* 下一个字符显示的列地址. */ + for (data = b->head; data && (height > 0); data = data->next) + { + size_t cp; + + /* 计算数据位置和行数,保证只显示height行(一屏)数据. */ + cp = data->sp; + while ((cp < data->cp) && (height > 0)) + { + if ('\r' == data->data[cp]) + column = 1; + else if (('\n' == data->data[cp]) || (column == width)) + { + column = 1; + height--; + } + else + column++; + + cp++; + } + iov[iov_index].iov_base = (char *)(data->data + data->sp); + iov[iov_index++].iov_len = cp - data->sp; + data->sp = cp; + + /* 以下情况应该不会发生,申请的块应该是足够发送数据的. */ + if (iov_index == iov_alloc) + { + iov_alloc *= 2; + if (iov != small_iov) + { + printf("%s: growing iov array to %d;width %d, height %d, size %lu", + __func__, iov_alloc, width, height, (u_long)b->size); + iov = XREALLOC(MTYPE_BUF_TMP, iov, iov_alloc * sizeof(*iov)); + } + else + { + printf("%s: corruption detected: iov_small overflowed;head %p, tail %p, head->next %p", + __func__, b->head, b->tail, b->head->next); + iov = XMALLOC(MTYPE_BUF_TMP, iov_alloc * sizeof(*iov)); + memcpy(iov, small_iov, sizeof(small_iov)); + } + } + } + + /* 显示" --More-- "提示. */ + if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag) + { + iov[iov_index].iov_base = more; + iov[iov_index].iov_len = sizeof(more); + iov_index++; + } + + /* 发送数据. */ + if ((nbytes = writev(fd, iov, iov_index)) < 0) + printf("%s: writev to fd %d failed: %s", __func__, fd, safe_strerror(errno)); + + /* 释放发送过的数据空间. */ + while (b->head && (b->head->sp == b->head->cp)) + { + buf_data_t *del = NULL; + + if (!(b->head = (del = b->head)->next)) + b->tail = NULL; + + BUFFER_DATA_FREE(del); + } + + if (iov != small_iov) + XFREE(MTYPE_BUF_TMP, iov); + + return (nbytes < 0) ? BUFFER_ERROR : (b->head ? BUFFER_PENDING : BUFFER_EMPTY); +} + +/* 持续发送数据, 直到数据发送完, 或者遇到错误,或者被拥塞. */ +BUF_STATUS_E buf_flush_all(buf_t *b, int32_t fd) +{ + BUF_STATUS_E ret = BUFFER_ERROR; + buf_data_t *head = NULL; + size_t head_sp = 0; + + if (!b->head) + return BUFFER_EMPTY; + + head_sp = (head = b->head)->sp; + + while(BUFFER_PENDING == (ret = buf_flush_available(b, fd))) + { + /* 数据不空, 但是没有发送出去,可能是内核buf满了. */ + if ((b->head == head) && (head_sp == head->sp) && (errno != EINTR)) + return ret; + + head_sp = (head = b->head)->sp; + } + + return ret; +} + +/* Lookup and return hash backet in hash. If there is no + * corresponding hash backet and alloc_func is specified, create new + * hash backet. */ +void *hash_get(hash_t *hash, void *data, hash_alloc_f alloc_func) +{ + unsigned int key = 0; + unsigned int index = 0; + void *newdata = 0; + hash_backet_t *backet = NULL; + + key = (*hash->hash_key) (data); + index = key % hash->size; + + for(backet = hash->index[index]; backet != NULL; backet = backet->next) + if (backet->key == key && (*hash->hash_cmp) (backet->data, data)) + return backet->data; + + if (alloc_func) + { + newdata = (*alloc_func)(data); + if (NULL == newdata) + return NULL; + + backet = XMALLOC(MTYPE_HASH_BACKET, sizeof(hash_backet_t)); + backet->data = newdata; + backet->key = key; + backet->next = hash->index[index]; + hash->index[index] = backet; + hash->count++; + return backet->data; + } + + return NULL; +} + +/* gettimeofday wrapper, to keep recent_time updated */ +static int _thread_gettimeofday(struct timeval *tv) +{ + int ret = 0; + + assert(tv); + + if (!(ret = gettimeofday(&recent_time, NULL))) + { + /* init... */ + if (!timers_inited) + { + relative_time_base = last_recent_time = recent_time; + timers_inited = 1; + } + /* avoid copy if user passed recent_time pointer.. */ + if (tv != &recent_time) + *tv = recent_time; + return 0; + } + + return ret; +} + +static int _thread_get_relative(struct timeval *tv) +{ + int ret = 0; + struct timespec tp; + + if (!(ret = clock_gettime(CLOCK_MONOTONIC, &tp))) + { + relative_time.tv_sec = tp.tv_sec; + relative_time.tv_usec = tp.tv_nsec / 1000; + } + + if (tv) + *tv = relative_time; + + return ret; +} +/* Adjust so that tv_usec is in the range [0,TIMER_SECOND_MICRO). + And change negative values to 0. */ +static struct timeval _thread_timeval_adjust (struct timeval a) +{ + while(a.tv_usec >= TIMER_SECOND_MICRO) + { + a.tv_usec -= TIMER_SECOND_MICRO; + a.tv_sec++; + } + + while(a.tv_usec < 0) + { + a.tv_usec += TIMER_SECOND_MICRO; + a.tv_sec--; + } + + if (a.tv_sec < 0) + /* Change negative timeouts to 0. */ + a.tv_sec = a.tv_usec = 0; + + return a; +} + +static unsigned long _thread_timeval_elapsed(struct timeval a, struct timeval b) +{ + return ((a.tv_sec - b.tv_sec) * TIMER_SECOND_MICRO) + + (a.tv_usec - b.tv_usec); +} + +/* Allocate a new hash. */ +hash_t *_thread_hash_create_size(hash_key_f *hash_key, + hash_cmp_f *hash_cmp, + unsigned int size) +{ + hash_t *hash; + + hash = XMALLOC(MTYPE_HASH, sizeof(hash_t)); + hash->index = XMALLOC(MTYPE_HASH_INDEX, sizeof(hash_backet_t *) * size); + hash->size = size; + hash->hash_key = hash_key; + hash->hash_cmp = hash_cmp; + hash->count = 0; + + return hash; +} + +/* Add a new thread to the list. */ +static void _thread_list_enqueue(thread_list_t *list, thread_t *thread) +{ + thread->next = NULL; + thread->prev = list->tail; + if (list->tail) + list->tail->next = thread; + else + list->head = thread; + + list->tail = thread; + list->count++; +} + +/* Delete a thread from the list. */ +static thread_t *_thread_list_dequeue(thread_list_t *list, thread_t *thread) +{ + if (thread->next) + thread->next->prev = thread->prev; + else + list->tail = thread->prev; + + if (thread->prev) + thread->prev->next = thread->next; + else + list->head = thread->next; + + thread->next = thread->prev = NULL; + list->count--; + + return thread; +} + +/* Thread list is empty or not. */ +static inline int _thread_empty(thread_list_t *list) +{ + return list->head ? 0 : 1; +} + +static long _thread_timeval_cmp(struct timeval a, struct timeval b) +{ + return a.tv_sec == b.tv_sec ? + a.tv_usec - b.tv_usec : a.tv_sec - b.tv_sec; +} + +/* Delete top of the list and return it. */ +static thread_t *_thread_list_pull(thread_list_t *list) +{ + if (!_thread_empty(list)) + return _thread_list_dequeue(list, list->head); + + return NULL; +} + +/* Add a new thread to the list. */ +static void _thread_list_add(thread_list_t *list, + thread_t *thread) +{ + thread->next = NULL; + thread->prev = list->tail; + + if (list->tail) + list->tail->next = thread; + else + list->head = thread; + + list->tail = thread; + list->count++; +} + +/* Add a new thread just before the point. */ +static void _thread_list_add_before(thread_list_t *list, + thread_t *point, + thread_t *thread) +{ + thread->next = point; + thread->prev = point->prev; + if (point->prev) + point->prev->next = thread; + else + list->head = thread; + + point->prev = thread; + list->count++; +} + +/* Return remain time in second. */ +unsigned long _thread_timer_remain_second(thread_t *thread) +{ + _thread_get_relative(NULL); + + if (thread->u.sands.tv_sec - relative_time.tv_sec > 0) + return thread->u.sands.tv_sec - relative_time.tv_sec; + else + return 0; +} + +/* Trim blankspace and "()"s */ +static char *_thread_strip_funcname(const char *funcname) +{ + char buff[100] = {0}; + char tmp = 0; + char *ret = NULL; + char *e = NULL; + char *b = buff; + + strncpy(buff, funcname, sizeof(buff)); + buff[sizeof(buff) -1] = '\0'; + e = buff + strlen(buff) - 1; + + /* Wont work for funcname == "Word (explanation)" */ + while (*b == ' ' || *b == '(') + ++b; + while (*e == ' ' || *e == ')') + --e; + e++; + + tmp = *e; + *e = '\0'; + ret = XSTRDUP(MTYPE_THREAD_FUNCNAME, b); + *e = tmp; + + return ret; +} + +/* Get new thread. */ +static thread_t *_thread_get(thread_master_t *m, + u_char type, + thread_func_f *func, + void *arg, + const char* funcname) +{ + thread_t *thread = NULL; + + if (!_thread_empty(&m->unuse)) + { + thread = _thread_list_pull(&m->unuse); + if (thread->funcname) + XFREE(MTYPE_THREAD_FUNCNAME, thread->funcname); + } + else + { + thread = XMALLOC(MTYPE_THREAD, sizeof(thread_t)); + m->alloc++; + } + + thread->type = type; + thread->add_type = type; + thread->master = m; + thread->func = func; + thread->arg = arg; + thread->funcname = _thread_strip_funcname(funcname); + + return thread; +} + +static void *_thread_cpu_record_hash_alloc(cpu_thread_history_t *a) +{ + cpu_thread_history_t *history = NULL; + history = XMALLOC(MTYPE_THREAD_STATS, sizeof(cpu_thread_history_t)); + history->func = a->func; + history->funcname = XSTRDUP(MTYPE_THREAD_FUNCNAME, a->funcname); + return history; +} + +/* Move thread to unuse list. */ +static void _thread_add_unuse(thread_master_t *m, thread_t *thread) +{ + assert(m != NULL && thread != NULL); + assert(NULL == thread->next); + assert(NULL == thread->prev); + assert(THREAD_UNUSED == thread->type); + + _thread_list_add(&m->unuse, thread); + /* XXX: Should we deallocate funcname here? */ +} + +static unsigned int _thread_cpu_record_hash_key(cpu_thread_history_t *a) +{ + return (uintptr_t)a->func; +} + +static int _thread_cpu_record_hash_cmp (const cpu_thread_history_t *a, + const cpu_thread_history_t *b) +{ + return a->func == b->func; +} + +static thread_t *_thread_run(thread_master_t *m, thread_t *thread, thread_t *fetch) +{ + *fetch = *thread; + thread->type = THREAD_UNUSED; + thread->funcname = NULL; /* thread_call will free fetch's copied pointer */ + _thread_add_unuse(m, thread); + return fetch; +} + +/* process a list en masse, e.g. for event thread lists */ +static unsigned int _thread_process(thread_list_t *list) +{ + thread_t *thread = NULL; + unsigned int ready = 0; + + for(thread = list->head; thread; thread = thread->next) + { + _thread_list_dequeue(list, thread); + thread->type = THREAD_READY; + _thread_list_add(&thread->master->ready, thread); + ready++; + } + + return ready; +} + +static struct timeval _timeval_subtract(struct timeval a, struct timeval b) +{ + struct timeval ret; + + ret.tv_usec = a.tv_usec - b.tv_usec; + ret.tv_sec = a.tv_sec - b.tv_sec; + + return _thread_timeval_adjust(ret); +} + +static struct timeval *_thread_timer_wait(thread_list_t *tlist, struct timeval *timer_val) +{ + if (!_thread_empty (tlist)) + { + *timer_val = _timeval_subtract(tlist->head->u.sands, relative_time); + return timer_val; + } + + return NULL; +} + +/* Add all timers that have popped to the ready list. */ +static uint32_t _thread_timer_process(thread_list_t *list, struct timeval *timenow) +{ + thread_t *thread = NULL; + uint32_t ready = 0; + + for (thread = list->head; thread; thread = thread->next) + { + if (_thread_timeval_cmp(*timenow, thread->u.sands) < 0) + return ready; + + _thread_list_dequeue(list, thread); + thread->type = THREAD_READY; + _thread_list_add(&thread->master->ready, thread); + ready++; + } + + return ready; +} + +static int32_t _thread_process_fd(thread_list_t *list, fd_set *fdset, fd_set *mfdset) +{ + thread_t *thread = NULL; + thread_t *next = NULL; + int ready = 0; + + assert(list); + + for (thread = list->head; thread; thread = next) + { + next = thread->next; + + if (FD_ISSET(THREAD_FD(thread), fdset)) + { + assert(FD_ISSET(THREAD_FD(thread), mfdset)); + FD_CLR(THREAD_FD (thread), mfdset); + _thread_list_dequeue(list, thread); + _thread_list_add(&thread->master->ready, thread); + thread->type = THREAD_READY; + ready++; + } + } + + return ready; +} + +void thread_getrusage(RUSAGE_T *r) +{ + _thread_get_relative(NULL); + getrusage(RUSAGE_SELF, &(r->cpu)); + r->real = relative_time; + + /* quagga_get_relative() only updates recent_time if gettimeofday + * based, not when using CLOCK_MONOTONIC. As we export recent_time + * and guarantee to update it before threads are run...*/ + _thread_gettimeofday(&recent_time); +} + +/* We check thread consumed time. If the system has getrusage, we'll +use that to get in-depth stats on the performance of the thread in addition +to wall clock time stats from gettimeofday. */ +void thread_call(thread_t *thread) +{ + unsigned long realtime = 0; + unsigned long cputime = 0; + RUSAGE_T ru; + + /* Cache a pointer to the relevant cpu history thread, if the thread + * does not have it yet. + * + * Callers submitting 'dummy threads' hence must take care that + * thread->cpu is NULL */ + if (!thread->hist) + { + cpu_thread_history_t tmp; + + tmp.func = (int (*)(void*))thread->func; + tmp.funcname = thread->funcname; + + thread->hist = hash_get(cpu_record, + &tmp, + (hash_alloc_f *)_thread_cpu_record_hash_alloc); + } + + GETRUSAGE(&thread->ru); + + (*thread->func)(thread); + + GETRUSAGE(&ru); + + realtime = thread_consumed_time(&ru, &thread->ru, &cputime); + thread->hist->real.total += realtime; + + if (thread->hist->real.max < realtime) + thread->hist->real.max = realtime; + + thread->hist->cpu.total += cputime; + + if (thread->hist->cpu.max < cputime) + thread->hist->cpu.max = cputime; + + ++(thread->hist->total_calls); + thread->hist->types |= (1 << thread->add_type); + + if (realtime > TIMER_SECOND_MICRO * 2) + { + /* We have a CPU Hog on our hands. + * Whinge about it now, so we're aware this is yet another task + * to fix. */ + printf("SLOW THREAD: task %s (%lx) ran for %lums (cpu time %lums)", + thread->funcname, + (unsigned long) thread->func, + realtime/1000, cputime/1000); + } + + XFREE(MTYPE_THREAD_FUNCNAME, thread->funcname); +} + +/* Add new read thread. */ +thread_t *thread_add_read(thread_master_t *m, + thread_func_f *func, + void *arg, + int fd, + const char* funcname) +{ + thread_t *thread = NULL; + + assert(m != NULL); + + if (FD_ISSET(fd, &m->readfd)) + { + printf("There is already read fd [%d].\n", fd); + return NULL; + } + + thread = _thread_get(m, THREAD_READ, func, arg, funcname); + FD_SET(fd, &m->readfd); + thread->u.fd = fd; + _thread_list_enqueue(&m->read, thread); + + return thread; +} + +/* Add new write thread. */ +thread_t *thread_add_write(thread_master_t *m, + thread_func_f *func, + void *arg, + int fd, + const char* funcname) +{ + thread_t *thread = NULL; + + assert(m != NULL); + + if (FD_ISSET(fd, &m->writefd)) + { + printf("There is already write fd [%d]", fd); + return NULL; + } + + thread = _thread_get(m, THREAD_WRITE, func, arg, funcname); + FD_SET(fd, &m->writefd); + thread->u.fd = fd; + _thread_list_add(&m->write, thread); + + return thread; +} + +static thread_t * +thread_add_timer_timeval(thread_master_t *m, + thread_func_f *func, + int type, + void *arg, + struct timeval *time_relative, + const char* funcname) +{ + thread_t *thread = NULL; + thread_list_t *list = NULL; + struct timeval alarm_time; + thread_t *tt = NULL; + + assert(m != NULL); + assert(type == THREAD_TIMER || type == THREAD_BACKGROUND); + assert(time_relative); + + list = (type == THREAD_TIMER) ? &m->timer : &m->background; + thread = _thread_get(m, type, func, arg, funcname); + + /* Do we need jitter here? */ + _thread_get_relative(NULL); + alarm_time.tv_sec = relative_time.tv_sec + time_relative->tv_sec; + alarm_time.tv_usec = relative_time.tv_usec + time_relative->tv_usec; + thread->u.sands = _thread_timeval_adjust(alarm_time); + + /* Sort by timeval. */ + for(tt = list->head; tt; tt = tt->next) + if (_thread_timeval_cmp(thread->u.sands, tt->u.sands) <= 0) + break; + + if (tt) + _thread_list_add_before(list, tt, thread); + else + _thread_list_add(list, thread); + + return thread; +} + + +/* Add timer event thread. */ +thread_t *thread_add_timer(thread_master_t *m, + thread_func_f *func, + void *arg, + long timer, + const char* funcname) +{ + struct timeval trel; + + assert(m != NULL); + + trel.tv_sec = timer; + trel.tv_usec = 0; + + return thread_add_timer_timeval(m, func, THREAD_TIMER, arg, + &trel, funcname); +} + +/* Add timer event thread with "millisecond" resolution */ +thread_t *thread_add_timer_msec(thread_master_t *m, + thread_func_f *func, + void *arg, + long timer, + const char* funcname) +{ + struct timeval trel; + + assert(m != NULL); + + trel.tv_sec = timer / 1000; + trel.tv_usec = 1000*(timer % 1000); + + return thread_add_timer_timeval(m, func, THREAD_TIMER, + arg, &trel, funcname); +} + +/* Add a background thread, with an optional millisec delay */ +thread_t *thread_add_background(thread_master_t *m, + thread_func_f *func, + void *arg, long delay, + const char *funcname) +{ + struct timeval trel; + + assert(m != NULL); + + if (delay) + { + trel.tv_sec = delay / 1000; + trel.tv_usec = 1000 * (delay % 1000); + } + else + { + trel.tv_sec = 0; + trel.tv_usec = 0; + } + + return thread_add_timer_timeval(m, func, THREAD_BACKGROUND, + arg, &trel, funcname); +} + +/* Add simple event thread. */ +thread_t *thread_add_event(thread_master_t *m, + thread_func_f *func, + void *arg, + int val, + const char* funcname) +{ + thread_t *thread = NULL; + + assert(m != NULL); + + thread = _thread_get(m, THREAD_EVENT, func, arg, funcname); + thread->u.val = val; + _thread_list_add(&m->event, thread); + + return thread; +} + +/* Execute thread */ +thread_t *thread_execute(thread_master_t *m, + thread_func_f *func, + void *arg, + int val, + const char* funcname) +{ + thread_t dummy; + + memset(&dummy, 0, sizeof(thread_t)); + + dummy.type = THREAD_EVENT; + dummy.add_type = THREAD_PERFORM; + dummy.master = NULL; + dummy.func = func; + dummy.arg = arg; + dummy.u.val = val; + dummy.funcname = _thread_strip_funcname(funcname); + thread_call(&dummy); + + return NULL; +} + +/* Allocate new thread master. */ +thread_master_t *thread_master_create() +{ + if (cpu_record == NULL) + cpu_record + = _thread_hash_create_size((hash_key_f*)_thread_cpu_record_hash_key, + (hash_cmp_f*)_thread_cpu_record_hash_cmp, + 1011); + + return (thread_master_t*)XMALLOC(MTYPE_THREAD_MASTER, sizeof(thread_master_t)); +} + +unsigned long thread_consumed_time(RUSAGE_T *now, + RUSAGE_T *start, + unsigned long *cputime) +{ + /* This is 'user + sys' time. */ + *cputime = _thread_timeval_elapsed(now->cpu.ru_utime, start->cpu.ru_utime) + + _thread_timeval_elapsed(now->cpu.ru_stime, start->cpu.ru_stime); + + return _thread_timeval_elapsed(now->real, start->real); +} + +/* Cancel thread from scheduler. */ +void thread_cancel(thread_t *thread) +{ + thread_list_t *list = NULL; + + switch(thread->type) + { + case THREAD_READ: + assert(FD_ISSET(thread->u.fd, &thread->master->readfd)); + FD_CLR(thread->u.fd, &thread->master->readfd); + list = &thread->master->read; + break; + case THREAD_WRITE: + assert(FD_ISSET (thread->u.fd, &thread->master->writefd)); + FD_CLR(thread->u.fd, &thread->master->writefd); + list = &thread->master->write; + break; + case THREAD_TIMER: + list = &thread->master->timer; + break; + case THREAD_EVENT: + list = &thread->master->event; + break; + case THREAD_READY: + list = &thread->master->ready; + break; + case THREAD_BACKGROUND: + list = &thread->master->background; + break; + default: + return; + } + + _thread_list_dequeue(list, thread); + thread->type = THREAD_UNUSED; + _thread_add_unuse(thread->master, thread); +} + +/* Fetch next ready thread. */ +thread_t *thread_fetch(thread_master_t *m, thread_t *fetch) +{ + thread_t *thread = NULL; + fd_set readfd; + fd_set writefd; + fd_set exceptfd; + struct timeval timer_val = {.tv_sec = 0, .tv_usec = 0}; + struct timeval timer_val_bg; + struct timeval *timer_wait = &timer_val; + struct timeval *timer_wait_bg = NULL; + + while (1) + { + int32_t num = 0; + + /* Drain the ready queue of already scheduled jobs, before scheduling + * more. */ + if ((thread = _thread_list_pull(&m->ready)) != NULL) + return _thread_run(m, thread, fetch); + + /* To be fair to all kinds of threads, and avoid starvation, we + * need to be careful to consider all thread types for scheduling + * in each quanta. I.e. we should not return early from here on. */ + + /* Normal event are the next highest priority. */ + _thread_process(&m->event); + + /* Structure copy. */ + readfd = m->readfd; + writefd = m->writefd; + exceptfd = m->exceptfd; + + /* Calculate select wait timer if nothing else to do */ + if (0 == m->ready.count) + { + _thread_get_relative(NULL); + timer_wait = _thread_timer_wait(&m->timer, &timer_val); + timer_wait_bg = _thread_timer_wait(&m->background, &timer_val_bg); + + if (timer_wait_bg && + (!timer_wait || (_thread_timeval_cmp(*timer_wait, *timer_wait_bg) > 0))) + timer_wait = timer_wait_bg; + } + + /* 当可用的进程全为0时,不能让timer_wait为NULL,要不让该线程将永远停在select()处. */ + if (!timer_wait) + { + timer_val.tv_sec = 2; + timer_val.tv_usec = 0; + timer_wait = &timer_val; + } + + num = select(FD_SETSIZE, &readfd, &writefd, &exceptfd, timer_wait); + /* Signals should get quick treatment */ + if (num < 0) + { + if (EINTR == errno) + continue; /* signal received - process it */ + + printf("select() error: %s\n", safe_strerror(errno)); + return NULL; + } + + /* Check foreground timers. Historically, they have had higher + * priority than I/O threads, so let's push them onto the ready + * list in front of the I/O threads. */ + _thread_get_relative(NULL); + _thread_timer_process(&m->timer, &relative_time); + + /* Got IO, process it */ + if (num > 0) + { + /* Normal priority read thead. */ + _thread_process_fd(&m->read, &readfd, &m->readfd); + /* Write thead. */ + _thread_process_fd(&m->write, &writefd, &m->writefd); + } + +#if 0 + /* If any threads were made ready above (I/O or foreground timer), + * perhaps we should avoid adding background timers to the ready + * list at this time. If this is code is uncommented, then background + * timer threads will not run unless there is nothing else to do. */ + if ((thread = thread_trim_head (&m->ready)) != NULL) + return thread_run (m, thread, fetch); +#endif + + /* Background timer/events, lowest priority */ + _thread_timer_process(&m->background, &relative_time); + + if ((thread = _thread_list_pull(&m->ready)) != NULL) + return _thread_run(m, thread, fetch); + } +} + +/* Get telnet window size. */ +static int _vty_telnet_option(vty_t *vty, unsigned char *buf, int nbytes) +{ + switch (buf[0]) + { + case SB: + vty->sb_len = 0; + vty->iac_sb_in_progress = 1; + return 0; + break; + case SE: + { + if (!vty->iac_sb_in_progress) + return 0; + + if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0')) + { + vty->iac_sb_in_progress = 0; + return 0; + } + switch (vty->sb_buf[0]) + { + case TELOPT_NAWS: + if (vty->sb_len != TELNET_NAWS_SB_LEN) + printf("RFC 1073 violation detected: telnet NAWS option\ + should send %d characters, but we received %lu.\n", + TELNET_NAWS_SB_LEN, (u_long)vty->sb_len); + else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN) + printf("Bug detected: sizeof(vty->sb_buf) %lu < %d, \ + too small to handle the telnet NAWS option.\n", + (u_long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN); + else + { + vty->width = ((vty->sb_buf[1] << 8)|vty->sb_buf[2]); + vty->height = ((vty->sb_buf[3] << 8)|vty->sb_buf[4]); + } + break; + } + + vty->iac_sb_in_progress = 0; + return 0; + break; + } + default: + break; + } + + return 1; +} + +/* Basic function to write buffer to vty. */ +static void _vty_write(vty_t *vty, const char *buf, size_t nbytes) +{ + /* Should we do buffering here ? And make vty_flush (vty) ? */ + buf_put(vty->out_buf, buf, nbytes); +} + +/* This function redraw all of the command line character. */ +static void _vty_redraw_line(vty_t *vty) +{ + _vty_write(vty, vty->buf, vty->length); + vty->cp = vty->length; +} + +/* Quit print out to the buffer. */ +static void _vty_buffer_reset(vty_t *vty) +{ + buf_reset(vty->out_buf); + vty_prompt(vty); + _vty_redraw_line(vty); +} + +/* Backward character. */ +static void _vty_backward_char(vty_t *vty) +{ + if (vty->cp > 0) + { + vty->cp--; + _vty_write(vty, &vty_backward_char, 1); + } +} + +/* Forward character. */ +static void _vty_forward_char(vty_t *vty) +{ + if (vty->cp < vty->length) + { + _vty_write(vty, &vty->buf[vty->cp], 1); + vty->cp++; + } +} + +/* Move to the beginning of the line. */ +static void _vty_beginning_of_line(vty_t *vty) +{ + while (vty->cp) + _vty_backward_char(vty); +} + +/* Move to the end of the line. */ +static void _vty_end_of_line(vty_t *vty) +{ + while(vty->cp < vty->length) + _vty_forward_char(vty); +} + +/* Kill rest of line from current point. */ +static void _vty_kill_line(vty_t *vty) +{ + unsigned int i = 0; + unsigned int size = 0; + + size = vty->length - vty->cp; + + if (0 == size) + return; + + for (i = 0; i < size; i++) + _vty_write(vty, &vty_space_char, 1); + for (i = 0; i < size; i++) + _vty_write(vty, &vty_backward_char, 1); + + memset(&vty->buf[vty->cp], 0, size); + vty->length = vty->cp; +} + +/* Kill line from the beginning. */ +static void _vty_kill_line_from_beginning(vty_t *vty) +{ + _vty_beginning_of_line(vty); + _vty_kill_line(vty); +} + +/* Print command line history. This function is called from + vty_next_line and vty_previous_line. */ +static void _vty_history_print(vty_t *vty) +{ + unsigned int length = 0; + + _vty_kill_line_from_beginning (vty); + + /* Get previous line from history buffer */ + length = strlen(vty->hist[vty->hp]); + memcpy(vty->buf, vty->hist[vty->hp], length); + vty->cp = vty->length = length; + + /* Redraw current line */ + _vty_redraw_line(vty); +} + +/* Show previous command line history. */ +static void _vty_previous_line(vty_t *vty) +{ + unsigned int try_index = 0; + + try_index = vty->hp; + if (try_index == 0) + try_index = VTY_MAXHIST - 1; + else + try_index--; + + if (vty->hist[try_index] == NULL) + return; + else + vty->hp = try_index; + + _vty_history_print(vty); +} + +/* Show next command line history. */ +static void _vty_next_line(vty_t *vty) +{ + unsigned int try_index = 0; + + if (vty->hp == vty->hindex) + return; + + /* Try is there history exist or not. */ + try_index = vty->hp; + if (try_index == (VTY_MAXHIST - 1)) + try_index = 0; + else + try_index++; + + /* If there is not history return. */ + if (vty->hist[try_index] == NULL) + return; + else + vty->hp = try_index; + + _vty_history_print (vty); +} + +/* Escape character command map. */ +static void _vty_escape_map (unsigned char c, vty_t *vty) +{ + switch (c) + { + case ('A'): + _vty_previous_line (vty); + break; + case ('B'): + _vty_next_line(vty); + break; + case ('C'): + _vty_forward_char(vty); + break; + case ('D'): + _vty_backward_char (vty); + break; + default: + break; + } + + /* Go back to normal mode. */ + vty->escape = VTY_NO_ESCAPE; +} + +/* Backward word. */ +static void _vty_backward_word(vty_t *vty) +{ + while(vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') + _vty_backward_char(vty); + + while(vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') + _vty_backward_char(vty); +} + +/* Forward word. */ +static void _vty_forward_word(vty_t *vty) +{ + while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') + _vty_forward_char(vty); + + while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') + _vty_forward_char(vty); +} + +/* Delete a charcter at the current point. */ +static void _vty_delete_char(vty_t *vty) +{ + unsigned int i = 0; + unsigned int size = 0; + + if (vty->length == 0) + return; + + if (vty->cp == vty->length) + return; + + size = vty->length - vty->cp; + + vty->length--; + memmove(&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1); + vty->buf[vty->length] = '\0'; + + _vty_write(vty, &vty->buf[vty->cp], size - 1); + _vty_write(vty, &vty_space_char, 1); + + for (i = 0; i < size; i++) + _vty_write(vty, &vty_backward_char, 1); +} + +/* Delete a character before the point. */ +static void _vty_delete_backward_char(vty_t *vty) +{ + if (vty->cp == 0) + return; + + _vty_backward_char(vty); + _vty_delete_char(vty); +} + +/* Delete a word before the point. */ +static void _vty_forward_kill_word(vty_t *vty) +{ + while(vty->cp != vty->length && vty->buf[vty->cp] == ' ') + _vty_delete_char(vty); + while(vty->cp != vty->length && vty->buf[vty->cp] != ' ') + _vty_delete_char(vty); +} + +/* Delete a word before the point. */ +static void _vty_backward_kill_word(vty_t *vty) +{ + while(vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') + _vty_delete_backward_char(vty); + while(vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') + _vty_delete_backward_char(vty); +} + +/* Ensure length of input buffer. Is buffer is short, double it. */ +static void _vty_buf_len_ensure(vty_t *vty, int length) +{ + if (vty->max <= length) + { + vty->max *= 2; + vty->buf = XREALLOC(MTYPE_VTY, vty->buf, vty->max); + } +} + +/* Basic function to insert character into vty. */ +static void _vty_insert_char(vty_t *vty, char c) +{ + unsigned int i = 0; + unsigned int length = 0; + + _vty_buf_len_ensure(vty, vty->length + 1); + length = vty->length - vty->cp; + memmove(&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length); + vty->buf[vty->cp] = c; + + if (vty->node != PASSWORD_NODE) + { + _vty_write(vty, &vty->buf[vty->cp], length + 1); + for (i = 0; i < length; i++) + _vty_write(vty, &vty_backward_char, 1); + } + + vty->cp++; + vty->length++; +} + +/* Insert a word into vty interface with overwrite mode. */ +static void _vty_insert_word(vty_t *vty, char *str) +{ + int len = strlen(str); + _vty_write(vty, str, len); + strcpy(&vty->buf[vty->cp], str); + vty->cp += len; + vty->length = vty->cp; +} + +void _vty_describe_command(vty_t *vty) +{ + array_t *cmd_line = NULL; + + cmd_line = cmd_strs_create(vty->buf); + /* In case of '> ?'. */ + if (NULL == cmd_line) + { + cmd_line = array_init(1, MTYPE_CLI); + array_append(cmd_line, '\0', MTYPE_CLI); + } + else if(vty->length && isspace((int)vty->buf[vty->length - 1])) + array_append(cmd_line, '\0', MTYPE_CLI); + + vty_question(vty, cmd_line); + + cmd_strs_free(cmd_line); + vty_prompt(vty); + _vty_redraw_line(vty); + return; +} + +static void _vty_append_word(vty_t *vty, array_t *cmd_line, char *word, int status) +{ + unsigned int index = array_active(cmd_line) - 1; + + _vty_end_of_line(vty); + + if (NULL == array_get(cmd_line, index)) + _vty_insert_word(vty, word); + else + { + _vty_backward_word(vty); + _vty_insert_word(vty, word); + } + + if (CMD_COMPLETE_FULL_MATCH == status) + _vty_insert_char(vty, ' '); +} + +int _vty_complete_command(vty_t *vty) +{ + array_t *cmd_line = NULL; + char **match_strs = NULL; + int32_t complete_status = CMD_ERR_NO_MATCH; + + cmd_line = cmd_strs_create(vty->buf); + if (NULL == cmd_line) + { + cmd_line = array_init(1, MTYPE_CLI); + array_append(cmd_line, '\0', MTYPE_CLI); + } + else if (vty->length && isspace((int)vty->buf[vty->length - 1])) + array_append(cmd_line, '\0', MTYPE_CLI); + + match_strs = cmd_complete_command(cmd_line, vty, &complete_status); + if (NULL == match_strs) + { + cmd_strs_free(cmd_line); + return 0; + } + + if (CMD_COMPLETE_MATCH == complete_status || + CMD_COMPLETE_FULL_MATCH == complete_status) + _vty_append_word(vty, cmd_line, match_strs[0], complete_status); + else + { + vty_print_word(vty, match_strs); + vty_prompt(vty); + _vty_redraw_line(vty); + } + + vty_free_match_strs(match_strs); + cmd_strs_free(cmd_line); + + return 0; +} + +static int _vty_read(thread_t *thread) +{ + int i = 0; + int nbytes = 0; + unsigned char buf[VTY_READ_BUFSIZ]; + int vty_sock = THREAD_FD(thread); + vty_t *vty = THREAD_ARG(thread); + + vty->t_read = NULL; + + /* Read raw data from socket */ + if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) + { + if (nbytes < 0) + { + if (ERRNO_IO_RETRY(errno)) + { + vty_event(VTY_READ, vty_sock, vty); + return 0; + } + vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ + printf("%s: read error on vty client fd %d, closing: %s\n", + __func__, vty->fd, safe_strerror(errno)); + } + + buf_reset(vty->out_buf); + vty->status = VTY_CLOSE; + } + + for (i = 0; i < nbytes; i++) + { + if (IAC == buf[i]) + { + if (!vty->iac) + { + vty->iac = 1; + continue; + } + else + { + vty->iac = 0; + } + } + + if (vty->iac_sb_in_progress && !vty->iac) + { + if (vty->sb_len < sizeof(vty->sb_buf)) + vty->sb_buf[vty->sb_len] = buf[i]; + vty->sb_len++; + continue; + } + + if (vty->iac) + { + /* In case of telnet command */ + int ret = 0; + ret = _vty_telnet_option(vty, &buf[i], nbytes - i); + vty->iac = 0; + i += ret; + continue; + } + + if (VTY_MORE == vty->status) + { + switch (buf[i]) + { + case CONTROL('C'): + case 'q': + case 'Q': + _vty_buffer_reset(vty); + break; + default: + break; + } + + continue; + } + + /* Escape character. */ + if (VTY_ESCAPE == vty->escape) + { + _vty_escape_map(buf[i], vty); + continue; + } + + /* Pre-escape status. */ + if (vty->escape == VTY_PRE_ESCAPE) + { + switch (buf[i]) + { + case '[': + vty->escape = VTY_ESCAPE; + break; + case 'b': + _vty_backward_word(vty); + vty->escape = VTY_NO_ESCAPE; + break; + case 'f': + _vty_forward_word(vty); + vty->escape = VTY_NO_ESCAPE; + break; + case 'd': + _vty_forward_kill_word(vty); + vty->escape = VTY_NO_ESCAPE; + break; + case CONTROL('H'): + case 0x7f: + _vty_backward_kill_word(vty); + vty->escape = VTY_NO_ESCAPE; + break; + default: + vty->escape = VTY_NO_ESCAPE; + break; + } + + continue; + } + + switch (buf[i]) + { + case CONTROL('A'): + _vty_beginning_of_line(vty); + break; + case CONTROL('B'): + _vty_backward_char(vty); + break; + case CONTROL('C'): + //_vty_stop_input(vty); + break; + case CONTROL('D'): + _vty_delete_char(vty); + break; + case CONTROL('E'): + //_vty_end_of_line(vty); + break; + case CONTROL('F'): + _vty_forward_char(vty); + break; + case CONTROL('H'): + case 0x7f: + _vty_delete_backward_char(vty); + break; + case CONTROL('K'): + _vty_kill_line(vty); + break; + case CONTROL('N'): + _vty_next_line(vty); + break; + case CONTROL('P'): + _vty_previous_line(vty); + break; + case CONTROL('T'): + //_vty_transpose_chars(vty); + break; + case CONTROL('U'): + _vty_kill_line_from_beginning(vty); + break; + case CONTROL('W'): + _vty_backward_kill_word(vty); + break; + case CONTROL('Z'): + //_vty_end_config(vty); + break; + case '\n': + case '\r': + vty_out(vty, "%s", VTY_NEWLINE); + vty_execute(vty); + break; + case '\t': + _vty_complete_command(vty); + break; + case '?': + _vty_describe_command(vty); + break; + case '\033': + if (i + 1 < nbytes && buf[i + 1] == '[') + { + vty->escape = VTY_ESCAPE; + i++; + } + else + vty->escape = VTY_PRE_ESCAPE; + break; + default: + if (buf[i] > 31 && buf[i] < 127) + _vty_insert_char(vty, buf[i]); + break; + } + } + + /* Check status. */ + if (vty->status == VTY_CLOSE) + vty_close(vty); + else + { + vty_event(VTY_WRITE, vty_sock, vty); + vty_event(VTY_READ, vty_sock, vty); + } + + return 0; +} + +static void _vty_thread(void *arg) +{ + thread_t thread; + + /* Fetch next active thread. */ + while(thread_fetch(vty_master, &thread)) + thread_call(&thread); + + printf("vty thread is exit!\n"); + return; +} + +static void _vty_thread_init(void) +{ + int32_t rv = 0; + + pthread_t ppid = 0; + struct sched_param param; + pthread_attr_t attr; + + pthread_attr_init(&attr); + param.sched_priority = 50; + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + + rv = pthread_create(&ppid, &attr, (void *)_vty_thread, NULL); + if (rv != 0) + printf("can't create pthread : %d(%s)\n", rv, safe_strerror(rv)); + + pthread_attr_destroy(&attr); +} + +/* Create new vty structure. */ +static vty_t *_vty_term_create(int vty_sock, SOCKUNION_U *su) +{ + vty_t *vty = NULL; + + vty = vty_create(); + + vty->type = VTY_TERM; + vty->node = USERNAME_NODE; + vty->fd = vty_sock; + vty->address = sockunion_su2str(su); + vty->fail_count = 0; + + vty->cp = 0; + vty->length = 0; + + memset(vty->hist, 0, sizeof(vty->hist)); + vty->hp = 0; + vty->hindex = 0; + + vty->status = VTY_NORMAL; + + if (host.lines >= 0) + vty->lines = host.lines; + else + vty->lines = -1; + + vty->iac = 0; + vty->iac_sb_in_progress = 0; + vty->sb_len = 0; + + vty->v_timeout = VTY_TIMEOUT_VAL; + + array_set(vtyvec, vty_sock, vty, MTYPE_VTY_TMP); + + /* Setting up terminal. */ + vty_will_echo(vty); + vty_will_suppress_go_ahead(vty); + + vty_dont_linemode(vty); + vty_do_window_size(vty); + /* vty_dont_lflow_ahead (vty); */ + + vty_prompt(vty); + + /* Add read/write thread. */ + vty_event(VTY_WRITE, vty_sock, vty); + vty_event(VTY_READ, vty_sock, vty); + + return vty; +} + +/* Accept connection from the network. */ +static int _vty_accept(thread_t *thread) +{ + int vty_sock = 0; + SOCKUNION_U su; + int ret = -1; + unsigned int on = 0; + int accept_sock = 0; + prefix_t *p = NULL; + access_list_t *acl = NULL; + char *bufp = NULL; + + accept_sock = THREAD_FD(thread); + + /* We continue hearing vty socket. */ + vty_event(VTY_SERV, accept_sock, NULL); + + memset(&su, 0, sizeof(SOCKUNION_U)); + + /* We can handle IPv4 or IPv6 socket. */ + vty_sock = sockunion_accept(accept_sock, &su); + if (vty_sock < 0) + { + printf("can't accept vty socket : %s\n", safe_strerror(errno)); + return -1; + } + set_nonblocking(vty_sock); + + p = sockunion2hostprefix(&su); + + /* VTY's accesslist apply. */ + if (AF_INET == p->family && vty_accesslist_name) + { + if ((acl = access_list_lookup(AFI_IP, vty_accesslist_name)) && + (FILTER_DENY == access_list_apply(acl, p))) + { + char *buf = NULL; + printf("Vty connection refused from %s\n", (buf = sockunion_su2str(&su))); + XFREE(MTYPE_PREFIX, buf); + close(vty_sock); + + /* continue accepting connections */ + vty_event(VTY_SERV, accept_sock, NULL); + + prefix_free(p); + return 0; + } + } + +#ifdef HAVE_IPV6 + /* VTY's ipv6 accesslist apply. */ + if (AF_INET6 == p->family && vty_ipv6_accesslist_name) + { + if ((acl = access_list_lookup(AFI_IP6, vty_ipv6_accesslist_name)) + && (FILTER_DENY == access_list_apply(acl, p))) + { + char *buf = NULL; + printf("Vty connection refused from %s\n", (buf = sockunion_su2str(&su))); + XFREE(MTYPE_PREFIX, buf); + close(vty_sock); + + /* continue accepting connections */ + vty_event(VTY_SERV, accept_sock, NULL); + + prefix_free(p); + + return 0; + } + } +#endif /* HAVE_IPV6 */ + + prefix_free(p); + + on = 1; + ret = setsockopt(vty_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&on, sizeof(on)); + if (ret < 0) + printf("can't set sockopt to vty_sock : %s\n", safe_strerror(errno)); + + printf("Vty connection from %s\n", (bufp = sockunion_su2str(&su))); + if (bufp) + XFREE(MTYPE_PREFIX, bufp); + + _vty_term_create(vty_sock, &su); + + return 0; +} + +/* When time out occur output message then close connection. */ +static int _vty_timeout(thread_t *thread) +{ + vty_t *vty = THREAD_ARG(thread); + + vty->t_timeout = NULL; + vty->v_timeout = 0; + + /* Clear buffer*/ + buf_reset(vty->out_buf); + vty_out(vty, "%sVty connection is timed out.%s\n", VTY_NEWLINE, VTY_NEWLINE); + + /* Close connection. */ + vty->status = VTY_CLOSE; + vty_close(vty); + + return 0; +} + +/* Flush buffer to the vty. */ +static int _vty_flush(thread_t *thread) +{ + int32_t erase = 0; + BUF_STATUS_E flushrc = BUFFER_ERROR; + int vty_sock = THREAD_FD(thread); + vty_t *vty = THREAD_ARG(thread); + + vty->t_write = NULL; + + /* Tempolary disable read thread. */ + if ((0 == vty->lines) && vty->t_read) + { + thread_cancel(vty->t_read); + vty->t_read = NULL; + } + + /* Function execution continue. */ + erase = ((VTY_MORE == vty->status || VTY_MORELINE == vty->status)); + + /* N.B. if width is 0, that means we don't know the window size. */ + if ((vty->lines == 0) || (vty->width == 0)) + flushrc = buf_flush_available(vty->out_buf, vty->fd); + else if (VTY_MORELINE == vty->status) + flushrc = buf_flush_window(vty->out_buf, vty->fd, vty->width, 1, erase, 0); + else + flushrc = buf_flush_window(vty->out_buf, vty->fd, vty->width, + vty->lines >= 0 ? vty->lines : vty->height, erase, 0); + + switch (flushrc) + { + case BUFFER_ERROR: + vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ + printf("buffer_flush failed on vty client fd %d, closing\n", vty->fd); + buf_reset(vty->out_buf); + vty_close(vty); + return 0; + case BUFFER_EMPTY: + if (VTY_CLOSE == vty->status) + vty_close (vty); + else + { + vty->status = VTY_NORMAL; + if (0 == vty->lines) + vty_event(VTY_READ, vty_sock, vty); + } + break; + case BUFFER_PENDING: + /* There is more data waiting to be written. */ + vty->status = VTY_MORE; + if (0 == vty->lines) + vty_event(VTY_WRITE, vty_sock, vty); + break; + } + + return 0; +} + +static int _vty_log_out(vty_t *vty, const char *level, const char *proto_str, const char *format, + char *time_str, va_list va) +{ + int32_t ret = 0; + int32_t len = 0; + char *buf = NULL; + + + buf = XMALLOC(MTYPE_VTY_TMP, VTY_TMP_BUFSIZ); + if (NULL == buf) + return -1; + + len = snprintf(buf, VTY_TMP_BUFSIZ, "%s", time_str); + buf[len++] = ' '; + + if (level) + ret = snprintf(buf + len, VTY_TMP_BUFSIZ - len - 1, "%s: %s: ", level, proto_str); + else + ret = snprintf(buf + len, VTY_TMP_BUFSIZ - len - 1, "%s: ", proto_str); + + len += ret; + if ((ret < 0) || (len > VTY_TMP_BUFSIZ)) + { + XFREE(MTYPE_VTY_TMP, buf); + return -1; + } + + ret = vsnprintf(buf + len, VTY_TMP_BUFSIZ - len - 1, format, va); + len += ret; + if ((ret < 0) || (len > VTY_TMP_BUFSIZ)) + { + XFREE(MTYPE_VTY_TMP, buf); + return -1; + } + + vty_out(vty, "%s%s", buf, VTY_NEWLINE); + buf_flush_all(vty->out_buf, vty->fd); + + XFREE(MTYPE_VTY_TMP, buf); + return 0; +} + +static int32_t _vty_user_cmp(vty_user_t *user) +{ + int32_t i = 0; + vty_user_t *node = NULL; + + for(i = 0; i < array_active(vty_user); i++) + if ((node = array_get(vty_user, i)) != NULL) + { + if (0 == strcmp(user->username, node->username) + && 0 == strcmp(user->password, node->password)) + { + return 0; + } + } + + return 1; +} + +/* 在终端输出版本信息 */ +void vty_version_print(vty_t *vty) +{ + struct sockaddr_in server; + char buf[128] = {0}; + time_t temp = 0; + + vty_out(vty, "%s%s", CL_COPYRIGHT, VTY_NEWLINE); + vty_out(vty, "%s %s (%08x).%s", PROGNAME, host.version, device_info.dev_id, VTY_NEWLINE); + vty_out(vty, "Compile date: %s%s", host.compile, VTY_NEWLINE); + vty_out(vty, "Id: %03d.%03d%s", device_info.type_m, device_info.type_s, VTY_NEWLINE); + vty_out(vty, "Mac: %02x:%02x:%02x:%02x:%02x:%02x%s", + device_info.mac[0], device_info.mac[1], device_info.mac[2], + device_info.mac[3], device_info.mac[4], device_info.mac[5], VTY_NEWLINE); + server.sin_addr.s_addr = device_info.ip; + vty_out(vty, "ip: %s ", inet_ntoa(server.sin_addr)); + server.sin_addr.s_addr = device_info.mask; + vty_out(vty, "%s%s", inet_ntoa(server.sin_addr), VTY_NEWLINE); + server.sin_addr.s_addr = device_info.gw; + vty_out(vty, "route: %s%s", inet_ntoa(server.sin_addr), VTY_NEWLINE); + temp = device_info.factory_date; + strftime(buf, 128, "%Y-%m-%d %H:%M:%S", localtime(&temp)); + vty_out(vty, "factory date: %s%s", buf, VTY_NEWLINE); + temp = device_info.deployment_date; + strftime(buf, 128, "%Y-%m-%d %H:%M:%S", localtime(&temp)); + vty_out(vty, "deployment date: %s%s", buf, VTY_NEWLINE); +} + +void vty_event(VTY_EVENT_E event, int sock, vty_t *vty) +{ + thread_t *thread = NULL; + + switch (event) + { + case VTY_SERV: + thread = THREAD_ADD_READ(vty_master, _vty_accept, vty, sock); + array_set(vty_serv_thread, sock, thread, MTYPE_VTY_TMP); + break; +#if 0 + case VTYSH_SERV: + thread_add_read(vty_master, vtysh_accept, vty, sock); + break; + case VTYSH_READ: + vty->t_read = THREAD_ADD_READ(vty_master, vtysh_read, vty, sock); + break; + case VTYSH_WRITE: + vty->t_write = THREAD_ADD_WRITE(vty_master, vtysh_write, vty, sock); + break; +#endif /* VTYSH */ + case VTY_READ: + vty->t_read = THREAD_ADD_READ(vty_master, _vty_read, vty, sock); + + /* Time out treatment. */ + if (vty->v_timeout) + { + if (vty->t_timeout) + thread_cancel(vty->t_timeout); + + vty->t_timeout = + THREAD_ADD_TIMER(vty_master, _vty_timeout, vty, vty->v_timeout); + } + break; + case VTY_WRITE: + if (! vty->t_write) + vty->t_write = THREAD_ADD_WRITE(vty_master, _vty_flush, vty, sock); + break; + case VTY_TIMEOUT_RESET: + if (vty->t_timeout) + { + thread_cancel(vty->t_timeout); + vty->t_timeout = NULL; + } + if (vty->v_timeout) + { + vty->t_timeout = + THREAD_ADD_TIMER(vty_master, _vty_timeout, vty, vty->v_timeout); + } + break; + } +} + +static void vty_clear_buf(vty_t *vty) +{ + memset(vty->buf, 0, vty->max); +} + +/* Put out prompt and wait input from user. */ +void vty_prompt(vty_t *vty) +{ + cmd_node_t *node = NULL; + + if (vty->type != VTY_SHELL) + { + node = cmd_node_get(vty->node); + + /* 比如用户密码输入 */ + if (USERNAME_NODE == node->node + || PASSWORD_NODE == node->node) + { + vty_out(vty, "%s", node->prompt); + } + else + { + vty_out(vty, node->prompt, device_info.hostname); + } + } +} + +/* Add current command line to the history buffer. */ +static void vty_hist_add(vty_t *vty) +{ + int index; + + if (vty->length == 0) + return; + + index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1; + + /* Ignore the same string as previous one. */ + if (vty->hist[index]) + if(0 == strcmp(vty->buf, vty->hist[index])) + { + vty->hp = vty->hindex; + return; + } + + /* Insert history entry. */ + if (vty->hist[vty->hindex]) + XFREE(MTYPE_VTY_HIST, vty->hist[vty->hindex]); + vty->hist[vty->hindex] = XSTRDUP(MTYPE_VTY_HIST, vty->buf); + + /* History index rotation. */ + vty->hindex++; + if (vty->hindex == VTY_MAXHIST) + vty->hindex = 0; + + vty->hp = vty->hindex; +} + +int vty_shell(vty_t *vty) +{ + return vty->type == VTY_SHELL ? 1 : 0; +} + +/* VTY standard output function. */ +int vty_out (vty_t *vty, const char *format, ...) +{ + va_list args; + int len = 0; + int size = 1024; + char buf[1024]; + char *p = NULL; + + if (vty_shell(vty)) + { + va_start(args, format); + vprintf(format, args); + va_end(args); + } + else if(VTY_CMD == vty->type) + { + va_start(args, format); + vtycmd_print(format, args); + va_end(args); + } + else + { + /* Try to write to initial buffer. */ + va_start(args, format); + len = vsnprintf(buf, sizeof buf, format, args); + va_end(args); + + /* Initial buffer is not enough. */ + if (len < 0 || len >= size) + { + while (1) + { + if (len > -1) + size = len + 1; + else + size = size * 2; + + p = XREALLOC(MTYPE_VTY_OUT_BUF, p, size); + if (!p) + return -1; + + va_start(args, format); + len = vsnprintf(p, size, format, args); + va_end(args); + + if (len > -1 && len < size) + break; + } + } + + /* When initial buffer is enough to store all output. */ + if (!p) + p = buf; + + /* Pointer p must point out buffer. */ + buf_put(vty->out_buf, p, len); + + /* If p is not different with buf, it is allocated buffer. */ + if (p != buf) + XFREE(MTYPE_VTY_OUT_BUF, p); + } + + return len; +} + +/* Allocate new vty struct. */ +vty_t *vty_create() +{ + vty_t *vty_new = XMALLOC(MTYPE_VTY, sizeof(vty_t)); + + /* Use default buffer size. */ + vty_new->out_buf = buf_create(0); + vty_new->buf = XMALLOC(MTYPE_VTY, VTY_BUFSIZ); + vty_new->max = VTY_BUFSIZ; + + return vty_new; +} + +/* Close vty interface. Warning: call this only from functions that + * will be careful not to access the vty afterwards (since it has + * now been freed). This is safest from top-level functions (called + * directly by the thread dispatcher). */ +void vty_close(vty_t *vty) +{ + int i = 0; + + /* Cancel threads.*/ + if (vty->t_read) + thread_cancel (vty->t_read); + if (vty->t_write) + thread_cancel (vty->t_write); + if (vty->t_timeout) + thread_cancel (vty->t_timeout); + + /* Flush buffer. */ + buf_flush_all(vty->out_buf, vty->fd); + + /* Free input buffer. */ + buf_free(vty->out_buf); + + /* Free command history. */ + for(i = 0; i < VTY_MAXHIST; i++) + if (vty->hist[i]) + XFREE(MTYPE_VTY_HIST, vty->hist[i]); + + /* Unset vector. */ + array_unset(vtyvec, vty->fd); + + /* Close socket. */ + if (vty->fd > 0) + close(vty->fd); + + if (vty->address) + XFREE(MTYPE_PREFIX, vty->address); + if (vty->buf) + XFREE(MTYPE_VTY, vty->buf); + + /* Check configure. */ + vty_config_unlock(vty); + + /* OK free vty. */ + XFREE(MTYPE_VTY, vty); +} + +/* Execute current command line. */ +int vty_execute(vty_t *vty) +{ + int ret = CMD_ERR_NO_MATCH; + + switch (vty->node) + { + case USERNAME_NODE: + snprintf(vty->user.username, VTY_USERNAME_LEN, "%s", vty->buf); + vty->node = PASSWORD_NODE; + break; + + case PASSWORD_NODE: + snprintf(vty->user.password, VTY_USERNAME_LEN, "%s", vty->buf); + if (0 == _vty_user_cmp(&vty->user)) + { + vty->node = ENABLE_NODE; + vty_out(vty, "%s================================%s", VTY_NEWLINE, VTY_NEWLINE); + vty_version_print(vty); + vty_out(vty, "================================%s%s", VTY_NEWLINE, VTY_NEWLINE); + } + else + { + vty_out(vty, "Username or password is not match!!!%s%s", VTY_NEWLINE, VTY_NEWLINE); + vty->node = USERNAME_NODE; + } + break; + + case ENABLE_NODE: + case CONFIG_NODE: + default: + ret = cmd_execute(vty); + if (vty->type != VTY_SHELL) + vty_hist_add(vty); + break; + } + + /* Clear command line buffer. */ + vty->cp = vty->length = 0; + vty_clear_buf(vty); + + if (vty->status != VTY_CLOSE ) + vty_prompt(vty); + + return ret; +} + +int vty_config_lock(vty_t *vty) +{ + if (0 == vty_config) + { + vty->config = 1; + vty_config = 1; + } + + return vty->config; +} + +int vty_config_unlock(vty_t *vty) +{ + if (1 == vty_config) + vty_config = 0; + + if (1 == vty->config) + vty->config = 0; + + return vty->config; +} + +void vty_question(vty_t *vty, array_t *cmd_line) +{ + int32_t ret = CMD_ERR_NO_MATCH; + unsigned int i = 0; + array_t *matchs = NULL; + unsigned int width = 0; + desc_t *desc = NULL; + + matchs = cmd_describe_command(cmd_line, vty, &ret); + vty_out(vty, "%s", VTY_NEWLINE); + + /* Ambiguous and no match error. */ + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + fprintf (stdout,"%% Ambiguous command.\n"); + return; + break; + case CMD_ERR_NO_MATCH: + fprintf (stdout,"%% There is no matched command.\n"); + return; + break; + } + + /* Get width of command string. */ + for (i = 0; i < array_active(matchs); i++) + if ((desc = array_get(matchs, i)) != NULL) + { + unsigned int len = 0; + + if (desc->cmd[0] == '\0') + continue; + + len = strlen(desc->cmd); + if (desc->cmd[0] == '.') + len--; + + if (width < len) + width = len; + } + + for (i = 0; i < array_active(matchs); i++) + if ((desc = array_get(matchs, i)) != NULL) + { + if (desc->cmd[0] == '\0') + continue; + + if (!desc->str) + vty_out(vty, " %-s%s", desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, VTY_NEWLINE); + else + vty_out(vty, " %-*s %s%s", width, desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, desc->str, VTY_NEWLINE); + } + + array_free(matchs, MTYPE_CLI); + return; +} + +void vty_print_word(vty_t *vty, char *strs[]) +{ + char *str = NULL; + unsigned int i = 0; + + vty_out(vty, "%s", VTY_NEWLINE); + for(i = 0; (str = strs[i]) != NULL; i++) + vty_out(vty, "%s", str); +} + +void vty_free_match_strs(char *match_strs[]) +{ + char *str = NULL; + unsigned int i = 0; + + for(i = 0; (str = match_strs[i]) != NULL; i++) + XFREE(MTYPE_CLI, str); + + XFREE(MTYPE_CLI, match_strs); +} + +/* Send WILL TELOPT_ECHO to remote server. */ +void vty_will_echo(vty_t *vty) +{ + unsigned char cmd[] = {IAC, WILL, TELOPT_ECHO, '\0'}; + vty_out(vty, "%s", cmd); +} + +/* Make suppress Go-Ahead telnet option. */ +void vty_will_suppress_go_ahead(vty_t *vty) +{ + unsigned char cmd[] = {IAC, WILL, TELOPT_SGA, '\0'}; + vty_out(vty, "%s", cmd); +} + +/* Make don't use linemode over telnet. */ +void vty_dont_linemode(vty_t *vty) +{ + unsigned char cmd[] = {IAC, DONT, TELOPT_LINEMODE, '\0'}; + vty_out(vty, "%s", cmd); +} + +/* Use window size. */ +void vty_do_window_size(vty_t *vty) +{ + unsigned char cmd[] = {IAC, DO, TELOPT_NAWS, '\0'}; + vty_out(vty, "%s", cmd); +} + +/* Make vty server socket. */ +void vty_serv_sock_family(const char* addr, unsigned short port, int family) +{ + int32_t ret = 0; + SOCKUNION_U su; + int accept_sock = 0; + void *naddr = NULL; + + memset(&su, 0, sizeof(SOCKUNION_U)); + su.sa.sa_family = family; + if(addr) + switch(family) + { + case AF_INET: + naddr = &su.sin.sin_addr; +#ifdef HAVE_IPV6 + case AF_INET6: + naddr = &su.sin6.sin6_addr; +#endif + } + + if(naddr) + switch(inet_pton(family, addr, naddr)) + { + case -1: + printf("bad address %s\n", addr); + naddr = NULL; + break; + case 0: + printf("error translating address %s: %s\n", addr, safe_strerror(errno)); + naddr = NULL; + } + + /* Make new socket. */ + accept_sock = sockunion_stream_socket(&su); + if (accept_sock < 0) + return; + + /* This is server, so reuse address. */ + sockunion_reuseaddr(accept_sock); + + /* Bind socket to universal address and given port. */ + ret = sockunion_bind(accept_sock, &su, port, naddr); + if (ret < 0) + { + close(accept_sock); /* Avoid sd leak. */ + return; + } + + /* Listen socket under queue 3. */ + ret = listen(accept_sock, 3); + if (ret < 0) + { + printf("can't listen socket\n"); + close(accept_sock); /* Avoid sd leak. */ + return; + } + + /* Add vty server event. */ + vty_event(VTY_SERV, accept_sock, NULL); +} + +/* Small utility function which output log to the VTY. */ +void vty_log(const char *level, const char *proto_str, const char *format, char *time_str, va_list va) +{ + uint32_t i = 0; + vty_t *vty = NULL; + + if (!vtyvec) + return; + + for(i = 0; i < array_active(vtyvec); i++) + if ((vty = array_get(vtyvec, i)) != NULL) + if (vty->monitor) + { + va_list ac; + va_copy(ac, va); + _vty_log_out(vty, level, proto_str, format, time_str, ac); + va_end(ac); + } +} + +/* vty的print函数. */ +void vty_print(const char *format, va_list va) +{ + vty_t *vty = NULL; + char *buf = NULL; + uint32_t i = 0; + bool is_vty = FALSE; + + if (vtycmd_connect()) + { + is_vty = TRUE; + vtycmd_print(format, va); + } + else if(vtyvec) + { + for(i = 0; i < array_active(vtyvec); i++) + if ((vty = array_get(vtyvec, i)) != NULL) + { + is_vty = TRUE; + buf = XMALLOC(MTYPE_VTY_TMP, VTY_TMP_BUFSIZ); + if (NULL == buf) + return; + + vsnprintf(buf, VTY_TMP_BUFSIZ, format, va); + vty_out(vty, "%s", buf); + buf_flush_all(vty->out_buf, vty->fd); + + XFREE(MTYPE_VTY_TMP, buf); + } + } + + if (!is_vty) + { + vprintf(format, va); + } +} + + +/* Reset all VTY status. */ +void vty_reset(void) +{ + unsigned int i = 0; + vty_t *vty = NULL; + thread_t *serv_thread; + + for(i = 0; i < array_active(vtyvec); i++) + if ((vty = array_get(vtyvec, i)) != NULL) + { + buf_reset(vty->out_buf); + vty->status = VTY_CLOSE; + vty_close(vty); + } + + for (i = 0; i < array_active(vty_serv_thread); i++) + if ((serv_thread = array_get(vty_serv_thread, i)) != NULL) + { + thread_cancel(serv_thread); + array_get(vty_serv_thread, i) = NULL; + close(i); + } + + return; +} + +/* Install vty's own commands like `who' command. */ +void vty_init(void) +{ + vty_user_t *user; + + vtyvec = array_init(ARRAY_MIN_SIZE, MTYPE_VTY_TMP); + + vty_serv_thread = array_init(ARRAY_MIN_SIZE, MTYPE_VTY_TMP); + + vty_user = array_init(ARRAY_MIN_SIZE, MTYPE_VTY_TMP); + user = XMALLOC(MTYPE_VTY_TMP, sizeof(vty_user_t)); + snprintf(user->username, VTY_USERNAME_LEN, "%s", VTY_USERNAME_DEFAULT); + snprintf(user->password, VTY_USERNAME_LEN, "%s", VTY_PASSWORD_DEFAULT); + user->level = 255; + array_append(vty_user, user, MTYPE_VTY_TMP); + + vty_master = thread_master_create(); + + _vty_thread_init(); +} +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/z_hardware/hwgpio.c b/app/lib/z_hardware/hwgpio.c new file mode 100644 index 0000000..8853732 --- /dev/null +++ b/app/lib/z_hardware/hwgpio.c @@ -0,0 +1,355 @@ +/****************************************************************************** + * file lib/hardware/hwgpio.c + * author YuLiang + * version 1.0.0 + * date 24-Nov-2021 + * brief This file provides all the gpio operation functions. + * + ****************************************************************************** + * Attention + * + *

© COPYRIGHT(c) 2021 LandPower

+ * + * 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 name of LandPower 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 HOLDER 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. + * + ******************************************************************************/ + +/* Includes ------------------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* 标准C库头文件. */ +#include +#include +#include +#include + +/* 用户代码头文件. */ +#include "hwgpio.h" +#include "cmd.h" + +/* Private define ------------------------------------------------------------*/ +/* Private macro -------------------------------------------------------------*/ +/* Private typedef -----------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +static array_t *gpios = NULL; + +int32_t gpio_dog_idx; + +/* Private function prototypes -----------------------------------------------*/ + +/* Internal functions --------------------------------------------------------*/ +int32_t _gpio_name_get(uint8_t gpio, char *name) +{ + if (!name) + { + return E_NULL; + } + + switch (gpio) + { + case GPIO_WATCHDONG: + snprintf(name, DEV_NAME_STR_LEN, "watchdog"); + break; + default: + snprintf(name, DEV_NAME_STR_LEN, "default"); + break; + } + + return E_NONE; +} + +CMD(gpio_show, + gpio_show_cmd, + "show gpio", + SHOW_STR + "Gpios\n") +{ + uint16_t i = 0; + char gpio_name[DEV_NAME_STR_LEN] = {0}; + gpio_node_t *gpio_node = NULL; + + vty_out(vty, "GPIO NAME DIR VALUE%s", VTY_NEWLINE); + for(i = 0; i < array_active(gpios); i++) + { + gpio_node = array_lookup(gpios, i); + if (!gpio_node) + { + continue; + } + + if (gpio_node->curr_dir == GPIO_DIR_IN) + { + gpio_val_get(gpio_node->index, &gpio_node->curr_val); + } + + _gpio_name_get(gpio_node->gpio, gpio_name); + + vty_out(vty, "%4d %-12s %-03s %d%s", gpio_node->gpio, gpio_name, gpio_node->curr_dir == GPIO_DIR_IN ? "in" : "out", + gpio_node->curr_val, VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +/* Interface functions -------------------------------------------------------*/ +/* 根据 gpio 找到对应的结构体, 如果不存在, 返回NULL. */ +gpio_node_t *gpio_get_by_gpio(uint16_t gpio) +{ + uint16_t i = 0; + gpio_node_t *gpio_node = NULL; + + for(i = 0; i < array_active(gpios); i++) + { + gpio_node = array_lookup(gpios, i); + if (!gpio_node) + { + continue; + } + + if (gpio_node->gpio == gpio) + { + return gpio_node; + } + } + + return NULL; +} + +/* 根据数组 index 找到对应的结构体, 如果不存在, 返回 NULL. */ +gpio_node_t *gpio_get_by_index(uint16_t index) +{ + return (gpio_node_t*)array_lookup(gpios, index); +} + +/* 设置gpio输出值 */ +int32_t gpio_val_set(uint16_t index, uint8_t value) +{ + int32_t fd = 0; + char buf[40] = {0}; + DBG(DBG_M_GPIO, "gpio index %d \r\n", index); + gpio_node_t *gpio_node = gpio_get_by_index(index); + + /* 参数检查 */ + if (!gpio_node) + { + DBG(DBG_M_GPIO, "gpio index %d is not found\r\n", index); + return E_NOT_FOUND; + } + + /* 只有out方向可以设置值 */ + if (gpio_node->curr_dir != GPIO_DIR_OUT) + { + DBG(DBG_M_GPIO, "gpio %d direction is not out\r\n", gpio_node->gpio); + return E_BAD_PARAM; + } + + /* 如果值相等就不需要再操作了 */ + if (gpio_node->curr_val == value) + { + return E_NONE; + } + + /* 打开文件 */ + snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/value", gpio_node->gpio); + fd = open(buf, O_WRONLY); + if (fd <= 0) + { + DBG(DBG_M_GPIO, "Open %s error", buf); + return E_SYS_CALL; + } + + /* 设置值 */ + snprintf(buf, sizeof(buf), "%d", (value == 0) ? 0 : 1); + if (write(fd, buf, 1) <= 0) + { + DBG(DBG_M_GPIO, "Write gpio %d value error", gpio_node->gpio); + return E_SYS_CALL; + } + close(fd); + + gpio_node->curr_val = value; + return E_NONE; +} + +/* 设置gpio输出值 */ +int32_t gpio_val_get(uint16_t index, uint8_t *value) +{ + int32_t fd = 0; + char buf[40] = {0}; + gpio_node_t *gpio_node = gpio_get_by_index(index); + + /* 参数检查 */ + if (!gpio_node) + { + DBG(DBG_M_GPIO, "gpio index %d is not found\r\n", index); + return E_NOT_FOUND; + } + + /* out方向直接读取 */ + if (GPIO_DIR_OUT == gpio_node->curr_dir) + { + *value = gpio_node->curr_val; + return E_NONE; + } + + /* 打开文件 */ + snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/value", gpio_node->gpio); + fd = open(buf, O_RDONLY); + if (fd <= 0) + { + DBG(DBG_M_GPIO, "Open %s error", buf); + return E_SYS_CALL; + } + + /* 读取值 */ + memset(buf, 0, sizeof(buf)); + if (read(fd, buf, sizeof(buf) - 1) <= 0) + { + DBG(DBG_M_GPIO, "Read gpio %d value error", gpio_node->gpio); + return E_SYS_CALL; + } + close(fd); + + *value = strtol(buf, NULL, 10); + return E_NONE; +} + +/* 设置gpio方向 */ +int32_t gpio_dir_set(uint16_t index, uint8_t dir) +{ + int32_t fd = 0; + char buf[40] = {0}; + gpio_node_t *gpio_node = gpio_get_by_index(index); + + /* 参数检查 */ + if (!gpio_node) + { + DBG(DBG_M_GPIO, "gpio index %d is not found\r\n", index); + return E_NOT_FOUND; + } + + /* 如果方向相等,不用继续操作 */ + if (gpio_node->curr_dir == dir) + { + return E_NONE; + } + + /* 打开文件 */ + snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/direction", gpio_node->gpio); + fd = open(buf, O_WRONLY); + if (fd <= 0) + { + DBG(DBG_M_GPIO, "Open %s error", buf); + return E_SYS_CALL; + } + + /* 设置方向 */ + snprintf(buf, sizeof(buf), "%s", (dir == GPIO_DIR_IN) ? "in" : "out"); + if (write(fd, buf, strlen(buf)) <= 0) + { + DBG(DBG_M_GPIO, "Write gpio %d direction error", gpio_node->gpio); + return E_SYS_CALL; + } + close(fd); + + gpio_node->curr_dir = dir; + return E_NONE; +} + +/* 导出 GPIO, 返回值大于等于 0, 表示加入数组的 index; 小于 0 表示失败. */ +int32_t gpio_export(uint16_t gpio) +{ + int32_t fd = 0; + char buf[40] = {0}; + DBG(DBG_M_GPIO, "[%s %s:%d]gpio is %d \r\n", __FILE__, __func__, __LINE__, gpio); + gpio_node_t *gpio_node = gpio_get_by_gpio(gpio); + + /* 如果找到表示已经 export. */ + if (gpio_node) + { + return -1; + } + + /* 文件不存在则导出, 存在不做操作. */ + snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d", gpio); + if (access(buf, 0) < 0) + { + /* 打开文件描述符 */ + fd = open("/sys/class/gpio/export", O_WRONLY); + if (fd <= 0) + { + log_err(LOG_GPIO, "Open /sys/class/gpio/export error!"); + return -2; + } + + /* 导出gpio */ + snprintf(buf, sizeof(buf), "%d", gpio); + if (write(fd, buf, strlen(buf)) <= 0) + { + log_err(LOG_GPIO, "Write /sys/class/gpio/export(%d) error!", gpio); + return -3; + } + close(fd); + } + + /* 添加结构体 */ + gpio_node = XMALLOC_Q(MTYPE_GPIO, sizeof(gpio_node_t)); + gpio_node->gpio = gpio; + /* 默认值是不确定的 */ + gpio_node->curr_val = 0xff; + gpio_node->is_export = TRUE; + gpio_node->index = array_append(gpios, gpio_node, MTYPE_GPIO); + + return gpio_node->index; +} + +/* 初始化函数 */ +int32_t gpio_init(void) +{ + gpios = array_init(ARRAY_MIN_SIZE, MTYPE_GPIO); + gpio_dog_idx = gpio_export(GPIO_WATCHDONG); + if (gpio_dog_idx < 0) + { + DBG(DBG_M_GPIO, "ERROR return %d!\r\n", gpio_dog_idx); + return E_BAD_PARAM; + } + LD_E_RETURN(DBG_M_GPIO, gpio_dir_set(gpio_dog_idx, GPIO_DIR_OUT)); + + return E_NONE; +} + +void feed_dog(void) +{ + static char dog = 1; + dog = (dog == 1)? 0 : 1; + int ret = gpio_val_set(gpio_dog_idx, dog); + if (ret != E_NONE) { + DBG(DBG_M_DBG, "Failed to feed dog, error %d\r\n", ret); + } else { + //DBG(DBG_M_DBG, "feed_dog %d\r\n", dog); + } +} + +/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/ diff --git a/app/lib/z_hardware/module.mk b/app/lib/z_hardware/module.mk new file mode 100644 index 0000000..eddec1b --- /dev/null +++ b/app/lib/z_hardware/module.mk @@ -0,0 +1,3 @@ +local_src := $(patsubst $(SOURCE_DIR)/%,%,$(wildcard $(SOURCE_DIR)/$(subdirectory)/*.c)) + +$(eval $(call make-library,$(subdirectory)/libz_hardware.a,$(local_src))) \ No newline at end of file diff --git a/build/Makefile b/build/Makefile new file mode 100755 index 0000000..c542cf6 --- /dev/null +++ b/build/Makefile @@ -0,0 +1,141 @@ +# $(call source-to-object,source-file-list($1)) +source-to-object = $(subst .c,.o,$(filter %.c,$1)) + +# 产生库文件相对路径,被每个库程序的module.mk文件使用. +# $(subdirectory) +subdirectory = $(patsubst $(SOURCE_DIR)/%/module.mk,%, \ + $(word \ + $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))) + +# 产生库文件规则,被每个库程序的module.mk文件使用. +$(call make-library,library-name($1),source-file-list($2)) +define make-library + libraries += $1 + sources += $2 + + $1: $(call source-to-object,$2) + $(QUIET)$(AR) $(ARFLAGS) $$@ $$^ $(ENULL) + @echo -e "$$(INFO_C)AR $$@ done";echo +endef + +# 产生依赖文件. +# $(call make-depend,source-file($1),object-file($2),depend-file($3)) +define make-depend + $(CC) -MM -MF $3 -MP -MT $2 $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) $1 +endef + +SOURCE_DIR := ../app +PRODUCT := $(MAKECMDGOALS) +CONFIG_FILE := $(SOURCE_DIR)/include/config.h +VERSION_FILE := $(SOURCE_DIR)/include/version.h +VERSION_LIB := version.a +SOFT_COMPILE_DATE := `date "+%Y.%m.%d %k:%M:%S"` +SOFT_VERSION := "8.0.1.0" +HARDWARE_VERSION := "A.1" +FPGA_VERSION := "1.0" + +MV := mv -f +RM := rm -rf +SED := sed +TEST := test +MKDIR := mkdir -p + +# 交叉编译设置 +#DEFARCH = PC +DEFARCH = MYiR + +ifeq ($(DEFARCH), PC) + CROSS_COMPILE = + CC = $(CROSS_COMPILE)gcc + AR = $(CROSS_COMPILE)ar + CFLAGS = -g -Wall -funwind-tables -rdynamic -DHAVE_CONFIG_H +else ifeq ($(DEFARCH), MYiR) + CROSS_COMPILE = aarch64-buildroot-linux-gnu- + CC = $(CROSS_COMPILE)gcc + AR = $(CROSS_COMPILE)ar + CFLAGS += -g -Wall -funwind-tables -rdynamic -DHAVE_CONFIG_H +endif + +#LDLIB := -L./libother -lreadline -lncurses -pthread -lrt -lsqlite3 -lm -lssl -lcrypto -lz +LDLIB := -L./libother -lreadline -lncurses -pthread -lrt -lsqlite3 -lm -lssh -lssl -lcrypto -lz + +# 这里如果是‘@’则隐藏具体的编译命令 +#QUIET := @ +ENULL := > /dev/null +INFO_C := "\\033[31mInfo\\033[0m " +ERROR_C := "\\033[31mInfo\\033[0m " + +modules := $(patsubst $(SOURCE_DIR)/%/module.mk,%, \ + $(shell find $(SOURCE_DIR) -name module.mk)) + +# 该变量仅用于产生输出目录,不做任何其他使用. +create-output-directories := $(shell for f in $(modules); \ + do \ + $(TEST) -d $$f || $(MKDIR) $$f; \ + done) +programs := +sources := +libraries := + +objects = $(call source-to-object,$(sources)) +dependencies = $(subst .o,.d,$(objects)) + +include_dirs := $(SOURCE_DIR)/include ./libother/include/ +CPPFLAGS += $(addprefix -I ,$(include_dirs)) +vpath %.h $(include_dirs) +vpath %.c $(SOURCE_DIR) + +.PHONY: empty +empty: + @echo "Please explicitly specify the Makefile target!!!Example \"make PDMonitor\"." + +include $(patsubst %,$(SOURCE_DIR)/%/module.mk,$(modules)) + +$(VERSION_LIB): $(CONFIG_FILE) $(libraries) version.c + $(QUIET)$(RM) $(VERSION_FILE) + @echo "/* WARNING: Don't modify this file anywhere!!! */" >> $(VERSION_FILE) + @echo "#ifndef _VERSION_H_" >> $(VERSION_FILE) + @echo "#define _VERSION_H_" >> $(VERSION_FILE) + @echo "" >> $(VERSION_FILE) + + @echo "#define SOFT_VERSION \"$(SOFT_VERSION)\"" >> $(VERSION_FILE) + @echo "#define SOFT_COMPILE_DATE \"$(SOFT_COMPILE_DATE)\"" >> $(VERSION_FILE) + @echo "#define HARDV_ERSION \"$(HARDWARE_VERSION)\"" >> $(VERSION_FILE) + @echo "#define FPGA_VERSION \"$(FPGA_VERSION)\"" >> $(VERSION_FILE) + + @echo "" >> $(VERSION_FILE) + @echo "char* softversion_get();" >> $(VERSION_FILE) + @echo "char* softversion_date_get();" >> $(VERSION_FILE) + @echo "char* hardware_version_get();" >> $(VERSION_FILE) + @echo "char* fpga_version_date_get();" >> $(VERSION_FILE) + + @echo "" >> $(VERSION_FILE) + @echo "#endif" >> $(VERSION_FILE) + @echo "/* WARNING: Don't modify this file anywhere!!! */" >> $(VERSION_FILE) + @echo "COMPILE version.o" + $(QUIET)$(COMPILE.c) -o version.o version.c + $(QUIET)$(AR) $(ARFLAGS) $(VERSION_LIB) version.o $(ENULL) + @echo -e "$(INFO_C)AR $@ done";echo + +PDMonitor: $(CONFIG_FILE) $(libraries) $(VERSION_LIB) + # 使用两次$(libraries)避免库文件之间的交叉引用问题. + $(QUIET)$(LINK.o) -rdynamic $(libraries) $(libraries) $(VERSION_LIB) $(LDLIB) -o $@ + @echo -e "$(INFO_C)LINK $@ done";echo + # $(QUIET)cp $@ //home//embed//LandPower// + +.PHONY: libraries +libraries: $(libraries) + +.PHONY: clean +clean: + $(RM) $(modules) $(CONFIG_FILE) $(VERSION_FILE) PDMonitor $(VERSION_LIB) version.o + +ifneq "$(MAKECMDGOALS)" "clean" + -include $(dependencies) + include config.mk +endif + +%.o: %.c + @echo "COMPILE $@" + $(QUIET)$(call make-depend,$<,$@,$(subst .o,.d,$@)) + $(QUIET)$(COMPILE.c) -o $@ $< diff --git a/build/PDMonitor.cfg b/build/PDMonitor.cfg new file mode 100644 index 0000000..5713455 --- /dev/null +++ b/build/PDMonitor.cfg @@ -0,0 +1,8 @@ +# debug功能开启 +CFG_DBG_ON := y +# GIS设备 +CFG_DEV_TYPE_LAND_PD := y +# 是否有GIS通讯协议 +CFG_PROTO_GIS := y +# 默认开启GIS通讯协议 +CFG_PROTO_GIS_DEFAULT := y \ No newline at end of file diff --git a/build/config.mk b/build/config.mk new file mode 100644 index 0000000..b99b9b4 --- /dev/null +++ b/build/config.mk @@ -0,0 +1,40 @@ +include $(PRODUCT).cfg + +$(CONFIG_FILE): $(PRODUCT).cfg + $(QUIET)rm $(CONFIG_FILE) -rf + @echo "/* WARNING: Don't modify this file anywhere!!! */" >> $(CONFIG_FILE) + @echo "#ifndef _CONFIG_H_" >> $(CONFIG_FILE) + @echo "#define _CONFIG_H_" >> $(CONFIG_FILE) + @echo "" >> $(CONFIG_FILE) + + @echo "#define PROGNAME \"$(PRODUCT)\"" >> $(CONFIG_FILE) + +# debug开关 +ifeq ($(CFG_DBG_ON), y) + @echo "#define CFG_DBG_ON" >> $(CONFIG_FILE) +endif + +# 设备类型定义 +ifeq ($(CFG_DEV_TYPE_LAND_PD), y) + @echo "#define CFG_DEV_TYPE_LAND_PD" >> $(CONFIG_FILE) +endif + +# 朗德GIS协议开关 +ifeq ($(CFG_PROTO_GIS), y) + @echo "#define CFG_PROTO_GIS" >> $(CONFIG_FILE) +endif + +# 默认使能朗德GIS协议开关 +ifeq ($(CFG_PROTO_GIS_DEFAULT), y) + @echo "#define CFG_PROTO_GIS_DEFAULT" >> $(CONFIG_FILE) +endif + + @echo "" >> $(CONFIG_FILE) + @echo "#include \"common.h\"" >> $(CONFIG_FILE) + + @echo "" >> $(CONFIG_FILE) + @echo "#endif" >> $(CONFIG_FILE) + @echo "/* WARNING: Don't modify this file anywhere!!! */" >> $(CONFIG_FILE) + @echo -e "$(INFO_C)Create $@ done";echo + +$(VERSION_FILE): $(PRODUCT).cfg \ No newline at end of file diff --git a/build/libother/include/libssh/callbacks.h b/build/libother/include/libssh/callbacks.h new file mode 100755 index 0000000..4e71b3b --- /dev/null +++ b/build/libother/include/libssh/callbacks.h @@ -0,0 +1,989 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* callback.h + * This file includes the public declarations for the libssh callback mechanism + */ + +#ifndef _SSH_CALLBACK_H +#define _SSH_CALLBACK_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup libssh_callbacks The libssh callbacks + * @ingroup libssh + * + * Callback which can be replaced in libssh. + * + * @{ + */ + +/** @internal + * @brief callback to process simple codes + * @param code value to transmit + * @param user Userdata to pass in callback + */ +typedef void (*ssh_callback_int) (int code, void *user); + +/** @internal + * @brief callback for data received messages. + * @param data data retrieved from the socket or stream + * @param len number of bytes available from this stream + * @param user user-supplied pointer sent along with all callback messages + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef int (*ssh_callback_data) (const void *data, size_t len, void *user); + +typedef void (*ssh_callback_int_int) (int code, int errno_code, void *user); + +typedef int (*ssh_message_callback) (ssh_session, ssh_message message, void *user); +typedef int (*ssh_channel_callback_int) (ssh_channel channel, int code, void *user); +typedef int (*ssh_channel_callback_data) (ssh_channel channel, int code, void *data, size_t len, void *user); + +/** + * @brief SSH log callback. All logging messages will go through this callback + * @param session Current session handler + * @param priority Priority of the log, the smaller being the more important + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_log_callback) (ssh_session session, int priority, + const char *message, void *userdata); + +/** + * @brief SSH log callback. + * + * All logging messages will go through this callback. + * + * @param priority Priority of the log, the smaller being the more important. + * + * @param function The function name calling the the logging fucntions. + * + * @param message The actual message + * + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_logging_callback) (int priority, + const char *function, + const char *buffer, + void *userdata); + +/** + * @brief SSH Connection status callback. + * @param session Current session handler + * @param status Percentage of connection status, going from 0.0 to 1.0 + * once connection is done. + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_status_callback) (ssh_session session, float status, + void *userdata); + +/** + * @brief SSH global request callback. All global request will go through this + * callback. + * @param session Current session handler + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_global_request_callback) (ssh_session session, + ssh_message message, void *userdata); + +/** + * @brief Handles an SSH new channel open X11 request. This happens when the server + * sends back an X11 connection attempt. This is a client-side API + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_x11_callback) (ssh_session session, + const char * originator_address, int originator_port, void *userdata); + +/** + * @brief Handles an SSH new channel open "auth-agent" request. This happens when the server + * sends back an "auth-agent" connection attempt. This is a client-side API + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_auth_agent_callback) (ssh_session session, + void *userdata); + +/** + * The structure to replace libssh functions with appropriate callbacks. + */ +struct ssh_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called if e.g. a keyphrase is needed. + */ + ssh_auth_callback auth_function; + /** + * This function will be called each time a loggable event happens. + */ + ssh_log_callback log_function; + /** + * This function gets called during connection time to indicate the + * percentage of connection steps completed. + */ + void (*connect_status_function)(void *userdata, float status); + /** + * This function will be called each time a global request is received. + */ + ssh_global_request_callback global_request_function; + /** This function will be called when an incoming X11 request is received. + */ + ssh_channel_open_request_x11_callback channel_open_request_x11_function; + /** This function will be called when an incoming "auth-agent" request is received. + */ + ssh_channel_open_request_auth_agent_callback channel_open_request_auth_agent_function; +}; +typedef struct ssh_callbacks_struct *ssh_callbacks; + +/** These are callbacks used specifically in SSH servers. + */ + +/** + * @brief SSH authentication callback. + * @param session Current session handler + * @param user User that wants to authenticate + * @param password Password used for authentication + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + */ +typedef int (*ssh_auth_password_callback) (ssh_session session, const char *user, const char *password, + void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticates user with the "none" method + * which is anonymous or passwordless. + * @param session Current session handler + * @param user User that wants to authenticate + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + */ +typedef int (*ssh_auth_none_callback) (ssh_session session, const char *user, void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticates user with the "gssapi-with-mic" method + * @param session Current session handler + * @param user Username of the user (can be spoofed) + * @param principal Authenticated principal of the user, including realm. + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + * @warning Implementations should verify that parameter user matches in some way the principal. + * user and principal can be different. Only the latter is guaranteed to be safe. + */ +typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *user, const char *principal, + void *userdata); + +/** + * @brief SSH authentication callback. + * @param session Current session handler + * @param user User that wants to authenticate + * @param pubkey public key used for authentication + * @param signature_state SSH_PUBLICKEY_STATE_NONE if the key is not signed (simple public key probe), + * SSH_PUBLICKEY_STATE_VALID if the signature is valid. Others values should be + * replied with a SSH_AUTH_DENIED. + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + */ +typedef int (*ssh_auth_pubkey_callback) (ssh_session session, const char *user, struct ssh_key_struct *pubkey, + char signature_state, void *userdata); + + +/** + * @brief Handles an SSH service request + * @param session current session handler + * @param service name of the service (e.g. "ssh-userauth") requested + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the request is to be allowed + * @returns -1 if the request should not be allowed + */ + +typedef int (*ssh_service_request_callback) (ssh_session session, const char *service, void *userdata); + +/** + * @brief Handles an SSH new channel open session request + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_session_callback) (ssh_session session, void *userdata); + +/* + * @brief handle the beginning of a GSSAPI authentication, server side. + * @param session current session handler + * @param user the username of the client + * @param n_oid number of available oids + * @param oids OIDs provided by the client + * @returns an ssh_string containing the chosen OID, that's supported by both + * client and server. + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef ssh_string (*ssh_gssapi_select_oid_callback) (ssh_session session, const char *user, + int n_oid, ssh_string *oids, void *userdata); + +/* + * @brief handle the negociation of a security context, server side. + * @param session current session handler + * @param[in] input_token input token provided by client + * @param[out] output_token output of the gssapi accept_sec_context method, + * NULL after completion. + * @returns SSH_OK if the token was generated correctly or accept_sec_context + * returned GSS_S_COMPLETE + * @returns SSH_ERROR in case of error + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef int (*ssh_gssapi_accept_sec_ctx_callback) (ssh_session session, + ssh_string input_token, ssh_string *output_token, void *userdata); + +/* + * @brief Verify and authenticates a MIC, server side. + * @param session current session handler + * @param[in] mic input mic to be verified provided by client + * @param[in] mic_buffer buffer of data to be signed. + * @param[in] mic_buffer_size size of mic_buffer + * @returns SSH_OK if the MIC was authenticated correctly + * @returns SSH_ERROR in case of error + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef int (*ssh_gssapi_verify_mic_callback) (ssh_session session, + ssh_string mic, void *mic_buffer, size_t mic_buffer_size, void *userdata); + + +/** + * This structure can be used to implement a libssh server, with appropriate callbacks. + */ + +struct ssh_server_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** This function gets called when a client tries to authenticate through + * password method. + */ + ssh_auth_password_callback auth_password_function; + + /** This function gets called when a client tries to authenticate through + * none method. + */ + ssh_auth_none_callback auth_none_function; + + /** This function gets called when a client tries to authenticate through + * gssapi-mic method. + */ + ssh_auth_gssapi_mic_callback auth_gssapi_mic_function; + + /** this function gets called when a client tries to authenticate or offer + * a public key. + */ + ssh_auth_pubkey_callback auth_pubkey_function; + + /** This functions gets called when a service request is issued by the + * client + */ + ssh_service_request_callback service_request_function; + /** This functions gets called when a new channel request is issued by + * the client + */ + ssh_channel_open_request_session_callback channel_open_request_session_function; + /** This function will be called when a new gssapi authentication is attempted. + */ + ssh_gssapi_select_oid_callback gssapi_select_oid_function; + /** This function will be called when a gssapi token comes in. + */ + ssh_gssapi_accept_sec_ctx_callback gssapi_accept_sec_ctx_function; + /* This function will be called when a MIC needs to be verified. + */ + ssh_gssapi_verify_mic_callback gssapi_verify_mic_function; +}; +typedef struct ssh_server_callbacks_struct *ssh_server_callbacks; + +/** + * @brief Set the session server callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for user authentication, new channels and requests. + * + * @code + * struct ssh_server_callbacks_struct cb = { + * .userdata = data, + * .auth_password_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_server_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb); + +/** + * These are the callbacks exported by the socket structure + * They are called by the socket module when a socket event appears + */ +struct ssh_socket_callbacks_struct { + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This function will be called each time data appears on socket. The data + * not consumed will appear on the next data event. + */ + ssh_callback_data data; + /** This function will be called each time a controlflow state changes, i.e. + * the socket is available for reading or writing. + */ + ssh_callback_int controlflow; + /** This function will be called each time an exception appears on socket. An + * exception can be a socket problem (timeout, ...) or an end-of-file. + */ + ssh_callback_int_int exception; + /** This function is called when the ssh_socket_connect was used on the socket + * on nonblocking state, and the connection successed. + */ + ssh_callback_int_int connected; +}; +typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks; + +#define SSH_SOCKET_FLOW_WRITEWILLBLOCK 1 +#define SSH_SOCKET_FLOW_WRITEWONTBLOCK 2 + +#define SSH_SOCKET_EXCEPTION_EOF 1 +#define SSH_SOCKET_EXCEPTION_ERROR 2 + +#define SSH_SOCKET_CONNECTED_OK 1 +#define SSH_SOCKET_CONNECTED_ERROR 2 +#define SSH_SOCKET_CONNECTED_TIMEOUT 3 + +/** + * @brief Initializes an ssh_callbacks_struct + * A call to this macro is mandatory when you have set a new + * ssh_callback_struct structure. Its goal is to maintain the binary + * compatibility with future versions of libssh as the structure + * evolves with time. + */ +#define ssh_callbacks_init(p) do {\ + (p)->size=sizeof(*(p)); \ +} while(0); + +/** + * @internal + * @brief tests if a callback can be called without crash + * verifies that the struct size if big enough + * verifies that the callback pointer exists + * @param p callback pointer + * @param c callback name + * @returns nonzero if callback can be called + */ +#define ssh_callbacks_exists(p,c) (\ + (p != NULL) && ( (char *)&((p)-> c) < (char *)(p) + (p)->size ) && \ + ((p)-> c != NULL) \ + ) + +/** + * @internal + * + * @brief Iterate through a list of callback structures + * + * This tests for their validity and executes them. The userdata argument is + * automatically passed through. + * + * @param list list of callbacks + * + * @param cbtype type of the callback + * + * @param c callback name + * + * @param va_args parameters to be passed + */ +#define ssh_callbacks_execute_list(list, cbtype, c, ...) \ + do { \ + struct ssh_iterator *i = ssh_list_get_iterator(list); \ + cbtype cb; \ + while (i != NULL){ \ + cb = ssh_iterator_value(cbtype, i); \ + if (ssh_callbacks_exists(cb, c)) \ + cb-> c (__VA_ARGS__, cb->userdata); \ + i = i->next; \ + } \ + } while(0) + +/** + * @internal + * + * @brief iterate through a list of callback structures. + * + * This tests for their validity and give control back to the calling code to + * execute them. Caller can decide to break the loop or continue executing the + * callbacks with different parameters + * + * @code + * ssh_callbacks_iterate(channel->callbacks, ssh_channel_callbacks, + * channel_eof_function){ + * rc = ssh_callbacks_iterate_exec(session, channel); + * if (rc != SSH_OK){ + * break; + * } + * } + * ssh_callbacks_iterate_end(); + * @endcode + */ +#define ssh_callbacks_iterate(_cb_list, _cb_type, _cb_name) \ + do { \ + struct ssh_iterator *_cb_i = ssh_list_get_iterator(_cb_list); \ + _cb_type _cb; \ + for (; _cb_i != NULL; _cb_i = _cb_i->next) { \ + _cb = ssh_iterator_value(_cb_type, _cb_i); \ + if (ssh_callbacks_exists(_cb, _cb_name)) + +#define ssh_callbacks_iterate_exec(_cb_name, ...) \ + _cb->_cb_name(__VA_ARGS__, _cb->userdata) + +#define ssh_callbacks_iterate_end() \ + } \ + } while(0) + +/** @brief Prototype for a packet callback, to be called when a new packet arrives + * @param session The current session of the packet + * @param type packet type (see ssh2.h) + * @param packet buffer containing the packet, excluding size, type and padding fields + * @param user user argument to the callback + * and are called each time a packet shows up + * @returns SSH_PACKET_USED Packet was parsed and used + * @returns SSH_PACKET_NOT_USED Packet was not used or understood, processing must continue + */ +typedef int (*ssh_packet_callback) (ssh_session session, uint8_t type, ssh_buffer packet, void *user); + +/** return values for a ssh_packet_callback */ +/** Packet was used and should not be parsed by another callback */ +#define SSH_PACKET_USED 1 +/** Packet was not used and should be passed to any other callback + * available */ +#define SSH_PACKET_NOT_USED 2 + + +/** @brief This macro declares a packet callback handler + * @code + * SSH_PACKET_CALLBACK(mycallback){ + * ... + * } + * @endcode + */ +#define SSH_PACKET_CALLBACK(name) \ + int name (ssh_session session, uint8_t type, ssh_buffer packet, void *user) + +struct ssh_packet_callbacks_struct { + /** Index of the first packet type being handled */ + uint8_t start; + /** Number of packets being handled by this callback struct */ + uint8_t n_callbacks; + /** A pointer to n_callbacks packet callbacks */ + ssh_packet_callback *callbacks; + /** + * User-provided data. User is free to set anything he wants here + */ + void *user; +}; + +typedef struct ssh_packet_callbacks_struct *ssh_packet_callbacks; + +/** + * @brief Set the session callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for auth, logging and status. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_set_callbacks(ssh_session session, ssh_callbacks cb); + +/** + * @brief SSH channel data callback. Called when data is available on a channel + * @param session Current session handler + * @param channel the actual channel + * @param data the data that has been read on the channel + * @param len the length of the data + * @param is_stderr is 0 for stdout or 1 for stderr + * @param userdata Userdata to be passed to the callback function. + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef int (*ssh_channel_data_callback) (ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); + +/** + * @brief SSH channel eof callback. Called when a channel receives EOF + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_eof_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel close callback. Called when a channel is closed by remote peer + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_close_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel signal callback. Called when a channel has received a signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + void *userdata); + +/** + * @brief SSH channel exit status callback. Called when a channel has received an exit status + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_status_callback) (ssh_session session, + ssh_channel channel, + int exit_status, + void *userdata); + +/** + * @brief SSH channel exit signal callback. Called when a channel has received an exit signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param core a boolean telling wether a core has been dumped or not + * @param errmsg the description of the exception + * @param lang the language of the description (format: RFC 3066) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + int core, + const char *errmsg, + const char *lang, + void *userdata); + +/** + * @brief SSH channel PTY request from a client. + * @param channel the channel + * @param term The type of terminal emulation + * @param width width of the terminal, in characters + * @param height height of the terminal, in characters + * @param pxwidth width of the terminal, in pixels + * @param pxheight height of the terminal, in pixels + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the pty request is accepted + * @returns -1 if the request is denied + */ +typedef int (*ssh_channel_pty_request_callback) (ssh_session session, + ssh_channel channel, + const char *term, + int width, int height, + int pxwidth, int pwheight, + void *userdata); + +/** + * @brief SSH channel Shell request from a client. + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the shell request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_shell_request_callback) (ssh_session session, + ssh_channel channel, + void *userdata); +/** + * @brief SSH auth-agent-request from the client. This request is + * sent by a client when agent forwarding is available. + * Server is free to ignore this callback, no answer is expected. + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_auth_agent_req_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH X11 request from the client. This request is + * sent by a client when X11 forwarding is requested(and available). + * Server is free to ignore this callback, no answer is expected. + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_x11_req_callback) (ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata); +/** + * @brief SSH channel PTY windows change (terminal size) from a client. + * @param channel the channel + * @param width width of the terminal, in characters + * @param height height of the terminal, in characters + * @param pxwidth width of the terminal, in pixels + * @param pxheight height of the terminal, in pixels + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the pty request is accepted + * @returns -1 if the request is denied + */ +typedef int (*ssh_channel_pty_window_change_callback) (ssh_session session, + ssh_channel channel, + int width, int height, + int pxwidth, int pwheight, + void *userdata); + +/** + * @brief SSH channel Exec request from a client. + * @param channel the channel + * @param command the shell command to be executed + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the exec request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_exec_request_callback) (ssh_session session, + ssh_channel channel, + const char *command, + void *userdata); + +/** + * @brief SSH channel environment request from a client. + * @param channel the channel + * @param env_name name of the environment value to be set + * @param env_value value of the environment value to be set + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the env request is accepted + * @returns 1 if the request is denied + * @warning some environment variables can be dangerous if changed (e.g. + * LD_PRELOAD) and should not be fulfilled. + */ +typedef int (*ssh_channel_env_request_callback) (ssh_session session, + ssh_channel channel, + const char *env_name, + const char *env_value, + void *userdata); +/** + * @brief SSH channel subsystem request from a client. + * @param channel the channel + * @param subsystem the subsystem required + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the subsystem request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_subsystem_request_callback) (ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata); + +/** + * @brief SSH channel write will not block (flow control). + * + * @param channel the channel + * + * @param[in] bytes size of the remote window in bytes. Writing as much data + * will not block. + * + * @param[in] userdata Userdata to be passed to the callback function. + * + * @returns 0 default return value (other return codes may be added in future). + */ +typedef int (*ssh_channel_write_wontblock_callback) (ssh_session session, + ssh_channel channel, + size_t bytes, + void *userdata); + +struct ssh_channel_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called when there is data available. + */ + ssh_channel_data_callback channel_data_function; + /** + * This functions will be called when the channel has received an EOF. + */ + ssh_channel_eof_callback channel_eof_function; + /** + * This functions will be called when the channel has been closed by remote + */ + ssh_channel_close_callback channel_close_function; + /** + * This functions will be called when a signal has been received + */ + ssh_channel_signal_callback channel_signal_function; + /** + * This functions will be called when an exit status has been received + */ + ssh_channel_exit_status_callback channel_exit_status_function; + /** + * This functions will be called when an exit signal has been received + */ + ssh_channel_exit_signal_callback channel_exit_signal_function; + /** + * This function will be called when a client requests a PTY + */ + ssh_channel_pty_request_callback channel_pty_request_function; + /** + * This function will be called when a client requests a shell + */ + ssh_channel_shell_request_callback channel_shell_request_function; + /** This function will be called when a client requests agent + * authentication forwarding. + */ + ssh_channel_auth_agent_req_callback channel_auth_agent_req_function; + /** This function will be called when a client requests X11 + * forwarding. + */ + ssh_channel_x11_req_callback channel_x11_req_function; + /** This function will be called when a client requests a + * window change. + */ + ssh_channel_pty_window_change_callback channel_pty_window_change_function; + /** This function will be called when a client requests a + * command execution. + */ + ssh_channel_exec_request_callback channel_exec_request_function; + /** This function will be called when a client requests an environment + * variable to be set. + */ + ssh_channel_env_request_callback channel_env_request_function; + /** This function will be called when a client requests a subsystem + * (like sftp). + */ + ssh_channel_subsystem_request_callback channel_subsystem_request_function; + /** This function will be called when the channel write is guaranteed + * not to block. + */ + ssh_channel_write_wontblock_callback channel_write_wontblock_function; +}; + +typedef struct ssh_channel_callbacks_struct *ssh_channel_callbacks; + +/** + * @brief Set the channel callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for channel data and exceptions + * + * @code + * struct ssh_channel_callbacks_struct cb = { + * .userdata = data, + * .channel_data = my_channel_data_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_channel_callbacks(channel, &cb); + * @endcode + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + * @warning this function will not replace existing callbacks but set the + * new one atop of them. + */ +LIBSSH_API int ssh_set_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** + * @brief Add channel callback functions + * + * This function will add channel callback functions to the channel callback + * list. + * Callbacks missing from a callback structure will be probed in the next + * on the list. + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_set_channel_callbacks + */ +LIBSSH_API int ssh_add_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** + * @brief Remove a channel callback. + * + * The channel has been added with ssh_add_channel_callbacks or + * ssh_set_channel_callbacks in this case. + * + * @param channel The channel to remove the callback structure from. + * + * @param cb The callback structure to remove + * + * @returns SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_remove_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** @} */ + +/** @group libssh_threads + * @{ + */ + +typedef int (*ssh_thread_callback) (void **lock); + +typedef unsigned long (*ssh_thread_id_callback) (void); +struct ssh_threads_callbacks_struct { + const char *type; + ssh_thread_callback mutex_init; + ssh_thread_callback mutex_destroy; + ssh_thread_callback mutex_lock; + ssh_thread_callback mutex_unlock; + ssh_thread_id_callback thread_id; +}; + +/** + * @brief Set the thread callbacks structure. + * + * This is necessary if your program is using libssh in a multithreaded fashion. + * This function must be called first, outside of any threading context (in your + * main() function for instance), before you call ssh_init(). + * + * @param[in] cb A pointer to a ssh_threads_callbacks_struct structure, which + * contains the different callbacks to be set. + * + * @returns Always returns SSH_OK. + * + * @see ssh_threads_callbacks_struct + * @see SSH_THREADS_PTHREAD + * @bug libgcrypt 1.6 and bigger backend does not support custom callback. + * Using anything else than pthreads here will fail. + */ +LIBSSH_API int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct + *cb); + +/** + * @brief returns a pointer on the pthread threads callbacks, to be used with + * ssh_threads_set_callbacks. + * @warning you have to link with the library ssh_threads. + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void); + +/** + * @brief Get the noop threads callbacks structure + * + * This can be used with ssh_threads_set_callbacks. These callbacks do nothing + * and are being used by default. + * + * @return Always returns a valid pointer to the noop callbacks structure. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void); + +/** + * @brief Set the logging callback function. + * + * @param[in] cb The callback to set. + * + * @return 0 on success, < 0 on errror. + */ +LIBSSH_API int ssh_set_log_callback(ssh_logging_callback cb); + +/** + * @brief Get the pointer to the logging callback function. + * + * @return The pointer the the callback or NULL if none set. + */ +LIBSSH_API ssh_logging_callback ssh_get_log_callback(void); + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /*_SSH_CALLBACK_H */ + +/* @} */ diff --git a/build/libother/include/libssh/legacy.h b/build/libother/include/libssh/legacy.h new file mode 100755 index 0000000..911173e --- /dev/null +++ b/build/libother/include/libssh/legacy.h @@ -0,0 +1,120 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Since libssh.h includes legacy.h, it's important that libssh.h is included + * first. we don't define LEGACY_H now because we want it to be defined when + * included from libssh.h + * All function calls declared in this header are deprecated and meant to be + * removed in future. + */ + +#ifndef LEGACY_H_ +#define LEGACY_H_ + +typedef struct ssh_private_key_struct* ssh_private_key; +typedef struct ssh_public_key_struct* ssh_public_key; + +LIBSSH_API int ssh_auth_list(ssh_session session); +LIBSSH_API int ssh_userauth_offer_pubkey(ssh_session session, const char *username, int type, ssh_string publickey); +LIBSSH_API int ssh_userauth_pubkey(ssh_session session, const char *username, ssh_string publickey, ssh_private_key privatekey); +#ifndef _WIN32 +LIBSSH_API int ssh_userauth_agent_pubkey(ssh_session session, const char *username, + ssh_public_key publickey); +#endif +LIBSSH_API int ssh_userauth_autopubkey(ssh_session session, const char *passphrase); +LIBSSH_API int ssh_userauth_privatekey_file(ssh_session session, const char *username, + const char *filename, const char *passphrase); + +SSH_DEPRECATED LIBSSH_API void buffer_free(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API void *buffer_get(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API uint32_t buffer_get_len(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API ssh_buffer buffer_new(void); + +SSH_DEPRECATED LIBSSH_API ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int channel_change_pty_size(ssh_channel channel,int cols,int rows); +SSH_DEPRECATED LIBSSH_API ssh_channel channel_forward_accept(ssh_session session, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int channel_close(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_forward_cancel(ssh_session session, const char *address, int port); +SSH_DEPRECATED LIBSSH_API int channel_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +SSH_DEPRECATED LIBSSH_API void channel_free(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_get_exit_status(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API ssh_session channel_get_session(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_closed(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_eof(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_open(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API ssh_channel channel_new(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +SSH_DEPRECATED LIBSSH_API int channel_open_session(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_poll(ssh_channel channel, int is_stderr); +SSH_DEPRECATED LIBSSH_API int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); + +SSH_DEPRECATED LIBSSH_API int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr); + +SSH_DEPRECATED LIBSSH_API int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +SSH_DEPRECATED LIBSSH_API int channel_request_env(ssh_channel channel, const char *name, const char *value); +SSH_DEPRECATED LIBSSH_API int channel_request_exec(ssh_channel channel, const char *cmd); +SSH_DEPRECATED LIBSSH_API int channel_request_pty(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +SSH_DEPRECATED LIBSSH_API int channel_request_shell(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_send_signal(ssh_channel channel, const char *signum); +SSH_DEPRECATED LIBSSH_API int channel_request_sftp(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_subsystem(ssh_channel channel, const char *subsystem); +SSH_DEPRECATED LIBSSH_API int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +SSH_DEPRECATED LIBSSH_API int channel_send_eof(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +SSH_DEPRECATED LIBSSH_API void channel_set_blocking(ssh_channel channel, int blocking); +SSH_DEPRECATED LIBSSH_API int channel_write(ssh_channel channel, const void *data, uint32_t len); + +LIBSSH_API void privatekey_free(ssh_private_key prv); +LIBSSH_API ssh_private_key privatekey_from_file(ssh_session session, const char *filename, + int type, const char *passphrase); +LIBSSH_API void publickey_free(ssh_public_key key); +LIBSSH_API int ssh_publickey_to_file(ssh_session session, const char *file, + ssh_string pubkey, int type); +LIBSSH_API ssh_string publickey_from_file(ssh_session session, const char *filename, + int *type); +LIBSSH_API ssh_public_key publickey_from_privatekey(ssh_private_key prv); +LIBSSH_API ssh_string publickey_to_string(ssh_public_key key); +LIBSSH_API int ssh_try_publickey_from_file(ssh_session session, const char *keyfile, + ssh_string *publickey, int *type); +LIBSSH_API enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey); + +LIBSSH_API ssh_string ssh_get_pubkey(ssh_session session); + +LIBSSH_API ssh_message ssh_message_retrieve(ssh_session session, uint32_t packettype); +LIBSSH_API ssh_public_key ssh_message_auth_publickey(ssh_message msg); + +SSH_DEPRECATED LIBSSH_API void string_burn(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_copy(ssh_string str); +SSH_DEPRECATED LIBSSH_API void *string_data(ssh_string str); +SSH_DEPRECATED LIBSSH_API int string_fill(ssh_string str, const void *data, size_t len); +SSH_DEPRECATED LIBSSH_API void string_free(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_from_char(const char *what); +SSH_DEPRECATED LIBSSH_API size_t string_len(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_new(size_t size); +SSH_DEPRECATED LIBSSH_API char *string_to_char(ssh_string str); + +#endif /* LEGACY_H_ */ diff --git a/build/libother/include/libssh/libssh.h b/build/libother/include/libssh/libssh.h new file mode 100755 index 0000000..6ebce27 --- /dev/null +++ b/build/libother/include/libssh/libssh.h @@ -0,0 +1,823 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_H +#define _LIBSSH_H + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef LIBSSH_STATIC + #define LIBSSH_API + #else + #ifdef LIBSSH_EXPORTS + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllexport)) + #else + #define LIBSSH_API __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllimport)) + #else + #define LIBSSH_API __declspec(dllimport) + #endif + #endif + #endif +#else + #if __GNUC__ >= 4 && !defined(__OS2__) + #define LIBSSH_API __attribute__((visibility("default"))) + #else + #define LIBSSH_API + #endif +#endif + +#ifdef _MSC_VER + /* Visual Studio hasn't inttypes.h so it doesn't know uint32_t */ + typedef int int32_t; + typedef unsigned int uint32_t; + typedef unsigned short uint16_t; + typedef unsigned char uint8_t; + typedef unsigned long long uint64_t; + typedef int mode_t; +#else /* _MSC_VER */ + #include + #include + #include +#endif /* _MSC_VER */ + +#ifdef _WIN32 + #include +#else /* _WIN32 */ + #include /* for fd_set * */ + #include +#endif /* _WIN32 */ + +#define SSH_STRINGIFY(s) SSH_TOSTRING(s) +#define SSH_TOSTRING(s) #s + +/* libssh version macros */ +#define SSH_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define SSH_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define SSH_VERSION(a, b, c) SSH_VERSION_DOT(a, b, c) + +/* libssh version */ +#define LIBSSH_VERSION_MAJOR 0 +#define LIBSSH_VERSION_MINOR 8 +#define LIBSSH_VERSION_MICRO 8 + +#define LIBSSH_VERSION_INT SSH_VERSION_INT(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) +#define LIBSSH_VERSION SSH_VERSION(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +#ifdef __GNUC__ +#define SSH_DEPRECATED __attribute__ ((deprecated)) +#else +#define SSH_DEPRECATED +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_counter_struct { + uint64_t in_bytes; + uint64_t out_bytes; + uint64_t in_packets; + uint64_t out_packets; +}; +typedef struct ssh_counter_struct *ssh_counter; + +typedef struct ssh_agent_struct* ssh_agent; +typedef struct ssh_buffer_struct* ssh_buffer; +typedef struct ssh_channel_struct* ssh_channel; +typedef struct ssh_message_struct* ssh_message; +typedef struct ssh_pcap_file_struct* ssh_pcap_file; +typedef struct ssh_key_struct* ssh_key; +typedef struct ssh_scp_struct* ssh_scp; +typedef struct ssh_session_struct* ssh_session; +typedef struct ssh_string_struct* ssh_string; +typedef struct ssh_event_struct* ssh_event; +typedef struct ssh_connector_struct * ssh_connector; +typedef void* ssh_gssapi_creds; + +/* Socket type */ +#ifdef _WIN32 +#ifndef socket_t +typedef SOCKET socket_t; +#endif /* socket_t */ +#else /* _WIN32 */ +#ifndef socket_t +typedef int socket_t; +#endif +#endif /* _WIN32 */ + +#define SSH_INVALID_SOCKET ((socket_t) -1) + +/* the offsets of methods */ +enum ssh_kex_types_e { + SSH_KEX=0, + SSH_HOSTKEYS, + SSH_CRYPT_C_S, + SSH_CRYPT_S_C, + SSH_MAC_C_S, + SSH_MAC_S_C, + SSH_COMP_C_S, + SSH_COMP_S_C, + SSH_LANG_C_S, + SSH_LANG_S_C +}; + +#define SSH_CRYPT 2 +#define SSH_MAC 3 +#define SSH_COMP 4 +#define SSH_LANG 5 + +enum ssh_auth_e { + SSH_AUTH_SUCCESS=0, + SSH_AUTH_DENIED, + SSH_AUTH_PARTIAL, + SSH_AUTH_INFO, + SSH_AUTH_AGAIN, + SSH_AUTH_ERROR=-1 +}; + +/* auth flags */ +#define SSH_AUTH_METHOD_UNKNOWN 0 +#define SSH_AUTH_METHOD_NONE 0x0001 +#define SSH_AUTH_METHOD_PASSWORD 0x0002 +#define SSH_AUTH_METHOD_PUBLICKEY 0x0004 +#define SSH_AUTH_METHOD_HOSTBASED 0x0008 +#define SSH_AUTH_METHOD_INTERACTIVE 0x0010 +#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020 + +/* messages */ +enum ssh_requests_e { + SSH_REQUEST_AUTH=1, + SSH_REQUEST_CHANNEL_OPEN, + SSH_REQUEST_CHANNEL, + SSH_REQUEST_SERVICE, + SSH_REQUEST_GLOBAL +}; + +enum ssh_channel_type_e { + SSH_CHANNEL_UNKNOWN=0, + SSH_CHANNEL_SESSION, + SSH_CHANNEL_DIRECT_TCPIP, + SSH_CHANNEL_FORWARDED_TCPIP, + SSH_CHANNEL_X11, + SSH_CHANNEL_AUTH_AGENT +}; + +enum ssh_channel_requests_e { + SSH_CHANNEL_REQUEST_UNKNOWN=0, + SSH_CHANNEL_REQUEST_PTY, + SSH_CHANNEL_REQUEST_EXEC, + SSH_CHANNEL_REQUEST_SHELL, + SSH_CHANNEL_REQUEST_ENV, + SSH_CHANNEL_REQUEST_SUBSYSTEM, + SSH_CHANNEL_REQUEST_WINDOW_CHANGE, + SSH_CHANNEL_REQUEST_X11 +}; + +enum ssh_global_requests_e { + SSH_GLOBAL_REQUEST_UNKNOWN=0, + SSH_GLOBAL_REQUEST_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_KEEPALIVE +}; + +enum ssh_publickey_state_e { + SSH_PUBLICKEY_STATE_ERROR=-1, + SSH_PUBLICKEY_STATE_NONE=0, + SSH_PUBLICKEY_STATE_VALID=1, + SSH_PUBLICKEY_STATE_WRONG=2 +}; + +/* Status flags */ +/** Socket is closed */ +#define SSH_CLOSED 0x01 +/** Reading to socket won't block */ +#define SSH_READ_PENDING 0x02 +/** Session was closed due to an error */ +#define SSH_CLOSED_ERROR 0x04 +/** Output buffer not empty */ +#define SSH_WRITE_PENDING 0x08 + +enum ssh_server_known_e { + SSH_SERVER_ERROR=-1, + SSH_SERVER_NOT_KNOWN=0, + SSH_SERVER_KNOWN_OK, + SSH_SERVER_KNOWN_CHANGED, + SSH_SERVER_FOUND_OTHER, + SSH_SERVER_FILE_NOT_FOUND +}; + +enum ssh_known_hosts_e { + /** + * There had been an error checking the host. + */ + SSH_KNOWN_HOSTS_ERROR = -2, + + /** + * The known host file does not exist. The host is thus unknown. File will + * be created if host key is accepted. + */ + SSH_KNOWN_HOSTS_NOT_FOUND = -1, + + /** + * The server is unknown. User should confirm the public key hash is + * correct. + */ + SSH_KNOWN_HOSTS_UNKNOWN = 0, + + /** + * The server is known and has not changed. + */ + SSH_KNOWN_HOSTS_OK, + + /** + * The server key has changed. Either you are under attack or the + * administrator changed the key. You HAVE to warn the user about a + * possible attack. + */ + SSH_KNOWN_HOSTS_CHANGED, + + /** + * The server gave use a key of a type while we had an other type recorded. + * It is a possible attack. + */ + SSH_KNOWN_HOSTS_OTHER, +}; + +#ifndef MD5_DIGEST_LEN + #define MD5_DIGEST_LEN 16 +#endif +/* errors */ + +enum ssh_error_types_e { + SSH_NO_ERROR=0, + SSH_REQUEST_DENIED, + SSH_FATAL, + SSH_EINTR +}; + +/* some types for keys */ +enum ssh_keytypes_e{ + SSH_KEYTYPE_UNKNOWN=0, + SSH_KEYTYPE_DSS=1, + SSH_KEYTYPE_RSA, + SSH_KEYTYPE_RSA1, + SSH_KEYTYPE_ECDSA, + SSH_KEYTYPE_ED25519, + SSH_KEYTYPE_DSS_CERT01, + SSH_KEYTYPE_RSA_CERT01 +}; + +enum ssh_keycmp_e { + SSH_KEY_CMP_PUBLIC = 0, + SSH_KEY_CMP_PRIVATE +}; + +#define SSH_ADDRSTRLEN 46 + +struct ssh_knownhosts_entry { + char *hostname; + char *unparsed; + ssh_key publickey; + char *comment; +}; + + +/* Error return codes */ +#define SSH_OK 0 /* No error */ +#define SSH_ERROR -1 /* Error of some kind */ +#define SSH_AGAIN -2 /* The nonblocking call must be repeated */ +#define SSH_EOF -127 /* We have already a eof */ + +/** + * @addtogroup libssh_log + * + * @{ + */ + +enum { + /** No logging at all + */ + SSH_LOG_NOLOG=0, + /** Only warnings + */ + SSH_LOG_WARNING, + /** High level protocol information + */ + SSH_LOG_PROTOCOL, + /** Lower level protocol infomations, packet level + */ + SSH_LOG_PACKET, + /** Every function path + */ + SSH_LOG_FUNCTIONS +}; +/** @} */ +#define SSH_LOG_RARE SSH_LOG_WARNING + +/** + * @name Logging levels + * + * @brief Debug levels for logging. + * @{ + */ + +/** No logging at all */ +#define SSH_LOG_NONE 0 +/** Show only warnings */ +#define SSH_LOG_WARN 1 +/** Get some information what's going on */ +#define SSH_LOG_INFO 2 +/** Get detailed debuging information **/ +#define SSH_LOG_DEBUG 3 +/** Get trace output, packet information, ... */ +#define SSH_LOG_TRACE 4 + +/** @} */ + +enum ssh_options_e { + SSH_OPTIONS_HOST, + SSH_OPTIONS_PORT, + SSH_OPTIONS_PORT_STR, + SSH_OPTIONS_FD, + SSH_OPTIONS_USER, + SSH_OPTIONS_SSH_DIR, + SSH_OPTIONS_IDENTITY, + SSH_OPTIONS_ADD_IDENTITY, + SSH_OPTIONS_KNOWNHOSTS, + SSH_OPTIONS_TIMEOUT, + SSH_OPTIONS_TIMEOUT_USEC, + SSH_OPTIONS_SSH1, + SSH_OPTIONS_SSH2, + SSH_OPTIONS_LOG_VERBOSITY, + SSH_OPTIONS_LOG_VERBOSITY_STR, + SSH_OPTIONS_CIPHERS_C_S, + SSH_OPTIONS_CIPHERS_S_C, + SSH_OPTIONS_COMPRESSION_C_S, + SSH_OPTIONS_COMPRESSION_S_C, + SSH_OPTIONS_PROXYCOMMAND, + SSH_OPTIONS_BINDADDR, + SSH_OPTIONS_STRICTHOSTKEYCHECK, + SSH_OPTIONS_COMPRESSION, + SSH_OPTIONS_COMPRESSION_LEVEL, + SSH_OPTIONS_KEY_EXCHANGE, + SSH_OPTIONS_HOSTKEYS, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, + SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, + SSH_OPTIONS_HMAC_C_S, + SSH_OPTIONS_HMAC_S_C, + SSH_OPTIONS_PASSWORD_AUTH, + SSH_OPTIONS_PUBKEY_AUTH, + SSH_OPTIONS_KBDINT_AUTH, + SSH_OPTIONS_GSSAPI_AUTH, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + SSH_OPTIONS_NODELAY, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, +}; + +enum { + /** Code is going to write/create remote files */ + SSH_SCP_WRITE, + /** Code is going to read remote files */ + SSH_SCP_READ, + SSH_SCP_RECURSIVE=0x10 +}; + +enum ssh_scp_request_types { + /** A new directory is going to be pulled */ + SSH_SCP_REQUEST_NEWDIR=1, + /** A new file is going to be pulled */ + SSH_SCP_REQUEST_NEWFILE, + /** End of requests */ + SSH_SCP_REQUEST_EOF, + /** End of directory */ + SSH_SCP_REQUEST_ENDDIR, + /** Warning received */ + SSH_SCP_REQUEST_WARNING +}; + +enum ssh_connector_flags_e { + /** Only the standard stream of the channel */ + SSH_CONNECTOR_STDOUT = 1, + /** Only the exception stream of the channel */ + SSH_CONNECTOR_STDERR = 2, + /** Merge both standard and exception streams */ + SSH_CONNECTOR_BOTH = 3 +}; + +LIBSSH_API int ssh_blocking_flush(ssh_session session, int timeout); +LIBSSH_API ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms); +LIBSSH_API int ssh_channel_change_pty_size(ssh_channel channel,int cols,int rows); +LIBSSH_API int ssh_channel_close(ssh_channel channel); +LIBSSH_API void ssh_channel_free(ssh_channel channel); +LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); +LIBSSH_API ssh_session ssh_channel_get_session(ssh_channel channel); +LIBSSH_API int ssh_channel_is_closed(ssh_channel channel); +LIBSSH_API int ssh_channel_is_eof(ssh_channel channel); +LIBSSH_API int ssh_channel_is_open(ssh_channel channel); +LIBSSH_API ssh_channel ssh_channel_new(ssh_session session); +LIBSSH_API int ssh_channel_open_auth_agent(ssh_channel channel); +LIBSSH_API int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_session(ssh_channel channel); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, const char *orig_addr, int orig_port); +LIBSSH_API int ssh_channel_poll(ssh_channel channel, int is_stderr); +LIBSSH_API int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr); +LIBSSH_API int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); +LIBSSH_API int ssh_channel_read_timeout(ssh_channel channel, void *dest, uint32_t count, int is_stderr, int timeout_ms); +LIBSSH_API int ssh_channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +LIBSSH_API int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value); +LIBSSH_API int ssh_channel_request_exec(ssh_channel channel, const char *cmd); +LIBSSH_API int ssh_channel_request_pty(ssh_channel channel); +LIBSSH_API int ssh_channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +LIBSSH_API int ssh_channel_request_shell(ssh_channel channel); +LIBSSH_API int ssh_channel_request_send_signal(ssh_channel channel, const char *signum); +LIBSSH_API int ssh_channel_request_send_break(ssh_channel channel, uint32_t length); +LIBSSH_API int ssh_channel_request_sftp(ssh_channel channel); +LIBSSH_API int ssh_channel_request_subsystem(ssh_channel channel, const char *subsystem); +LIBSSH_API int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +LIBSSH_API int ssh_channel_request_auth_agent(ssh_channel channel); +LIBSSH_API int ssh_channel_send_eof(ssh_channel channel); +LIBSSH_API int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +LIBSSH_API void ssh_channel_set_blocking(ssh_channel channel, int blocking); +LIBSSH_API void ssh_channel_set_counter(ssh_channel channel, + ssh_counter counter); +LIBSSH_API int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len); +LIBSSH_API int ssh_channel_write_stderr(ssh_channel channel, + const void *data, + uint32_t len); +LIBSSH_API uint32_t ssh_channel_window_size(ssh_channel channel); + +LIBSSH_API char *ssh_basename (const char *path); +LIBSSH_API void ssh_clean_pubkey_hash(unsigned char **hash); +LIBSSH_API int ssh_connect(ssh_session session); + +LIBSSH_API ssh_connector ssh_connector_new(ssh_session session); +LIBSSH_API void ssh_connector_free(ssh_connector connector); +LIBSSH_API int ssh_connector_set_in_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags); +LIBSSH_API int ssh_connector_set_out_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags); +LIBSSH_API void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd); +LIBSSH_API void ssh_connector_set_out_fd(ssh_connector connector, socket_t fd); + +LIBSSH_API const char *ssh_copyright(void); +LIBSSH_API void ssh_disconnect(ssh_session session); +LIBSSH_API char *ssh_dirname (const char *path); +LIBSSH_API int ssh_finalize(void); + +/* REVERSE PORT FORWARDING */ +LIBSSH_API ssh_channel ssh_channel_accept_forward(ssh_session session, + int timeout_ms, + int *destination_port); +LIBSSH_API int ssh_channel_cancel_forward(ssh_session session, + const char *address, + int port); +LIBSSH_API int ssh_channel_listen_forward(ssh_session session, + const char *address, + int port, + int *bound_port); + +LIBSSH_API void ssh_free(ssh_session session); +LIBSSH_API const char *ssh_get_disconnect_message(ssh_session session); +LIBSSH_API const char *ssh_get_error(void *error); +LIBSSH_API int ssh_get_error_code(void *error); +LIBSSH_API socket_t ssh_get_fd(ssh_session session); +LIBSSH_API char *ssh_get_hexa(const unsigned char *what, size_t len); +LIBSSH_API char *ssh_get_issue_banner(ssh_session session); +LIBSSH_API int ssh_get_openssh_version(ssh_session session); + +LIBSSH_API int ssh_get_server_publickey(ssh_session session, ssh_key *key); + +enum ssh_publickey_hash_type { + SSH_PUBLICKEY_HASH_SHA1, + SSH_PUBLICKEY_HASH_MD5, + SSH_PUBLICKEY_HASH_SHA256 +}; +LIBSSH_API int ssh_get_publickey_hash(const ssh_key key, + enum ssh_publickey_hash_type type, + unsigned char **hash, + size_t *hlen); + +/* DEPRECATED FUNCTIONS */ +SSH_DEPRECATED LIBSSH_API int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash); +SSH_DEPRECATED LIBSSH_API ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int ssh_forward_cancel(ssh_session session, const char *address, int port); +SSH_DEPRECATED LIBSSH_API int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +SSH_DEPRECATED LIBSSH_API int ssh_get_publickey(ssh_session session, ssh_key *key); + + +LIBSSH_API int ssh_get_random(void *where,int len,int strong); +LIBSSH_API int ssh_get_version(ssh_session session); +LIBSSH_API int ssh_get_status(ssh_session session); +LIBSSH_API int ssh_get_poll_flags(ssh_session session); +LIBSSH_API int ssh_init(void); +LIBSSH_API int ssh_is_blocking(ssh_session session); +LIBSSH_API int ssh_is_connected(ssh_session session); +LIBSSH_API int ssh_is_server_known(ssh_session session); + +/* KNOWN HOSTS */ +LIBSSH_API void ssh_knownhosts_entry_free(struct ssh_knownhosts_entry *entry); +#define SSH_KNOWNHOSTS_ENTRY_FREE(e) do { \ + if ((e) != NULL) { \ + ssh_knownhosts_entry_free(e); \ + e = NULL; \ + } \ +} while(0) + +LIBSSH_API int ssh_known_hosts_parse_line(const char *host, + const char *line, + struct ssh_knownhosts_entry **entry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session); + +LIBSSH_API int ssh_session_export_known_hosts_entry(ssh_session session, + char **pentry_string); +LIBSSH_API int ssh_session_update_known_hosts(ssh_session session); + +LIBSSH_API enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry(ssh_session session, + struct ssh_knownhosts_entry **pentry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session); + +/* LOGGING */ +LIBSSH_API int ssh_set_log_level(int level); +LIBSSH_API int ssh_get_log_level(void); +LIBSSH_API void *ssh_get_log_userdata(void); +LIBSSH_API int ssh_set_log_userdata(void *data); +LIBSSH_API void _ssh_log(int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +/* legacy */ +SSH_DEPRECATED LIBSSH_API void ssh_log(ssh_session session, + int prioriry, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +LIBSSH_API ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_reply_success(ssh_message msg); +LIBSSH_API void ssh_message_free(ssh_message msg); +LIBSSH_API ssh_message ssh_message_get(ssh_session session); +LIBSSH_API int ssh_message_subtype(ssh_message msg); +LIBSSH_API int ssh_message_type(ssh_message msg); +LIBSSH_API int ssh_mkdir (const char *pathname, mode_t mode); +LIBSSH_API ssh_session ssh_new(void); + +LIBSSH_API int ssh_options_copy(ssh_session src, ssh_session *dest); +LIBSSH_API int ssh_options_getopt(ssh_session session, int *argcptr, char **argv); +LIBSSH_API int ssh_options_parse_config(ssh_session session, const char *filename); +LIBSSH_API int ssh_options_set(ssh_session session, enum ssh_options_e type, + const void *value); +LIBSSH_API int ssh_options_get(ssh_session session, enum ssh_options_e type, + char **value); +LIBSSH_API int ssh_options_get_port(ssh_session session, unsigned int * port_target); +LIBSSH_API int ssh_pcap_file_close(ssh_pcap_file pcap); +LIBSSH_API void ssh_pcap_file_free(ssh_pcap_file pcap); +LIBSSH_API ssh_pcap_file ssh_pcap_file_new(void); +LIBSSH_API int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename); + +/** + * @brief SSH authentication callback. + * + * @param prompt Prompt to be displayed. + * @param buf Buffer to save the password. You should null-terminate it. + * @param len Length of the buffer. + * @param echo Enable or disable the echo of what you type. + * @param verify Should the password be verified? + * @param userdata Userdata to be passed to the callback function. Useful + * for GUI applications. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_auth_callback) (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata); + +LIBSSH_API ssh_key ssh_key_new(void); +#define SSH_KEY_FREE(x) \ + do { if ((x) != NULL) { ssh_key_free(x); x = NULL; } } while(0) +LIBSSH_API void ssh_key_free (ssh_key key); +LIBSSH_API enum ssh_keytypes_e ssh_key_type(const ssh_key key); +LIBSSH_API const char *ssh_key_type_to_char(enum ssh_keytypes_e type); +LIBSSH_API enum ssh_keytypes_e ssh_key_type_from_name(const char *name); +LIBSSH_API int ssh_key_is_public(const ssh_key k); +LIBSSH_API int ssh_key_is_private(const ssh_key k); +LIBSSH_API int ssh_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); + +LIBSSH_API int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_privkey_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key); +LIBSSH_API int ssh_pki_import_privkey_file(const char *filename, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_privkey_file(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename); + +LIBSSH_API int ssh_pki_copy_cert_to_privkey(const ssh_key cert_key, + ssh_key privkey); + +LIBSSH_API int ssh_pki_import_pubkey_base64(const char *b64_key, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_pubkey_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_import_cert_base64(const char *b64_cert, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_cert_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_pubkey_base64(const ssh_key key, + char **b64_key); +LIBSSH_API int ssh_pki_export_pubkey_file(const ssh_key key, + const char *filename); + +LIBSSH_API const char *ssh_pki_key_ecdsa_name(const ssh_key key); + +LIBSSH_API char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len); +LIBSSH_API void ssh_print_hash(enum ssh_publickey_hash_type type, unsigned char *hash, size_t len); +LIBSSH_API void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len); +LIBSSH_API int ssh_send_ignore (ssh_session session, const char *data); +LIBSSH_API int ssh_send_debug (ssh_session session, const char *message, int always_display); +LIBSSH_API void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds); +LIBSSH_API int ssh_scp_accept_request(ssh_scp scp); +LIBSSH_API int ssh_scp_close(ssh_scp scp); +LIBSSH_API int ssh_scp_deny_request(ssh_scp scp, const char *reason); +LIBSSH_API void ssh_scp_free(ssh_scp scp); +LIBSSH_API int ssh_scp_init(ssh_scp scp); +LIBSSH_API int ssh_scp_leave_directory(ssh_scp scp); +LIBSSH_API ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location); +LIBSSH_API int ssh_scp_pull_request(ssh_scp scp); +LIBSSH_API int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode); +LIBSSH_API int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int perms); +LIBSSH_API int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int perms); +LIBSSH_API int ssh_scp_read(ssh_scp scp, void *buffer, size_t size); +LIBSSH_API const char *ssh_scp_request_get_filename(ssh_scp scp); +LIBSSH_API int ssh_scp_request_get_permissions(ssh_scp scp); +LIBSSH_API size_t ssh_scp_request_get_size(ssh_scp scp); +LIBSSH_API uint64_t ssh_scp_request_get_size64(ssh_scp scp); +LIBSSH_API const char *ssh_scp_request_get_warning(ssh_scp scp); +LIBSSH_API int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len); +LIBSSH_API int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, + fd_set *readfds, struct timeval *timeout); +LIBSSH_API int ssh_service_request(ssh_session session, const char *service); +LIBSSH_API int ssh_set_agent_channel(ssh_session session, ssh_channel channel); +LIBSSH_API int ssh_set_agent_socket(ssh_session session, socket_t fd); +LIBSSH_API void ssh_set_blocking(ssh_session session, int blocking); +LIBSSH_API void ssh_set_counters(ssh_session session, ssh_counter scounter, + ssh_counter rcounter); +LIBSSH_API void ssh_set_fd_except(ssh_session session); +LIBSSH_API void ssh_set_fd_toread(ssh_session session); +LIBSSH_API void ssh_set_fd_towrite(ssh_session session); +LIBSSH_API void ssh_silent_disconnect(ssh_session session); +LIBSSH_API int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile); + +/* USERAUTH */ +LIBSSH_API int ssh_userauth_none(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_list(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_try_publickey(ssh_session session, + const char *username, + const ssh_key pubkey); +LIBSSH_API int ssh_userauth_publickey(ssh_session session, + const char *username, + const ssh_key privkey); +#ifndef _WIN32 +LIBSSH_API int ssh_userauth_agent(ssh_session session, + const char *username); +#endif +LIBSSH_API int ssh_userauth_publickey_auto(ssh_session session, + const char *username, + const char *passphrase); +LIBSSH_API int ssh_userauth_password(ssh_session session, + const char *username, + const char *password); + +LIBSSH_API int ssh_userauth_kbdint(ssh_session session, const char *user, const char *submethods); +LIBSSH_API const char *ssh_userauth_kbdint_getinstruction(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getname(ssh_session session); +LIBSSH_API int ssh_userauth_kbdint_getnprompts(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo); +LIBSSH_API int ssh_userauth_kbdint_getnanswers(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i); +LIBSSH_API int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer); +LIBSSH_API int ssh_userauth_gssapi(ssh_session session); +LIBSSH_API const char *ssh_version(int req_version); +LIBSSH_API int ssh_write_knownhost(ssh_session session); +LIBSSH_API char *ssh_dump_knownhost(ssh_session session); + +LIBSSH_API void ssh_string_burn(ssh_string str); +LIBSSH_API ssh_string ssh_string_copy(ssh_string str); +LIBSSH_API void *ssh_string_data(ssh_string str); +LIBSSH_API int ssh_string_fill(ssh_string str, const void *data, size_t len); +#define SSH_STRING_FREE(x) \ + do { if ((x) != NULL) { ssh_string_free(x); x = NULL; } } while(0) +LIBSSH_API void ssh_string_free(ssh_string str); +LIBSSH_API ssh_string ssh_string_from_char(const char *what); +LIBSSH_API size_t ssh_string_len(ssh_string str); +LIBSSH_API ssh_string ssh_string_new(size_t size); +LIBSSH_API const char *ssh_string_get_char(ssh_string str); +LIBSSH_API char *ssh_string_to_char(ssh_string str); +#define SSH_STRING_FREE_CHAR(x) \ + do { if ((x) != NULL) { ssh_string_free_char(x); x = NULL; } } while(0) +LIBSSH_API void ssh_string_free_char(char *s); + +LIBSSH_API int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, + int verify); + + +typedef int (*ssh_event_callback)(socket_t fd, int revents, void *userdata); + +LIBSSH_API ssh_event ssh_event_new(void); +LIBSSH_API int ssh_event_add_fd(ssh_event event, socket_t fd, short events, + ssh_event_callback cb, void *userdata); +LIBSSH_API int ssh_event_add_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_add_connector(ssh_event event, ssh_connector connector); +LIBSSH_API int ssh_event_dopoll(ssh_event event, int timeout); +LIBSSH_API int ssh_event_remove_fd(ssh_event event, socket_t fd); +LIBSSH_API int ssh_event_remove_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_remove_connector(ssh_event event, ssh_connector connector); +LIBSSH_API void ssh_event_free(ssh_event event); +LIBSSH_API const char* ssh_get_clientbanner(ssh_session session); +LIBSSH_API const char* ssh_get_serverbanner(ssh_session session); +LIBSSH_API const char* ssh_get_kex_algo(ssh_session session); +LIBSSH_API const char* ssh_get_cipher_in(ssh_session session); +LIBSSH_API const char* ssh_get_cipher_out(ssh_session session); +LIBSSH_API const char* ssh_get_hmac_in(ssh_session session); +LIBSSH_API const char* ssh_get_hmac_out(ssh_session session); + +LIBSSH_API ssh_buffer ssh_buffer_new(void); +LIBSSH_API void ssh_buffer_free(ssh_buffer buffer); +#define SSH_BUFFER_FREE(x) \ + do { if ((x) != NULL) { ssh_buffer_free(x); x = NULL; } } while(0) +LIBSSH_API int ssh_buffer_reinit(ssh_buffer buffer); +LIBSSH_API int ssh_buffer_add_data(ssh_buffer buffer, const void *data, uint32_t len); +LIBSSH_API uint32_t ssh_buffer_get_data(ssh_buffer buffer, void *data, uint32_t requestedlen); +LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer); +LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer); + +#ifndef LIBSSH_LEGACY_0_4 +#include "libssh/legacy.h" +#endif + +#ifdef __cplusplus +} +#endif +#endif /* _LIBSSH_H */ diff --git a/build/libother/include/libssh/server.h b/build/libother/include/libssh/server.h new file mode 100755 index 0000000..aeacda0 --- /dev/null +++ b/build/libother/include/libssh/server.h @@ -0,0 +1,369 @@ +/* Public include file for server support */ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @defgroup libssh_server The libssh server API + * + * @{ + */ + +#ifndef SERVER_H +#define SERVER_H + +#include "libssh/libssh.h" +#define SERVERBANNER CLIENTBANNER + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_bind_options_e { + SSH_BIND_OPTIONS_BINDADDR, + SSH_BIND_OPTIONS_BINDPORT, + SSH_BIND_OPTIONS_BINDPORT_STR, + SSH_BIND_OPTIONS_HOSTKEY, + SSH_BIND_OPTIONS_DSAKEY, + SSH_BIND_OPTIONS_RSAKEY, + SSH_BIND_OPTIONS_BANNER, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + SSH_BIND_OPTIONS_ECDSAKEY, + SSH_BIND_OPTIONS_IMPORT_KEY +}; + +typedef struct ssh_bind_struct* ssh_bind; + +/* Callback functions */ + +/** + * @brief Incoming connection callback. This callback is called when a ssh_bind + * has a new incoming connection. + * @param sshbind Current sshbind session handler + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_bind_incoming_connection_callback) (ssh_bind sshbind, + void *userdata); + +/** + * @brief These are the callbacks exported by the ssh_bind structure. + * + * They are called by the server module when events appear on the network. + */ +struct ssh_bind_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** A new connection is available. */ + ssh_bind_incoming_connection_callback incoming_connection; +}; +typedef struct ssh_bind_callbacks_struct *ssh_bind_callbacks; + +/** + * @brief Creates a new SSH server bind. + * + * @return A newly allocated ssh_bind session pointer. + */ +LIBSSH_API ssh_bind ssh_bind_new(void); + +LIBSSH_API int ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, const void *value); + +/** + * @brief Start listening to the socket. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int ssh_bind_listen(ssh_bind ssh_bind_o); + +/** + * @brief Set the callback for this bind. + * + * @param[in] sshbind The bind to set the callback on. + * + * @param[in] callbacks An already set up ssh_bind_callbacks instance. + * + * @param[in] userdata A pointer to private data to pass to the callbacks. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_bind_set_callbacks(session, &cb); + * @endcode + */ +LIBSSH_API int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, + void *userdata); + +/** + * @brief Set the session to blocking/nonblocking mode. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @param blocking Zero for nonblocking mode. + */ +LIBSSH_API void ssh_bind_set_blocking(ssh_bind ssh_bind_o, int blocking); + +/** + * @brief Recover the file descriptor from the session. + * + * @param ssh_bind_o The ssh server bind to get the fd from. + * + * @return The file descriptor. + */ +LIBSSH_API socket_t ssh_bind_get_fd(ssh_bind ssh_bind_o); + +/** + * @brief Set the file descriptor for a session. + * + * @param ssh_bind_o The ssh server bind to set the fd. + * + * @param fd The file descriptssh_bind B + */ +LIBSSH_API void ssh_bind_set_fd(ssh_bind ssh_bind_o, socket_t fd); + +/** + * @brief Allow the file descriptor to accept new sessions. + * + * @param ssh_bind_o The ssh server bind to use. + */ +LIBSSH_API void ssh_bind_fd_toaccept(ssh_bind ssh_bind_o); + +/** + * @brief Accept an incoming ssh connection and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @see ssh_new + * @return SSH_OK when a connection is established + */ +LIBSSH_API int ssh_bind_accept(ssh_bind ssh_bind_o, ssh_session session); + +/** + * @brief Accept an incoming ssh connection on the given file descriptor + * and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @param fd A file descriptor of an already established TCP + * inbound connection + * @see ssh_new + * @see ssh_bind_accept + * @return SSH_OK when a connection is established + */ +LIBSSH_API int ssh_bind_accept_fd(ssh_bind ssh_bind_o, ssh_session session, + socket_t fd); + +LIBSSH_API ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session); + +/** + * @brief Handles the key exchange and set up encryption + * + * @param session A connected ssh session + * @see ssh_bind_accept + * @return SSH_OK if the key exchange was successful + */ +LIBSSH_API int ssh_handle_key_exchange(ssh_session session); + +/** + * @brief Initialize the set of key exchange, hostkey, ciphers, MACs, and + * compression algorithms for the given ssh_session. + * + * The selection of algorithms and keys used are determined by the + * options that are currently set in the given ssh_session structure. + * May only be called before the initial key exchange has begun. + * + * @param session The session structure to initialize. + * + * @see ssh_handle_key_exchange + * @see ssh_options_set + * + * @return SSH_OK if initialization succeeds. + */ + +LIBSSH_API int ssh_server_init_kex(ssh_session session); + +/** + * @brief Free a ssh servers bind. + * + * @param ssh_bind_o The ssh server bind to free. + */ +LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); + +/** + * @brief Set the acceptable authentication methods to be sent to the client. + * + * + * @param[in] session The server session + * + * @param[in] auth_methods The authentication methods we will support, which + * can be bitwise-or'd. + * + * Supported methods are: + * + * SSH_AUTH_METHOD_PASSWORD + * SSH_AUTH_METHOD_PUBLICKEY + * SSH_AUTH_METHOD_HOSTBASED + * SSH_AUTH_METHOD_INTERACTIVE + * SSH_AUTH_METHOD_GSSAPI_MIC + */ +LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods); + +/********************************************************** + * SERVER MESSAGING + **********************************************************/ + +/** + * @brief Reply with a standard reject message. + * + * Use this function if you don't know what to respond or if you want to reject + * a request. + * + * @param[in] msg The message to use for the reply. + * + * @return 0 on success, -1 on error. + * + * @see ssh_message_get() + */ +LIBSSH_API int ssh_message_reply_default(ssh_message msg); + +/** + * @brief Get the name of the authenticated user. + * + * @param[in] msg The message to get the username from. + * + * @return The username or NULL if an error occured. + * + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API const char *ssh_message_auth_user(ssh_message msg); + +/** + * @brief Get the password of the authenticated user. + * + * @param[in] msg The message to get the password from. + * + * @return The username or NULL if an error occured. + * + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); + +/** + * @brief Get the publickey of the authenticated user. + * + * If you need the key for later user you should duplicate it. + * + * @param[in] msg The message to get the public key from. + * + * @return The public key or NULL. + * + * @see ssh_key_dup() + * @see ssh_key_cmp() + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API ssh_key ssh_message_auth_pubkey(ssh_message msg); + +LIBSSH_API int ssh_message_auth_kbdint_is_response(ssh_message msg); +LIBSSH_API enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg); +LIBSSH_API int ssh_message_auth_reply_success(ssh_message msg,int partial); +LIBSSH_API int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey); +LIBSSH_API int ssh_message_auth_reply_pk_ok_simple(ssh_message msg); + +LIBSSH_API int ssh_message_auth_set_methods(ssh_message msg, int methods); + +LIBSSH_API int ssh_message_auth_interactive_request(ssh_message msg, + const char *name, const char *instruction, + unsigned int num_prompts, const char **prompts, char *echo); + +LIBSSH_API int ssh_message_service_reply_success(ssh_message msg); +LIBSSH_API const char *ssh_message_service_service(ssh_message msg); + +LIBSSH_API int ssh_message_global_request_reply_success(ssh_message msg, + uint16_t bound_port); + +LIBSSH_API void ssh_set_message_callback(ssh_session session, + int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data), + void *data); +LIBSSH_API int ssh_execute_message_callbacks(ssh_session session); + +LIBSSH_API const char *ssh_message_channel_request_open_originator(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_originator_port(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_open_destination(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_destination_port(ssh_message msg); + +LIBSSH_API ssh_channel ssh_message_channel_request_channel(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_pty_term(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_width(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_height(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_pxwidth(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_pxheight(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_env_name(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_env_value(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_command(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_subsystem(ssh_message msg); + +LIBSSH_API int ssh_message_channel_request_x11_single_connection(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_x11_screen_number(ssh_message msg); + +LIBSSH_API const char *ssh_message_global_request_address(ssh_message msg); +LIBSSH_API int ssh_message_global_request_port(ssh_message msg); + +LIBSSH_API int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port); + +LIBSSH_API int ssh_channel_request_send_exit_status(ssh_channel channel, + int exit_status); +LIBSSH_API int ssh_channel_request_send_exit_signal(ssh_channel channel, + const char *signum, + int core, + const char *errmsg, + const char *lang); + +LIBSSH_API int ssh_send_keepalive(ssh_session session); + +/* deprecated functions */ +SSH_DEPRECATED LIBSSH_API int ssh_accept(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_write_stderr(ssh_channel channel, + const void *data, uint32_t len); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SERVER_H */ + +/** @} */ diff --git a/build/libother/include/libssh/sftp.h b/build/libother/include/libssh/sftp.h new file mode 100755 index 0000000..3750e0c --- /dev/null +++ b/build/libother/include/libssh/sftp.h @@ -0,0 +1,1036 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @defgroup libssh_sftp The libssh SFTP API + * + * @brief SFTP handling functions + * + * SFTP commands are channeled by the ssh sftp subsystem. Every packet is + * sent/read using a sftp_packet type structure. Related to these packets, + * most of the server answers are messages having an ID and a message + * specific part. It is described by sftp_message when reading a message, + * the sftp system puts it into the queue, so the process having asked for + * it can fetch it, while continuing to read for other messages (it is + * unspecified in which order messages may be sent back to the client + * + * @{ + */ + +#ifndef SFTP_H +#define SFTP_H + +#include + +#include "libssh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#ifndef uid_t + typedef uint32_t uid_t; +#endif /* uid_t */ +#ifndef gid_t + typedef uint32_t gid_t; +#endif /* gid_t */ +#ifdef _MSC_VER + +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ + +#endif /* _MSC_VER */ +#endif /* _WIN32 */ + +#define LIBSFTP_VERSION 3 + +typedef struct sftp_attributes_struct* sftp_attributes; +typedef struct sftp_client_message_struct* sftp_client_message; +typedef struct sftp_dir_struct* sftp_dir; +typedef struct sftp_ext_struct *sftp_ext; +typedef struct sftp_file_struct* sftp_file; +typedef struct sftp_message_struct* sftp_message; +typedef struct sftp_packet_struct* sftp_packet; +typedef struct sftp_request_queue_struct* sftp_request_queue; +typedef struct sftp_session_struct* sftp_session; +typedef struct sftp_status_message_struct* sftp_status_message; +typedef struct sftp_statvfs_struct* sftp_statvfs_t; + +struct sftp_session_struct { + ssh_session session; + ssh_channel channel; + int server_version; + int client_version; + int version; + sftp_request_queue queue; + uint32_t id_counter; + int errnum; + void **handles; + sftp_ext ext; + sftp_packet read_packet; +}; + +struct sftp_packet_struct { + sftp_session sftp; + uint8_t type; + ssh_buffer payload; +}; + +/* file handler */ +struct sftp_file_struct { + sftp_session sftp; + char *name; + uint64_t offset; + ssh_string handle; + int eof; + int nonblocking; +}; + +struct sftp_dir_struct { + sftp_session sftp; + char *name; + ssh_string handle; /* handle to directory */ + ssh_buffer buffer; /* contains raw attributes from server which haven't been parsed */ + uint32_t count; /* counts the number of following attributes structures into buffer */ + int eof; /* end of directory listing */ +}; + +struct sftp_message_struct { + sftp_session sftp; + uint8_t packet_type; + ssh_buffer payload; + uint32_t id; +}; + +/* this is a bunch of all data that could be into a message */ +struct sftp_client_message_struct { + sftp_session sftp; + uint8_t type; + uint32_t id; + char *filename; /* can be "path" */ + uint32_t flags; + sftp_attributes attr; + ssh_string handle; + uint64_t offset; + uint32_t len; + int attr_num; + ssh_buffer attrbuf; /* used by sftp_reply_attrs */ + ssh_string data; /* can be newpath of rename() */ + ssh_buffer complete_message; /* complete message in case of retransmission*/ + char *str_data; /* cstring version of data */ + char *submessage; /* for extended messages */ +}; + +struct sftp_request_queue_struct { + sftp_request_queue next; + sftp_message message; +}; + +/* SSH_FXP_MESSAGE described into .7 page 26 */ +struct sftp_status_message_struct { + uint32_t id; + uint32_t status; + ssh_string error_unused; /* not used anymore */ + ssh_string lang_unused; /* not used anymore */ + char *errormsg; + char *langmsg; +}; + +struct sftp_attributes_struct { + char *name; + char *longname; /* ls -l output on openssh, not reliable else */ + uint32_t flags; + uint8_t type; + uint64_t size; + uint32_t uid; + uint32_t gid; + char *owner; /* set if openssh and version 4 */ + char *group; /* set if openssh and version 4 */ + uint32_t permissions; + uint64_t atime64; + uint32_t atime; + uint32_t atime_nseconds; + uint64_t createtime; + uint32_t createtime_nseconds; + uint64_t mtime64; + uint32_t mtime; + uint32_t mtime_nseconds; + ssh_string acl; + uint32_t extended_count; + ssh_string extended_type; + ssh_string extended_data; +}; + +/** + * @brief SFTP statvfs structure. + */ +struct sftp_statvfs_struct { + uint64_t f_bsize; /** file system block size */ + uint64_t f_frsize; /** fundamental fs block size */ + uint64_t f_blocks; /** number of blocks (unit f_frsize) */ + uint64_t f_bfree; /** free blocks in file system */ + uint64_t f_bavail; /** free blocks for non-root */ + uint64_t f_files; /** total file inodes */ + uint64_t f_ffree; /** free file inodes */ + uint64_t f_favail; /** free file inodes for to non-root */ + uint64_t f_fsid; /** file system id */ + uint64_t f_flag; /** bit mask of f_flag values */ + uint64_t f_namemax; /** maximum filename length */ +}; + +/** + * @brief Start a new sftp session. + * + * @param session The ssh session to use. + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + */ +LIBSSH_API sftp_session sftp_new(ssh_session session); + +/** + * @brief Start a new sftp session with an existing channel. + * + * @param session The ssh session to use. + * @param channel An open session channel with subsystem already allocated + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + */ +LIBSSH_API sftp_session sftp_new_channel(ssh_session session, ssh_channel channel); + + +/** + * @brief Close and deallocate a sftp session. + * + * @param sftp The sftp session handle to free. + */ +LIBSSH_API void sftp_free(sftp_session sftp); + +/** + * @brief Initialize the sftp session with the server. + * + * @param sftp The sftp session to initialize. + * + * @return 0 on success, < 0 on error with ssh error set. + * + * @see sftp_new() + */ +LIBSSH_API int sftp_init(sftp_session sftp); + +/** + * @brief Get the last sftp error. + * + * Use this function to get the latest error set by a posix like sftp function. + * + * @param sftp The sftp session where the error is saved. + * + * @return The saved error (see server responses), < 0 if an error + * in the function occured. + * + * @see Server responses + */ +LIBSSH_API int sftp_get_error(sftp_session sftp); + +/** + * @brief Get the count of extensions provided by the server. + * + * @param sftp The sftp session to use. + * + * @return The count of extensions provided by the server, 0 on error or + * not available. + */ +LIBSSH_API unsigned int sftp_extensions_get_count(sftp_session sftp); + +/** + * @brief Get the name of the extension provided by the server. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension name you want. + * + * @return The name of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_name(sftp_session sftp, unsigned int indexn); + +/** + * @brief Get the data of the extension provided by the server. + * + * This is normally the version number of the extension. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension data you want. + * + * @return The data of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_data(sftp_session sftp, unsigned int indexn); + +/** + * @brief Check if the given extension is supported. + * + * @param sftp The sftp session to use. + * + * @param name The name of the extension. + * + * @param data The data of the extension. + * + * @return 1 if supported, 0 if not. + * + * Example: + * + * @code + * sftp_extension_supported(sftp, "statvfs@openssh.com", "2"); + * @endcode + */ +LIBSSH_API int sftp_extension_supported(sftp_session sftp, const char *name, + const char *data); + +/** + * @brief Open a directory used to obtain directory entries. + * + * @param session The sftp session handle to open the directory. + * @param path The path of the directory to open. + * + * @return A sftp directory handle or NULL on error with ssh and + * sftp error set. + * + * @see sftp_readdir + * @see sftp_closedir + */ +LIBSSH_API sftp_dir sftp_opendir(sftp_session session, const char *path); + +/** + * @brief Get a single file attributes structure of a directory. + * + * @param session The sftp session handle to read the directory entry. + * @param dir The opened sftp directory handle to read from. + * + * @return A file attribute structure or NULL at the end of the + * directory. + * + * @see sftp_opendir() + * @see sftp_attribute_free() + * @see sftp_closedir() + */ +LIBSSH_API sftp_attributes sftp_readdir(sftp_session session, sftp_dir dir); + +/** + * @brief Tell if the directory has reached EOF (End Of File). + * + * @param dir The sftp directory handle. + * + * @return 1 if the directory is EOF, 0 if not. + * + * @see sftp_readdir() + */ +LIBSSH_API int sftp_dir_eof(sftp_dir dir); + +/** + * @brief Get information about a file or directory. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_stat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory. + * + * Identical to sftp_stat, but if the file or directory is a symbolic link, + * then the link itself is stated, not the file that it refers to. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_lstat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory from a file handle. + * + * @param file The sftp file handle to get the stat information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_fstat(sftp_file file); + +/** + * @brief Free a sftp attribute structure. + * + * @param file The sftp attribute structure to free. + */ +LIBSSH_API void sftp_attributes_free(sftp_attributes file); + +/** + * @brief Close a directory handle opened by sftp_opendir(). + * + * @param dir The sftp directory handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occured. + */ +LIBSSH_API int sftp_closedir(sftp_dir dir); + +/** + * @brief Close an open file handle. + * + * @param file The open sftp file handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occured. + * + * @see sftp_open() + */ +LIBSSH_API int sftp_close(sftp_file file); + +/** + * @brief Open a file on the server. + * + * @param session The sftp session handle. + * + * @param file The file to be opened. + * + * @param accesstype Is one of O_RDONLY, O_WRONLY or O_RDWR which request + * opening the file read-only,write-only or read/write. + * Acesss may also be bitwise-or'd with one or more of + * the following: + * O_CREAT - If the file does not exist it will be + * created. + * O_EXCL - When used with O_CREAT, if the file already + * exists it is an error and the open will fail. + * O_TRUNC - If the file already exists it will be + * truncated. + * + * @param mode Mode specifies the permissions to use if a new file is + * created. It is modified by the process's umask in + * the usual way: The permissions of the created file are + * (mode & ~umask) + * + * @return A sftp file handle, NULL on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_file sftp_open(sftp_session session, const char *file, int accesstype, + mode_t mode); + +/** + * @brief Make the sftp communication for this file handle non blocking. + * + * @param[in] handle The file handle to set non blocking. + */ +LIBSSH_API void sftp_file_set_nonblocking(sftp_file handle); + +/** + * @brief Make the sftp communication for this file handle blocking. + * + * @param[in] handle The file handle to set blocking. + */ +LIBSSH_API void sftp_file_set_blocking(sftp_file handle); + +/** + * @brief Read from a file using an opened sftp file handle. + * + * @param file The opened sftp file handle to be read from. + * + * @param buf Pointer to buffer to recieve read data. + * + * @param count Size of the buffer in bytes. + * + * @return Number of bytes written, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API ssize_t sftp_read(sftp_file file, void *buf, size_t count); + +/** + * @brief Start an asynchronous read from a file using an opened sftp file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous read. To do so, you must call 2 functions: + * + * sftp_async_read_begin() and sftp_async_read(). + * + * The first step is to call sftp_async_read_begin(). This function returns a + * request identifier. The second step is to call sftp_async_read() using the + * returned identifier. + * + * @param file The opened sftp file handle to be read from. + * + * @param len Size to read in bytes. + * + * @return An identifier corresponding to the sent request, < 0 on + * error. + * + * @warning When calling this function, the internal offset is + * updated corresponding to the len parameter. + * + * @warning A call to sftp_async_read_begin() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_async_read() is called. + * Not calling sftp_async_read() will lead to memory + * leaks. + * + * @see sftp_async_read() + * @see sftp_open() + */ +LIBSSH_API int sftp_async_read_begin(sftp_file file, uint32_t len); + +/** + * @brief Wait for an asynchronous read to complete and save the data. + * + * @param file The opened sftp file handle to be read from. + * + * @param data Pointer to buffer to recieve read data. + * + * @param len Size of the buffer in bytes. It should be bigger or + * equal to the length parameter of the + * sftp_async_read_begin() call. + * + * @param id The identifier returned by the sftp_async_read_begin() + * function. + * + * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error + * occured, SSH_AGAIN if the file is opened in nonblocking + * mode and the request hasn't been executed yet. + * + * @warning A call to this function with an invalid identifier + * will never return. + * + * @see sftp_async_read_begin() + */ +LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_t id); + +/** + * @brief Write to a file using an opened sftp file handle. + * + * @param file Open sftp file handle to write to. + * + * @param buf Pointer to buffer to write data. + * + * @param count Size of buffer in bytes. + * + * @return Number of bytes written, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_open() + * @see sftp_read() + * @see sftp_close() + */ +LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count); + +/** + * @brief Seek to a specific location in a file. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek(sftp_file file, uint32_t new_offset); + +/** + * @brief Seek to a specific location in a file. This is the + * 64bit version. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek64(sftp_file file, uint64_t new_offset); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. < 0 on + * error. + */ +LIBSSH_API unsigned long sftp_tell(sftp_file file); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. < 0 on + * error. + */ +LIBSSH_API uint64_t sftp_tell64(sftp_file file); + +/** + * @brief Rewinds the position of the file pointer to the beginning of the + * file. + * + * @param file Open sftp file handle. + */ +LIBSSH_API void sftp_rewind(sftp_file file); + +/** + * @brief Unlink (delete) a file. + * + * @param sftp The sftp session handle. + * + * @param file The file to unlink/delete. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_unlink(sftp_session sftp, const char *file); + +/** + * @brief Remove a directoy. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to remove. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rmdir(sftp_session sftp, const char *directory); + +/** + * @brief Create a directory. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to create. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode); + +/** + * @brief Rename or move a file or directory. + * + * @param sftp The sftp session handle. + * + * @param original The original url (source url) of file or directory to + * be moved. + * + * @param newname The new url (destination url) of the file or directory + * after the move. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rename(sftp_session sftp, const char *original, const char *newname); + +/** + * @brief Set file attributes on a file, directory or symbolic link. + * + * @param sftp The sftp session handle. + * + * @param file The file which attributes should be changed. + * + * @param attr The file attributes structure with the attributes set + * which should be changed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr); + +/** + * @brief Change the file owner and group + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param owner The new owner which should be set. + * + * @param group The new group which should be set. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group); + +/** + * @brief Change permissions of a file + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chmod(sftp_session sftp, const char *file, mode_t mode); + +/** + * @brief Change the last modification and access time of a file. + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param times A timeval structure which contains the desired access + * and modification time. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_utimes(sftp_session sftp, const char *file, const struct timeval *times); + +/** + * @brief Create a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param target Specifies the target of the symlink. + * + * @param dest Specifies the path name of the symlink to be created. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_symlink(sftp_session sftp, const char *target, const char *dest); + +/** + * @brief Read the value of a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param path Specifies the path name of the symlink to be read. + * + * @return The target of the link, NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API char *sftp_readlink(sftp_session sftp, const char *path); + +/** + * @brief Get information about a mounted file system. + * + * @param sftp The sftp session handle. + * + * @param path The pathname of any file within the mounted file system. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path); + +/** + * @brief Get information about a mounted file system. + * + * @param file An opened file. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_fstatvfs(sftp_file file); + +/** + * @brief Free the memory of an allocated statvfs. + * + * @param statvfs_o The statvfs to free. + */ +LIBSSH_API void sftp_statvfs_free(sftp_statvfs_t statvfs_o); + +/** + * @brief Synchronize a file's in-core state with storage device + * + * This calls the "fsync@openssh.com" extention. You should check if the + * extensions is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, "fsync@openssh.com", "1"); + * @endcode + * + * @param file The opened sftp file handle to sync + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + */ +LIBSSH_API int sftp_fsync(sftp_file file); + +/** + * @brief Canonicalize a sftp path. + * + * @param sftp The sftp session handle. + * + * @param path The path to be canonicalized. + * + * @return A pointer to the newly allocated canonicalized path, + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_canonicalize_path(sftp_session sftp, const char *path); + +/** + * @brief Get the version of the SFTP protocol supported by the server + * + * @param sftp The sftp session handle. + * + * @return The server version. + */ +LIBSSH_API int sftp_server_version(sftp_session sftp); + +#ifdef WITH_SERVER +/** + * @brief Create a new sftp server session. + * + * @param session The ssh session to use. + * + * @param chan The ssh channel to use. + * + * @return A new sftp server session. + */ +LIBSSH_API sftp_session sftp_server_new(ssh_session session, ssh_channel chan); + +/** + * @brief Intialize the sftp server. + * + * @param sftp The sftp session to init. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_server_init(sftp_session sftp); +#endif /* WITH_SERVER */ + +/* this is not a public interface */ +#define SFTP_HANDLES 256 +sftp_packet sftp_packet_read(sftp_session sftp); +int sftp_packet_write(sftp_session sftp,uint8_t type, ssh_buffer payload); +void sftp_packet_free(sftp_packet packet); +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr); +sftp_attributes sftp_parse_attr(sftp_session session, ssh_buffer buf,int expectname); +/* sftpserver.c */ + +LIBSSH_API sftp_client_message sftp_get_client_message(sftp_session sftp); +LIBSSH_API void sftp_client_message_free(sftp_client_message msg); +LIBSSH_API uint8_t sftp_client_message_get_type(sftp_client_message msg); +LIBSSH_API const char *sftp_client_message_get_filename(sftp_client_message msg); +LIBSSH_API void sftp_client_message_set_filename(sftp_client_message msg, const char *newname); +LIBSSH_API const char *sftp_client_message_get_data(sftp_client_message msg); +LIBSSH_API uint32_t sftp_client_message_get_flags(sftp_client_message msg); +LIBSSH_API const char *sftp_client_message_get_submessage(sftp_client_message msg); +LIBSSH_API int sftp_send_client_message(sftp_session sftp, sftp_client_message msg); +LIBSSH_API int sftp_reply_name(sftp_client_message msg, const char *name, + sftp_attributes attr); +LIBSSH_API int sftp_reply_handle(sftp_client_message msg, ssh_string handle); +LIBSSH_API ssh_string sftp_handle_alloc(sftp_session sftp, void *info); +LIBSSH_API int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr); +LIBSSH_API void *sftp_handle(sftp_session sftp, ssh_string handle); +LIBSSH_API int sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message); +LIBSSH_API int sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr); +LIBSSH_API int sftp_reply_names(sftp_client_message msg); +LIBSSH_API int sftp_reply_data(sftp_client_message msg, const void *data, int len); +LIBSSH_API void sftp_handle_remove(sftp_session sftp, void *handle); + +/* SFTP commands and constants */ +#define SSH_FXP_INIT 1 +#define SSH_FXP_VERSION 2 +#define SSH_FXP_OPEN 3 +#define SSH_FXP_CLOSE 4 +#define SSH_FXP_READ 5 +#define SSH_FXP_WRITE 6 +#define SSH_FXP_LSTAT 7 +#define SSH_FXP_FSTAT 8 +#define SSH_FXP_SETSTAT 9 +#define SSH_FXP_FSETSTAT 10 +#define SSH_FXP_OPENDIR 11 +#define SSH_FXP_READDIR 12 +#define SSH_FXP_REMOVE 13 +#define SSH_FXP_MKDIR 14 +#define SSH_FXP_RMDIR 15 +#define SSH_FXP_REALPATH 16 +#define SSH_FXP_STAT 17 +#define SSH_FXP_RENAME 18 +#define SSH_FXP_READLINK 19 +#define SSH_FXP_SYMLINK 20 + +#define SSH_FXP_STATUS 101 +#define SSH_FXP_HANDLE 102 +#define SSH_FXP_DATA 103 +#define SSH_FXP_NAME 104 +#define SSH_FXP_ATTRS 105 + +#define SSH_FXP_EXTENDED 200 +#define SSH_FXP_EXTENDED_REPLY 201 + +/* attributes */ +/* sftp draft is completely braindead : version 3 and 4 have different flags for same constants */ +/* and even worst, version 4 has same flag for 2 different constants */ +/* follow up : i won't develop any sftp4 compliant library before having a clarification */ + +#define SSH_FILEXFER_ATTR_SIZE 0x00000001 +#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 +#define SSH_FILEXFER_ATTR_ACCESSTIME 0x00000008 +#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 +#define SSH_FILEXFER_ATTR_CREATETIME 0x00000010 +#define SSH_FILEXFER_ATTR_MODIFYTIME 0x00000020 +#define SSH_FILEXFER_ATTR_ACL 0x00000040 +#define SSH_FILEXFER_ATTR_OWNERGROUP 0x00000080 +#define SSH_FILEXFER_ATTR_SUBSECOND_TIMES 0x00000100 +#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 +#define SSH_FILEXFER_ATTR_UIDGID 0x00000002 + +/* types */ +#define SSH_FILEXFER_TYPE_REGULAR 1 +#define SSH_FILEXFER_TYPE_DIRECTORY 2 +#define SSH_FILEXFER_TYPE_SYMLINK 3 +#define SSH_FILEXFER_TYPE_SPECIAL 4 +#define SSH_FILEXFER_TYPE_UNKNOWN 5 + +/** + * @name Server responses + * + * @brief Responses returned by the sftp server. + * @{ + */ + +/** No error */ +#define SSH_FX_OK 0 +/** End-of-file encountered */ +#define SSH_FX_EOF 1 +/** File doesn't exist */ +#define SSH_FX_NO_SUCH_FILE 2 +/** Permission denied */ +#define SSH_FX_PERMISSION_DENIED 3 +/** Generic failure */ +#define SSH_FX_FAILURE 4 +/** Garbage received from server */ +#define SSH_FX_BAD_MESSAGE 5 +/** No connection has been set up */ +#define SSH_FX_NO_CONNECTION 6 +/** There was a connection, but we lost it */ +#define SSH_FX_CONNECTION_LOST 7 +/** Operation not supported by the server */ +#define SSH_FX_OP_UNSUPPORTED 8 +/** Invalid file handle */ +#define SSH_FX_INVALID_HANDLE 9 +/** No such file or directory path exists */ +#define SSH_FX_NO_SUCH_PATH 10 +/** An attempt to create an already existing file or directory has been made */ +#define SSH_FX_FILE_ALREADY_EXISTS 11 +/** We are trying to write on a write-protected filesystem */ +#define SSH_FX_WRITE_PROTECT 12 +/** No media in remote drive */ +#define SSH_FX_NO_MEDIA 13 + +/** @} */ + +/* file flags */ +#define SSH_FXF_READ 0x01 +#define SSH_FXF_WRITE 0x02 +#define SSH_FXF_APPEND 0x04 +#define SSH_FXF_CREAT 0x08 +#define SSH_FXF_TRUNC 0x10 +#define SSH_FXF_EXCL 0x20 +#define SSH_FXF_TEXT 0x40 + +/* file type flags */ +#define SSH_S_IFMT 00170000 +#define SSH_S_IFSOCK 0140000 +#define SSH_S_IFLNK 0120000 +#define SSH_S_IFREG 0100000 +#define SSH_S_IFBLK 0060000 +#define SSH_S_IFDIR 0040000 +#define SSH_S_IFCHR 0020000 +#define SSH_S_IFIFO 0010000 + +/* rename flags */ +#define SSH_FXF_RENAME_OVERWRITE 0x00000001 +#define SSH_FXF_RENAME_ATOMIC 0x00000002 +#define SSH_FXF_RENAME_NATIVE 0x00000004 + +#define SFTP_OPEN SSH_FXP_OPEN +#define SFTP_CLOSE SSH_FXP_CLOSE +#define SFTP_READ SSH_FXP_READ +#define SFTP_WRITE SSH_FXP_WRITE +#define SFTP_LSTAT SSH_FXP_LSTAT +#define SFTP_FSTAT SSH_FXP_FSTAT +#define SFTP_SETSTAT SSH_FXP_SETSTAT +#define SFTP_FSETSTAT SSH_FXP_FSETSTAT +#define SFTP_OPENDIR SSH_FXP_OPENDIR +#define SFTP_READDIR SSH_FXP_READDIR +#define SFTP_REMOVE SSH_FXP_REMOVE +#define SFTP_MKDIR SSH_FXP_MKDIR +#define SFTP_RMDIR SSH_FXP_RMDIR +#define SFTP_REALPATH SSH_FXP_REALPATH +#define SFTP_STAT SSH_FXP_STAT +#define SFTP_RENAME SSH_FXP_RENAME +#define SFTP_READLINK SSH_FXP_READLINK +#define SFTP_SYMLINK SSH_FXP_SYMLINK +#define SFTP_EXTENDED SSH_FXP_EXTENDED + +/* openssh flags */ +#define SSH_FXE_STATVFS_ST_RDONLY 0x1 /* read-only */ +#define SSH_FXE_STATVFS_ST_NOSUID 0x2 /* no setuid */ + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_H */ + +/** @} */ diff --git a/build/libother/include/libssh/ssh2.h b/build/libother/include/libssh/ssh2.h new file mode 100755 index 0000000..3521433 --- /dev/null +++ b/build/libother/include/libssh/ssh2.h @@ -0,0 +1,81 @@ +#ifndef __SSH2_H +#define __SSH2_H + +#define SSH2_MSG_DISCONNECT 1 +#define SSH2_MSG_IGNORE 2 +#define SSH2_MSG_UNIMPLEMENTED 3 +#define SSH2_MSG_DEBUG 4 +#define SSH2_MSG_SERVICE_REQUEST 5 +#define SSH2_MSG_SERVICE_ACCEPT 6 +#define SSH2_MSG_EXT_INFO 7 + +#define SSH2_MSG_KEXINIT 20 +#define SSH2_MSG_NEWKEYS 21 + +#define SSH2_MSG_KEXDH_INIT 30 +#define SSH2_MSG_KEXDH_REPLY 31 +#define SSH2_MSG_KEX_ECDH_INIT 30 +#define SSH2_MSG_KEX_ECDH_REPLY 31 +#define SSH2_MSG_ECMQV_INIT 30 +#define SSH2_MSG_ECMQV_REPLY 31 + +#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#define SSH2_MSG_KEX_DH_GEX_GROUP 31 +#define SSH2_MSG_KEX_DH_GEX_INIT 32 +#define SSH2_MSG_KEX_DH_GEX_REPLY 33 +#define SSH2_MSG_KEX_DH_GEX_REQUEST 34 +#define SSH2_MSG_USERAUTH_REQUEST 50 +#define SSH2_MSG_USERAUTH_FAILURE 51 +#define SSH2_MSG_USERAUTH_SUCCESS 52 +#define SSH2_MSG_USERAUTH_BANNER 53 +#define SSH2_MSG_USERAUTH_PK_OK 60 +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 +#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 +#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 +#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 +#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 +#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 + +#define SSH2_MSG_GLOBAL_REQUEST 80 +#define SSH2_MSG_REQUEST_SUCCESS 81 +#define SSH2_MSG_REQUEST_FAILURE 82 +#define SSH2_MSG_CHANNEL_OPEN 90 +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH2_MSG_CHANNEL_DATA 94 +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH2_MSG_CHANNEL_EOF 96 +#define SSH2_MSG_CHANNEL_CLOSE 97 +#define SSH2_MSG_CHANNEL_REQUEST 98 +#define SSH2_MSG_CHANNEL_SUCCESS 99 +#define SSH2_MSG_CHANNEL_FAILURE 100 + +#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 +#define SSH2_DISCONNECT_RESERVED 4 +#define SSH2_DISCONNECT_MAC_ERROR 5 +#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH2_DISCONNECT_CONNECTION_LOST 10 +#define SSH2_DISCONNECT_BY_APPLICATION 11 +#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH2_OPEN_CONNECT_FAILED 2 +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 + +#define SSH2_EXTENDED_DATA_STDERR 1 + +#endif diff --git a/build/libother/libssh.a b/build/libother/libssh.a new file mode 100644 index 0000000000000000000000000000000000000000..606bd01c7475230acaceb0716ead02d570d10ece GIT binary patch literal 1143086 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 hw|4H>2JLEi14nV)^ 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
E=s9uBhrsg@#TX46!d3& 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 z z%f{D@aj-uw_BYYLD`lK~XJjqJIsy0{&`((Zcc`o6nHyt0^C;+w-Dlh0D^7N-w(uq|B7ZAR#CW8MQg+7d2)>!_m(e_WjRqF*X&_srgK>$|TT7h8A=Z9)xg z3j8#Err&LKMVo7VH}+e(?CYG}^aID&CjCd$dGu{S8)kkX{jdY5|0hxZPoe(5hWh_H z?AaUW=bYSC)brZ?pXzyIFY7++*%Gm5&!A7kzD=ZHC)Q^S*}uu4t+RccF#0#w_EZ&z z?AjIdBNDJ>(EXxL%%M{^8R$D5Hf7Z6e@V>5m-Puq8G{Y{(OBpm{Rgy9_aNwhba z%KWy5{Kz+rxD0>vTi%qm#HZvp1#kHb8H=?p9J!o zF>T4=aMoYq`aP!n!jv;p_Ni0yPTyec{jtn$&&)5JZ}FELry_s8qcowV+m7|YU#GUzv5Dw+hl{`X zL$vdL+Yoc~W&J(YE~xLAHjZ*LFWDC^L0K_=4%Ee|xIc+DJc2SqTa>B9xNEz&QRVAK z+`U-urF~NRc}v93kY*2_Av5}}oa3?m);*AM3)Vy4i~6}`xE&Av^DJ*Y#@f~YJO=(A zJNIn!9&NTePK#O_*mX0`)9uCD1dQ{?q`{AUeB7`2jcE(S){8IW>?Z4DWj})FXsB;O z#sJxLv)wRh#~1*9ic&KUKpwLH(uuHxBMLA3E^JT|^`2|*SZA>}tNKJ_JkrO$Yq4p! zm|mpa%I|9vJUOF;y77nWHfKi^^IJKl9iz{bblEl%-waB?M>YBR(E0@Olxe5Embu*0 zg9x{MaBwf%O`Mfc%{g=Q2jSB#F5QZE$QQ$x@{y(l`tLSnhjY+QT|Sjxmmfhm%!3oY zxuKYpVf8<=&jQ_;`Kcg&nX$36-H-k6W8I71{7uMP^C0QHmy_83fjT!K`RP8y;g+qw z3u9+X_@xNZ%Ld;2=%j_9W)x z5}bd-I<^Gn-Z+*uGC`g`Upd$K6I(e4P~Kzf?O&NbLt%XFnfzH39b67m{Cud`1eKBa0;FUzX$?ysI*O&&6I^6NX zv0~Vy)38Zib!EJFy47t0$3#K5cX@@$PbY^PJ>EqfhrNvt#tM(`FWy#sKV?p=2=|nu z+`$Jr@K8AX7S1WG-a@?~os>ODADo|Y^+5f9LB8R{`hgrX>81T7pWipVHQ~yhN~Du> z3h*NgRGg`JvE)qoi#QMct&-^0{mGHA?XZWZVawS@okTmzc<7U~w#~+C`nH&dvS~rT z7Iij+=gc>&;H!!BaQ#+a-#y1BV?OC#*tu+c?|kFMlKw3=z8zclp8{Rhk8b=E9?rR9 z+d{iyb(KIl(zy+>0VeI~NdE%XC6u{rn|#g~X`y-(eX9Y#%nO|>CbkXshJN8}JM7j8v@h8@D*X@i#j@w=z`uE{8|x82fKM}2 z-`4*y($)8jor?-f{7LwBk78`jc~RJhMM>Do$!8+(UT^Gz-N#66rz2Mc{}6Yu(jpLe``nb{akHGD=NZ#SH~<*U7x<|Me5P3 zZ+jv!&RfX&v%$*9kiJN}Ry=RJkZT;4U?K^Ru zlh&@=Dxcax)jj3h3i(Kfzajf|v26cD)>^?%>H1dJj&kdM7tk~7Ntu`V;j{H|eaQIMtv=XiW|)Vk46+@+l%l_zd!b9t(b@RutCQl=la@-v5%tA8QY!2o$dYF zE<6;zXzjg78`6&afj&^5v|Zp@$lP}r|JwKhCa1mi1rx5nvlnaZVAGBy(LPPS7v~wG zeJn?uSv!TXdKhU@zCXF^8@nj8mQ(J{AZ`0E=5MDBWZZq6b2M#_9S@Txar@%_Nq7(2 zW#)B%g|uPVb^ z7_z({nOMs8PVvY^YkTpm@iXp^{`RVcTx-C*KY?GS*B^f;@C|5ZzKLJAZ!YDhG&^^X zHBl@_u_)WkrDLSaP74%qfug)Yo4My{0XSZ|88Vuj#k#sMQ7idUhOceVI!0 z6xNsMy`~%WEzeh)tJ}HtSn3C7nc%oC3VV${2-kF(JyDm_hG3qb{qm6Q-|(A7n1e^|XX6LN`@`-!AC^O|1z=p)58=}Z_w?V4v5T}j>>KhMO!y9OX$a}jwSm?TJRaW{vUv`9OdkSu zr24(Dz`jlN#zZl<-#z&@edURIpfxdQcLpznO0cf#CeSpnrW z@;{>;P}v_rdK35~-+PME6Umot{y|9t{IEy<-j=!i{tfmO>00VhlkUm;wzc24^gh3B z9Vs}(Hs9Kzu=xh^M>Ub1TvKCwP0dBd*K|5E4CQzt)~#(dHmMh557iNWUkAO8GhQ?P zd}|LNb9TJup*&js!I#p!Ja0^tKzl_#KSueZPmb|~F4=mlucpMzIUsM}{Sf_!UaTGV zMiq~l9F46{qpauRoCx{Ojg#X)?hB$1t`}zxGmbRcC*Du?E?V1(JU9xTF?^eNiRaxO zVxA!0amZ(`G4IFtWS}OBJ$rWVT_4h!fR9Au(y=c2K8HA5dD!+a9l5Yvk81gtF$C(7 zOV4#RnN&_j-c!at^kMqe=44Fb`Ntf0f|st9ioHK5&vu@>_q4HLrd*Sb@@4Qn?QX=u zaxv6*vvc3fw(Akt&td&!p0Vsm{mhmZo{MbC2*z!wpT|B#Sz>)iPet!KgX(=$iFxF4bqgA1- zv0uizO8b3qeCLXfF=qoFCNJps#eH!G_89%WAN9g+XHXtJUDpcxjIs{9p0<0OrA<8z zdiH17*EoW;Ec^PkAXT+kRtD zF0nQWd!~^W7^_HGNkgXC+D&GEJ=*SSwB4U#A7}A%-ly1JmEf2CNANZMlMLkk71|ln zJ#^>%er?;>AJKJv-B_>b721AE`u0Wr_ML4f*`6ug>nKC0m)l=6^4K=91mUP(tuM4I z)HD4ark#Y2v$~ZuaPPI>_roHZxhDC&nJoQwi)lybC+SQJcQQ@UN@> zh@&hZKhrB>$VaAuI!MAl!gk+7x#j-I$pzzDJM-+5NvFw!2>4QN`e(?G;kizQzswWX zrDriNth;^HLeP{(z_X^K?ui=ohrWV(UvvAEg-f1*-`%UavoFtvj5X&UoIstW|0sD_ z`qkJU)&N<4hBB0#wB^Nz>ysUQ-h?Z+Y!0pO_J#jPgeym1@~Lp>t)uV-B_P9O#8nQu zEr_=oy1;nFl+Wxq#H?p|R@P|b&iuB05NkV_za>Zq>sUYR1jkh$v5r9p?Dz8gbz@6p zUY5F`Zf*Z5*SAa*dGizDi?*+VjzXGOpauWZ^o8hSj%FX{qU~RUoN=_*U&SxSVIBBo ze+d4LBhaB)%gH!Urw*BQuAj5@oOzhwI#1010<2l&*OH5hNwMOh`F%GQ@xJUlOuKDJKJ=xb#{r?Q^#XQqt&bxmAc_>TYQ}7o& zW6KEjk>EO0`Wi>5Z^-J2Wd`Z9{SCZ(4|0x0IuaSrVF=i{FGT8aBf$csBZ7|gB}eq6$GOw9J0zZ`qHdj6g~v%g)2^nM?|ut#~e zKG$#do%5dc|3W8bzCOKTRIaZ#(D~^tSRatucKr|8 zezKY0we2L!xS5~CJQC{iu|f2k6KFe_XC=@X^61#fyRn`XdpM};KIjf}LIW}AFA4i; z%k;T(P2H$}q{pAJA?#mUAFf>|KX`W-KG$&1S@3@E=18{PX5LUHQ@3uk?KJBzc}N+7j2%9i^CQ!CsVtCpC+hjm!7T+n z{H|yQbX)EU^=!E-+_Qz>jX*pZtOHD-ER;gmiBkB`M?fc2=u3x=e$TkLt|l=TVWIQ? zEU#mx?AZ3l!+xK%(Tk3Qkq0;W{PvOuXE#+~?_wqT*gY#CS-haaU1{-FfIA@2tGt_1d;=fh}QseL^=hZrigZKZAJXp;`39hv8> zd1HTo_Snb?yVwOAJP&2WuH#|K@9AT^svTXWdW8 zjCpAX$09vTAb$dNbMiKCeXrGnsT)z?2!HH+lsDGjto;=q3F}}KeJ5={I#m{DJRENc zt$!FiSK)jM?hz};J$bFdx}|V^$wKN3^JL~-)r8oco#>N4jXsmhllq`vpW!AW%nuQd z^)2Sor2S8(sTgU(JcDU7SZ{N_1FOF@^v5$8mYlYG*2p&seeDX5-A>-BPo0m34p#76 zDfDxzPuuQ`E;W6}ug?767fUX(m4)sN_uJrv_=c4T1>eHyBlwr$a_Pc0{53a~OgLNmEOXx%0 zMHw@4bKH|z8yh$ZJu1e$WLOV6=CA8V;FzN1or1i1qEnEY{lJ5tH##-?w&By?vE>W4 z>Bm8~$@=<{*6bXIo7Q5alk*ntTwFQgVi}-sM(Zkd!nBm5eF&EQ+&Q9s&%fsIKD$oG=$ico z(zE<(y;T1Sa9fTculj7Jf={^I%DM$>H2>7>>9qMuzvW=$f@~Z6{;dV5KPF${%d+R2 zoJP4v9;1&t%*+!FtR0TN?yyqZ#~p$GZo#3CBO?y=T?bu%x19aRfxd@A_(ebAd)|@3 zuXqC^;QRe&v?Zr7ACN@&u}I~o-aco5#9Ghr=x__+4b=x~N-mR#=v=Jw+!!O!%%2;u2=Q;ZgsoTtm z^NfExJObyQ;Tt-c5!d&WAb&OfynMLM=EKhs_oT?=R-T80{`|Y#Yqtg8gZBc$ZACct z9j8P%-wykXHZbbY`gM44Y3GVR2mL1WM_Kk0ktHuh5w`DScxT@irJsay*!M-uWuZ<} zZ>+~k7^XS9h3?9yem%OwaaqbIGxRP;we6j$04K6t}EM|RtVYQQJ zWBLBK@t^XA&J~$vuZMZWGrcGi>rIIxD>2#}l~40d`KsP&bDjo2(!T_L94Ap$KGQak zuLpZz#uGobwrulMoD=rL80WN(nRxI$D~(I}Q|B5d!V%aogaQTyz^nipFv&o)Zcj7 z;e)b!LR_ET-6-Gs{uANwNexBMu(zVGtYbv3OYeq$t>1-yJi89@O2~?P_`pSx%>nHpTL!F7FwfCLT?XYz75JI_zrp4S`xQo)Ja10o z;xo%YAL>&^+GNdZ=JBK5#@}Tb}~0-NB$3gLjF%L z?_BXFd17pq5P2zM6!WZ@cRrFv-l@!;j0ZCB1J;Fi#Np!}I1N8K^}_LlTTWF^Zh1R+ zlhw@;#JLqbxF3RN`)M5I5{L3fA0~TV2jlSDo{ANyyRe;FH^}E8dBeZy=dC*LqW>Fo zzl-+?;r%b*w-EYfdGSO?)N$u0)$z@pE4~dLC%(dReeogMqpfJ8C_^7G{O%~mAAWbD zKmIQJ7F}VSA*$sAdd;A1fV=}tEA_zkN_9P?o~x~%GdKe)j<^|@*-wG`t?f85@H~`$ zWzft+@cxt9jqsNP>;~#upPUuJItV+w0%iXg`D%Y_<+U|8N7p5rHb&Rh zZA^JBb*(idsq1J-Hl-Sxo1%9#wADAaw@HA;hE$tZ*R-LbwYkag+K_B*NUmzEi(WcC zb!ikartrOvXia0Y`MvZTmwI>BwKiYgSl6_stv)(bNV1E9O?29(>8Wo->yxQyQ!{|Z z_BzjqQ7dmQ|GbGr6N)d1;Ketb$9Vg~==AAcLzDU32a~xjX}$nYy^wtR^vkISuf1t) zQ}Z27Ofsb&Y%!I#x;8q!HrkMi z-Wb2(#v3Y@-B|fW!SHF8Hima!I6Q79kD1O}A3q#2?z|M9HT>Yo?`C>eE>6pJOIht&g+|RjIX%nmYc6Buc}%R zHL5a714Wb^@B`&tzdT-jjE+#oT z!^tFmG0H%yy``nOwXLpJxvoyGYiLA?s715eR@364R(O!Ev9bM*>9gmw*W$jt9*ExZ zc^xU*7ia(TZk?X`!^L3158c!^r`qC-V{tT@YC#sZtu~tQy+eUoe1$j5OV-vVTi2vO z+`6XS)Gk)e#^$Cqo>X7F_+4*Ne37EIX07p2tqurFy?_2{Ch!W+yQ@7_*V=@$>p?8B z+|V3Nwb#@~`G&7tEhOZvnO0;lX^=tc|1i;`2XsMq|06Y#yt> zw7ly272XwX>sqdW-Mf6Hb3w{;v9`K(uzziJQK{aPK2?vL@uUE@FilgYr>0~RX3XR* zO${B<+U9l1h9)T`o-Lmc!`7nqrpAV*wKJxtSm!KMlWbx=Ue(ZKGQzM%0j5Uf70oDg z1M}*>B+U?o{L2e&ykXf_-70eZa+GNc8?HvjZEYL9WFx*7Rl6}-o2*;c+|+O<8Ubk* zSmfFlddXC(ZXL|Ex6XW2O3mE#RP>HyLt9IOscO(5WJMd$z{&w3Qw?kINhg*Y`^IMZ zOeizi)(+ZQl+R?NBt>`{!9WfpNo6Ea9@n%t!pyHm+vKfEg7Fh19{6XY7t- zy4o9bCF*K6*#GO0hl`ONRIsUtD4t?*nZ1kI|1c(y9i^J~)>ih2+8a#i=jT9@kj|BIZwPtI%d3}qXs%ay z%Wk~xhVn06<1M`r|1B%O;X0!YSDj>=RVg3%S-CZ>&Fw8IsDEa3db{m<@D=J%Yh7v$ z+m3dz1`0x%IWR8~ttQIVwTx5(AyKyZX=-k&^OCFQ%;A4?`QJSJHX-~md*2OQnT?l2Y++%$LBKq&BxyY{9WnQt@dD>8k_IHhvbqQy|#KJY)w6L z2p@YvH@Bg&0Y-L1ZF3_GSzP+caob6yeU3xIRcyQ1yKo=vgXPNm);g3gH21A06kC*d zTQhp!Zl6KB1Blyn-BCpGIlRW&I*rg5zdnj|A{Cl}X11d7HgxszixP*A{WUegTzc!) zvRRC`v^KXjL*j-G^WDKZSY^}586&u1L32~%M$c(bAjgJywjHsJM4Vkz(&brCRK&J; zvb8n2F`jHee%8)qYZSMwiI-}tU0sV$_H8hohE)41$Wc?5N?~*+*Wlpb7QN^14EF)| zvwhDCea}mM&&z$!n6u8i>hqpFuf_M=;d|cZd*-0oz4o~eL)=GwSj_jF@I9w}&)vS~ zUf*-Z_xyzK8DsCft0?a&_l*{Xu~sMVLjDXI;Qxbf9G8L71mQk|g2^|Iuh~`DHG^)ah?wZ>*=bF8<_FaD8%>AFezx2Rmk?TXhe)XST$$d}R!xCoC z>%|{0Q@)mtE_xvRVCb4xe?6KQb(rA|F3QGh8vah3JI`Cj-}2G=J>#y>b^N07aeh|W zlxO`uqoied1wU$B!NgQDF%^5m4I_X3>OW>b*gO;LmPJTU6P^DB0ZbiS>9 zU%~!JxZeHJH_O3F3D?ii@}2KWmH?ysS`@TwF)(Fqo6x)RXuVlZYFjSX$( zZXC>79N3==Vs)wCW^{FlwMgb#YQt+iKpn-W#c?qYqLTSqSE-XS!D3b?rW{u0mBWhr z!W$uhV;yEAT%&~RaKcs4mShWYxY}QSyCqDm4bvNbV8(-&R_^*t*L2d_0z3`8pA))z zOZP&!oP-sI@D?fQq8;V1+g$Cw_ zfuK3-&jOcwvUc`1;bH73Y(@Bi&pbH2V%M~;sbQKmV|*y{Jwi9i#AwUiz9F#3R3?Y6 zcL=)ULtt+Xft?wGUlBI(@KRZ#xSu9i6<<`?_M&Q6_zRM49%+^e%^A{^kIvZ>4Aa(QE#@wJHHB~uuqcwclLiI7+P@7io$f6>jOGNHMOG;X{;8*-b_J!;eSLa6+K z2@j61*fp-}qRQRHd&XAn9TR>q+kvoslpPXA%F*&`+2??qmF#w`N9Vy-8=HQiz1J_t zALi1}GIdJmx*WP|X!D%@P7LoxjU)jkNUXdq4~%~Bf{Ocw?;lpNw{YKxs$HYH&fh(9 z&v}*i7aWKr5rx|uWB-r(SS~zbPF_~BNV05Juv}KM!Ms0wAQb-Nd=@56J0$#V6hz9G zcAsvNqQJ8JgwRRW8awyAV5V$Vj5c=8ghj*4q)Iue8tuJ!{2zY(stMykZeE*h`Io*5 z8>K3?XsC62hJ|lNCcSJ632i``gu&b&?Lnrz{DA|758hu`v3psQ^r>ukm;d|tsl()xmJV@&hHhNfz7@xHN*PAz7-9+xoh zh`ialQb7e*Qo+|)1y?hNa#5C_AO5l*0YBP+f>HPnSBvGDs~=VD=I#xfR$1yu40L5e zkNHT-oen)v^x%aRyGD0iaP96SE2CS@WAxhS|rSIGVzsfx6?KguMY@q zi$iO=kL*X@!UApV7OS4g$LEFqw54BOo*l24!B^$pa4KtUn9egoUrc&jOk0t$)G|gY zAN@|a@`0HTezsy)Y1d^Nbily0FBCfSEm({Xb8#7?TD3R)^}J4g3}G52%tBV$^3lD+ z%O9wHu%=>nVo$tk*Q&1MHG99l?+@?0ZU3#6_kZocS0iWr13t$4xbV0K_uyf4Zfqm< zjuh?-vBl8ZYE(nYb1oM30`FZrX7;^}czk>A74f9Q?0Xf-j*#+SL_FzuIk2@?=kjMB zhMjn42noNVPF$bOSQOZ$f~Q#rahWkRem{>t(!VTi{ZhWfwH$xMUtwjy^^W~9|3^IO z2ss$mCxJSLbD;pQ8vGI0XDLEnv*21k@ckrnVeOZppC`{!4ZUv)j&%*@l5F*M3H~L) zmH$J6W38OIl>YmI*9&g->v=yGyhU&^o8B{mQ!ZZ0|L<|nbZs-J=c)YvB=}yzu_n@7 zZ{UB@|3IEKUGE9bg(SR+?2pHSM*6>zXN~uKqywh}H zZ?fT|1zPzm6#N4G^3rrw3U2!5z?J?6!LcyOTx0By_f^5K5?tfuxQy~&C%E!y793{) zm`myJG|vUXHTUxkL!ZO-j^8W~p?>M}QVH^>f-jS2egBZ)EbqLu2GVyzKDSty57{f? zNoC0*o@eDe7vGC25pw7s&*LK_}l>ea{>650&w<;hmt=L zfY%4$%>g*}n-0l;YXH6@0N)FI3`%HJ4B-qZEcB%jUn;oPo1Y5aCAhXP2Lt$XSwKgR`EdvZSTq0)=9XAJ$zKKe@n=szp;uL%9467Q7(^q&vFzaV_>5kvcm=<_Rr zCq!7uPA?&Ny$`=#@D{6bt|gb)3hL-E-efIlRBwtdPnm>}u; zvjF!ovS{J0Pe>+pw6*SkWWxF}!#!2tTvC}1pa z9nzsVEPCT12gGMe2TSu2Ya|U`CiqgJzg+Omf^Qalp5Xn0$0WT=fe)20%Y}a7#g;*b z(BBy#f1S`jj)l^^jDHrt>jL=L7JUfHPm7pKJv&{t4W^9Y7yK$Ak1UqL%&z`{P}KU*i3OYd>y4_`h9Z z2^AkJd@@rk4l;9H2z;n~oFw$8guYPp^VtCUxdHg10Q_`7danzhr%!w+IdO23(ZkzQ zZF*Erj$?+R$G!+de|nmw*M4h50DYU#7k$RkYyW(+;FEm#J%Y~=T1QpU?dEc8jiCkf7e8?W`ihmwDj&~Kh)=?>c;Z(9KUPNAR83XbbJ`{O+z zc)#F{f`3o&+SwMkO7K4q;QuqB?-qLFQ%0B<1L%Jhfa9FGA=CSM0RC^lX|I;b0A9=0 zS;4Eytl;;G{G%{rBK-@3cL_co_)vPD9Dsi&0KZ)L*Uq<$jjtNF3j^q{75XQxwDd8_ z_v!%pe}laoO3ruyJ?C_WqVEX6?+(Df1>Cd?i>#d5zH|v*z1ZSfj~*8MHo=$M*WUL9 ze@yVrV*h^vd?-1e7Wx@d0F6%@yq*uB|EB=_odA3|1Rg5heu+0hhZU|@?2k7rfc^sD z)X!B*ETQp#kADlBe%*tlIR`0Q&eZv4`?y7|1X68ZJ|FdFMlO?@AVdF5u9^w)bs2wSm3na9|%6}ixwyre8hQ{excw> zKL+?v^87&nerW(cNBGZNW+OCyYuqjoyyHt2H@<1yej$L*O#%3q1Mv3>hL-=<0Q$NB zd|d$E5rA(E!0!#fcLm`01>oHQ_@4o1zCSLGwtK8vF)(OI)y%5#reS|cTPs#yHmr+d z4+eKVU=L6$mQvPW)p_80&bHPXtd3ll+_Bh;!w1V@v$D-C%iUwGY!;v6@ORid+#P|P zc${e5SRBT=+u7c#?PUv?RiJKarp#tF{5k7~ z!Ea5nfyx45GZ>rI#dbk)Zf^`>E+)phTSMV2Gv1iuewuiz-96x?@N5<$%QN4ZWys0Z zR5@NTRYc6*29jo1DMJWTmzW-NbBEV*M@=&n(NSX-^m`qA(~b>PbGiA6bZyN|*eXSq zb*&p#Z8S`ibKBaRj8c-==K#gTM3woDqm)$7{gd|utkJr~Ht zCcCeSMZvdz8cbxDh_WFsx(@6}vD+KC0NP!|&9Z|<)Lf74tma1I>(-$(8CjvImOCI8HP&(m zw;psT^Sn@TZkDm)Gcnk{$o1H|i!+|T6r6bujmNpP-b*F1aK8a*HOsKgmI;(YJh0fp zS~yzedVg-oO4WI0&ldI3h;r(6)ODcX*RJ+DvPq}F@pf=BzgUJZzj3UqH@C1|T;)y@ z^9vIBC5gE;=8YwR)pQ+XZZ*nmuom7@3>p?W=2{>>Sw!GC)Odq! zJ7>i{HhOn8S=OIw!43xxMb|00`Ro0~Fw!*3 zE%taoB6&atuFBpcZMIBX=1nLr_KsU)u))zj7*p!oMj)HfP)b+V#ambhxk?a%Y}*~hgWm!3`$%6&IIVai6+PF6h=Ma20yL*tqlUhsK>3P zYHMyV_S{;4r0mZz#get5#aJk8r=X#tSV`=A$Rna*Z{+K^X~R|0EYH&< z%xQ<-#dV(m<%~J}mt$Upm&z$_NF4BJag|f?+Z=jV9?sA4BE5^(I`pnQ^$xv@Zx-Ay zr|v5tezlFt{I7cl{CGiuCGz8w1lROlFTA3HYkC#ud=f9Ef89QsL%DgY^or}=0LEJ` zwEXGt@!|RuUYmXKD*mKH@9MKxaPoKU!?O;(tIrpF_+}fecU*9lU-4HQ+>JNm;4Xj8 zqwu0U753Krf5yQp9lVf%arx_4k>LJ%Izw>sckAhF2N&0y;d_pYt)ih`V<6xWmUS52qcRw<^yWhmTtx3gBnwMgFe+FLv;2Es6Pm zl7ov&(cser*YtiYVP*)f=@9O!mkH6~wIl&p9tN*th+_f8p@L%&Hy_+u+9Q;~KV*ZahxSKCC z1lN4gb$2rb*L+cYxsU#8(HrM8cqzT&^$xwOH_lrqy^FUv^se689C{b$JO?ko-uC(U ze8rM^2Yh@KKjZMxHiSV|!e7kGuje)gcl$Yy2~K^w={oG-uKc=(g#5(?Y~p*~;eUfc z?0?4{{6+_VMR0#P%=qG6Bl)F!QZ&C5f5+kD%2^1%DlfmDCkgJ4cZP$zd}a!+ zPxSF`aqtzEwfVn8aDV?WEx5mbxI=Kt>FW7O2Y2npvw}0d)lR%GIJhhS%MR}9CnLDO zJf9L=^)^lP_NGH`eFPB}$1liqs4 z&(Xg_Pd)Ec{X6t7-sYoM{df5274H(`gzPp zulTcq`}OvUgS&cr)4^T6op$gWo%Eg&T=iBg{xw~UqIy$&hAdL?m#dkA`^(irhn{-C zw*bs_l|%31%YF2!hiV_a;wuIB>t~yTuX57c<={0A{)phJhp&sAj|#4OQ2em~K2HST z#{=+80DeYrzkMi>eFJ{`Fhg+aq0UKHnc$i(^}{X{T+^j^%;B@z;ZrTRU!IkM`{ikI z__+3JUjUy+0{A@Z@Nx6&1qXNi6|XqB>!*6t!PhwHIun486309B>C#VdaF_pV!TtIv z6Wp(#Wey)#Kg$Kz{8ImTwcwgxim!C|)H~^Fb8xpjJTACj&L;%-%lU%C$F(P!06wPz z_?&V0xaln$YlWts8y)$JigS2_gGU|Q_3M^7`0WmThv3v_frD=roceU}ZG!)S5ASmL zyYmZ=2hhJMxaOnIXS^-A=A+`?MY;4gIdV=DT={UH3a=T0D<8$H0{ATR@%fhUS?=Sb zc&*_6a?&WcznruM(02vUKPEWy-7ODK3eNtRTi>4*T=j6D$p4(+st3i7JM?aQ{j%VU zx5cW`{QrtW&-O^$mp2`HxBS23;BI@`FF56K<2~c!|FFpKjk7|i{E8R)aFu70;FLdR zgPQ-RIrMJ4GacMbSDE0d&!37s3w`v8FBP2ey8WePg8R$E;|~1-N6xne*LeS4;(bSO z)q~>w4j*^kykNX#N_om0{<8&V`C0Ga%LC}kCgk|kIP_OJc$bC)hkx3^Qx1NQgS&k81>ny+xZ6%055Qjuz)uC>g%?}lsDD?^Q4a3Pxm0k! zJ*gJlUw*bZd^#LG9B^<~Z_hfoo8IRG@Z$ma%Yysmd_{1-oTnW=t{pq$;I90|6LWg| zf0_FlxT>nG@58;QsHmirs7zjzip&pGG%TAg9~PPwB~4b&00N4MfPjYL)U9mBnQVfj zva+PI5_8Hans9O^Dr=m{DJPxADQhlDWg|_LR^msz|8>qPKP{?B4u zu1A6wDtTSMMM_@d6-u7!QR<~q$!mNC<5VtvpRiWpH>q-MPQ25Qt4^fPZALxB_ zTA~>jKM0bvhWrwiuQcQZU%@!};TESz_WvqF zkBom>ZRinvgCQ^WZ#3iu-^I9DUwauh>#LP*x1G#>LJD*v=%z#ZJNfO3xk2 zo(zTmO5vFbzf|FK6t3;8RQR1rekJ3kA67AL`k_|o(dAvoxRke%%Uj2|lvnUAO3ww# zp1q7yy;m#z1mkAAisS}qwySu?P5A^Dc|YUkc`So*Dc8&4j!`oimvRX{hjBCRRAlJc z%X&%;J%X=boa~va{7}ufS*}{fO+V~YdWw~vR)ycC@bEL79aDca-*jfF8VjR=xQAv>5V&A7q@$b&D!jo5FRuj=JEr zXF7>V&x1v+b=evOY-@;zX}dtA6gC9m;xL!O(lm0`#WK9g}OSE}+uj=~>Sc!k1s zf58gI&H4&3Zu)<%(xcmLql=zxE_!w;J?oX7tuA`nT=YaFIEl#*+MWc)&2l9%Zk8)U z>3Kxilcn$o>uT;41XIbrn*D-F|xsh?x&K*jRE^n)go+B=LV$SaJn{NN36|Vg- zRpA?yA2JpGsKW8NEqgJ`wSsZ8T(wG%E?0|-o;DXfiKCo+sXaWQ>`7+av}Y>graf6o zkG5yF!e=V|g$mdCRw_JG$p;j!{l81$SxUY|;rhJP%D8!6YGd3yFCA6#bpH92&r2ti zyvC!>ag31bk(_^`8JFvk;4zGw=cSp9oBq#Y-1L8;(xcB0MJ{?OT=c9~dUQRmbJ4TG zMNgyBqwU%0qGykbo)b!swrBKc$IqsJk{CDrlj9H5ioBr9(xapr3 zrDu-Pb3)`-}idyZtB%B%4hB~Rtbd(jFW#-m7P-+ zewo5&GH&`OlX25Og-VZZKa~n^Q2JLXT<5!1;g>7ecI>CYC0Kd0<(Rk$wi35DzOhEH%3o8^sQ+$?XTlBaV0g3BAF-0agTkLz_;!VBKkQMs_QPJrO+V~o-1NgiB~Rsjp8l}3 zDtV2E^B^A5Pq>sff^pKX@kqu^KSV2B`@yGh-Tr4XF8RuP%bAQzzJgaUZk~TuC_Vc8 z8BlsOzEa8Sa;;MG8n0&DJij$EZu()D!mqMD@BHmkxUTmWh3k56WnBF8IT?kejdAgh z;E~+9XxbCSxaqfO#?5k#R(kY#dk^DcPrsgyJ$o4!djvnI^xW=<+W&0|*X<-?k|RX+ zzo6t}6~0a3@e2RF!V?)MJ9WJ$F-~@Be5#Ts7t_4LwD^_0##IK>Bt3KU3i^D*ZVM=c=%eRw!KSS*dVsXOqIUe8jmU+_w$=S`(&wV`J}+gWYs5xiFEc}nS7XXts6_0$=91aDA!(v_Yjh3``M zPDB4;*1yZpFL+St(SD2KATRlOy3+4+!4nvldX)DOMl&w;D0q^Ko>Iom_7-5=Y;Sc+ z&s)mQEegL(;f)H{d}(4_>>t+4Nwl4Dv0w0gN>8!UA7tFLzlCwr{u4@%=1UCUiY({)x~U-mLi z<>E`XZGW4>^?iH1-#%>5cc<-7VB9R%XeCefU&G}}RPq{6GUUbnWJ6x?OvcH6-n3}R z;T^*F*ogDDTH!A$e51m3c^h2tCWULi?NYebv(E)T!ML>Zs zb{@|-m6tDlw%kmGYkRU7r+mX5mG=K^#wlNo&r$MJUjeo=N6Blvh;gzfUFok-dbIvZ zrAOn{N)O-4+1fTMJ(TZVY|j>@N8^o3UhChcCZh%kG?J(QTSq2-iUN( zhx}8n@JNN{DLhu;e^Ypd!nNP#C_G=uXHIi=C|}K&wTzqPUB|dt-W^I$fzrRvMNiO0 z&rzjE`|Skd(%!~#d$TTbGL!Zucmm_5{fUg5_NOy$w*N}TCEr}mcLn2;uiz_{9(^8I zW#~DV^{h7Z2)C^;;)h?bd=lg0 z2f?Q@PX5&S`VBpovz~NAkKma~&%4SGIgFe2o6opezblj;-L5vc=-J|;XRp%pp0ej4 zLM`r{HUq zp8d+s1{XYrA2vk)ldM0ManUb$65}TCrYihpm2ZZj|3%h6)6g&YIzxUh%hwt5f^TG; z?9tbepu)93k0@OGCx3=xH2Fc__ZBive$aS@lJ9|}|6)5UmAuAR81nJ#qkthV_*%xP zTz;0N)i#A!DLm+cA5plz9wlAkBpwR=A1FOpjGOIw4&!EfE@hnToTKzqD*T|r*DCyD zg>P2)zZBk}@XHjwP2u{vay#Q@J?>!Ktj9e{p6aVlZzq$zN?zlwhP>2Qn;|dw5yr_s zsmlHnjGKOz4;7exj+yBsCO!JRF_m%Que@L5XI%Izc#hI@yRxTL;o8rY3fF!PFmC#J zCF7=_S1Wn)r_A?St>iUcYsiZ~*BbJIH!yDcxkcf3s`8$2!J{wj^0SurDO~$!w8FK2 zk{CDrlgzm3pQ%co{2=q*`jx!K(+zp?Lxv$Qcn;&He@Ydu`%hOhZnn=_#-+W9{&kF- z?RJBbk5v9|RXCOFhs?V+#--f~enjC~PZU>%$csJEjGN_+W!x;6k8#ufWTi**&ad!K zRJqa>ezU?e85h4@!{yClT!p!#)6Xpm-)URo{IxMI_Q*WWM-2UfA62-vCpOd3 zNBRC+=}%C&_D`b1S*4vSnQ`)i)}O9$UEU1F#r{EkoXlq$@`C3uZu+NG;h!q|S1G(& z;cH#+4UC)iZ)9BT{Fv?BtmLU&f;Sqt@NOsLrv3XAuKnDiaP8+-rC4@ zFZTP`{wT(!-3lJfxM^ps!mE`(<6ZC!h0j&;vl$nA#;`qe7#Dj4&oSgLVflPRUhqQ3 z$^P9c-%3MIChJ*Y=n*{NqNm!>a~12UHS`F+)8Bea~GC8g9_L7v?=}C&qo!m?TMM?6q5Y*nJRCr!arB| zXoVkEc)G&1J(-M~{>f1|*&}|)XI$!KCD%)#Ausp}#?5-KR`?NR=LUsqJ2x{f`L5@D zw=gce5WGR*+Ru9wuFJKTansILg@2*!l*#T%UfUUwGy-qZF>~oXR-q zzf9bx;U_<-Q;jQb4yYsM1|+}qay{01Juc&35(V?4*e2Qpr2;DZ?t7 z)dn8Nc%6Zt$@mrnKZo%q15adpmw``WJZRuk7;iK13mC^I+3iLAoWgjNflp)HXW-Kr zPc-mL8TT9b6^v&Z_*IPO82D_)OAY)w#sdbP&v>fmblzV&D<| z90Mb{9HK|Q_uw;d`QAg4f$w8I^8FCev$DUVf3+c>6XoD_242W`gMpVazSqEyGJe9q zSDoVMkM}xjv8QgJvoGJ-6udCn!B-pdIj1^!lYv*B=HRUcK6lYWDNZyD;~I}JRV8@PN&vNPWwIP$S{lEotUF2?2kMZtFtcjQV9 z`2?SX*Bbay#v2U0HO`Tj_k={h%zJgvkZ&2`$jkQ%MZR^UgXi>g)`B02clK8qc+{B= zzRtj78JF(>ik<|fv*_r$R9ND#BmND&F4k2Q{Hnll~s%88zgiH>}eA)n4S zH3toR7voWUKO%baCpmhu47~AN2d_2oqdYinn}KKX;FMhkK8NveKHp2en;B0w@B|LJ z%lE%TK8MF~HW>0#d0b-z-;|5|=oH8PWCLH9>frJ{DUnZ{=HPXP{3^z`8+i0Zj(n?u zS6}SlF?{_I{qY$NKH9*O8220aY{n}Lyp{1f18xpSH%9FI!ZRI~VJUGG|>VTV(}BxnnB|Dl4q9`MDLj z*4Vk_<<{7;651j0@ngr!F4iP$AHf!c#uk<2Kw<WZvzpGs>Jb zRUhRO;p|#5yr$DC;e2^#)Uqm!eB}Vi>SZ)`NT*x+tBeGu{o|3AL?+;;7s5^mmKL;2|^63~`yl~Uv)4ia!EbT}+7O3q`A#99uX_}HwWN+m3rvDN-eUSMl@?yX2 zFT+Nt@<;H8jdyVZDnC`Fq?h(b_LzACvZ5;J-?CQm{UMn_mnJhkU5?DU17xkeo&GJW zk*`m7 zPfkkm`I0A221ZLxpPQ!5yvP&Qr7_HUzO!C+wh4T%N|@h1;KymVpLMe85A{u9fh$jX zt(wDqt#%x$g|_NAe0J}!*V;t;C|r_YLGK_N%kg{@YYO`&<_m!ZFibedxUlh>u*0<_439hZ}!_KtDdPRTkuX5*;0LYR#-hOZvWysPyLtPuuWg(dF$I3hSwi2 z?@@o;>)Ev5>)jNFjdzck?D|ocR{=@Zs>knM$Z|}IyY7c~`)aQhU z)gMUn)Ony9` zCa<+Mc)q_q)(Q+n`u1W>l^v||Kl4wIb?EK80{4?lj@Q5Si1*Y@gOSe={DLLcRv)bw zT3g8nIhHl)gm=g&gi7hDs&+r}3Bu-P_VZD%HEMXcHU6bu_G7kvoXTn0KdZL7qvPJX zLDr;XDu;Ja-{7sV=~n+%%NleEmFbfc9a+JJ{;h#itfXex5%fme<$>N!xv)FvJ#Ca- z{#Sg5W<*(8!4m&g!fnhNyzt2ZRu=YZ9`+2qeYh2N=+Q7ME7j|%X@+0^>G2#2Lg&7_ zZn=LX?Ah;)wRQUjSPjuu%%kdFA=gJpOAmn4O)LKWUHx-HJA9?Jy^$Co@&GujG+Z$0o?Y&U;-$VT{7+79JYi#%{{#iMQD$~QYY z26v#{5nc{_NNdNrL9{)xo&41fUtY&DYaw6W)2i9}$v0&RAwzBcajePz!i8-gbxdl8 zzsdhT_HBt-X8TWuYkW3QD@%pn&7##Mtd&@KL%iZ1J-F_H6N~@hV)*XD=w=4SuCAvtlA?e zQ}a3g_Lst}hW$2YoO50f7#+vYX)oZZZZ82R6jpl^+TB-n*`uAZ52EwiTU_2Pc6nO{ z_lM0^^-3z^FqCmP%80TaKv`Q*)(=qD4^h^SsI0>W)ZYfV55`&TJy5_RYT{a57(!mY+)!{dS}_3^SHl#|2Tj9Na#$pyobQe zxBP7T$*Ns!=L?s-b%E;&%XfQ`!%Qd+LMUu$t7!l=_a5R{JfH zIM1T~h(padA0lmSAnfSm;7EV_AU?N{KAUrAb@yQoxIpq~3uu?onai~%Lm$g zt!=aYJIMC$$*Nb`=BI6&_Yde_kNbh@RM_}!zHJ#i06vPUPowg{zf_+6Y2h{dZ}-;k zhky5@jQe{82JVM{_rt&Y;otr6Z_B-D^(}+L>RawjuTPJ5+ReB7_OEQG)8?CR?_V!z zzRk`9YX;PR@IX}kM|WE7BVg-(*lPP1ZN2&D-uh;;6?Qel-sYZxfz7bD8TK~AUOKmu zzr|+qdzamgf5i^>b|LNe1>#6GU86~l&D%$<8v7cIyq#+k)h&+K`Df-lV$IC)_}l&9 z60q9x5mpVgr<{(C1}9IeCKW!XV?o?2U>&ku__=*b8ak)o8h9nuJ<>$ec~6cFvUS+{ zo)b*2JFw%qa@Z3@yUWEIcXNlx=D3p`6gIZ+dp<_nl5NU^Y^%dHs0)MclN}AA^fVvq z$RgXMO(N}oxy($}o{=WBbZ|~j%4GBoH0zX>X=r5&zo#(;$ zheoEZ8BrUTcDHZI`|thDmc)2n9HvUA#a^fM4|pE*Jv6-V{rAWiAAURVqlFqBbY(u+ z=b_%IYa(iUEr7+ykaV=tF)Xa3+VkLuhvHZV9gjqX)3S~J#GXY?9`wvQnvW;an)67f zJaYOtnWXeBa5hLm=!KGoTc34=I_kAJ1LL6A3iY%>&mcVvNoUvljmUsJ1AS9JJzsfL zzIJ^BBY%W`${{quKC1s5`QG+Tt;T8FYxOoEd*3U8Uv(7MvCcNBVCx(ge1Z!;)diqZ~^BCAk{6gtD!ty&= zUi5sycm>Dmw_%?aFPeTRJrr^vdlI=x+uVlysV?%dE_h!ToccyX* zo?lQ_vZOrQ4kH#=@%dJ9QFamF1;z79EJPpk7L{7r+2u>-F3n#sk+;h2t=#Z3LuhKv8=Qp zdwxMh_M*Ajc}vQ&7v)x3i{@68+>mFL6(GP#=9QJ@F0+;{D7S9NyJ10oMPawh=H)IZ zvKAFA$}3%FXF==SaxrV^JlUF8x}>5U68R-dDy&821r=Oy?c=@C4GkDK;nGA?>1@_rVt#U8=wd_{|?C*OfP?YV&W3wbSi z1TSUW)U%FpdWT^UZ_{c6Z__&r8V|=lEu=^4i1uR?E_Fh9yu!H~nP5B6L~GK+;XT^5 zRx5d_Yr@wldg~vJ~_P>k?F#Q(Jw-jbQMlf#HqmOaY^8=;duW)T=ro#34PrgY_dNjU4 z$!q^?SGe}`UWK2o?1>0>c1-__cEPI|H~p}Vanlb`e9J}p2daD%6z)@aqQZ5%O=8^4 zH<@uW-%O=P`+tRto`8#M;`zV4dsA%StTTuF?Z<;qujv^^CH*M6&X z(Z9k)f34D^{kB2jBUODhC|u_&V;anI?Q_XDgYQ4cKO>ZW8KYq8$z*xcKZQzk!S_jKzLAWZ_Qx}Bwi6i}Vd|OcqDRI|&~=chZjg^vZ49%}M}%h(ID|7@kFn&nOVYZ*8Fu$ghw4_g=)d(t_N z2FB&OEckZD>D_4x;>xnq&?DDW=}Qwmg70B@(|&oko$RD%E?I(x{%L%mg>lg@cq`+k zo+FH#{)yyAMDfE6)*r>V=odW3ke|u&@@}ul3m(t%WRLdSXhVm06c-bGBO z`$G3}g!#L}oi~eH`ZxzD{zCakdysol;vV7poQ7Sij`Q`=j#?zWKy)q-mH!sL$;@Cu z%AfL(^pgL0#?1VijB>LY+g918^<@0W{t2|B7D=C@Fxn(pT4YV<5!y~9P%ZNA;5dcR zrlgbgWeU@xm$TnBM%$83@I1!&wpw6I-r1XNBoP0J;7_m-%Kt~%|4l4N{wJ48dhtK0 zH}j?>8z})6A2+y2Y}0eFnVxi-bqC1W!8hp>4f~B<`_}haWVKu>vt#%*!7CJh5*Ef&!)Lb~sYVYU>oAL$q(O-gonyVp4V-wKF z5bPVa?8r^AXE%F?ppX94QRv&b5;jsl*e3LS{ieC6(=P@)w+`wFgmOc73DHI&O-rM_)Sn{RfAxFCL#j^+jz;>MX!@_7Up!X4rWq@{Ah| zd$HF3+dnXHCGJ)1`XbqH*41g7D9v2RQQg>e^@>v-+JA@oN6`;Ie5&=?0{A(2wZ9#8 z`<&F9)FWi=`Xd{3{+?4Ez1`{$<0EvRaTASYKwe>y{;f17!@?L0^fhjkJkTaa9l(!t zOMa5a%Z`3KZ_@E0?6so&TR*^>+Jg`MXrf2bimlYvOnYw`qLsnj`_M!j@)4_zvC(WN~w(3AJx z`|oyj&?O_uZ2V}MN*5yP-%ELL=tDzN?>_CGQ`6SO)S`zkdhMXp`v%^Bis$C{-tS6l zCkpXLX8hml`{E0rkx!^Tv4OS><^IhjR9_iABZa9uKfdHzKE`EV$BjeviJfC-_uuam z8w)+uM31hPb-vQyF8PX{B*@W1@>);2!s$XP%S^^4wh+e$G8q^Bg42bF7SsQ7&u{v9 zj*{2q%`xJn47cx%e)iKpdh3j@0U|jTz->Q|oZYOIQH|<}iaH^M7+e-9R8Mx@zeJEPL z^r1+(L_WxR%zA87xNe7${6J;;KZ7~mW`_pVr+TxY*y%~ z*K^YOpQfjn8|j>YpI#(?x?j?Z`VW$1khgvOyyq5YCI2Rjas$T$J$M8*;l#A;B`^4Ou z*nU477K!w9?=Shsa6WD^HxaC3Ia!(Ir)!|uc1SjZ?Jt#tyf)L9Ex5ioKNoK#^1DUf z7IRA;KXI}XqxHfG?;SZiRM6+Q~@8f;x@^_WpGHDfpSXXMYx+&yhsL1R4R9*d2FT*N>e zA4SK)!~I*sqL4RjM_@bJ*$#`LwRc8XthF^T2yqlY-c>;CB0in`=3P9}kFv~EWyw%w zfxh@Gls6MHbCk?%C4+pLb$RPTlsEc+sJwRBxV$KzU3U03%*SPq$2(AwR#rITJys6l zU+|6NBlrV$Q|u}}pZpNMcw`RBSft8WsLF_Z;w#{XQgyu2KP7GjerRL4R+#PIFvQ}} z)~fSR?|m$9O(E)i9_oEQ>U{y~y+jt~tJu@Cby;GgR zjUYYHX$7pB5zFS8HTEsUT~A=$g#BY!pR9Ve^W6_TPgUbx zA;ir+h{byAD<6qMJT<(2`6IrXWse{(j`#3RAU=5Z1#GxCg;z5H9>2(xDRaW1$BynQ1M+tL0huR)&DMC3$_^5cA9%l3sH z$Y$W25aElM;m5gvWP0TE80E+Lz?K={-&$u`Gp6C3(8Jebh9BpFrW|Y3MK~9v;vDcA z@{C^*hNASG;qiH9;C-M`J%60J`(gOJWxlob0NxEct!iE)-fy`pr7E}aZBN*Ymwp;E zYu@sCjd9qIL%PRNM}+4N@^7Ve3euj6G_U=@Nkg*F0S`kuNplg>5SBX_?FMy7+b>;^ zu{#>+dLrE$upt8blqaRH#qozB_ZaLLTvgneH{rR%{99?AQdQXa71nW8a~tO_FMw^H z8ML1}+`pC9h-=fMO6}7>$=rR%4>NYZbz{t|*3* zXW$$fS2f=*gQsd~<6~&Uxu|bir&KM$=Vqv!H{f{B8MK{?dZsmk^mwlG9nV*nUG}?((ILl((mLF!XzQf3e?Dg5T(N0+&mRZlUPbCJH|vet!G{_|fh4tCnL-?Qi0laD--{jaB=p7!FpbtAvpuwm9cWo0ebH8nl(^7GFxi9LC; z`KdK)F1Y^8GiP2qd2&(8g%{rF`Rc1LF5kKH);UQ@g-0HH?24ohKKN_IXP>pNS-g0E z`QE)x&Kf^{?uDnGIw~?eJmShf{_*aDks~kq$t$lsm=F__`0zdVoI9XLk3P@cfB(4N zy?XVZJaAzA1A)M>3-<5-=b48N{bj*fXI=J}XP&uf{gNgB8rHk_z=v+V^#>KBN6#KR zbZF9;VZ$arQ(V07-`8H-aJ;VWx-YN1a>J$PpT9Ks?YEzQSN`z8f%g`V z8~2lYSFRlN#jIJ67yk9HTj5J<^e3OZl;iXHho3lcV8?HMv+R@WuKVpDuD^cIMX$ep z$3On?hxwOGnNs%tj2X3iX3kuDaQ5siGv0jjwuQTPy)f$2Phb6D)27Q$JAV9=SJTpd z_V-IKz5l9-6X(_5cH1d$PMh}2sP^`cdwM+KH{W#A*KL`Zb?H;5R{n1N`ZEq}+<3|1 zE3SCt>qj5`ag*O4_~o5<_W1Q(cU`#c!3X0ql9Nlrzxd*_e{S2B_tI~Fd&81<-g!DN zE-p1OD#|ysqvM1AhYz1z^wwK1&iu+jdFNG}-ltFWyVIxN)u(6A$fEf8jFvfb8h`xq z%j=7G@BaO#n>VLDK6mc+jWsn>Hr##p_;XuYcHh2i+2<2J`sieA9+>JNXoV*7K?E#5S5-p;)l8Pz?! z-kxD!e)-kF{{3Sg&CPu|d(x!q+poU*sa;#QE~xq0&j!za>81P5`oRy*+xpN$XC*f` z|6|b6qlcbdyY`%kr<^k4g#`=VD*eYl>VNU8U-f#TwszugA9!HQh|^D>de_f?eyZ=| zkN-6NjW>SYan)6uDiabim;dvhkKMU?wfE@54__1b#V>jf?$>YVJ1@RiF#f|2|NdHP z>dMB#!drIt|47jPO3=Rm^#2Lyp8)zl4EhfM{htH< zdxQRyLH`Fp{|i9>GeQ3ap#NV$|Mj5%Fwp-Y(7yuo9}D`A0sWr={r?U69|!%v1pO}s z{c}P8H$eX?(Eo>^|6tfX z|Mx-vJ)r+V(0>N#zYz2v1^Ryg`kw~+zY6;Q9rV8n^sfc|-vs@mK>wbg|IMI(8|a@7 z`u`5}KLGk42K~PV{hL7lUxNO>2K~2z{u!WuIOzXR(ElaSe+lTH2l^+1{zF0k{-FON z(0?ZA9}W6X0{zE<{>`BOBcT6I(0>Z(e>Ld;Ip}{2=zlTjzZvxZ2=qS(^uHVQuLJ$p zg8qL6{ci&O=Yjq=g8ol}{B!WuX6l(EkUZ|5DKZe9-?#p#No{ z|LLIryP$s`(7y=uZvp*(4Eh&?{-1*WkAwajLH`Y)|GA+5?V$ez&_50IzXtSw9`s)g z`saiGZ-M?n(7yrnKL+~u0{zbb{htQ?e+Bw~4Eo1_{(l4gSAqT|pnnte6e>v!XC+L3^^bdgkgF*jyK>zWe|7)OsBk2DM=)VQ@zX9|=1@wO% z^gjXmzYY4I2m0rM{wqNL8$kam(EoGLeweD{>MQ7Ye4^tLH{<;|JR`Z9?<_0(En-Be;w$*0rW2e{hL7l z=RyCIp#K`s|4h(-GU$IH=>HYyzZ3LN0{tHY{XYQxKLh<2gZ_I#|M8&zsi1#2=>JF1 ze&j$Sufd1n^|CONsEYSb2HveB5{R!yr1N~2c z{=WhJuLJ$B2mN0M{r>>^PXYaBfc`T<|Jk7bo1p(L(En4=e-r3`9Q02E{VxUmCxZUB zf&SA#|8~&d1Nz?t`e%avQ$hdrp#MhD{|eCmQPAHH`rirq-v#K_3{$GIp+d%){ zg8uJ-{&Apx6zJap`X2`U-va&r0{TaS{)0gOR?t5i^uHJMPY3-6gZ|q=|0h8I8$tgI zK>vKu|Hq*J5YWFI^v?nPZwCD@2mSYf{v$yDQJ{Y{=)Vf|zX3Pm7xb?I{qF|-TR{J1 zp#Mjp{|?aqT+n|J=zj<3|2NS8GSI&s^e+Pae+2p;0sa34`WJxy%R&FAK>rBP|97B& zCFp+!=syHt( zKM(ZJ0R6q7|CgYDf6zY{^q&O!Uk&r1x z|35(gUxEI$p#KA)|LLIr&q4o>LH{>E|Eoa%1knGVp#N&n|6$Po7odMX(Emly|3lC} z74$Cz{Zl~y`#}FOp#K2Se+%fJ1^T}L`kw~+=Yjq!K>r&+|0>Y`bI^Yw=wAZ*zY6*% zg8pHk|9sH@9MJ!3(EsnC|C6BqQqcd;p#LqP|DQnrpMw6!K>uq%|BFHYHqigqp#L7w z{}IstY0!Ti=)VEz1K|C6Bq8qoht(0?-Memv>N6>#H=>H1n9|QW|1N!#>{qG0;dx8D~LH_{gzaR8J1p1!^ z`ac8uF9H2~gZ{UI{-Z(vp`iaT(7zb;zZUeb1O2ZA{m%#e-v<4k1^v$k{SSct<3RtF zp#Ln;-{wE){|V^t1N~2c{=WhJuLJ$B2mN0M{r>>^PXYaBfc`T<|Jk7bo1p(L(En4= ze-r3`9Q02E{VxUmCxZUBf&SA#|8~&d1Nz?t`e%avQ$hdrp#MhD{|eCmQPAHH`rirq z-v#K_3{$GIp+d%){g8uJ-{&Apx6zJap`X2`U-va&r0{TaS{)0gOR?t5i^uHJM zPY3-6gZ|q=|0h8I8$tgIK>vKu|Hq*J5YWFI^v?nPZwCD@2mSYf{v$yDQJ{Y{=)Vf| zzX3Pm7xb?I{qF|-TR{J1p#Mjp{|?aqT+n|J=zj<3|2NS8GSI&s^e+Pae+2p;0sa34 z`WJxy%R&FAK>rBP|97B&CFp+!=syHt(KM(ZJ0R6q7|CgYDf6zY{^q&O!Uk&r1x|35(gUxEI$p#KA)|LLIr&q4o>LH{>E|Eoa%1knGVp#N&n z|6$Po7odMX(Emly|3lC}74$Cz{Zl~y`#}FOp#K2Se+%fJ1^T}L`k(fFho?~xffhYB zjWFpmkv`URU@{&ioFWh@{da}g!_DlGW;EukbF^9715xV+c#aq|N+8XDu~2a1e{$>@ z^$9s46nmrFU#Kx@PM6L%JH{xO7iM+g39O06O%N{oc9aGf1!ZiUd`1heX&wa|r$`Ph zjE}^ojXMT+;v^DkJ_g6mh|V!jBqvL#F>iD%)R?-nfa@`AQt#&jmo`FaWtoNzk{3C< zP0+#4PA>c1d5r%m%foQ4tb#=R#^W~uzlohvmlQ0URZy{{bXsv9Mt8}}@}dK1*|Jg^ z&SsBAqxB#(6y5*4jzBEbr?2r<)S$4K@~as) z9~~@qdT9ruBY_JzP5@q0UbM(MRc+e``0r|QkOo7@Ud@ zF)To*wrbMTjQ$h!&$gqF!=Ae_(9d}{A9Y&q;=+X989*`b_9B3PQbjdn&z~y8o~yl&os#M z0yeX~(UH4{1~I>0?T!95BqR7B;OnonJ}U=4_x`M7t1zc$`>kO$o*sAy1^GAIKCx)&GIPVfU z^Ln?fLktDoe-tR{LzT{bYA< zKS9iUO0wi9t1tRM(S|JQ(*{OuC=P81^NW7AVW|ID6W(1q)(`V@s`*thf9WQ)!G>nM zlY@7;-fBPKw3jAKT~f||I2HJ5_+5baeD*(zHifp(>DRBFxipa<+PBjNnj)zF9KY8^ zU@ltp%?_`#KD*g|cgm{$5_$R}oO$+Wo=`t;Q#+~ag}TK%R5YLE%^rtWfle&i!MLq8 z^`v<(X&zCUFBb0^q0Bye&Qs#b5O8If%@yG%wXHx8Ymyg!+7Efm4LeHOO&$7<$zRk4 z4tv)OrTV9GceNv@eMvqvpDg7k?NG`aJRkj|grf{OpLaAgqwKVg`ELiW!Fgaa*1M4h z%{eN0P#dH7ifI2N)?~wetk+;IGVf!b+U1Q{ll%&-L$$@@;N!`vop#?SpHsj0t2t3H zm=^u)lZ<`2)@0B{uG@1?8a?M+L=QUlTQ#@6|DJxUW-7#~ky$(>p1@`A^r7|H)?~$}!Y45M4vN?i}o$ z&c9I4A~fcXuDH>iQe7M0=UIdsgy_Vt|w_6 z$;RzwcKL$&#obD#jBE~_Xg?30lDRYEpXeu}si@8hrI|3d*mepz0<-5OiibEq z$mSIl~8Q-$i;xC_I@T^a&rSaCy(ltoIC- zH|u?-(i5-r%vQLr-y(&dq2wzSPH`+*h?Mrb&9}o=TP$C9nC8 zn^Jor`LjhB>jZ`Cc9p1b-LB;Ofuv`Y(&Jb18qZL;Zikr)KS$}wQ8>l#WD$-?ELHH? zydk*UkNl@G*3R!!{eSYF)?W69l-JmGjj;y#^cv9*Yx9&f^<&kOzCV$da+jyOa8L<;{a_ZiU6;DZ2vd|mvpk8j^nbe zMKAx3<)^ucC_SI9I#pzHdbuvk0kb^U-~iRBUj%ra$>f!CQT{%API*ma%TD8Tj?I%+!q&GfjR8&i;%U!KcR)^Clmj{p918`(y( z1%7+&$6Ho?@%<(zHb!wTJ66T9v96ey*ZyYGQTlcg;$z1V8~f6Tjin;CmyPd*;5^hY z9CHa>`)#o?yt@MvYW;(p*s5RRVi8Vk)sJ@-=s3lN5c?UQi!cSnSXvQVA)Mk-0et^~ zVqz3)abijoH#)Uj4Azf$S1R(rcR)JhNfhHk185*U6yx%#7*`PSto^Sbj)qv8#FHpa zBrzqj2j_U_8#NqfBCO6!aW&^VPxP$}i9OLVT=OUCxE^6X6-PY|`AUwX;snx8bpU-T zjyeqGBAt$Y^k+0+E-4j9mH3wvKNVZW{`K$~#mgvOMX^GPqsl(Tv;K{JI!3XpL)iYd z*p;*wCtlh;mP&0#zOQA+;W>8N(b1#6qoZg2Ro~Jsa{4>%qHdtmF6t0-|B_;|h|wPN zhHs)e8;&;8jCdu*PPZd=8o+kl(9rG0o)ZXZ>{u$=OEY4NQa98N>iTrWSC67Ck^X>+ zuj0HDt=odXqXYHq#24v1MI>+aqPlltoWY(>Otm?WW3?wCkM=`xS(5c*pVkt0Z@zt6 zJ?+=^u#!lQ{6zaXJ)p;K*D;8dI&pa2x8M)6COrxtIWgA3XxkD)?;b0q7%3f-egVpZ z^mp2e7_GgJz5#oU*r>hcvfYFBK(W!^V@rm}T z;-BAl%m-}hjBDuFC&e%28jwgApU(It1uUmg{F1_#PBato%NvdOWipcBoMkWR-O}4p zPdolekxecP}Q;ZH36QyIJVypBV ztYewBzk!Xw&&dNDYDvO&D14d=PW`l@v4B-Ojrx6KojiDCnx>|f!6-|2$WoHL>HJnDk~ z)&<}0g70?054hmCcL`|^1={!~O`?`SvhCY8w`irFKFU2fN#V+ZyzJry`PmgE*@d~# znp zw?;DOO33lQ3}uFlp0XWw6tT<1&IqUD%kLHAq!1{5_{09739F~Lrxdd*7YcTE?b$(2 zXBUCskYP@=aayEpQjCWmCv9A=I}|I@_#FGFWts9hjGOY6j8iNjlDBEKO5r-Tx>n&j z7PUp;-C|1$m$DF~W10MzVoTEbiWag*$3%53Q{%Ca6L}pgrSp>D(axs*Kh*`Nz7bj| zUmb&)t#BRVl$a&S>li1Ue?*Ve5$&&1_z+vd`J?j>Eu=@sv^FbT$1LeQBk~fDq5Yi- z*D=dIF8Dr$>)4W??nAJc?Pn(AX8Wmdk*{=-mp&>guWoOfS)Op+-gYQlx3{3eb$dIa zaGzZg=Py#lbpzsk2Pg1yU4^tIBQprm!lkC^^u49(*O1@C((YVYpMS9Lq@&P5U zW0utl*ZFQ%xaOUXS?U<)K_#!-RhzY9*VtnYUlgJ^21PL@ z$|r&qNn3G?JvNvbaXQkW7q{5scTGPY%1CFTR*G#&|823u^Ekbc+&b_b?Z2GU^J$|~ z%|T9I-HA9G;y;0BV1q>c0`R(&NhjqZ|I<)iNiY7VvYUC6ib}3G;U9$p>1U=Vy-Y=H zbuEQ-0Ly1ag&Di9vBNsH{~AY4=f9aAH$Bd6>6r3`6h8d6*x_XRtMD{S(|4bvsZCwL z{kA1CKJ&?t~g(p)beVhr7ZG#XQfxX2UE*t(9J`qta?Vc*oR zzN6e~zX;9mpA=6-yeM9e(R{)fV|TU_FS0m}Xh|F~!HFZ@gc#9E#E9^%)hr7!#o;S@ zE`I4OnqwN@1;)5d6vd7K=A4BwkYV^ktKK7`$ z=J=!GHP4Sk>?jX&xFa4!FPs5%Z~?j#RCzDrBnf|D%bPowG(YeHP} zt$f5(283DBlQI6Y8S{oqjLC<1Q}e}L@kfjyoYZ_t*S-(?lkx3#kq-<<+$axzeZ^zP zn|u_1qWuHTe%%RYpYr#eZaqh9ibeTPM_Kw_QqOo2)dy`OR=KGz)@u0CFxZdru|bS` z3_hJYC0K>|=NDoh^PHb3cVb2qPYXm@&*2;OZ^ffLG=8rIbDxtQi8r;BV_p-)WKTn! zNidopCOn+J{Z8L=KW&qr$H1ZwZ&Y1R#7=RXeCL4-=^`9){YlMa2iGBCejD>|)mRqg2?ZNbJlhAFX7n&;idF^1J%qYcx#CC|S#4zWD? zHneW%IsZaC9NE=QgCAo*t!qDUs3U0x#Q@XL_IUg+jSu#H+JU%s?4}g>jr99z3@W~X zZbc-X8@$}f6aAx~HCLe?2d^L6?2Q{07klyU;a1o}`hEemnOjh9%_F;=v?6~ywmXEl zXzRAy$svl@A`Q50rm^f}v9>>(VH34KX_J)JLK$hF>dH*(LwQB^gi3dSwh=qs4}COd z*!Cf{WB#r^#d6KIH5q;){wI;2;V_SKb&S}LN|0;)KDwr3e5~ieQ4gJ+dT;#Nk!foZYR{T>_nG&cao>ph z<5C~+tsCxL5cXYP1Vp>Q>E!>`x0m^b2RiiNj;m5Zt-3*%Qf?w)_qYp8po`o)WnG1fK3%=R~zt07K!Ug}W3;vP|zQ+aM?}C5k zf}e1~BhVB=`5*TbA@MjDe6$OGo(n$R1xG($$b9F!;5WG7t6lJ?T<{Gp__HqfOD^~h z7yKVCc#8|(6*H!r^AKUQStnXD(GCrhS_#epY?eo0TC$=Tb(cx@UYAMOwWH6X z1!m4GDC4khR~*+Z_G|~d7tGJjFQ~wGrOTbstX(l-+tltuT`90!Xn)dyJd$5gvZSnN z?&PFSV=;7>i$I^2?rifEyVJ2XA9za(#qKm-=)iWYPvb?5oAN6er&x`S(NUilErjbB zU6YMExQ@}$`G^*h*XNB$>)7%-h3nYzW*59s;aX3V!sQ->^4q2GA+`n1 z-#&%wSagKPIZl4ic)Y@OjC!;S?pL_P@kq0bhb8?w##pN4bu4kE!g0UexvW*VuM@Q_ z9b1l5_;w|)_2c=qU=m z)%XS_uVc$PhOFfql)T2bx!~Isu4B$S6`r8%-{XSsQ@FPCh{E-GBb;w(sU9^?qg`-4 z4p*PArYiZfm7RM0tma)M%ggzSVvMw`U|h~Kf(Mj--453(obGXD*=*>q-~(F>{em|r zJzD>Eg=;?UQMi_un5dNNX3n?O$XD<-7d;loHmP3DkwRe|sc`L2pTgzbOv$DyT=O(v z;re_cWt2XG&aB6lFI!ZRI~RQg6=lx4P`2z3xKQACDBI zb3$iIXG7B8YT%N;to=An+lhwUdbWR@flE4BPv`W8nDxeYEdNh9Jzrir)$HQ*vpW%I zL;NrBHQ1m!^$Wo3Qoh-cdZPNHw31$MDukt~uo?E{#L>ToCjQ zzn%-?E1nDDsJAD^jfUIfmF>O;o)e-2bG3J%|KTF;W0+-*Nkt5`+dL46<07xx9GU+i z(OP^n-rdg`>Tmbsoql6I+ko z6pq-U#1!k$Cqd(YX$}mEA4(sB*d^&@%xEKGh{qoF)KFZK;*<2<(r)_D*P#1QXl!*I z##SRHy|s?w<#(n`sY9%s?8p7WrXa>zH}}PS>evrp3@v>-mh$w#M#3Q9`w)=BX=cJe7&>(R`KI+ezx5JLJ*U&Ke{?%y%=BH{mrrqG%{2BE*UM<~XF0`J;Vasvv9lQXY;V)}*~hU>zMcq~ z4cMmjT&#mXzqlUvysx7U4?T=_REf5ED#n774(I!@&KU1|NfzURzY(`RhvpV==DM&a zpmN@A%`V8FFlo~GDcKaO^-&O^ARk{H&8KJ9F?`GkOwg0L2$y#TCH|9Px}DFHZZX&d zy7+XPqafY>{wK{*kVcafq&^Vk`JFLP7fIX}I!FD#h`~C~qvjdXu`<*+Tzc-*-vHJ3 zSE1$_@Y$;V```K;2|YSyYx^G<5m7(;+u7mo5*K`q3tsGkFL%N3aKRsU!5dugS6uLa zxZpt-{E!R&xeFeSDi7tiSQmVx3r^$XLg^Xjg1g0Fr@6@Ex)xIZTo-(i3tr)Z-{FGS zy5QeD>F95;+8SLjWa2*qjR=C{flbpmfNqz_cyv$VcI+mBOa2?apF->`XCRM~sT1daf zb*xiAd+V5{&R54Yb*zEzF28 z(_~3j^4kCMo&xEQ7h$Y(l>8YAmsqF7R^*V3ua%gQ;FYY0^avcYTO4q<-Y zmsMSFXRC?**B85R;WwVq$=8Il7`B|H&;%GKS-rT$SiWm|w-}40pQJOxw&X8sKV#h7 zI;9_E`}H}E4oW%^%wQ~00A4@K>Ax*Ta|Ne2l3REN4Y`>4&*Su_{RcVyY(tC0SOm_+ zMyUG7bF{t4IfCk+nuMemoa{04ri6ZOXwrTnfsslxJ?RWtR2F>DD6p~X8l$OX`%9r; zFJ^jN+Q*c2zDe%u8V@=V+lh&Z>VI-llFv6~d?Nm7`R*~AC|{QaetT15G=HdvJ@(kg zlU}Rla9^vPV>PvKDqRx0jf<=cF)k9d>5MDnYUw57dqozuQFp!7z5WW=EU#E*Y)gr4;pT^ z;}eKEO(OgNVn`HMl5u8)$sCX*OFxQ%k z$D!B}#WO6}O~>j`C&w{HlD5rsR5#tz(L2r@8^Z0C?E~1hANnxIs-2e~<232LWbiMV z-gT#POAJWnlk9p|TIL_+cei09+3^7UL}_F2qw$(;*q3(j#dWFphGklP`@-~kXFkUr zcH5M9;+=8MVRm~uJS)temo|>aK!1T)q%;4h2lFW4dzTms9rX6V_(Ieh-jk=g#JSC$ zKj9jzFGTz&hcx4jmh~`fXqFn?iPTA8MkG7K) z*nCjUt$P&la?)D|o|VI| z&9^#nY2kvjPjfznH^^rg@0Bd@%Q3lEcFR&0(=H zHv(6Skt^Evw%N{^&q67^D?#9#aNTh5*xSdZwr@?uxgfjs1GB0)D-yX_+h91Iy}~) zmvF9*;LV;hf?jKewNZqBg|%wKHboJH5GSe#3Sjl#HT`;w*pj_pi0LHc3=RsSZ z#%Lp6;gqA;s-ZU3x&J$><`~AVQ@y!!(~0}@eV>JUzcJ-BQX7M`yLKjxx5xF-o#B|g zg0hM+OB2U;8^W!VeTxKig_f}VR-LD#;}K8d_{4rad*}QtBdow1?g{H*h5Hh((2y69 zKQS(`ch4T@^!Iv(VmJCf=xvCCb7|3i4LcYiCT zZ#>|sbPUT%`eT{YHJ;kAYe;GjiCM`wluyU8*kiC#`Uk=ujCyE5>fMp|^vzh)zqa4K zeb)BAFXH}Q5A^gb?bd1GN18hPXgNp^6y4?~vFBD!)02~|>*smW9CuDOwvYlW_Sm7$ zeiP~_Nuv+hOp_1U*q^X@*VN#oHUDwVu>N24nb0%WH=j72xT41*tDMEIH*8XThH%-p z>xyMU^|AcWKGu^Zx>hOJ)O|1V%ytRmvM+JNs~DF!oZ#7vkHwl69mfpShvV4u-|xej zkG!O<+Zpp;G1k<#)5iNUPViUQ4<%pcg8#+^f5ipg<$^c6;H@tBVHfQ z_{%Q%UtI8@3;wwae$oY}_8ZFn0WNrq3my-g>Lo~lRxC2^a-xg;g)aCdE_Pn&BA@Sq z7rEfLCkR_RErYI#g0k*YY=7r>6RyOx z+UP|~%!XA~=gz#`yowU2#3=i;tl2UFHP1txzoe87U}~1^c}2PNZKM*5+|;w0 zanUn^_p5m=dIVqVqNk2=(Q_g1Z{W4)5qvY_rag^@o?PDFX6O;TiE&d8PJQ-5eYxTT z0wNiwzFCdSdl{xY1#D?CdYJ7=bQkMAO;8*mC8wg3aQ$2%gH(F)N_}rRT}8j zR^R4?BoImuerua>h5(ULew9Gq%>GzIpwI!`>BRU;t>dYuw$C+9i5UVr# zwpuTcD$V!*uf3j=l})y{&U`cP%)5Tc*?a%@v!3;=XI=MN&sytggWkknHgI!J^@IzK zPOEa%k2$AWZr~VaE;@5=(_ciPcZ1&Kv&6tnK5T#J7_ML4**`KTNxag)%{j&@1Mf5V znD~Ad{9YIQpbI`|;PZ`eOZCKwGapuHJO=sAeB0_m-|j-cL*veTyWhage2Z}nK}=3YJK9PfF$BBnfxjBwr7N8}CqYJ+~e zfq%rndktLbUcNThL7Z>UAJp_x@5!qNG%od?;Ex#mtIfq{n%JYnEwe6KL@utC4vz#|6UrE$@Nv|qA^jOan|f`eZ2^mdI4z2L8C z+*vN(*0{4=EYhFAPWtmS?xfFX+*vM~4BRXi<{6-7x#%|N7aIE6XW&r-f55GQqz~hEIOAXx2w|N8q zutAS={FEdA3l01m25y$q2MpYd*CPgguEFO;12^l9cAX)e^-`C{sRy%Ox=G`d)8v1< zft&FfG;p(=4jK4GhMW=@`E%?`T!j|Gi3bea%qKC8JL7e+#+~t6;zECg3%&FWoVGRz zEwJFN`nkn{zoMV_Iq(y@U3}4j59xCL{`XR|jdzOYIs7NH<=L$DtD}oC$+6DOmS;|d zO;>IYD60K8k!6bC6H+HJFA~?ytnCTE4>-b=7o;^E%NDch2ldZ3h&;_U2am<@Z{&mF zBz(Ch@Q(L7TNEi1lkHo~os4l#x+Qo^|7IOT4}YZnn{6#|30JP4G%&sw_YLh|p5-e3 zOfJrL;CH=<>n_eE%`R1T5S5tto zUU&K@PiNW#vUW`4uVsDJ5iv(}Ty1&!wEXQho8qt2e^X2G*j*avysIrwFouC1Mfu)s zc^*p_gV%btZFxo>@;rWWlc)b=8g@L(y#2I20A=@Un2&wq8ejh#joj0oHZCRov|Txh zbyc(>*z!9Ib`3LOk29ek^t}np`C6DeL)>8dL%PK9d|XqO?pk=cNMk|868Q?_>@KM#}Zc_+5~rqW|A7a1f>(L>dgLfsF+pYc^`q3vTB->Nt5Fpj)28iv=}UyN%K zVW3>P>bUCkLfxN_BRj^_UHy2c7V2(;AExmUq$l-TW%U(pm-1L2#&o8xakj(#mCzma zMEoh0W<0mS&KJs#1MwK*USi%vyzkc|m{&)BGi*qhPlkRqa(^G+|7wI?!ruHuYq-yl z(abkh<>Tj@uR&(zce16{`q6eNkGW=I&{2(>{_g?5&nX?vJdjVlJAD<_k=21-@<#(~ zbhzi)Ao3eA)Mwogq?>t|VZ$b%Iu9D;k?Hz*V5rO*U_O`#ySpRM8T|H`alA*bRr?2!?45SnHc1!;%?->=X|q@b(hy)sH2_Pn^?z3TH6+GMlnw(kk=&s z5>GSyL-0QY|F+$eZHGV`lLp#*L2s~UtAh=NDpSAk&R(;&1a@5r3*D&hUgQ(zE0(j~ zkS+5N>bDnsGhlbceKKbqFvENpe5lt$l{UXEQ?2zk!Ty8%DEmA46@6{1vi;Jw1Ld|_ z(zMUEk*SvQPg@FvUwmddJO)2C2>(xUMY*pYlMm;^E(Uct^WMo#-|vTaM zGjF~z!?w?=MA}#3|LD1PyLIF_JMPOoC!&7XosgC^N6#JGUZF3(T*Y&qZKK3{<{6fs zPe9+6b#OZ7b=JJh_6j;oYQ4|1X0Evc*cNZedWHE5b@_V=9NtuRoh-|Y51;A3;V|t_ zD-Kk`W`$TDZDSwoNB&vG+fHNHaYl~#xKZ!mz%OuP4G31^3_)lE~TTVM(#j^6F-+grbqtKs}dGaDX%jP!FOPN)ASeER6L#6ve z`|(^cG_tMkN62Gt{*S(k|M9|1UHr~rjy&Wo_9uteOyF5#%YQlX5PsUue;5nv?(?F2Zz=81qmM$r_(y-uwzI>%O7Hv>>;7+pt`jt(SH1)P z0bWr*AwS!G3>_(6sPhg~J~Fob@XoaBDwHY8+iUwpz3}O=^#UfGRbGb8XR*Fw8+H`- zf!x>gyptol6KJz!pVOgbx~|_vJJyOdtw_sy>#Q{&QP(+Z*5b@F*tf3v3)DBfegbv& zFT8Wsd<4%&A*Z%WJ3yIG*MBofJ2!;GXX=P$h4;hQ?^f>r9C}#=yM)SL3X%>VEK=ULu0L(toMiFegpv}szVO30yfXQbZ)Z;$NGzlFXg(rX^l>n5brJfzp<>N;zU z2UpmZ7t@|JA9a)a?|7vHtKfKZA}|mS6{dgEC0JX zHmqxG=xT1V@L}0#`NzD&?yUaRo&I%gUECqS@U?m!CgE2%`EAILL$|v+{hXj5kEp$4 zHRj1zcin)%RyVZz8{0O(RAC%$By9F`=hvrG@J^C`xgHTAAFo==P(NH!VO=rWYY&f2+tqrAvu|PH!4KN$k#=^-^HXW!< zhGIxt3YH5X&C@lZbUc#{rP8TfG+t8^3)Ti>;ds0z60c2Xlku8pGFuabu%T!$7LVnU zftp|-SW_E{WsyOW$#gOqjh&JOoWOtKGVtF$10+$IWRjV1I9MCV1R^!aVc~F+`8b$N zWpcrEDjAJr)45cv25)1TR5lkt#>pmYk&9|FfoLFIn@NS^wSio$CK=5JvypUdGL=KU z6H8}Ofp|EasSV;jj!H6;#F%R^o=KyUQdu&Y31zdWt%9K(vSu_BkD;!NMl!M5XdqM@ z4rkM`D8h#@xlk&I%w3y8SxuvYMTJ&VTbqm`Z%5NCX|Y&3kV0vUM6)P|*-!)wYa__z zsW7V5OsXc8OJ`A)2k|nJiiJ>f2a@p+9s|K(vNjY>2Sdr)SZy|%sg1>IgYjfAkjkWz zHK|ZE9!D8P!HOnxkt`{b2qqSeq!2ua5UW@?8&4-g$qZ`eEVPtLr9+5)B$JJy!VmE` z9gGBXsVqxeC{~-UiANw_G?fn2Wa22{@hByZqozPx6Aq_PH=vfTNukou1anceGpI$f z*-)l7iNpw|GMU;aDvv-UhE^yLKphpT&D5jMS%Ms(Cq6$QZHyEx(BNIYv6OV<{$pAWHkrZm%bS@AMWs_(d zubV+mQ-v_d( zU_6~p1(V@m7_z4`$(ks{OozxJ3h`pKIW#-9(QG&!K+_b6gduhmz19dikl8F0mkY6V zNY$d%$waf!cqA7OqOD^~S`)90B*SrN740z6G8+NETJ(sc$#^(~#7<`r=X4|!s12v$ z_-09lQ|VYZl#0eO@k|CuUW=wJS)0nmlDRZeEfc~A3;LtsXet>AMIc8qnTm$vY4i#r zxf(W|Av9675r_qK*TxWmTo4%mJ>N{UCX~sBbD0#v%%x+I7&2Lij{OHDQZ0JH!EhGe zM9Ek%84Bmn0|`fJ!#N~Ay3fflzJns@4!-1MWHdGs}K}u(!#vs(5jl}|~ILbf}y*6b04EkGHL_gzb{&P92qj~)X3|`=a zer>}>^{vk@`wgA2b@zwX`0-KgU)9mJq1_*hXf!egjcPPD28~%CZSHFPC`|J|Y7cW* z9oDClbyt4;%1bZg@P{6AHcAzU)Z=^6BpxMwl>S{lZc9A@w@G$=ZoTd`Rg{MrK;O{+T_+S{8OI)Et3^Zct;U&m-+MTIf8f?*$;`q!`S ztXD%ie8#9ly^3}HN~{N1(a^ZYYHVq1!*EPf%lkio2jflU9CU&+*JMWUEH)W28gwM5 zwkPkHa%Wj;*W}*QGB=mrGAX@#;+_dUujl8_{^Qd>eTwTZ;mB(#Z{TR7K}VaMCUuAX zylH=toSIMGG5OBZQoBlfC*^LQc*}&{CEi}}vDqvF2?l?aQ|A&Sr!FOVn&j!5Cwi05 z{`{vpyak{dv`MXpdDXOvHE2JtNlEvjrn}mv3*0gB&IzeqCB44X&EBP&j`Ejdz?iPW zzNk1UIUE>mGU-gZD;yYY;_@1^cuh`K;pQ0bWjCt@t|~6cva7I)Ro`Awj9>y@`n3N9 z4O@~^@AV|_sJL@R>eeZ@m8Ex0@12&px%`$hc2C`NdTQ_F+fVZz`uVdzd*+}1c{EWd z-!aW2g8O%KB>j(AYl^XLI4!8Uw|{8UW3Dw-`BA?gZ%rv}aLu)&k`F62F3*zhnLkM{ zHiDR2RjyX!A^kjAUv2Z768$uK$>G)m&50|O8(ha*YijfP{nwiQA^1yNj3BMN3*O;^ zZ*swJa=~}G;J3NpUw6U3>4G10!4J9MfA50-%mqIRoNEpxKS{oLL*p5pl(H6sHlpL{ zd72CEcfrqf!E0RbOMp|(LA!uhQWst3LVvXj-r|CHY5vDB$wc?CeX+jeLXUZrarMB7 zw(<1Sr}^*K1wz%o2!m@`885LRmU{I28W$U4!T(m{i&!DR3I0zS7h7S$U)1}t=6p8*EcjZUDvrf3_qji zUai{D(1G2myE>YhTUW0g^Gc4y;x?ur=xlE1rK=4~J2{y$b|EO2VFGMv-WXc3PR+G! zSX<9Y6yB>@Az5O|<&NkTaSf^=;wc@c%o2NXYy0|DWPuJ?pB>XrYv^pWK+HJdqSk`ioC5U}$D|$e5FGmy z8T%L=`xqPZI9}gyC400u3JZsAb#$z2tH*w`nCNQ5nPFWWK-xMih~K&zD|b7ZuV1~c z$y(Xiif*OV(9sy_Xjs>Tb<7*qtzOyIv6exhUyg~xuIABl$h9j{_L%h+=={fil8I+D z&b0w-tHmwVH%>mwHSXlos&Vp>Hk;6912_5Ku5npIq93h3eIsi~1ZVq0M|!g@f53(< z(wq38#)ZFxE%mAJ7yJ=TPx)zZChjqPBYb2%<&zp0K7tQvoP6f!dtOTLOh^1o`&Rw> zH7@+6-y`jc@E3fML2t(QJdHc`AZt^Fzr;6U(9?`t@Ff~|>VbPT&=Hro@|o=$ofB_0 za8nO#uY}&z!#<6RoKc(1+OKiZgWwN4aFOXTgTEQ>D+X?ci_WZa67DrR+!BpTxPmXz zIOA*bk2v^j*L-3QK7zLxd`x)?1`hk1qTAu%Bk_f;lX6aZ?sLH(HSn{G+_837sO}+IRxT&8#F8s~4 zxMu!2sOcrX67B;AZiYK(;AXf_8sVDhGGyT9TI-h$9QKq&cS7T$hn&`fxu%!l3NGIT zq%SFQ!8$knaE=4NTI0_4t4ZU|_G`1onXkO z?=|Qd-<5bzcb~>3y##;MK`-_8V-9-3pVT_=_GA20d>D&uHAKA9D?}S4pFQ)bd~J;4iqWVJ3f5KRb+Y7bs2HKfMMnbv)nfafExH4)=CPxPm`m@Gdh>_iwxY<+Z6`>VI#gRF8D?RH{*MgfiE)n^cnbK1HaF}s||e6z$*>> zb&WgqAeF3B4;4BSQx6<#6t~F0&Hh8gz|FlI5(aLTqa`l*6&iQSxm4p$Ia>@qW_rom za>{SYxx=70_l6iWa5KJ78@NgTqJf+7m2n{{r_w(z(c?K%P6aR3xX@3~?fn!7z2M~< zKMV0P*Xql-lkl0O`OIYus7S4?6hxwVV$-_z3=p3!kSoPCW#r65v&jLyA75ntzGLMW2F~ zYTPO39F3E|%vq3q#KHdx%|GVgFZjhS{I4+hhsjvGCXGAmnRbmk>zQtiGrqbjr#SU$ z+}W@0Gw7LKE42LkG%orPe7}RfL(|I|c%c{kUQJJZ%C|lRdO+h&c^-D~mvA4^xbPSJ zQG-*4b?L(YQ+exZRsY~U9exa>hd zJzQ+yPilH+K6ystQm!RGVBN2B&U|vrLEkUw=%5$;ZG+y-4>Ivi{w9vIMwJtJis`Fy zkw@@yjXTqQk%61((qiC$U9=jY_b}m%A~H4F=l>) z5!c;c+}W-~S^CJ+=vAw|2%LvI@^tzy)&3CX5B(W{Mm0UYl+4J0_bbTZ{L_@l8E@1>n9D&_F?~a`pu`&d+{g8O56~0 z%D>TXQ=D*kaO$rCWaFj(qLl5Q?JynFpIuY&FX=yC*rac-;xFu}*r|YH^~n0q>7P8E zX%EP{dJ%sutKJbYM|3P!zvKDOr6=0$og@$-h5)A&OD={VWKrn&d_*s znJ12ZRkuFNsru_!cm5RC70cNr^D3-Yhp=x6!+cf5u??sLk zlL~duV%_YRbG7GlpM)RleG&%xML%4(?@WgZod(oB(*f((4=@eRBp$VT#-s)IN#cGA z_MQhb5BMOf_cX`adF){k_hF5*TIW5x`bN-;{^yljulf!B&MVmGue_6=GZ z3RyGFJvBt9zrcAZe*)f;CzwCPALB3SR;Bkf!u|=YHM<$_BiPr;+}mLY>#iA=@IKne zbD+47k9}4b_GYLq;Jh8uJdZS!a3pLA>yQqM=a+H+BJ7(2+S!oVJ`)#I4p>-^Y%y_v@b z!?vNJ$=`yX<_<;0=L`KS0FoA_9c0zf+}YfLCBXJegI8xW>>^CYAIRc%)PBd!<`#NZ zcCyOkmWH+&+TEs=-R-R<-nwUh{!c{%PkuWt#XsH4w!ii}rrvpaW_RhHN$FivddqS* zPrl`}TPJ#3O>edbmtV5^kFWu?hgO|6xh>>)ww{ym%Cz;AE)lLN9bi_UT5ak*(FKmj(!$0AIv(Su3f2|9?!UbRB zf^T-gzvP17;)3@BryeB!DqjNojtl+wUGRro@F!gGAs77DF1W5V%!AE?Fp$#raK$su zSv9DlKaQ>2m~#MV+{Qx!HiScVO=MxagSD$0SGT=`*_FvutmJsMz}gLJNkKgi78q0K zuv((AmI?-M6mi4G;=u@fW))UE9bM`(a)GJWXddDijnRcEj_=Div-Iiw&>x+AGYif> zJ{|j#CZC86E88ivl(4R%BOeoAYQt*o*393`*K{I}{bX5reIxP+&iqTqZ*uk@#qH2H z#MuCf>()2agZT~Lr*X#1#P@5Qe9XGyJ_DEW6G9IdIQn2kC*Rl-FZrZ;L{mt-1V5&6 zXT0Q_+?kHAXnJS5ltOMg@-gQc%MD!m|D=;|cxQa&8=kls-;BY>jPDg1cg9z~`JM6I zqv@UT>NEJ5`A5bAocU_Mrg!S$puyi9iy1Wd^YcdX`NJ9)IR!uFpcmcA7=+LZF7==@ z9beacD5oh8ZIbDn`mZo>Q~wJL+|<9ES0MVoz-DU&G`+|v_!R~pvm9M*;2d8N*JR*x z1;n*o;}Y)2^t&#NOSppLyGS{wes0(JDfvO;r=+jJ$IR!025!zhKW5;j&4k#ZQ6AH# z;bl!P@mi+iB{pgjFTv4S7&l&X4BV_w#DdP zO_Qq+0Nx8O?e~ARv9ngK7l}8=NleGQJ>J;KkQ97;^w7QEv9oVD!nL`KjvdLf+t}HA z^&bQyRw*H2sITI`$$^uWPOb@kud_K{DOau)W!6!AA^vPC_p!5T?f;`fsITH*uAdx- zCrLk}{cCT+t2`j6dt_@v>UE?xeMgM}2krZ|`VZg-&CqI#3LwXzw<57OROx z-tXAiBSrEC)E~W3kCV-|$7cMXZ`k&g z!=U5ddd&XaP{8sdmnLp_5PSGtVwJ83ljY#q<4qLms!FT@=!eZ{PZIa&vrg?)&Tb7w)bDf{&-*da02^0b018ryAkPdH0N@5s$Ex%Nl!E~s)vl9Q-XZF`s&hbNrbI++P^`BE~ z#m?CXKd*CdLX1~Z7tdo13dOH`JHqfxDj@v}U#mhIRa(|h3SMg`Y0y{behdD@&zX}F z1GnMb+efw^j{B_3tuwf{bK&sBHJf7C3mkEn`_lh|{DAXDdVaCgv%V7NuU~>aTsb~A zgfoV}0A485-Du}~&|g)1IKEK6=Qf62xh1iS;fqWRAGYZ|DadB;Gj7W*vRn;WZiOsc zr{Ju6$dX7G2F`*k^mp`$?!(xh`!{d-&Vo$dm;MeieHJop^_{h|@|F77C6MhwobmiK z?4wHE(vPQ5ID8Spx28OJ+i`?@x8{X?J`W^uhVff}yD2ss?}ny6cpK?|>9v0HC4_xF zXp#3(My;Og8Gm&f#&3ri4-ew=0?u9LIG@Cc@l^6nEDVSriO)=&k$t2(akBF1hR;7|tj0?hS;Ms@g@N;N-n;nY8O@z|FaA9k%aro!v-jM^7&rFcWt>n381Cw` ztdqFf<@l=P*zr#2gETwNNt|Ta*g8Ei%)C#GW#KO5*^i-YbC2I7uDh{EJNJDQ{aJ5c zw(E<=v!ns>JAqr!C-?BJL7Dj}^#0TY>pn9se}{J%3w`x;><@bX7ZbaFI`yZw%{jES z?#P2%ryrd9H1-mnQ%zY=MyqeecsK94XXnf*KRv5qQlYr_@DaTyC*O%~IHrEY>rwd{ zefEf?#|xnSCHOrL-b@>&8~L&>p)6kL9A}0Wb-oWW&h%_NhkF?R5`LeDU*To?=U&(n zx81mR+Z*%6b1z+sx=`(pIRu>`Y}6sBS4tBD4eA;7FXTPB>4)|n!KmvgAIF|qpYk4c z-z01vghVz*Jkou-`v!_45)Z3r8d~3HP ztbq-0m-IK{%y$V-;x&jmis8$1!Dl~n4uZ7ie!Y|*>0hU0&~X?q9!Yy%0QnPT3iX_lho$t3u5+k>+ruX0bmQxFCE%0C6miYno%?<-ny+#CtkF{$H7+P+$X2@O-S8QcjqUU>{{A;)y-Gl@`>G7?795b%WnJl-X*tR zI$=p}!pF0|I5xN#-A`fSl8GN@4`mVlRoJ**i+^j0eP%h%BVQtCmZ!8Z3-J0G(!dq? zqvch5Po!`5en#*1z&Mp4bJ1<)k3Qo(z014Y=9wuIo_y`*$l~8~?0%XGSPN*@6d(tmb=qP@d#sd!gFEk!=;NR4ELgOOmA&oE5xP<%H8ei(b`8eL(iLKAcW71oD z1wChUPIcqW+57{@L}#Xte{#WJa=~A9!KttD_+z%EXf+BUnn!)n~nQQzK%$>%OGP z_g}4m4J)o|QhPGcvwg>n3`Xy`W5e1uY#NTOyX%|UaE>&BT(=hcQmyM+iNHHB1y0WE zY=q1e+SmXsaBh8cMr(Z4SzV2onrdIw!TlYS67*Pttywjb#bdo2TJgTE-Jy7w8{y=zFL9H)-4{|2~bA&wPE)%YJ?DlxI-m&UiiSLjRb?xsG6gzUSp>eeaa> zn8uxQvOc79hFh+2r<`*%?o8hWF8CrBJ~B7uf(LoA^Noz2xr)9Q1-eqH$-up3%57U!5@cp9XnKv`lXsd`!F?9~E@u z!w*k!93-Q2hP%WCUvBXEi0xJVUaN8HhvD*{E^qKLag5(6M|s#+6t~&nbGm@I77RWn zF8dgek13DrOW=%`%ms;@nyvMu!Jl#p{^s?Tc{3VZ&-EvJYa$c&#xLV^Pr{L`df3x1}GU#;_6(>1^T=?fT{~ZQB z^S|JI8W(!|dkp2yK`;2d8fUyrc@8@GH0y9>9{`D$;O6>$Q+{*(zKOrB`8)dqvQD4! z1a+9a%+=om#6t#-xg6z)FE;QNjZ+V1xUCwe{3d>r!6#twk#+av!zy`Dzw0&lnE34m zpNPSyPvgQzUhQ-65&Ro2dg{!nJM}hY zk{uYsHT5QEw>#^l1)84xnQuR@WtDvcBwq=Bo~C!!V*!m*4`w|k-vwghpxGB?6I{N* zr5p%8s6DjktKd)S=ldMEd>;=vaQWUXg?x0vN4{(SjchJ1Xzc8YSr`2Oe2_TeVQ`|PV4yJ$NHzGS{BNYP{PRiryus) zbKoThWDe|?n1kDHB?j*DTkBge=Lvb$9CGE7rdSiyxphb7PQNwpDbC;CZCUXLarX2j zIK#IOU8qY+c`hpb6sG?I@Al!{cAph_7}pH0VLdmPz&*rJ>r;@w)jBB&2Y5IBdyZi( z3DzG~SK;h&(k9`TXZ|+edKA~Kxbke?yK&_jqaPu>pW|9@Sv)7TZVRqzJ&LmVu;&~{ zZuC7a;T6{%>9g2*J|@j$3&I}Yx+&TRVE(mkiwCXBO+ZTo>)2Pn$|(2>@AwjJN^`bN+F zuKY6R(`~wpraP+7&uoA!5~s?D)zgP(^4q8BR%m_|=9;3mv9>aKzdgr3{B>{7$jIdW z1pIOxng@I?+2ZNHWQ(_d=-aRj0Y3B>h}(_cp68&ep-ZS^+g_!BdEp^__U6x!4#yg4 z(=l7cf7=AwZ`kn~EJ50rSf7Ob!1_|GWs-FFFUiLmkiYyp{5xUA`4p;8WrxA|F;0w| z#FO#cuH%=&`I5{ZJoi!jyk^{U&RcNW0LlHo8uu^Ye(0(MYKR8nFjl)K3{rr-|@F%8P%Nt4( z@h_kpKX<7$d<5^1p3@PZ-XWytT-bFC`R20h^gefKVwmOVD4rWiRh)Z=>QixhjT&r< z7^`l)mR`zLbk~#LzXVIL{dMK<7g^ZX~V&DXf4!*T+4X^b`ZmBO8U>ld)Q_5z#g^xv$!5dK8t|P!#Y#1+Z?&Z z6F&ysKZiA%YE5H3&fmVq)AM@0C;o@D^FW(%?3!f!`TAu1!~`qyf^PzBWJ;=^53??_ z?JRg6HP>JM8S>qBqt1a$hlfBn&zkkpBdBwD&T>NAi9{jC&__{r5&yd;Tk9W0KKKsK zg_n9sbk8{AAMH{_b>)+`ewpsxYFl^qd&XyM-I}m%M|ziomUR?mWZgYm`i_R{Ep{y$ zE$##EZ)$6{&!BDIh_hEMOm8(fu?_3itZTb|onJPY=APND>bTeq9mX-M4ISWzRhm}Y zhAw~GO8?sCwQU_YSl40Q=E@uDTiP&vMp+QHb!D3$xBjj+e`{OY8XTHZX>mvI4QoN$ zs7RG;jkaCO^{cyD{8xVJiYxqNqINIuT(cVIuQd5HS1wa5aE1&vw)SfdkgiB+P$>D; zqS2K&B$ubm;z(Ca_%-_t{ivf~;K?e|tX}sHR92QdZ~Gx>WU7P$X+x4BrxxaUKt-O;lU(?*UhOgaXxUkdz@hknyKeIFoonJfOzwiqiXppqg{;}-W zwr3FF&*EE5-F9whZ*S}1!8;ADtJ*Mq*|OG;t;|0Pl{c+kg+0vcsYH~px###7z*XVa zI=y_xEaWmw_%<}Do!!UFg5%^jt9c^>V-Bt7BD?o(0W~^bZ`wflA(DKPyh8c3T~KD1 z_d2n6N&uF5%_aOUNlqQ|WbZiZ&NEZHEB4Gt@0!y)JAL!4TV`f%oqpT2)ZX&j&+z@h z7|W)qRt(+>_@|>$9(bsA#3`;GR_ln>isIC*6K|W~{WO*m8)wWYPviWM&A+ED5`!Gi zRtSADIEFsfO~#{XqonkyNyf7wVncuG2!_2zi|dQ0X}>c6vJlV2Y4b#f{*`h8{1Ol8 zXTdo(O+2S@{b{J4#BDKors9j1BtA1*kH$awVp#vTvf2%NSdui#3j@+7V| z;eR~(J6-TUbHTskf**3h|G@=+)&+mj1!sK5lmEC2KH`F(fvhWpn)s9uT6=+!$LsBgTe>%StTTZiJo}Q0Z_SdzSGW zJ}=@A9LJtbyvv5wTWLbEAGfp|2vR;LNvl9LpEpeD-UcWAi4yNaMm^ z-kqm$;V*c^pr`&NzT`_M=^}Wm#u=|UHi`P(Xz-yPcI$VW4L&Bm$KYe?r_aHMA1ZYF z9DD>HH29eEJZ#XL@e*4P(a+O1d+Skyo^lHQqyrbdz339|D;jsE*XtU0rq>jF2+>iW z_~t4)u>m2DZ?K{>Z9q7VFRtC-Bl9qPw_W3mFZIANSvs-Zkn|P&c7vXJ*r2E_t54&S ze+1uW;HIAU8#wuh-tKkqr_CqbeGYoTAJMooznQiqC5q8oCA zEBK2JIb}Fj)+dV|1Q#0<;UBcwTVi7(^n%L(w$NuaeTmP$5qiPrYFy}NYrWwcTREW@ ze2K=LdTTQ1&Gc$9aOzFct6k#~Zc6LLv{fO!;N3>Jq8kc$pTXY@chJDia36MrE9Kd= zJt4i|Lq@n}J%BMf&lSCA|diHTanFW9&mY;j==^FK0&xAHg4U;Ui~6P<}IAFcvt5e}m?KOyj~|@Rtq# ze%q`1oy)|4BmYYKR{h2_F7kg#^S@Z*!e8*E1|L%oO$J_N@Nad&3mTVj|4fG~HXRbK z;Cl={X1Mzue15I@d_&{HNAQOoxRj$uT=+lc;Qt|=Po8w}5&UTbH_Oo!-LIwoP5I@_ z1ZR76wWfErN2cutgZiIZ9@BQi#M?C=(er?&@6x#FNAO1+^k3HWk2>fDH*F;r80jv{ zNf_US25#C!R2jIOnLs&Bz4`4A+Ee;DrmY0y`$H|KX)9skVk;qfkaQPY3DJY#OLVx- z^twXh&h%QU@l(>v;A8e5wi~#qw|y?S^cf~VKRT*PdkL*W!C%qO+a0)kSN$8=R#+D_ zwyj-@z2&S6U{T+E!KGJz>U?FB4||K|)`kn3TX5VTMm4n*AN=BnS40^j1!G-8KR%-M zC2w{0Cy%bS6esk@V_%s~$_v9`-lLN;`eED?*Ub@MDOW0l0_ZDgO?Mt1Ncutjvp+(f zGJb+vi_pw`8x#c|D^XJ=S-N`O65hXqQ7Qp?R@y&q<9K7VwM3h#UQ zkNLm@LuYS0Yco+wewk>vz|RH3UH*nu4VWhB@-J-Nc%J`;whjK4hU>7YXX{2wQm)ZjT6g88 z{?+zmQ)hE?6V^!aQs04HJ^ww%+Dn+^7z0mn6VCLm$zS;Svrm}gvv@nkk^DbmtlO>v zM)RjB)Ocgsj04A<#c6XU^*G*`v)x|*{$tK8svO%Cr}T>l_KS2gB(K!9)&;-J1^=82 z&M|=T_yfVY}fn6Iq%@cgFH6+GeF%;U0O)0zT zSA3zlqfM>xGX+tDv8ug5)YO=sneRoH?BkjE0usZquc)ha^}I;^+PL^76f^K?`kt4B zz9%1(eyPT#@2VeFg~NO4y9$1_z9GHYJ-Sxoq&M-rLC-$(h5ETkUxmNm8#PWj-^_pDcF>v(i&i>sSc{!dam9Jz>e?cAs+Zl>yb&F6S{_&z ztmy0%TW697F9^!xf119!)O!-Y61fq*;7Tl^pXG8w6UrDW>wT#Fl)l!7HSX*$Wk@`Z zbHZ+Y_4n%Et*CuW_v|e;*a-qOYl!Dzz4(a(}W+iul|HH zAkEteuLMraDgV=&FlNZaTj?tc+<=?$(!WJl0&_Is`|YcjYk?N&EBor4x0djQuUlVT z){*^4hc6dr{8^Vf^B!pnTK;zNsIN}{t2#T+U)8*>LptllzIu54zIr?oQ9bqd(^qdd z0!=7668P?cCO&}8h9f%ak?h}pU(lIjY|g%MwK=}s1AX{Z?y>zAe~U_Oe8>9ql&aKi ze51(t_q*ZT5Op=$8@2f*?8_;?MzX1QmG~v*%x?+x_S7Gz|8qgY{Qp7yv#umhXI+y3 zM)z)Y4L|OjGSOcff5vf6T0W1LYov_r*F0GC`2SwZ`Wzh)>j=|1!*?s|3p8QY^o)C_ z{|##!8l{}G5Wiblj|GBJyRg6a@A9S6u|W6&AI9ef-1l1U7c9LIJ3+xaJs;d{MubK+|j?ziJQ zVY6p%b4AJC#?98={3O`H;<>Sq-MPImVQ0SF+B>f>;iVM6kzc0$rS~t=`kwoCy$IXY zi9^$RPHy^9|0I0N|Lc~-x43q~yL#1$L40f1Z%w^a37gNqvFcCY{1cp|a@v7kU@yo) ztViHI=~6FSk7KVkg}vandVY-mk%aZ?5uB@Xa?^uqf3yeTpYnU&ZhFGHDe<++QqNAm zW$nBhcGpKdgTLY4ihlyRjgg0~DKM;@hx?p8c%HEW8}Q zpk1{+X5m%Ow5xZFVO%oSu67Xlf&85OhrXQa_fOiopZft;LUz)?M!A~uKL&k{Ig3Ww z&_1HiqWSI^y{WzEpf}nvQ%}Q?=dI2O{hK#WSe7nL^iP_TSUjOTxtM$TJy$lV|An*5 z7e7B^+F}W>Fx|G@h0S?&p~}AJnJ|TreSd=YDf|8{-q+dpm}9Iitgw1sY*TxZ_F|vM z>OzP8?uE0LF2>kXb^gNC;-6Tv%buU{$;H36W|qC!mKea?YT4RQdhs;R)Us7)Wfu2a zv&z0=%_&>+N8jArJUzSkrStFId)zy@;U zY?E(N*=Kzd%ASM${bhdy`Y-O^+q(^WV{W;7@9mSU^@|t(<=##_-~YMq?|o{;_x7&# zoVK&kI&J6u-dQ_S*36xEA0Jt6>i4bAZ2$Z+3;Lt2^4};umn`2t@SM+E?afa^UM$&p z57Ol1rU%B?{n^&=?4_B3-B@FA1pD~Gp1zv;oz-&o^bM(UoO{FbYGx^ndg(h)5v7STnRc|A(1@hPuoE{pSC$WI#P%kQ%nyKeX(PYf}A{gZ`bz&rS?)^;*LX zq13P+&xMKB@Rn-t@RkekAMy?}UMDv_tm0rT&kPKm9I2rUo)XG6^8nW<*!#53IN$~L zV}$!l6}Rsf$-Wf>4Gk0>oQ^vD^cy9TfwSG0q z$%d6@O}EQj1#Hh}R^J6bD5sax-&2b3%rer?WLfvk0B@`j01g^1zT@=v5TPuSa7TC%rlbNR9oE7@OpG3qVUvB{E>y?I?f zp-#Aub*JaS!eQ2%)(NBo!eCt^bsz8Ty6kSHJ6m6)>kOn0qux}#b=noI)&IS?92K_8tV!WKj`6rr@T~Dtb-%oikG&cJ!AeOE%TtS0&Cik4^xWItOK=(>zCkfW7UkFp+8Ry=cC@? z{I^)9?7BC9kA2_udF1zXB?H%g4DX}Rb>8|KX@|ZIKNkW!Z1_2({dANJFx>ogB?Aoi z`jmHg1;RzWGu%}*9qTu+=PmibKlQ*>mOSO=lLpL@*w`((AIGc%~7;>KD1j6rH5{-yve)Q zv)wu|&u6{V02<2a!&(l|LcenkBp?gZgZC<({uc2M+3A}9`;q}~1>2j^_GQ~Se4cq~ zdvknjdy~*(=1eEEj$@fPx#<_GES%hAuZ!V+rffgZkLtGbt{3GVeGuLD-i)?^d3TO~ zde7`W=mz*vtb_3~|2~$%da&~KA3qK2DHkMuGlX!5(MDtc*aP1|+<0X^diEshwU4ES z=e=TmbG7cD+lP{v;cO~c=220WJMGRa^0l*13wwgDLRd^-HF3_M4Fe=q** zGvt^TSFD)U)9|az0LxiD@_NGZ_HQ{{GBAieqkno%BJSNhXBpa(>8XwLrYGi>hS5h^ z^B~qkZH0U*{+03au3>x{{6zZPx&PM&u^WxZ^RU9}z-_x@O z{MhD78xYX^`D~%xFCYxkehPYEUSWMs{W1^U@13!eJZ4$j58v&b@slmTJL6T@VJ>GJ zJAXTJe^KAirx$Uo#6H!`mkGU&iTy3|_CdBce^%1}Erh|i9ofP?q|2w-VcUH>h9@$H zK*#mFWAu+USIz=_zu)xb5R}ebgF#HjM-qGqM~vTpbW81v+H)?U@7sAzPc6U0d#C3Q zv4QJUD_KTAdE#&Z`=Z9dyW}1P8*@$t&mHA=o{`!$wfFSQ%~NhEOYfe%=d{$VrMFG; zCdVwixESR3YQHVE-)_$x)9;*?+Ew0rM)u~Zx17Fv%AT@YC*O8jYH#W7le||w`}5J= zY^SO?yrTWfSTE%&AuqUR_>-KPxTEyWNvT~EdnaUWF1f|G+uMZr{Nqobdg4h&3M3Ky z$F!dz+{4e(J0{0+NoP1=zCsn4wW3~(ZJQqCdf?uThe?t#` z4dR(D=R$v_3x2ic(>mGqFKY&xfKNlo7Kb&&wo`*!Y8zTL%i7-1WVN@o-VlwbOMOd2 zXN$GE9m@=xHZ(TZw}+$Fnx>W3x|aHe4I6ps>S$QkiDjv4`GA#^SZRYBy@f11f(-U& zFq^>Mwe9w1wO9wkWB*1M8m-xYtJ+%v&-t6*QtqSSwQpNriS&=gE%df zT(RXW`7^flP0g)X!mHNXFwXilcb`?S*EL^nZRDz4EZJe`kX0m53oSKco7j%AoTg|; zsmMudL2+&D+DL^<-r{~wGj$U1lE5=vEw0jr^LeSpSznpBv@M($6+1}M->2z`oAZ{k zCWH8yB-BpY8scXOi0jJ+AJ(F-(13yQUgXh`c~*k5oh{FG*15W4L)K+O}x^;#rB!_0t4?g z_?Y;;25#n)K?67Q?PCURmb;e?e4!Dp*cwwF6Tg@f6mU+w$pvqB!Jl!#Uv$AQ*6o5b z+$ApfwFYkLq16R%*SIr%yEN`h-ko9QlF z@;l`((e0p9{<#{bK27;E25!o;S>sOrH@WcthQVi%5wDkB@Fna3!%?0}`&Rw7Yup)b zm&Tp(y2;>U#_OPgoAL;a*rZF)(oYpUpnhdbksM>3_4|M<=-S zzhAfEA};;!Qq523rT=}g1DF1Hmjjpn_YMax{lfhYT>5!K4qWX6AF;4K{&VkdF23NLi~rp3H5V`QeaH)XpDbTB=ci;o;P}@u z2aovx&c)w=xmeD*&&Sy7bC{3EI)THSi|1T6=i!fF9vkDqdR*0>ywJ1JudYfiUUgP# zar5-_;zTLN{xN@&z}aj=*pHFp)4#r`wEv}#Okeyj^Jgr6;p_^`&CguSxKSsFeGl)k z2YhjEUd_{oY+TK`k25!ag7lc1r(e!n$-F%0o?ke7`Ql#8jlcAfy2a08-u?L*pIW@s zGq2~eO{Dtv-f4Nt$~|Y5&G*bM`<8WP+5h%Utp4>yOZxeK zO?75*d*WMrS65{rZ~0EF1>IThn_SihUaw-VztLA#w#_=L>}B7CYHRbodjnQ!Sh~1|GC8};NH@&@9p!Jm6iJ@!p|hocz13CpXTY8F8+JZw4J269q%mA1z`t} z_LY_$^`2H1!2JJbeI>97m{fM$JE5#`^EcI;e`?CTdr@=lJc|9zkfuAS?|fBaU=!>P z@*hqN%;B{#&!g=O?t)DAT##*N@FUFG z!M+GK6z^hZFsSVepo0%$XYgkTr+~PgVrKvyxY-%(fgfdO0Dnb01JJ)GJA;mYBRhj9 zwVgpGJ!TZ2xO=fu{)-di>AouzFJ zW-40)=7+Ji2BY<`_kTHU4aj@O0pB0nwg%ULKkG-zO90)=h!PTb`^^Q9!(A_;2)=9|fX1^7q6^91MOzYZO(n5lGEH6@|!2CRbB zQ#B=t`%BOkdV22iS}#>%{vZ9WVtq_qQ;t5^5e&eN;772dpdG`UY=bP*9C{+Jt>8u50`CIa42NhC+0rVBT%#$ryWk2vIrK9!( z$I-9gJnR7CWY5RWIPgu($8JRW-T*z%hCd6sF>MLZ$sP85IdOtIy&vtGA7yzEek#{o ze~|VB1=QomweCk!)~g<*=?dhbM(||WXh8m^jX=Gw*YcLtpSMuQeSX3~{*SW*hhQ^M zZ&_>ZhEDR^@C-WvJ`dt~5H|y18-&l3FY}mf z`}A%$2pb_!0e+MX0@46uJHM9=LKFCzHV9Rkf6)d3VKAT1h7LZk4FYs++92dzY!Kf3 z*@XUe2#4`2+99a$PqjmM?d_4>&pFdT*&*0AAV|aay}qDac|y+oLY-Bz@f^?DQ(y%G zOuji2`aLmzztb(XFCWMjAp*Z$+W!I%Zi==D$_zmnBxqZN&E_Qe#UTBL_II&Y`McM1 z$E-VNrgl~I&dA(6{g!E&-Q|1ExOM7nr|+F|dztsfQ|%C5*8cTyhdqu!8Q|CmK$cVN z5Y~)ihY-(8Gx_sgW7WqwG_`xNy|6{o4}9QAx_skg(_GvyFl1SQ)k zHVoLK)BCAs#W+Jp<&+D>{`a&+u!p*|a*WV96v8=saS46Hc{bwmEP0soGsL?!UAw+6 z(zvjaG>d9n#zIvd2F9@^@>$Mwf>U`E*ChTGe?2xMdGAV%@6))-f55nQjr6zcxtt+9 z(>36q_+~vuC-T^3Ni$KDqd()>BG~l5zb(S`2&Q~AsN&rhUGN<)I1I1G<+INPzt;u- zD;NAJ7yO6|{*nv+8{o7d_=eWMDzCsi$ZF&1VX_Ne0i1lE)_hdC1|Po*{UR5fW9Q?A zo6-E`3=viK!5^{~_iGzlR-y-U?KY zX10#s7}evA@G|4w!Zd~cX0@KISONM87TZ4-KRI&&`TOgqWdugtL!H|L^F+XNGr zwukhljl(g6ze#_>z|C+=b$v=c=G?f<;W+WRn%+6*=GVA$&Mju}*TdKSL;FSh0`-PEyfq{1! z^d{bI;ATGTGjKB>-fQ5dZNj4lZrUKs)sr%m$HW)8;Co!~eJ=RxE_ekef8ZFdDgOc& ze38bT>2jXNo#~P>_?YErqYIxME_^B`kIH$jeXD*q8n~G*w`<%fXP?HMavn7Jm~svo zxEZb-s7$??xL>!6PCVg)%bdSc&h47sDd#?e&muz)$6WBllu^A^8uTq1cZS=lac8*O z4L+uR?lW*x9;tI=E?t5S=o`Tk`Z?pkyY+LE14kE0xs487`rY?BaOroy;=sF0Y@kH* z6M3ZH&G^#^F8%Iy2QK~Y?G9Y}gZmt~^zVioxb(}U{gQB{pY?yb4Z<;=juokOLBmh<&5gUqsKt9eFzaFVg;t z{aNip!WRZX+>oc_Z^6a?2ev_YkMg_PAZY&y-LDY2obq3W7vt%Fk1p&>wZf=>Heup_ zj%LW$jv&;VJ!jYiAJ_(AhV6fgBf%Vxt~LlS>Wb(yHk;zF(?7PaI=_8QV`ngEn<2cL z4MHRkkH*x70Po!fVaM&o;B`K?_vy1Rmd~}JBWcg$&trZVdMxhQm%?}`&+dE8=R5Gm zH9oAdEjcg~bHi!i&z)<}rCSM%Db6h%{`xxXdxtr9zvbPFwOW|Ro^YU|1@p%kv&>tU zJT>IC?i<1w_zQT4;^exQUj}mp+{CvF|4kH;x0#oX5#c52-oaRb1}}9sCly7!x=9 zd_(Bbj#C5ZWPS(p5RuNy62mV+zLE5_;@q8c-5%zMUgf$rrp0lr+gmUv@mn=!ey(@e z1D)qF*HHLMVqg&aH;$*D6wXC+>bLSHTOQ^^`yJM9o|}UCMgnVdeJ{~oqwfqmo%Q>H zz7WqB+xWGu2nY2+H}>{^mG6cSH}r>!=Ni6HUbu}qkn}lfgs;Ob>Vs!r(mrBvf^{F# z>afTu@gO~UZP)qp9>3ZPq3FLbIWhd*&nJc}u1E|oo5;LwkCpRWN4q@ObCLz`B~NjF z6X{o^fxUy8Q$*VI-t`sls|Doi7Ub6_ORW3u+U9+A2+tzN3rJftPASaw{RFZyQ5tLn#g;&ho)iE4p8-n$T!dLRje7sNL?7dsSle*_wX?K0i z`)VKN3hwfjy!zlHBAE0BKgY3-3)HqU);pg}N`fyxWF%UvznQ6z{IbyD{^5 zm`^~y)a7J!Zv&PStU)i9lQ$7p+P9oQ9zBk<%umJK(M0Qh#+Uzj%$*LQ+%T_6xuI=@ z>_dRh%I-gd{z?#U#+`Lx3eU{jeto`OzRDWF-l9S5SFoJ?C7+Q0pmEL}&$M&ug}Oa- z(b#;$^AnwXz&ql^62LqqkOQ(XEObp+m#OpZ`u;S5w6J>n{?r;kUuAd*c`<($+6-dBU!%?D;*mRry({$B<8$ZxT2k)-C-l zN&AqU_IcgzP|o};=o{tq2-+Vz&)D*lH~MqkjmSToM`k-j`gcl0rUz-9>FAbzTM>q& z)z;9dX@~p^HJJ5kVobf7VBSD@W6OfQ4+Zz{J-Nx-fAU7Atxu)x8`qSe58yrU2K0qE z)g5n~fU;(xta*^ez5_{M!(nUqOvIaf{a)+-2E@sa{Bm*=_62G3fLFR-@{!6z*JS#c zH;y-AZxiJo_5Kc=@%s?>*0Cz)U>=?IrLaBj}u@~6ZBb^&odK7>1dKmtZ26gwq zzl1?KnEx0)!+rzdF|1+COG_A4CD!BfTB>5$N98~YeT6~j|@Wh&@X{GY9&EjnCmT(a*|XhPJPxQsKy$7-=zo1jskLfG)!e_Rt#kjGpLR|p7!ilMPK4d*^ z)}^3ReF%x8=m33!;d{<3)IEp1K^a~{UHcl!nY1^R8Lz4ag8iwo$N8#lk3;)~6^LsRa&AR?E9u61M$(LV+KAx^AI zl8DnaC^!6Wd1|6}?}O-19`Te%AN*&WR}G$(C{NfArtTp0ned#5x`k170nS@SzJ|R| z-4M>_9`T*tkGA6QP$z6>{kUFl4G*V%J?t9}ZGznv+TP!$aR&1iYq(LBS@^ri8@DP! zdn4MBi}3Et{D#8wPS7J=kKqj4ttgM&v*C-woXVPO=6I zbq_$+qn9U!Pb{;B2S1N|fwRev-01CT{7du~bb0PWKA^paUP$%Y$^o8N-M9 z-TB!B_I__l48OSw`nG&~k6z;K`R&d!?2!Xqp?x^|9Lfmltb&dk^E+kg!+#~@kvN(% z$@#-PbN$36sW{sj#t-}BtUog8uCs<;vijNIIdZmDJkuOuRWprPX34AJO|Pn(*fy{Z z+NRGme)_~nL>m0}RJQoWl*y{7o!Z*v@B&VfA2#!<*KrD+u^;?KXrcu zrXt0-J?Rp+)M(uJoy>4q2j;bo=Zw*@Z5OL_Y|Gor_6Ga*DIFg}J2?S5w{<=9farP% zV?`o|=vU%Y3As#Ndoy;Ps(>u)BT*(vTb3WnCw=4}K+e~p_k!2+_`Gt@t4q+&yAQ{3 z2=5=G4&@jP{q+7W`2_oL7W%O7RhFvkSXrJ0FYe*U@X=pZx>DmgMjG`aOv=u-n&EP+ zh<*mW6+6Wb%SqvPBWR!bJ!J30N7})+M`}bi>P2)V`eDCeTW&PH=tsigcb)1VV10n> zM|X0Q?)#_R?YEafR*nNozT~$yZMnIg7lIlX@f z-v_kKpSRiDKZLets15m8x0x2o=xbXh_gh=6y<88`mqNXDjivUg!U*@yGf|hP+xX3M z=7A$&yUiLz`V2h;K4=^H{h3GIY)vWY8M@pWe(oyd`>%U@GM%;`$|Y^VAjWx|`eMJ~ zp1p-Sj4xNnJNDI%q3lwpe81whamKI^9(9|%-VVoaw55_h!Vh$I-3ULDPUL&&>vrC; z-?O|rY@IW9c^&%yxO*G;DvK+B{JAecAczqnB2c-3pn;+d2pTNA-n{T4Qbk}ZTDnQN zxsXUmViGi#v|cf`{1*0CvIIdvh;J=qZGY}A{cW=AuBoM#-THH>&@S7x_VT85bz7`h zhz0Zio|$uQPKNs^w%h&e=Rcq1&hvbqGiT16d3k2e%ro!o zN1>M_*2EvVGuqP(nM+pU9&4FHt6T+>}qHLTA zgKJTzmuXeJT@_&M_z# z$THB8_l8&B5nkk(z(6@71@&4UA z2KOX?4&oIVbpAb6-9LV6Z*E0?P<+(3bfZ0zaKuN4UB0?6Eb*r>6yIklKR;dAMQv1a zGv$T({;T6iF=M+dMs3!vkoRsFw&EPTU;N@Js!1$*lgb#%1GU?z6Q;dPpg*qjCKq)D zahW=FEEJwhGLgJ5Aw0=&3}Njawxdix>huR#$NWL>jBd8!U~kxt$PjCn5Ltwu@G3zZ zrcTycp2@G-v`9LWoHlvCeD;^Ud!f@s%b~kt$e$vVf9xA-;{Wz<(RZ60?fF-%$KMsa zV%1o@&p$l@<95)?9(6(VL5`rmkwn_D_o7Mn5rm0&Ue9-fiLNnCIcMpcEwpWW4}p&j zYrid|un$pKJhx%3tdy^WA(K)6DgHzk>F3O^u_xd?H=gZgK7^r-n|uU)_a6lN`n04| z>XRBhJk_UjqVM<+@5pep3+q)V-={qo@+Pe^dDmCJdmkLS=Ul3eQ=W`XTgM~$wvKz` zf5JK*<^H_nW6s|Gb^NKj6N?5pbroUy*HtG>a9Lu} zY})$iAQ~n2sdIS{`I0I*cFC=JxeB??YW9`Do6-%xQo> z(Iq@{o|mb!vm|C|#7nHH({bf9gIFEq`m3L_a?xZ&8 zxBc6o_KzutFU9)v{W_%`e9K@5N=Y)Xy^549f-?`~S4EL0(rc%1R4pANu!3 zd}}nuF-{U?E{Xo6=_jJEWBQ5I zUqnCgDEf)XuXx+3e@OkC1oagupV8Jmj6Ndu4X9s0?I_7#hWBMR`ie3~(R9@SdY73p zNqxl@^bwDtA4&a1qQ98F;=2ERVPCP|yYtV}^cBaNc-tPj-^vxan7$&yXuj*V*nG6| zBaNs}WEYdYMN!+9P(G*nidkq|p_`+m3-lMMKSBM$mX)|ydPUx295Uu(`-4TB5{v#B zW$dleXwRUD=zl+rzJ2eoJs&(w{-t}4!Y&m9FY4o``g0AiYem2xea;B&Nx#A773JGw z$am?>T@9H=n*Q9#RDTZnmg<*Eo|WI@=;S`D6RLB%Ov}4`U2Fd`O?8CY#WHI5(557) zK7wbuut)FjAC|_u?<0-NQCFT+?}($TXin6l?wlg78`(#E<~jQMtW+EIFrNwf}XE7S3O2|R@#^ESF!!|%;; zqwp;}(vvgD)(1+HAFp-I>3QXV7x5rj=%09aKJo;=oLEG2l4RqcIv{)XEbA@q|&r$N{A@n11AwNYMw4SX?g(KRxR@esH7QMC8;Y^J2QY~a05BCR6tN6EVG z!NekKm+DI!>Do7KiNqp&vu^SPd?$O#)~scI33S_y4zBE{gGZr*$a|k$2S*>WI-s&n z_Dy={p_~hXS@V*>v z@;Q+~@5?Jw?@Q=qa`O6UPnhhU#TfgB!m9?Ly~V!K&^5*&km)$;{t4J1h~IJ4ed?E= zfc=5`gN;Ywvp7&$2yv>5d3v*{dIE_^%I;n zRr(1>9X*Qvu0@z*&?U}QNYw$9(?zRL2EL6v(eur8mId`6&aXXa)-MIo-W0-iT#9t% zpsd4A3E2~Te!)Mc`W6`1m^r}BL7bsrY_oKp#7l-pdGMA!7;Em0Ebw~hIzT#teXSMo zlYavKuOUYQ?etmr=hrgdOs{9>Z!qVBydc|Oz`JG7LuvRt$b1gMC)wW1BwMbH8*y!- z{5=i6cs3l9ELZeC9KxJeXwP}f!@d870jV`z?=24RLEgZ=I}r2eLG+6Tp&br%d;r_} zZyPe~{s`#r1W@*=Z&QSK73t+}@{y+q2qkGCTsRL#8 zI5&a%k63S&RQ(5$SS^$MR`&VIS?KiS*9Pw?|3=}=BcJxVh%cp|_>@0YIP)d&N?z)v z#sWtscwIDJy8op>YuSqE%;X4+F_uSXVvoEYDnqnh0cTQ}JfJj?y@kqXD4TSEF$wo+ zPon)xD1F#5SVq6LIQad(vWV|wvuS-pH~JlSA}>fz2_rhu<#-*3HG0gTyt3;cqQ9o` z>&2DNqE~JFk5E}cTYZw-a5H|Ty!~VJ<&Zv|HY&Sr+O({?Y%PBoYg-`87VxLE(L9tX zQ$=XoNLN^IfqT=(O5O=O?GK=9(Dh<{$J3a<8ii-H{v!vp-XN^e2o9#TI6Y-WnA;o# zpVxk!`2og+SlfVb_f{Y2#kl&GJx_k>!ZvUr$_2H7&%nPi**inZXij3FX(!U@`K8ya zj^0PRPGhfQupu=UMSE!cMRxZcA#XzxSQ2BgGSrnL(3@8oJ3D?Xy=%sHM7eB352>|3 zqA#~xzw`qe*BPuC`A6t1f%jE=VBw9VLmfuTxevOTfdA$DZP<4yFHLTQC8Djvf8{E*IPvhyUhDU>0Jg0>I0_>L$puLWSqBBt!W+WH)A9Dxt z2ewL`erl2BPrSRSyg-*Hpwko3?eXGJ>(|hqhg`3DmGQIaGr#s-{J;4k>LS;-H($j3 z3mgLA*;qOUX$>#{;-E+4Xa#ietq zYASADeos8s*l-KZ&5qDH*71r+UQKypV?9ojUo+_@`lUm%{KR_G7do{HY`=LW5 z@4oWts)|W{K}^(M4UPzp)K%3);x&!+4@_+EO5!Wx_4QRXDO@nk zaOm3yN?F1(7A)2tAixP`YaEUzzz3gYz*A~w^4S?S99T2v%kKE2{w3!#qE zg}2{w+Y+=PwULI^IF!L?sc`XaQ%i3zUNU=0>Eb1eZn-U`ty-hPg=>~qSH*7a%N10K z>WZ3c8Y9TZNNi30eep<5RfX5Es;Ul+*UHG6nuaxXb+z@4Xw4`y8y~2{`4F~YM(edA z-WaomrLr7BQMEwS;1OkcH8q(JM9{`muc<%_BFe;x6V%2Vahqo3&y-Xx#kDC5b!(Or z15T_2Bht@C(C(snF}g(_q>@uw(WjjY0>op6p0%kc*VL@4slC4@5{uV2qG643m9vRV zs{&0)sxVDos!5O}*T+{@H6SD76{bn6sIIMx*HELBYF`>qtxrf)l%Ga0IKTr~Hh}BosQF1Lc_*VbD)t0dI;_moJZ3TLq+3;p$qP!D1gb#OslD_IgEi`O1b!e$&M2 zCXYH5_3?YxRH1x9U$>)drGI}d=+`@N-)SGAB0p7|y3MUSy{GssUn=ya)wQ*&*3=1X z-XEoubU{#cOB))?>l;h&uP?8wi`RR#4Gq<$=IxJOPGj60Bf~MIxFRZ3goR7$!I~Pp z)CFBNb!zE~`gmOKP3&?*Zz)JGx2BU&OwbUYpfoa3~vRAqRrv9Q1O=Fqk&toD_q~B)xe{V1d6w|1cEz$_6DD92h!>I-;aNCjaIh4J?M%_wBoJ7ZGq3x zHgt#kqV)180MWyl&y88eIa`9oB<4brkWbIc2h9|wUCQYqUJ0F@7reDE z4zC1Z$~eqB6bbf$D@+}@S41IApxKWF7Y)JIbA7@sRd+Uzu=c<>$phm3LVtP6<`8cq zi#g114hi&FuhyQKK zcyv(FW8vUkdW$}jZrLY@_XV_j5T^38*yQKqDnFxOpHDKv%^HFqlu_&@IDh9YMO)@P zI=guDytcU|TT8YT7e7|?c;WWwj+;Y?AjExL$s0wmM&=RCFg%_QK5_NViA9@p+a}E3 zGXBw=t&wfxiXXe`@lS0ZyJJi!9PGadBZ5$!?PMNfaZN54|F1B%lz_rv5z3OH&B3<7 z928_O#}PcjItSy*=(msVcA}!Jivk`MriY%l)XJC)6q=19X^+U+v|A+Y6InzXMHbUe zk)c6uI&J6+e<_Rpkb4@MLM4@2MSmHtY41OAlq_-S&IzP?M)X3L;Gon_5x$=9*E18^ zQ-bg;E{pB#moxr6>(jJP!0u&S&bKl32-ri6pW?kBOqm8o?Gy3Og<*=EX|Lda6aR#F zGj7@@U}U5vyoC}DCwt`m0M~?%;)K0po_gK^#>0axKEHPxcnm|CI4D z?7zqO`+RTfkLP{JcnCf@(a#W+YvO-7 zgTLg1|Ii2jsSo}iKKN-LJQMG#bm_$$Q(Bzf&*|ukeek<{@LC`IejglbiqppXRUdqd z5B{tV{x?4OJ|Fy`4}QW2$6R?@`A48&r_<*|AH2W^ztsnCMj=ke|4twJQXjn52XFGh zH~8R>`rtc#@aKH+|LcQ)-v>YBgTL;B|H23VwGV#Y2Oo${B+}{sav%ItK6suFPUml? z<6q>1-{pf>``};p!8iNhPy671?}P96!4LW1Z}{Nv_~5_w!Ncgvq|@6-AN*<`{6-)A z79ad>AH3EF{}OQ8i+R)4_H88Nk*_lT9OFy*Wwn*@@HG}FWBe}}uVCDaJMip9;OX@6 zvJd{M4}J(Z?YS(xP#uhWi*de9Jy$bSb~|Ra{b;jMMcf|CEM}qK)mBDUGBN15_AMQs z90RSaX&kk+IT=3vZCEL5Tm9;Wy7E~3I!sbE;uLBQm$I>4obm0WjBR~pvg@iE>dX7$ zun&ywOP-XLu7F9rwDh`}ID48er|Rn&3ZK?@J-zQzq0^=+IKS`uhQ7-i`!1&{iUI{I zCUgw9NTs5js&Fk}AGKmVRY$2LnOflGR(O=Fb{@=JVxkbXN3t;r%6IhO!Xv>k-GrRz z_khgUUPEJjY;_%Zn7`_GymyV6QC(3Qt6b&Ld`)Rxc|${8Wqo-A9S#m7qB$H~Ci*Jk zE6UeYH;OC-ldv@V;uSBtYvGdHWj+w|Tcs%V@g_4>W{5D2HMO(`Q(XNZV`?QkYZ?^Q zD0SX=6HRg9+;QF?oT8?AG1isb@=T;Rd(k2*3C&NNJ7Z&p@tMr{9!~`s)A`~Ba{Ia2 zOXl5PVm1_K0!GMsYz)8sVTxbFN**R8L`1r99$Tii&74|Ab&O`BjTR_A31(7;NoWyL zKeIES4fGTdQJbUVrLBxC$DRRvC1Q=U61YUtzU6dX>11|(7o+hjs zFd0%_Utj)!*965ecqDPOWwrE?eE{wE7<%-wB;pPTKauKFY-Na3|L@#?CYxJ4*$*knKZ z^9xHC(&YAX)TC0(sHOlA<(DE?N!k78FC7xAKT!cGmz!pqfs$)XGOot#NMmZ&1G7hc zW`7RQZz{k}Mq;wjPP&*$N8yIG74dq^=~dmA;wSHchSd9y_Bx&^FGcwBUN3NPvG0rk zu1Bj)9G2_^8mIm~Iii;_Kiwzj2af19-eO@xPkbix{d)F=zu@hRlfCICz9#P(zIM~K zn{hW?-Ha2T&+;{SXZc#vdn4bUV_)PEJPYB;x#`Vj+)eK&MX%F4R?%xb!nm7WN{_@# z^p*D3o8|Bke4(P(`d^~xHNI5QYk8I_dX1Mc?#e0lO;*0CDU*qpM^ir-)IrM^`X538|jGD#~K8xcdFPCw*98PDP@@=M~U&^?Y zH?j9EV_eFc;B|^#>#b4IYrIL(>-=g~^ct7B8aKTgnchv8*qz;U?Phx7qwCjmN*>Bb z>90S}_@_Xx@oq&=a(;_-hqax?iQcv|E@w}O-h}=D(+e*2ha5it!h8-p^n#yK^jgnk z-y-MMcbS6{Ii*~|_?M#B`VTYi>Ywak?6F&Gxh5xal-_5w_Z5PS~x#t*{d=y;f#)N(#(;sl?1wX0cz1gxh zzcP15>53{`8Xe(J&&h?kF%o$MFJWBhr!f6IhhFe9hhFUF6%M`N%?|xnn18~d7yJc9 z&zDC2Zbh&0<9tKuDKeDy=cK}m6)tC^P>uPvKN=s)xaj9amOrBCseB4P zg>jOn#Bj7fQHRf~%%{ZRBltYUXSU+AQ1Q|AXo=#Z@nwphlGi;r_3X6RD3kPN%5JZ(!0aqBahl0K7v1^_{GRQN3lKdbQh3O~mGq+3 z=}AAgTNdVbmckb*e2K#EQ1~*&MShugFJoNf7ra{WS)}+hDO}f=jf#)fvp#F7Q1RKV z_~?4nsc>D7_9=X^;&VvhOB8;bako62WZcbPnGz*E>;9gc*+hElSN=0>WP}sTRQYF| z`22~Ir;KsZv*uHwaLuRD;XjJYPm@D0_zuQLgTJm9&oNH&d|t`3SMkyE>{Gaw=P=`v z-f1k)>khr(NyaH&Essnklbm-dd8YFev+!TU{0kT-y^%bEmnnKJe}$sgc(bC{>Dr{| zHQvU!D}TGgXAR5wjKfFpLkidO9CrA~IOKIjPx1(U+`*;1on_pW6MJ$QM|zgI4FVQ0 zF8UNcWsJLgb}%ma@=Z=}yTeEDy^3C^w^Pw;e7~aC`hP{yYdp!gE9W~3FSJQFzZtv` zM&$o4%RiKH(SzW5jJy2v6|T!|fe)_F2-EaSncgk`O^l12|ITtYGcIxpo^a?VbDCQm zdcmJp^jiOW6}`q^VBFQiA%*L7on+iiS3WOnqI7Bg(;269X?&rgr+gQ?!4gHU@ud#E zq<5J^FL)W_Zn|0+ci$Hq6@M+~CdS?O$Tmf<%Wb!VOS<+kF8M3?eue9JlM2`Tk2CJ3 zOU}%aa`hgUD>=(b;uU-fFW_?X`Ll=Fg6i@g#klYnd%;*m zPy7YXb#Tew0>)iEEK~Ga9-LufoW%QWd+SvwdWu)@Y6q8i6O6m@u4nv$a>ck?ex6Z$ zbU)-2dXnd#SO0g zbvcYEdW}za=tchp4!z(}#@&1=Q}{Baw`zsoqVR2uyXo4&xSOtaMX$@%Gm2j0yB&Iw z|2c;7bz7h+R-NzSosRLEdl%8%gLjFUWns^ZO6d~`j{ zQ+zZ&MbT5dQ#syzMX&Mc4!!87z@Zm>A>*#zsuhkssZy@V;V5*c~_7Y?7S7x3m#_N)n|sH*YXTyT=esooUSZIPy7YXbLb`BDGt5h`HZ{r z%v1DQo&^s7Hm*kt6+Ou#c%?%x@m4$Zg4Z$b%Ck}7^T1!+e#J-2d4O@zo8;FaMX&Sm zq(d+AyyMUdeu{Bd&T|T1E<(c37-erno^P?vZKhd_tr*7g+vA#zlU?n-s3)-=X-&lzyJ^q2JB8#QXm^-scoOrC0F14!!87 z)1epq6~;+F6-u6W6dqUjS;a@E>m1`E=Z{%V8JviI1P^myp%-~F7#DiMBaFNGn5XbN zm7MuLc!3WdRrm_UX943R4_}(62}NJ1=&`x4aU|zTg}{@~Bj7%VzqCL56+M+(!A~n(^EvDA zk#_Z*q9;CrPZ`svAKgw?Gw$YN9pj=mk*877YdJSM^rD|l4!z(wT*Ww7Z|w@NvLxoW zQ}NMyc!6<|^DVAl-HM*%6#TG5FY>(Z& zU#0M|3a?gpfx=fSyo7Pl4>or;ZXV-O{sk{nd}3YTCPv4i3JK*pa{IudzXG!fZe=se2E@eJp z#zoJ9XERQ6-mCcJDtfKAJcs{%%zuiaC%p+?;?RqJ<~j6&FJRo&Tb;t=O3r4*N6VRD zT=XFCwH8HBatgk~p%;1D9eTl^VceB7sqlIw=P8Bj_kzd!1BjfjNPaOc`6c)$hhF3v z>(C1xVceB7PvH$p&N9Z`aZ@GZ?zrg`<0QY<|7phE_WYcpC;e~Y^)%i%D}eK&jaMpql3(;(t>`u0$hfQjjf#)vze(}Yc$=ap z{?g~zrsy@k!=V>B+Z}qrI~jN7e1UPdJajYemWRWN&u49xn%_vo-bg;mdpwtM$#=nb z`_MnyVgk8O1LOTKJ!_zV7wLoe~}cIX9vo^e;6{R-FpnFBuf zVIMrH@J6dz^LtX^dK_^|;rA%|a|+k;WRJH*B>x&kA7Nbb@d&r)xr|Fb3ZAFD>(}omw2bLoU<60cm=OheBz2vv%^RFMG1$G;4Kclq-(uH zFL*oSZhH4CT<6PSg@0Med7N=cFYN(C4Ta}K@W-o(Cqxk>3iOX0fz98q|oqF?BPS1Wv_qTiu#&1bK|HT?mF>-PDi z!Zn|>j7#|ud(Jt=rThqz2S$IvKK8b$@$GwDc;V*a-<8J>p z!MNMM?NKs0TH!41lDESi#*XdfX_~?9WQ@BpoHpV4g0eT3x!=V@adB)vzbt-(RWnq5z zD}0{94=KD-;U^gvc_y+v?=UXyjNqpfuG5=6$ubps$;VNQyYfdAuKUkCy@JnT+>N)w;WIG9=4++HNAMOOKD!-05$5xp!$i|lHq{Bz>cN|>!pY!1#xz6f`;{79gX?_~Q$33EIfp9X^6bK4YJ|`I5`Hn=i8zJ>`ph?+|5N z(j_=gzjyP0nDsgkJVdp5@^3U3{g3%YMr19bEQPmi;b-zkK&C--`$? z-=9to_1A;!zue^Dvj6f64ld&p**`(}%YJ`n9s2M!Homdk_zHamsQKEeZ0E-y2f}@qjw!0K;&JwyrIFnuD+ITh^pGHI8BOuA{B*uA6(? zpInQhC}OKXKwRRL*yD*dC;NR}1PmyvxtOHbMfvIlF8A_4zjFv1gMY%J_}8v|vAwID zWBp8c6pz#cUWVdb%{Z&s665!r6R$krVkx)g^+r71@MdFt;L}r-_Cn$pcq%T4=QIJt z=L)==>w~}&zxYu+pldG9LgwwBX)pH*AwO|$y?MY1llFqiyk$K2a@8Q=#lM5YOE~d2 z=)s$SX=qn4=E}c`!!LC*+|^$>!l%psjK^)H(vDI7lPV>=2KqP#yuUSN!HLp~Wc49Ya~bt6@rFY0 z_AJ?nl@**@}b{)Eo9oINJ7Xkc>JqLRuO_=IoGh2H{xE#834M-0CSoiXrx1pFQZzwd%y zE_fBEXrvPjv*cw6TZ+3WCl3i0SNO?;l!dhLjxv@ZhnBY%xhZNc}-JA zXA9#0xffm)x@6FpBE_iiNMEja=FKXE3c zgdOtz($2SI{X@3P~e#3H(%5cHN5HH}-0a{_K$(HvMD!+&m5tn+(O@3IN-TbjdTV$Gp3 zv8;?SMbKps@;wW=UxbY1=X#gW+3?pOj+eo6l8y6@x0(l!Ihv6%<|LjW&3BNDD=XtD zBk|5Ry@B&z_QFHnfe1LhW-fWXM5)C?_{w#lXTO2 zivxSm+V{Nwh2Wm|V>h&(iG^@JW*FyV4%ia{er(sE9x6}C*sRv%&0bFgSn}(GdM4nU z=3|=&_2gz3?(ynAHT=lUz+B!AvK6C2s%3y;y$l0Tn?YMywlu(GV+$uC7Z zQOH}zA*~NVZ^_sYoB?}zM=%TgKWpS^yB_*pcd0FJ$%dh=?~Ms{p^CJ{LAY#7$M zZk&Ba*tys+$UCz2L~LN|562`!SiG&yW`A<_C6V# z4!RK?zlsH0!-iKF_jGRer5(59-iD{RMs}P-7^;6KaPBSgV$qXT%R47jZeKLPYp+Lr z9uq-*j-tF2p}a(>K6CqWWNTs9(a?YnLxcQGUJ*2Py3EvRZ%h(pzSxU)Ay4m>cA>b$ z>!dcJkn;D6qOM=ZGLXiR9Vh0}`RjqqWN3KD5%4(zzWX3oGBz5|t(-~ZRmh|lG)Qw# z?5fseY;5a?M2B;9k0AY&2c(~)C^yMiq}Ax-YjgHco*e@XooRlfNq;7e@JOHxdTnoq zyvgIj-qaIwagOpY(N0AYuhDtRa>n`Fu>zcFJQn;jJ3Pamo@IBOH}Q@z@qU2oqj+xf zVNA!rnR}wU0@#?2AE#)q==c>~L)S-v6aB054A@C?oo(_9zl8!sc=71;cT2Xo*x3AKLB}zJfX9PZJeV!b_1UrAU;$+M|9vF z0C{XV`$xsg#%XzV_Tx30d5!4U0^G`E!+%?a|4Vaiaf*}TA{m~=b$_{tW>m*I;6qTh zq+V=S;kTJLbjS63Ht}RY+id@yW?&D?aImvd53L7)@Rwrc&|DwY0R`{K`elKVeJM5gq8CZ7TA-*5l`Uw;sFVcGU69&AIcj zv2!<K zgZGptoknyK-85#`O60 z(`QUVzwo;2v9EZ_M&x&9-8@bPb^q8*`BXgw20C11BIOx6y}VvP81 z;_K(B&s;dH`>MhxPKI{A6`Hg8%~0Ek(3az&M~{U{P;9mx2|bn!J^u61_BTR1eijPt z3OJ{OF(V(3dc1$gv$O2jqOV^qVvNBBo!ul(^h-CWu>V-pU&NXHf8KH&A?3H3bP89SCwhR<0&87g$&Wx6w| zNH1A^B&n-KxS}%?%6I^0$_=nu{HLRT$Om8VgMZxz_Zn8yw(jQqUyQb<&Md{jn)Y-9 zY##05#9puA-Zk+!HX0{Fy1|jwIOVvS4)Vpe_1J`ZO+%g9G2R{pTiSq4!prL&=Kc1k zUkOeKA734-dw@Lb_1*8e*}uMy^+9v4mm>}0WRKbc2bTdX*;4s4JrhLsFd_U3VFG^& z4|rW%_HZOzw}rANqsxC87XcR+n>^V}Y1}QYn(4^~s_|DCchh@-aW}muRlK&Y+8D-i zL}F8uc&T2I6MUY%^-9R6Amu*6fYg)guj&Y-_^$V-)7Wfxg+qA zRlc^pEHa)b=a_nYZF{n359KR4yy#6loUN%d$^2}4V&gY~)eo~vA@t58p00i8V#AAV zkE>&fuY^ye5PRXKpX^N}laxOpKfrqXINKchOeVa96TjEdyWnp6X`Y-gSAL^#;7R;z zC;AtR+UIn(xvWbq#cn2~{G+fEUg~7pxIrg-t1V^0$hJrAjT@el>E<0Euiz5;#a;vE zx9`VIzn^WcljSe(3u><1@O8D-4@|v2f5vsSpD`As^Vg8 z@2|FJ1&43=o91tBpS;2w{x_d%-#+!jyAS?o_EXzm1(p@OY{Oq6+-N+jDfr9nS#REb z@ZH(%+g}Y_wjn?I)b{T-|0S-oHk5~-+x~Bm?bQ=^AI$MaY&h8b-1dqE&ustY%%`^h z4QO(_VH-v)_~!Nk$e45d?t_~_^W=c1wl@R=8&-mM1LABLG9t4fI3)Aiu#t@%?0siB z@WrbZXO_di9)2185%BMU|6b+d%vJDXUm3l)VdTJRv5Zh=3^vp__)n-T&U`Z4yR4$J zFf&+Lk~ycUD6`?7IhiZJGHx;UkiO%s(4bp?;SCs)<7Esvficm;@M8#f9L;79{3zXf z11966-r+gk;K``5lb;zL?Rp7#PGxBFe|SR>HoPG`W$5A}(7fek4k_}6On!J&@#JSl zM!On<8ABR^gC@s7Q&KW~aZ&T|#fkjr7kQ;QEQH7Z99fC-8~RC zx+rYAD62;UiPws}kv}Z2yrH0I)2_$MJZ}T+D%2FUjl_8FP1yZTd~fdAB*uYMHs5-B z!6Yi5$JfFxgtB?`Ytb$$n^abRQ5)@|vP$LjXr#!L)z2fp(v1Pnul=XpWV8IQ7=uM2 zth9k<9#PhzJD3R=< z#<%mn6EKHT>V@wOWqDo5zfG81>k4IiT@y0kkAgoG{#dUI@BSq@QLnWKwp$7lir^X1 z<}z&_=3GG=nBsL6;yKdTRfy+-FnsU|WWcBAL+v~<@hJj7niE|f@b=PNPAK2&D#CRU z!Ud;$UE_fT3&5B7%u3BKFZvC_z@EG44E*zJ|9TIyc@y=EThRZYzA^QQ^<3g3z)#eA zJv4V2`;<3548HG$LX(fL^16OQb6DVi9DR^5u>t5G`AA0xB!~8Gi za$m#Tdch;;%U~Y0^}%3x)q2>fX>Q;s!qGg{hqr!i((%x!$?q25Ht9s@%E@nk`u0h0 zhAyA{iwO%Sy%oxy{HxF2G3lMqn92Vc8aw$;%$>Xu%FJAZe&^KS;LNOGM&=JfgXYf( z4xB$RIADG_7@q%D^lKwtj(&B-v(ZOJ{K~sDvnARx;#F^0=C`~{GB$E%2AKKM=o-wedF3h{{S zBscM=^a%f~VCIGVA+Myt@JBj6l0VB%a-SLK-Am~?@?P(fGpnLqe;&FdvpR$^O>j`= zhrz2d&!E5gjo`q{iJ<|RzYY%1Jkt>E3Wvg(IAdb!yTQvc&)gsF`p(ELqaO?g?mZRE z$~=QU>)Vj=%!ARc|3IFec{tkj3+Uzy`miUVpEDbxUB3!mntA5y(XL+xhh^S~GW@EtzN7sj(=MZ5lVo;T@^Xkbzc?r+TtPP!!$nsj?Xc+%(c2TYnf zYT%^VS%W6!g}f!@7_%H7lo)>ZAn&rA=7EbR3=NOTL4W)~FK{nj7^Y6Vd405JHtNOq zD|2nVz&eIX)QiB9fs@coZK5&7ky>Xg!Sgs6gWXZ&1zL0Pf30*Yr;=kKR#*!;*m~#_k+5 zZ}aH3E4Pe#^op$`w_W~N_TwYA58rWF$O~NTfB>xM-SUSV6VN*}eQzQC`~&qn^*n?0 z-^s>9xZI0R+|$iJSey0lKmRZq{PoyE@@X>TaxZx`lksl&DEsF~tg!bcq3ji2f(VNyPXd>+gHabqF$OHFtQC;t5xnSMVzyPR2;5Qgl_ zSVUkyJA0A|=P@DWA3+jc-aC|bS9cUd&I)^;6-M?3YMb5gzGERa-`q>glymj(Hx|m{ z@khA|OP6l=&rF?GVD0Ai_v6MwGiR`w{S(;Pn^dGxBTp3TiJ^&4xaPtHP4h$SKlwX2 zU9Qo)(5;C!!gO{sdJ;m+7r0neU1hxfTKi@pn-(UxKgy_c!FL6I_M8i1`ker>zmU%K zdx#iC=_id-*=L^S;FmC0Q&IEu!XG#MH6WpS@VkbmHjj9^ZB7&zxsPjeW`M?(iNe*= zPa5aa(tWzpQ^#RVXqgj{yUZ&TBvjI9l(F2mUGR%FEPixu&!NhCt zWk!3>uYHNsnN9Np=hyzjo|`e2kvX~dM|eF+j7>1l_8NsTa|}+3D5fug`@jlVLW5-fnHBpZ|~kUn3tpbqgYQzYi)C3|DZLbv_6{f z=!d=2*RZ_n)_G<ggKUblOOahIc{ty-`SIs(S0|i!>q+NI`GW;?B9gK zli$O7bkd2LTbvMVrMbnPrvt4YVBV4D7SCet@pNbq)~sXQ>75?teG;$rVm)m;WWG7m z>+0>z*hA}M&tdL|Wa|A&V9ycQS1^}jWbY1oGxi1(!-LR`T?-pDI;=ROTh56N=xiQM{rfgmv|Vb)W0Ku`%RLzkYx>1NpEh`E+0=(UE*4 z8_6zmmHoDNIz9VsXav^xj+`7UzJ5a?{)tcGgWen4K7c-pZ`g1QGA$2|%%ruf(U7+x z8+?f773lW22uIJ?BAlh6b(mv2#)QTgKD2(9?kTMlCW<)sh7!a70b$#bpXuak{|)3a z&qho>iZ!bguaT*z_Xe7S#QM+4>F2e6Xv*RFwST{7*HAB2Zf%*zTr$eE=v148~YN}_=~MU7qIu;&9$jNEy7@s{C_V&X5mZA9_b ztZkPTJvQv|ONzG--H{nwdU>RipW;i0Qw2_kj|Wb2 z%GvFrH}8Tz=C##T%k5f7yPPs*>YxrNzBrA?z1g2%SXz9`!g;e7l@>2qYB757rFeZU z+d&L*P5gcj%PFgCV{Q<7LR@Uuc3QrDZo0d-)KoRPx~rVlKC@FI;MbOlwks zk+8ZdR@GR#oFqadtNM|d8DEnI6QMI(+*pOBp(`88SHw$~$5xA7$E0Uvyiw$kc8Ba9 zG$s=l;R_d^&N!WUsJb+s*lmbj&*`=(K9}+}d2I?GqVOFG*YwgB5r17S_9=Sm2Z}qu z7cM=OPjZB7`qLIR_;9`^Px_!_N6J=s9`4B%ICz3_m;W|}>;CL+AH35Ce}QpVo^Hln zc@8T+dOhwt3b(dRn;tn^*Oh-dckl_<^s;V_a4Ac~saer$e51mzv{9Ph=M}E=MXxQ; z@n-O=!qr1IqQ-0-UHPRF75I2 z4leEAM-8LXf1~d=mi{mP7@y^hz^9_-zLBHA$jKfC_Rh}3%Qg9aYa_ZdEL9onoyL$9 z55uXl=x0wn$KfoOzF*>@-4xsqPnY~+!%sz6;+c_@oqFXMaCf4Z?f$>YXtg0MYvJwK#(0R2n+(q{PC z2G&z#a8%E=b8daViBGWncj>^mcEdL`R9+jeFkfbv1rjNHAS?iBzlpzb+9zHcA?KWu zC{g;s?=V7uh%pde(c>^idkEjVHH?h*EQXz+`^>J_ zlD+@u*DnUWxA&j#UD6c{wDx=&HV2GdiT*6c?TLZj-s7+#9K+bk*b0gRt@JE{@ow_d z(VlVMP^9&u*IT@90#!=APZy>WfioDBzNId9S1Y>pjzL4%e zK=}J%KcVX+()T{@qd{-4Jg0An?RUYK8~V>f4?hK+Jg09+>6=Rm|MwW*Vch&0>E|r2 z@h$dnTIWIjUih$!Trj3RzxHJw&vu%8GIj~b0Di`H@g9$5Wj%<=8+=DjW7=NiQ7^vx zzRS%MtSQy&R+E?5@oFR##hQ;`#|I|w!$xMxpB&^*WK6UtdA@h=gF)|I$)lHsM?aW{ zJem&Neh+HxI;5k>U~lh{SfEwL%%s=kAm|)6l_L)a@NK2lm(e$HDkqdC{F^+AW>6kj znn;HCo%is4F4hsen}PCke{kf6Ge{TNNl34h$B99dC-$DoNZA0yk9qf_C?}E^wEl$b z2#MebW0%N9n#gWp%Fy>nCYw*)u$^qi_#SJ+CgYv5>1D`7>uUC4tUiHk0hc8fsq&PP z*{=7sF6IAcqdgSwIB(?J?}avxCVRkUlofh*5IEV( z8cF8JZ;H!P`hkWpjKKL#l{I7iQ z7kuy^_~5Vl;6L@jj{zrriXLU`^BW)fAc{yjz0tRY>G11(@EJb%Z9e#qedKW(3oIos zEnTsuCRSRCFSTI?(X9uTg4FagD)=lcM;NRSDQ$e9E?!z!U0zk=t+ob{G>sQUuzbym zQZkP4W!)NlHjIYVtuW)V4cwOPTZ92&~twH|O@UbE!{0fEJcZGeZZ7WwPda+)9NgI6nDms@RP8n5_E*>G)1(#{Aj@2yX68~UHUf8*<{ zFHwnW0`apI$v4gk4nTHps_L$-!OvDC`(sMH;&XE$x4!!~UTpZO;6yy9r8wFP32*nn z=zA#fiyy@!y5{PYBE%%Aak|YXK*HIlp6~vRt2z9~*{beH!;^T+T1<&Yd^djTXQq?C zo#Wpm0oj-Q6Sy1~L>d)<{e}GUm3mG2Pj)2iM1SHW z*tzw$Rk8eaB#d%y_|Ht0?{b{AYtz%)s&1Hi;|-Sb;@7d1sTY3K$Pz_gWtcZE%e!|y z=GH^aYd53(nLU;Yz0igt?7avZ)T$@3E+sOQ>_6C#Fl*Q2=KEHR+dsrOnbxt$8Wxn% zO$|Y>mFxzz9$*b@Q7__~g>Hl|$DBRc3mUM#1W~s|dV9$ZVeK+%oy+;PFB*RTj{VFa z<6aN@^_^e)UAEQOXNT}CLC-S!o_UbxyphM!P&kUm{5MB83y%=KeK-o+4%QcR;ah@L z08993JN@Ip9&IUFFKd^f)p{XY66qufpY%d9 zku68o>(E|;N6me9zjZ&L%O*Z2@C?`HJ0;YR9$IVkCU_l(P084$uCR8gcBJXgp_}8^ zcwLlNumN=>AB_I>x~%92c%LsR8$x*)etSKx+mX(rn`y1jKx>ae8UjpnDCIDf7vuWp1=6*I zXeBTD^s|raPJh`+uckcm9uQ~7cs*wzKk?9I^Em8ySifY-CzVs^r_YuOJKy=W`*tHt z_UB4wc#G#1PNkn|Gj1U7`hw}?&6w)G6G3lWmRzupwDohUxiYx>ZjAJ!zHpvL-Wfv1 zuOhj<;9b)TmwK4);uEg%mGo&F9bxj$n#FPC!k@y|3(4p8K}EDasANlU0k7AgJ|tx_ zIn$pk9G3M&aA#ol8$UB|8NwRPQ_mxw;A5X-ff21PqUUY=JVHJ{pQFO><>w{j&#~e4 z^F#c+AAb%{*lB(~k9;Q`^En+6Jd$x`H?DlphhqQfK`v50O$w1?G+80jTEV+Q6&Q?l~)7eC?G2sT9U-u`SjnmQ( z@v(^xaaoxAbhg#WctSQjk=xV>V1@Xn`s2C<0Em7gKcI3>t_=Ui7DzX6rp&^>7ypF6 zLLgkk{CeJ3;iu!@=7WC&IPrOh!qz76Zh%( zkMzNFeDF^LC;4~4C)aI$F&E%N?`O+IA1bXp^~`@6H{7NkBaU@G^nc-lZ)ZMY3o&&a ze4g{6{~I6t&wcQheDFiSNk4dR8%Oy{?oA*1cbOjVa^uRZ=Y^2|%e=mN)XjJ%@O1e& z!UrcCR66==eDG;LxK~lNGTzX5EtyCg8mjFQJF?1J9N!6+mRGM7YcqpD=s^t~_CRLW z@`gB>UR5xnu#Yt>`s7d%EyqH8)?nOBXqSEL*g3(n>{&QpJFM zWNPalFdDTq`XQ&=#?fGiR#(h0enVyXRJouo93>1Gb>sMwyK+Oh{E|&CgRjYxvPm{Q zjZ3@b(l6k!E`1&2t_}9E!nHl*b%kg1HF;T(gBT);3;hugHt$F8%J`Yub*<-<}B|tdKh7PmwztfF8^7Ielz_m?V$|T{} zDtghC&@)@FfN2Q7PSH!f6MC|nky~m#k8o`ltW-G3BlWpS;rV<`-g<=-z0`qi3Mad$ zI4Q$o>lR#i3O zJk>Doag{(kQQriEw1kUyKR-S~I2{M|&PoTOi%C@zSUeC;|fvv;Q$B>hnyNqEuU zB48xH)KgptORKLbyM2m^;bwom)t9 zC+JI-P3qSTZ##SDo0|UXV6RUzL_$Q3B>zAEE?z4&_CM!~cTUK&7yxZ19RTzEghQ9_6sr>)0;i;`9o^D$c1xD`U+L{@lab==#we*w5 zxwLfeR7Vqv+8}bft!MgcRQ)fuu8-n~F6Hpzx$#qz?COqauVF%Z7ii~(uUuUo>)Q=b z?FZ;Hk9d8-)Eho&T|Y^au^+KM}d~}weR6< z8?=|%v~O=2uUU`w_TGSJ!{u9fnrj=6^SAK*@BHz6PD}ZaqCKUUo1*i}$}p#hINQqb z?Jw~xz9g|oo?{(_neQ^s&-dO~Hn3<9=4AfD+#_7MnfHM?ym{PW!j55n$Bm2ThiHxl z&$GqvKd*;*PRyUYkNKb9U_J;fALepsz6R^$O;}=LL)84^8N>8OJcpet8b6z*2*x~M?5IJ@rOryx-n;3j_(0d zdH_bx%skw{bbA#1_b5Fbi$i-Kzkoh zex_)LcsLO$aK86Dzo0z%FP?{yZeRogQ~N{i z0R0rEzU5wssRVq?U6`2#cp*K5le$k>g+cl-;?u;~M)IAxiAMwTX?DV_zNYWKBOsBs zr3}|&Nube7`l=hOD2J zS&>^@0eyyhlV|ip`K!7Dsrfasi}BFh(9YrzgzNrKqr!DLY4gFKQ8*7>4f4Fgc}QoF zPK6KUYw`~BwOc-u3a2?#abd_!j^x+r6?+lkV*4UGZC}>%=rzzfy=98O<|Fna;;-dj zujsF&2-$7&!MFL~Vvi>NTAzCry|!=eXPoq=?VBeRyp$!kJcMU%kVM#72H@GxT;{UiY>P?{Ls=*~;|ZY{2ySWtxh-^(!uEKPXt1~3L5kA1RfZ>%uR z=zg*?R)8~l=HtxLA>;>|a8^3)7tgd1V-uw5IfAu=`a5OFM@?NDt>vRJT5s{-9|dzT z?&CAJX+16e!xavv7 zodY>;hF*{0J8ep5aAWp@`0z$JqrzoGDFb%2NJ3g|*Z;BmUNP_ryBKd%;@^ zk%vW*K<4s$?0NPN;=I};lqRhEydUdEN!KQ?t|?5LS45}Jp+%mx;+$@*$NrXDqiXZ) z^wYHF+UEJut#)l{q;h#d;!EQxU9D7>9)g@f=!x&6R?5C9_g}Yi9YH;)K-gRf>jjK#U(hmqJ-rO) z*S@CX?+jHUe&ksx^BC70-ZQRw0R9K!f6yM1$HKgo%197p;U&+TA^A;u ziaiACeHqLVz-tro6ZxtuyvR`fP?be0E}7K|EU8`D}La!P%SmIx^?r?Dla5D-xxhFMC%le%2c} zpYG$he|Bi$!P(FA^{A4Av%C2^ckaR22N0$kVZ!5V7~IEk|7=##!P!Z^9y|Ns>{H_k zO1(jEKlxj{Lz*BDhjT?ov} zbVk=xpquAG7X{r=)(!5B-Xm6rR@Y-K57B8hbbSDH3Gm26o%MiRf}H^d&672fs>y>+Nd3vL-^35gD;1#vN+O7VGMn9c=5~G zB?l>-34?UXvlw_jcq;I23(n8nMJ^;{1hS19K>0KIPtEW95`#s!hfQo9mN0p_+N&98k3?g0UMHC%Yug3+kyWx z_wXdcIryjU zEcs+->-|0cjm_l0z~(&`GDx@d8C=7DY}UZ9GGUZ75M;jjbYq9^9>yrpRK zytcW;Tjo4EJGkO!Z^+X3T#!@}F;uOOgsSbOk^+?^Bdw8D_y_Yn?9a*-qKu(|3xZ;mkZ+l0I^Aslb8=)f}3z;rSbU2ef_(a~$Yl=2s-8QjgOKu&1Jb*`(u4bmKw6yJkC$e{r zSlXAa9U=$&B=7TlEpb!1-N)Dg;`1?%NdJ(weLJ*bfs`y{=hNRBNS>12g>bolK?P0s z4T8<%3m1gYSX!R}lfXT>srV;6K{s$M)-%89_F1v~fB!RJs7#phrQ)N@IX?KEKKP&d z;Bnw&8!tmAkesB8Iv~b|&$!g1ml&4;bp-{2`z8K~z9M99Q+0@JZiH`QT=>jpT-HcR zy=i3p5Yr1zYg~zs?1d+F5ves!L)7Fw+F}@rpIEcxib>MQOvD<=*o0`uccd zW2uE{A^d7@^_qUmx_!1Vy(A{Deab4TI$BykCmB4sA@2K-`{a+|z&D90aw9_N#nM+d zUNk9k_G6<*&X?BIBF{gTa`l?(i=nw_jox2XQ&D^W^&iXA(#`l-x@lE4jq&>Gc=>(t zX|~wo-C!N9eb((4R}0Twn?IE4-L?7jULuE%YCk^B7QW!jrUc_I{Wiv_J-Upq$$NpX zT{&N2+~sqeaoeu(4ZPDn_*ur?^qynfO>YyoFT|gh+ZoCP<1U{T#$7&nXotwT>6*g0 zo30YZUH#Pg@M-elBQ|MQKVoBd<$1w}&mkXqGWfmV^2uV{O_%KB;?hTb=$A6?>fr@0 z&@P|-jJxs5*;lR}mhpSUp8GjeE1*m z;eS%`(e+E-_c9kHQP1KF!Jpy#Y6q8oNt=To=kl@J!KGia-@&C{^16ddzvPsIOTR?s zVnt5rmyF_iC%E)WrZ~9tOQH@g{gNdPF8vaz@4{dDCG8Hq^h@?Sxb#b84psO_zvPq8 z`Puz`{+pNbdy)T-xI2&2xqKh~Z-#8y*X(1-nw_lK#uh~g*=j6hC)q;Eo-GVn64}L| zLMmlnvW2W6GIrS_TejS<*Id_BpT3{__q*@=pSd27=i}Pm@AEv)<2cUu;2Ja3#ia#5 zstHypUc4&7N2UC5=O4G=cKquV|1RxoRsMP<@ZcN&GS4k>@C5&!?L4txpPuqfZh7JB z!OMa##oT)RYu?z`J@%hB{~q9&|F7o<{@(FFKmUrIKej((=lu8j%khRW6^lI}c>lHw z=Jx;h*aPzfOqFf9!z`dGO!rw;}i+ z?4||JcR0_DJwLX7|7~u^9u)ioN9_OQ@b8=cIpyEyTjqb)j+f$XkNHQEc=0a%YhnLd z&j0qa!&iaPK07R4qF9+{i}~r{|M~aQzkTrUS*ygv68qWV-#=?7 z_GilYdEB${5=Qn84dvfS1K%U+_lvd;O%OSV*EpTu`{d8(P0!EgO%VCd??>gmzrSBM z<}<#)`jXDw|w_7wlFJ}5L{%sH{YyXx`KC-=pEF6i%c@@E1wPR!3tzRSOJ-)x(N zIr&*AeqSs%`lZn191j`=-X)Zt$4c=!G= z|K$A+>yP<7qiBfV`^)FDf%pSILpzxJn03eI)ZY{Kcm4uD%R9EW{GD3nKai7u=BIB= z5BfW${++XbKeuLqpD7*qon#sL9`M*4nzIZ%|5wh6{vYNnH@{}E%wBmTxrMnxtMitoiJYH&Ft~gM1;m`cshQ`f?Plw z_!%*uy*7CE_}d5k9`rf6LdJx?Q>~yqyPy3R5t^f6o6I>H#vIFW8_kEuh5miqc8^OK zuVI`1*#pM~$J`j2_-fPUKvnOg4 zsAKSZ(PMkJ{eS+$B4|IxGN#XaD)!RyzZd4KopH1hAYVmj-* zJugw@1AaC=-`h5!T>N;E59U1?xOUu){T_qB?=0*4Zk2L@7z6!$b6ygzS^kc+K(F3? zH<p?8U%d5A=E9`eFaY_Ot!=_cbVREeRZZe_ryKYhX0LXDuct^tHcp?dCjxrgDAcl=i|_@&V%@z`k&VXzb*uRe_U*T z#QyAdJ~zEvmCMiHzqieEos+NfF+V5lzg_1Z@H+$Om*Bpg|35x=wr65KZ$9I7p~}5G zd=4G+_2gviXU^%P1_!Sh#%T^?YaD&XuQk3O8uPR1jOCwsj6E)1)=Z6B(^LEjsZPYZ z@%*D&;Cg-X|GVcO4LBbBJURG(U;h4nnGgLsOy3vu`r^D1eEcvgl;5Ao&sKBK#QwjC zWD&#mKW_WXuGp`wF<(Fb@wL_Ox4CsD@U_)z{LM3gudV)0foY+sBK=*85BXgB0iRvR zgQ=lU#mpc3X9E8$U_92*`_L+b_Q&j#GpS>q2lh^MU-8+Q!v)9_@8uQ&>=LhEX z`1mP@2H(#;X60Y+2lkA|C#XMk|DCpZxTXgF3=kD_jE@5`tl;?*<73Bodd%Fv-VdzX z;}h3_W+h{59%r5qNFA>WvKueFy21rx=Zi#v-FDlxiHTI`{}&~|M5Ld zgM!zGR-AvG@27Du{MTzl&l-!_BV%IzJhVC6e|&BE=l7@eVXxf|cfSSxJk*yS_iM}j z@TVg0g=dc0D+B2Nit+fq9gaEub^O4ei^8);Mwg8F==j6a;VsMg{Xgd9&zN-uz6RLq zfqo5KH>1NdL`Kuc{Pkf1e=&)S_V;J-`!P;Yk6YmhB11d8Q`WB)GZMv%yy7_YoqxV{ zpO~M(pKtA>z%}1%ZtQEme>U9ftY4oJ#Prfxeg}!Kp9xPK^SZx3)$g~7{qyhH%FgYz z&z~t7_&$}`>vZh({7iTX_F%fG*mGmQ-kc3j7xTJv`P$i9{A|PUq=9`LvtRskVerpw z{7#L3em%Glo{V_>eWZyvCQH)l_*vcW7=&@igbVQBq))=J{CBdW@ZVkXU>@Fvarp1jJK_IFa_-((f+MIJ zIVxfReB;2ciNJz<#vXecT+g@B+$W3Cx2z zo`*ls37?@RoHqrmLj!nDyWr!L+?T^t_#yy0gH2UKkxF+|@P}IZ??1i=Z8H-?VgrO)>qXc%MHH^6| zPU1SOx5r(A$K}WGu$Q`^2M%EnhQk~`kync>AHGBo@^Ao1R{bfyjuRZMj)hL69 zSd131-r3>!XM>O1aPKh{@iqoyJZ@q%-ary`$1-$7KiI41umj!_;sA2OnC)SE%3kV< zW>^ut@5Oy?tj7hE#0PMnI9DWQAP>x+eO3skVIN+DxpMw|aBg!mAC7AdecfDagn3v3 z_tFvgp5rINI6Q{?r4#(KgL!bBuGe{rASI?@7T&^UyoVuh-%JT|@0-KU7>SE8=iAU1 z9+v`hU|kv_8ph@8t>Lx692bWB%vjBj=SIWzRzWMg5A*yQjL-RR!1L{^tEi9aF#i`| zPLpF(@NpXM_rtZF#BNMN80>+)utq!46{D~ku4g+uHVf(@0(s$_?_w(MpbAdF*B`=l zo9BC&fZ~Xc^B9OGD2FGJE%@AAdoA*u%&3Np=m`7FI6cSbuE#uCKj-xI!tmU(sDiP0 z0p{chmPu^!G5iabaR_q+SszFUrUn29oQe;IFGcX}~_dp1u;kDy3%uOvg#u7A#H82mu;l3`9Vlc)q zc#g4-Lv{3m$5b(&|_?Dfqant?Yl z5azHTwqhjgnI{o}ZE#-4_yG2iHF^)$#eQ}k-|Gw4xB#1w3@5M#_I4P$qAASzD%g{b z;p2mN9xr18?2nDu1=o5W>)|yi8|vXI65(Cg%f@>LU0^K55r%X~fkilqd~odPcn(=$ zE*zQ=F$_OqC9F|wPZZ(tMNGmNcx#8du;1;$UWkUhWUtl6 zi$OmA3YjnmRba0lg?q3n-ht~X1CJ?;<8WQBuLBC94qD(4>`8ki687yW6hvKYK`q?C z8uW+j+J)^f9_L&N_rw!0UpHZ18p7*EYIMeP*a-XEW11lxli~U9@hRws+qebaa}O6l zLd<~iI@eP;26JbAJ$kgQ3_#5hs}t>VR+4X29>ZD z#uJ9qus-Ih0-VR|Q5|^xGI(wCy$G1YS73cNz_k?&8qEE4^oO~zX2!Y>*4}fI!5q~@ zG&yb?a$#Sc%0)zA}gLiT;D?CIDkvcZ$JM6_fvUzTa8dO!!G27wfGhvVL7IvD8|9t zM0i|%SZ8~`7P4RztX&>#fc-WQhY*Gq7=(qv`!Mb&!+P0s?jh%SADh)L@jTMPdb+kN=m?Mh5!G-F9^-pwa1~830PA6FW8oMB z;p;Qu^}%z@g}L|r?nn${xsBKGBGThGJVZS-MSMI9bLBeiEBhr9hjAJ^VU4Z5aoT^* zIUCkuJW65$n&U&fh0&;s3n+^4EN7PY`{d=@8NJC`rJI5k0nToBFKvD zXa{p({LkQ1cn$Fwue(3sAa=q0Re;Bsb88hJ#6Jowjdfaa2;pR z7{=>)5pez!aNnPY^XEb}m`iWYzZ{*=7N_A_o531o#@F!nG74Y^?2XtQT0i^9-gyny zJ|FDGAMqBPBQ=cO_q-l|h}sy8chL(+Py*)6`8_8;B4B*a!uRdZRj?1N!EE^2892@j zJdJvAj45aj^WgEDumX+XnvY>PuEE!>qdB;VZ?P8lu@~K7-aKbMRw5MRVINqhxW0wN zaR8;5-}?RzbMi83!5Sq(6?jbugZZrk`}ZeQhR6E4HO!54_yX>&i}2V*Scu=y0M@8G z?ByP?|1+QutZ71ALOYKJr@f4&0*;VI1~~WB8uq zdaUc8g6X(}t%!!}h>tu-ffwLhUK2gmIbE;k8uJYl!Auxy349Fmxd1)!3r1o&D#5Xx z)4q269B2W@`y4~yxqC4fsgMBX@n_71*DJ^I9CPy|`r|S};681LTWEk$@Y-N*t#L0H zR~op7I^b)(0oUO^^R?qJhE*_^A7KfOB0t)qE5@J-I-)naU^iN#AdImD2EaH%;Tm$m zSYAXX*n1mr49?jH#_t$Wu%D9RHRQ!Xtbuu}jgfc{j_>Qg!+tTBzv2Ya<0>5AnvMR$^I#1Jp$?YeMfm=Dn9t|owJ0I%#da`n5tsnu zvnJKi7pq}Do`t#FgqLs|#&Hs^`2|>0^V0#=$93gEB|OF`OvG$BW?bLw(YU+~Wb*o$ z0sC$PtebVcf_pHZm$3pF;IZ@Y242HH*nige8<^j$F#q=NCD=brVXdrFL0Gf8_yYCN z4XIH94X_rS@Caw|H0(L&^tj5H2Iuqmbod^=UlC7Y5$?jCasP(H-in0RB##@9SttqP z$^nn_x$oIakKyaj!Sgy}J$&y6n8&o}f;@0tzHaaPJURY^bC19>xXyksc4M;_j>GXA zAsXi7W0*tds*IL!t;67PAHexGU{;WQUI=6HEu8Z{e#dT{L183>H7l=kN~lqX;^}IzGbpSPSReiIbR) z>UbB<@9U1+71kghtgqJ+*H#nWT))@0S6~dr?RiP?D_sA}Scl%&g(qO_zV7iCkQXE1 znjaz)T#xfQ*Fks$w)iVa*G|x_yPS z@aA4~{^zg)_Ny`NfwkR_5AY^d;s8p)Tu*@Sr9w`WgloEhZ&4qK;J)*?OBjgRFxJCZ z4(B`vd(Iwjf!nBow6G`Jp*b$2CG5kOVH|zndP89^Zo=nq9na!V)WKk zw82ks-E&bHVMq8<@fakd<&Aqv5 z0Qc`qm@mh(PO}k(n@E7eaE?iEjM6Yyt>K)$pBUC>Jw8Q$OojV2C5GV;N}@1a-wv3U zLGWDTN(%Gg{HNfx!E>!|?0hG9+!3AOwZ!_(!1tJlHE^BQWGft_H~xURxesIh3Xc6D z+F}}9V{N>Rve=F9@D!}eW;kbiOo2Hnk0fXUd&^qA1M};3EDOA*IqoI6Zu{nGxK?wu z6Bl6o577+8Fb|_J94Fz};V`zizJ_fEhB4U7zE>6YWF4eO zBG|{>VIEw!dHe;&HxE@{e!QO7fPLKu=EWF4z%y{H%CKh#A_11coSP@>u^;Aj7e?be z?C;FT17q=+2t0!I=!yAA9(sKnpa4{a+jfaSo&K z3p&6){tAxmHP5~{0Iw5`;CV-|2VS%8p(5&HF0#Qj8IOBw9s1)T?9nxdJ+By#9pAX0 zz&4!4IGC$8uwP%qY&1bX41w#pgKuG6!;lp7PzsJ|Tt4rFIVc17+6lNu_rOZLjZEl+ z>}U+*H}^$g4L8DbUxhh8g>G1kL@0?$@IB{t-)6-uEQfJ?i5>7<&zTC3b&a_(9_FKYt?`LE{I10lxmqjSxw*9P9TzQerz!;wRjPYqno1;~~6; zn3FBAhiky=$#?L*H88jH;aW0c5{$7seu2lQg1JZxd&9X0p#?evy0gG#2CGZ53XI^Ehuy*c+wccr$Sp zEl?H464y8D8ke{9Og<24P#f;Ybf|{iu&&nA8f=6;U@Z&6++V?I*!Ov0Uww!(SO-Ld0`}T`^u{#gL=V_U zD-jL%fid=kd&d3I5&7Y~?iu5AO%>p}%HSrl;Sh46Gk%2gxV|pfi^oWaQ?M7E!yey= zWbit(9wp%#2jdu4pgxAcUbVk{F9~j7KSJ;umf{u)!}|Gp>~YR-o@U`3cEWwx7C$2l z%h3qtEfg2A48~Rp2XP(thcQH98(zfwFn^a|o-g19_*w#3hmP<#-)n$KxWD6L8|;lH zu)iK)1g^lGnAalkyh7NH`{;rnD2?G4tw_wzQ8M(jE(ph=JqPA(>o}I0(c&tTLW!i zU%6&$R}fi|6Z7#j1|T`cB0b9CA;!aT7r;I+W{;l-$3BcDSc_fQidSJy+;?$(Gmmk3 zvmXwi8myCb@KzD#`5DZBxf%@jU{CBrLL9;tya)ULJJ=W2+jHx}I(YvptnDatMj<#} zGK_?^sf`!m-m>0xumcUz39Zo&j@1YD{BZbwOJsrbo`SVEwv>1Z?#p_xSF^&H-7g7X z@0o*L$N^(ZjRvsy+z(3;g-ZAY&KZHPu?fc36k{3Z(Mym_6ug6Cj9Jnj{oMn-teaBt1R0u+O9%L+QHb3$(p-2?XTxC7LNS_&ckbm^BY@1 z3_%hk#xhv*8E69QcMl%_6W)PwS*2M}Tsvj~bi`!1ruSj2*2uA~tF`o+ z=JPzL26Jj(PJ_8|EfMfF$MF3GFqbuv2F5rJ#&tG$Z|qs&+CyL-&B0gLiY@pR=F%~1 zqYj#5DUKos!tn>%z?{1VV>5Rp;k^B@2d$A1_RkTRGjn9EC6c!*+H zg-hrT_k+24fE4%?p5wJ+1&lcq&Sww$^2OVG^%!TW6tc-XX&TZbuz%_h_D#6EVxVMic;t6cQ1{mvG zur4XE1bg8cui`g2cVQUc9|%Vad<$Q{jG}0W{n!U{Uj?q)G0$Te%zI6AgzH%e^Am+j zm;vVwgYmY)Sfquyxr1!j3TxwQ#$FIvu?-$;tS{p@949%R!a9^jJG6!|hhh*uhq<|n zg{X!D7>^>bSMp*J%2W&zFPQaL)!ybzd>mMCdiThG$iL`it4RDUPQ3%HW3S7&_=!3y{ z50fwhPopCC<5x^UPPm@LxQ(lrjMVrH3D6ww+l$zWH1PV614H5ZjqyW#go7xIduWJe zFm`LR5ZBQg@4_*!;0Ji^G}p$I1npt&ykChbC;+bs)+Y;$`xCgnuh9s`S^+)a`>w~B zGs7IYH=Wyfj4vGZ;hM|g7R;H)o0}(KpH;^^xUP0^9Q&aw2I3^L!*!UqOE8YtU`*g6G-;cVYhQL*qAB*4Sefp)59`DVm@freiry;}6t86kJbjIDbZL!OO^p z<7fqsv;V4M4UEISF^A^z8qCpV+`y-Bz1G8VC!#y7UnIVP`8H>cRT1XOc+G8VG{7>9 zh3grB?f3=WN?{%5;0wHqxW0wNaR5&0w1qwXGd#}wi)ab^@G)k? z>q~t&=GQ2J2z-PO@hEt2jD=y&?w~oU;|5&wGjNVecn8CR_x8(DoPlHQfctJE9Ah

p6&B@V!j% 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