From 15758d848b48d4d9e0bbd251be329ea408d724a8 Mon Sep 17 00:00:00 2001 From: Wuchuyuan <1917507415@qq.com> Date: Tue, 8 Sep 2026 11:01:34 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E9=9D=99=E6=80=81=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E6=96=87=E4=BB=B6=EF=BC=8C=E6=8A=8A=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E6=BA=90=E7=A0=81=E7=A7=BB=E6=A4=8D=E8=BF=9B=E6=9D=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AC701N.cbp | 19 +- JBao_JL7016_SOC_SDK_README.md | 7 +- Makefile | 17 +- apps/usr_le_code/ble_proto.c | 245 ++ apps/usr_le_code/ble_proto.h | 133 + apps/usr_le_code/bleproto.c | 529 ++++ apps/usr_le_code/bleproto.h | 53 + apps/usr_le_code/bleproto_api.h | 303 +++ apps/usr_le_code/bleproto_packer.c | 3054 ++++++++++++++++++++++ apps/usr_le_code/bleproto_packer.h | 702 +++++ apps/usr_le_code/lib/libusr_le_code.a | Bin 257070 -> 0 bytes apps/usr_le_code/thirdpart/mjson/mjson.c | 1066 ++++++++ apps/usr_le_code/thirdpart/mjson/mjson.h | 220 ++ apps/usr_le_code/usr_le_adv.c | 340 +++ apps/usr_le_code/usr_le_recieve.c | 377 +++ 15 files changed, 7054 insertions(+), 11 deletions(-) create mode 100644 apps/usr_le_code/ble_proto.c create mode 100644 apps/usr_le_code/ble_proto.h create mode 100644 apps/usr_le_code/bleproto.c create mode 100644 apps/usr_le_code/bleproto.h create mode 100644 apps/usr_le_code/bleproto_api.h create mode 100644 apps/usr_le_code/bleproto_packer.c create mode 100644 apps/usr_le_code/bleproto_packer.h delete mode 100644 apps/usr_le_code/lib/libusr_le_code.a create mode 100644 apps/usr_le_code/thirdpart/mjson/mjson.c create mode 100644 apps/usr_le_code/thirdpart/mjson/mjson.h create mode 100644 apps/usr_le_code/usr_le_adv.c create mode 100644 apps/usr_le_code/usr_le_recieve.c diff --git a/AC701N.cbp b/AC701N.cbp index f2b466b..f570e7a 100644 --- a/AC701N.cbp +++ b/AC701N.cbp @@ -258,7 +258,6 @@ - @@ -790,6 +789,24 @@ + + + + + + + + + + + + diff --git a/JBao_JL7016_SOC_SDK_README.md b/JBao_JL7016_SOC_SDK_README.md index f7d5594..de49de3 100644 --- a/JBao_JL7016_SOC_SDK_README.md +++ b/JBao_JL7016_SOC_SDK_README.md @@ -16,7 +16,7 @@ JBao_JL7016_SOC_SDK_V1.0.1/ │ ├── earphone/ # 耳机应用 │ ├── usr_jb_proto/ # 应用协议层 │ ├── usr_periph/ # 板级外设(RTC 等,源码开放) -│ └── usr_le_code/ # BLE 协议库(库文件 + 公开头文件) +│ └── usr_le_code/ # BLE 协议层(源码直编:协议编解码 + 广播/收发 + mjson) ├── cpu/ # 芯片相关代码与工具 ├── include_lib/ # SDK 头文件 ├── tools/ # 工程工具 @@ -29,7 +29,10 @@ JBao_JL7016_SOC_SDK_V1.0.1/ **apps/usr_le_code** -- 静态库:`apps/usr_le_code/lib/` +- 源码直编(已弃用预编译静态库 `libusr_le_code.a`),源文件随主工程一起编译: + - `apps/usr_le_code/ble_proto.c` / `bleproto.c` / `bleproto_packer.c` + - `apps/usr_le_code/usr_le_adv.c` / `usr_le_recieve.c` + - `apps/usr_le_code/thirdpart/mjson/mjson.c` - 公开头文件: - `apps/usr_le_code/usr_le_api.h` - `apps/usr_le_code/usr_le_product.h` diff --git a/Makefile b/Makefile index c4761fe..f1fecac 100644 --- a/Makefile +++ b/Makefile @@ -621,10 +621,12 @@ c_SRC_FILES := \ apps/usr_jb_proto/Utils/jb_ringbuffer.c \ apps/usr_jb_proto/usr_jb_main.c \ apps/usr_periph/usr_rtc.c \ - - -# usr_le_code 预编译静态库(Release 不附带协议源码,禁止本地重建) -USR_LE_LIB := apps/usr_le_code/lib/libusr_le_code.a + apps/usr_le_code/ble_proto.c \ + apps/usr_le_code/bleproto.c \ + apps/usr_le_code/bleproto_packer.c \ + apps/usr_le_code/thirdpart/mjson/mjson.c \ + apps/usr_le_code/usr_le_adv.c \ + apps/usr_le_code/usr_le_recieve.c \ @@ -729,7 +731,6 @@ LFLAGS := \ cpu/br28/liba/libllns.a \ cpu/br28/liba/libkwscommon.a \ apps/common/third_party_profile/tuya_protocol/sdk/lib/libtuya_lib.a \ - apps/usr_le_code/lib/libusr_le_code.a \ cpu/br28/liba/lib_icsd_adt.a \ cpu/br28/liba/lib_diafx.a \ cpu/br28/liba/libFFT_pi32v2_OnChip.a \ @@ -794,7 +795,7 @@ LINK_AT ?= 1 # see: https://www.gnu.org/software/make/manual/html_node/Suffix-Rules.html .SUFFIXES: -all: pre_build $(USR_LE_LIB) $(OUT_ELF) +all: pre_build $(OUT_ELF) $(info +POST-BUILD) $(QUITE) $(RUN_POST_SCRIPT) sdk @@ -814,13 +815,13 @@ clean: ifeq ($(LINK_AT), 1) -$(OUT_ELF): $(OBJS) $(USR_LE_LIB) +$(OUT_ELF): $(OBJS) $(info +LINK $@) $(shell $(MKDIR) $(@D)) $(file >$(OBJ_FILE), $(OBJS)) $(QUITE) $(LD) -o $(OUT_ELF) @$(OBJ_FILE) $(LFLAGS) $(LIBPATHS) $(LIBS) else -$(OUT_ELF): $(OBJS) $(USR_LE_LIB) +$(OUT_ELF): $(OBJS) $(info +LINK $@) $(shell $(MKDIR) $(@D)) $(QUITE) $(LD) -o $(OUT_ELF) $(OBJS) $(LFLAGS) $(LIBPATHS) $(LIBS) diff --git a/apps/usr_le_code/ble_proto.c b/apps/usr_le_code/ble_proto.c new file mode 100644 index 0000000..e5b71cd --- /dev/null +++ b/apps/usr_le_code/ble_proto.c @@ -0,0 +1,245 @@ +/** + * @file ble_proto.c + * @brief BLE-网关通信协议 V1.3 序列化 / 反序列化实现 + * + * 结构: + * [Cmd:2][Reserved:4][gateway_int_cnt:1][gateway_int32[]:4*N] + * [device_int_len:2][device_str_len:2][device_bytes_len:2] + * [Payload][CRC16:2] + * + * CRC 计算范围:Cmd ~ Payload 末尾(整个包除 CRC 字段) + */ + +#include "ble_proto.h" +#include + +/* ----------------------------------------------------------------------- + * 内部辅助:Big Endian 读写 + * --------------------------------------------------------------------- */ + +/** 写入 uint16_t(Big Endian) */ +static inline void put_be16(uint8_t *p, uint16_t v) +{ + p[0] = (uint8_t)(v >> 8); + p[1] = (uint8_t)(v & 0xFFu); +} + +/** 写入 uint32_t(Big Endian) */ +static inline void put_be32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)((v >> 16) & 0xFFu); + p[2] = (uint8_t)((v >> 8) & 0xFFu); + p[3] = (uint8_t)( v & 0xFFu); +} + +/** 读取 uint16_t(Big Endian) */ +static inline uint16_t get_be16(const uint8_t *p) +{ + return (uint16_t)(((uint16_t)p[0] << 8) | p[1]); +} + +/** 读取 uint32_t(Big Endian) */ +static inline uint32_t get_be32(const uint8_t *p) +{ + return ((uint32_t)p[0] << 24) + | ((uint32_t)p[1] << 16) + | ((uint32_t)p[2] << 8) + | (uint32_t)p[3]; +} + +/* ----------------------------------------------------------------------- + * CRC16(多项式 0x8005,初值 0xFFFF —— MODBUS 变体) + * --------------------------------------------------------------------- */ +uint16_t ble_proto_crc16(const uint8_t *data, size_t len) +{ + uint16_t crc = 0xFFFFu; + for (size_t i = 0; i < len; i++) { + crc ^= (uint16_t)data[i]; + for (int bit = 0; bit < 8; bit++) { + if (crc & 0x0001u) { + crc = (crc >> 1) ^ 0xA001u; + } else { + crc >>= 1; + } + } + } + return crc; +} + +/* ----------------------------------------------------------------------- + * 序列化(pack) + * --------------------------------------------------------------------- */ +int ble_proto_pack(const ble_proto_packet_t *pkt, uint8_t *buf, size_t buf_size) +{ + if (!pkt || !buf) return BLE_PROTO_ERR_NULL; + + /* ----- 参数合法性检查 ----- */ + if (pkt->gateway_int_cnt > BLE_PROTO_GW_INT_MAX) return BLE_PROTO_ERR_GW_CNT; + if (pkt->device_int_len % 4 != 0) return BLE_PROTO_ERR_INT_ALIGN; + + /* ----- 计算所需总长度 ----- */ + size_t payload_len = (size_t)pkt->device_int_len + + (size_t)pkt->device_str_len + + (size_t)pkt->device_bytes_len; + + size_t total = BLE_PROTO_HEADER_LEN /* Cmd(2) + Reserved(4) */ + + BLE_PROTO_GW_CNT_LEN /* gateway_int_cnt (1) */ + + (size_t)pkt->gateway_int_cnt * 4u /* gateway_int[] */ + + BLE_PROTO_DEV_PARAM_LEN /* 3 x device_*_len */ + + payload_len /* Payload */ + + BLE_PROTO_CRC_LEN; /* CRC16 */ + + if (buf_size < total) return BLE_PROTO_ERR_BUF_SMALL; + + uint8_t *p = buf; + + /* ----- Protocol Header ----- */ + put_be16(p, pkt->cmd); p += 2; + put_be32(p, 0); p += 4; /* Reserved 固定填 0 */ + + /* ----- Gateway Parameter ----- */ + *p++ = pkt->gateway_int_cnt; /* uint8 */ + for (uint8_t i = 0; i < pkt->gateway_int_cnt; i++) { + put_be32(p, (uint32_t)pkt->gateway_int[i]); + p += 4; + } + + /* ----- Device Parameter ----- */ + put_be16(p, pkt->device_int_len); p += 2; + put_be16(p, pkt->device_str_len); p += 2; + put_be16(p, pkt->device_bytes_len); p += 2; + + /* ----- Payload ----- */ + if (pkt->device_int_len > 0 && pkt->int_data) { + for (uint16_t i = 0; i < pkt->device_int_len / 4u; i++) { + put_be32(p, (uint32_t)pkt->int_data[i]); + p += 4; + } + } + if (pkt->device_str_len > 0 && pkt->str_data) { + memcpy(p, pkt->str_data, pkt->device_str_len); + p += pkt->device_str_len; + } + if (pkt->device_bytes_len > 0 && pkt->bytes_data) { + memcpy(p, pkt->bytes_data, pkt->device_bytes_len); + p += pkt->device_bytes_len; + } + + /* ----- CRC16(从 buf[0] 开始,到 Payload 末尾) ----- */ + uint16_t crc = ble_proto_crc16(buf, (size_t)(p - buf)); + put_be16(p, crc); + p += 2; + + + return (int)(p - buf); +} + +/* ----------------------------------------------------------------------- + * 反序列化(unpack) + * --------------------------------------------------------------------- */ +int ble_proto_unpack(const uint8_t *buf, size_t len, ble_proto_packet_t *pkt) +{ + if (!buf || !pkt) return BLE_PROTO_ERR_NULL; + + /* 最小包长:Header(6) + gw_cnt(1) + dev_param(6) + CRC(2) = 15 */ + const size_t min_len = BLE_PROTO_HEADER_LEN + + BLE_PROTO_GW_CNT_LEN + + BLE_PROTO_DEV_PARAM_LEN + + BLE_PROTO_CRC_LEN; + if (len < min_len) return BLE_PROTO_ERR_LEN; + + const uint8_t *p = buf; + + /* ----- Protocol Header ----- */ + pkt->cmd = get_be16(p); p += 2; + pkt->reserved = get_be32(p); p += 4; + + /* ----- Gateway Parameter ----- */ + pkt->gateway_int_cnt = *p++; + if (pkt->gateway_int_cnt > BLE_PROTO_GW_INT_MAX) return BLE_PROTO_ERR_GW_CNT; + + /* 检查剩余字节是否足够容纳 gateway_int[] + dev_param + CRC */ + size_t consumed = (size_t)(p - buf) + + (size_t)pkt->gateway_int_cnt * 4u + + BLE_PROTO_DEV_PARAM_LEN + + BLE_PROTO_CRC_LEN; + if (len < consumed) return BLE_PROTO_ERR_LEN; + + for (uint8_t i = 0; i < pkt->gateway_int_cnt; i++) { + pkt->gateway_int[i] = (int32_t)get_be32(p); + p += 4; + } + + /* ----- Device Parameter ----- */ + pkt->device_int_len = get_be16(p); p += 2; + pkt->device_str_len = get_be16(p); p += 2; + pkt->device_bytes_len = get_be16(p); p += 2; + + if (pkt->device_int_len % 4 != 0) return BLE_PROTO_ERR_INT_ALIGN; + + size_t payload_len = (size_t)pkt->device_int_len + + (size_t)pkt->device_str_len + + (size_t)pkt->device_bytes_len; + + /* 验证总长度(含 CRC) */ + if ((size_t)(p - buf) + payload_len + BLE_PROTO_CRC_LEN != len) { + return BLE_PROTO_ERR_LEN; + } + + /* ----- CRC16 校验(从 buf[0] 到 CRC 前一字节) ----- */ + uint16_t crc_calc = ble_proto_crc16(buf, len - BLE_PROTO_CRC_LEN); + uint16_t crc_recv = get_be16(buf + len - BLE_PROTO_CRC_LEN); + if (crc_calc != crc_recv) return BLE_PROTO_ERR_CRC; + + /* ----- Payload(零拷贝,直接指向 buf 内部) ----- */ + if (pkt->device_int_len > 0) { + pkt->int_data = (const int32_t *)p; + } else { + pkt->int_data = NULL; + } + p += pkt->device_int_len; + + pkt->str_data = (pkt->device_str_len > 0) ? (const char *)p : NULL; + p += pkt->device_str_len; + + pkt->bytes_data = (pkt->device_bytes_len > 0) ? p : NULL; + + // printf("cmd = %d", pkt->cmd); + // printf("reserved = %d", pkt->reserved); + // printf("gateway_int_cnt = %d", pkt->gateway_int_cnt); + // printf("device_int_len = %d", pkt->device_int_len); + // printf("device_str_len = %d", pkt->device_str_len); + // printf("device_bytes_len = %d", pkt->device_bytes_len); + + // /* int_data 指向 buf 内部,地址未必 4 字节对齐, + // * 不能直接解引用 int32_t*(对齐访问会触发硬件异常); + // * 必须按字节读取,用 get_be32 还原数值 */ + // if (pkt->int_data && pkt->device_int_len >= 4) { + // const uint8_t *ip = (const uint8_t *)pkt->int_data; + // printf("int_data = "); + // for (uint16_t i = 0; i < pkt->device_int_len / 4u; i++) { + // printf("%08X ", (unsigned int)get_be32(ip + (uint32_t)i * 4u)); + // } + // printf("\n"); + // } + // /* str_data / bytes_data 不保证有 '\0',用 printf_buf 打印 hex */ + // if (pkt->str_data && pkt->device_str_len > 0) { + // printf("str_data = "); + // for (size_t i = 0; i < pkt->device_str_len; i++) { + // printf("%02X ", *(pkt->str_data + i)); + // } + // printf("\n"); + // } + // if (pkt->bytes_data && pkt->device_bytes_len > 0) { + // printf("bytes_data = "); + // for (size_t i = 0; i < pkt->device_bytes_len; i++) { + // printf("%02X ", *(pkt->bytes_data + i)); + // } + // printf("\n"); + // } + // printf("crc = %04X", crc_recv); + // printf("\n"); + + return BLE_PROTO_OK; +} diff --git a/apps/usr_le_code/ble_proto.h b/apps/usr_le_code/ble_proto.h new file mode 100644 index 0000000..7ac15d7 --- /dev/null +++ b/apps/usr_le_code/ble_proto.h @@ -0,0 +1,133 @@ +/** + * @file ble_proto.h + * @brief BLE-网关通信协议 V1.3 序列化 / 反序列化接口 + * + * 数据包结构(Big Endian): + * [Cmd:2][Reserved:4] + * [gateway_int_cnt:1][gateway_int32[]:4*N] + * [device_int_len:2][device_str_len:2][device_bytes_len:2] + * [Payload: int_data | str_data | bytes_data] + * [CRC16:2] + * + * 当前协议头(固定 6 字节):[Cmd:2][Reserved:4] + * (说明:本文件早期 V1.2 草案曾在 Cmd 后加入 8 字节时间戳 t, + * 现设备侧不再使用,已从线格式中彻底移除。) + * + * V1.1 变更(相对 V1.0): + * - 删除 Magic、Version、Seq 字段 + * - 新增 Reserved(uint32,固定填 0) + * - Cmd 移至偏移 0 + * - gateway_int_cnt 由 uint16 调整为 uint8 + * - CRC 计算范围:Cmd ~ Payload 末尾(整个包除 CRC 字段本身) + */ + +#ifndef BLE_PROTO_H +#define BLE_PROTO_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------------------- + * 协议常量 + * --------------------------------------------------------------------- */ + +/** Header 固定长度:Cmd(2) + Reserved(4) = 6 Bytes */ +#define BLE_PROTO_HEADER_LEN 6u + +/** gateway_int_cnt 字段长度(uint8) */ +#define BLE_PROTO_GW_CNT_LEN 1u + +/** Device Parameter 固定长度:3 x uint16 = 6 Bytes */ +#define BLE_PROTO_DEV_PARAM_LEN 6u + +/** CRC16 字段长度 */ +#define BLE_PROTO_CRC_LEN 2u + +/** 最大 Gateway int32 参数个数(防御性上限,可按需修改) */ +#define BLE_PROTO_GW_INT_MAX 16u + +/** 最大 Payload 总长度(按实际业务调整) */ +#define BLE_PROTO_PAYLOAD_MAX 2048u + +/* ----------------------------------------------------------------------- + * 报文数据包(解包后的逻辑结构) + * --------------------------------------------------------------------- */ +typedef struct { + /* ----- Protocol Header ----- */ + uint16_t cmd; /**< 协议命令(上报、控制、应答、OTA等) */ + uint32_t reserved; /**< 保留字段,发送端固定填 0 */ + + /* ----- Gateway Parameter ----- */ + uint8_t gateway_int_cnt; /**< Gateway int32 参数个数 */ + int32_t gateway_int[BLE_PROTO_GW_INT_MAX]; /**< Gateway int32 参数列表 */ + + /* ----- Device Parameter ----- */ + uint16_t device_int_len; /**< Payload 中 int 数据字节数(必须是 4 的整数倍) */ + uint16_t device_str_len; /**< Payload 中 string 数据字节数(不含 '\0') */ + uint16_t device_bytes_len; /**< Payload 中 bytes 数据字节数 */ + + /* ----- Payload 数据指针(指向外部 buffer,不拥有所有权) ----- */ + const int32_t *int_data; /**< Device Int 数据(pack 输入为主机序 int32 数组,ble_proto_pack 内部逐元素 put_be32 转 BE;unpack 输出为 buf 内原始 BE 字节,调用方须用 get_be32 读取,不可直接解引用) */ + const char *str_data; /**< Device String 数据(不含 '\0') */ + const uint8_t *bytes_data; /**< Device Bytes 数据 */ +} ble_proto_packet_t; + +/* ----------------------------------------------------------------------- + * 错误码 + * --------------------------------------------------------------------- */ +typedef enum { + BLE_PROTO_OK = 0, + BLE_PROTO_ERR_NULL = -1, /**< 空指针 */ + BLE_PROTO_ERR_BUF_SMALL = -2, /**< 输出缓冲区不足 */ + BLE_PROTO_ERR_CRC = -3, /**< CRC16 校验失败 */ + BLE_PROTO_ERR_LEN = -4, /**< 长度字段非法 / 包长不足 */ + BLE_PROTO_ERR_GW_CNT = -5, /**< gateway_int_cnt 超出上限 */ + BLE_PROTO_ERR_INT_ALIGN = -6, /**< device_int_len 非 4 倍数 */ +} ble_proto_err_t; + +/* ----------------------------------------------------------------------- + * 接口 + * --------------------------------------------------------------------- */ + +/** + * @brief 序列化:将逻辑数据包打包成字节流 + * + * @param[in] pkt 逻辑数据包(reserved 字段被忽略,固定填 0) + * @param[out] buf 输出缓冲区 + * @param[in] buf_size 缓冲区大小(字节) + * @return 打包后实际字节数(>0),或 ble_proto_err_t 负值 + */ +int ble_proto_pack(const ble_proto_packet_t *pkt, uint8_t *buf, size_t buf_size); + +/** + * @brief 反序列化:将字节流解析为逻辑数据包 + * + * @note pkt->int_data / str_data / bytes_data 指向 buf 内部,不复制数据。 + * buf 的生命周期必须长于 pkt 的使用期。 + * @note pkt->reserved 的值被读出但不做校验,接收端可直接忽略。 + * + * @param[in] buf 输入字节流 + * @param[in] len 字节流长度 + * @param[out] pkt 解析结果 + * @return BLE_PROTO_OK,或 ble_proto_err_t 负值 + */ +int ble_proto_unpack(const uint8_t *buf, size_t len, ble_proto_packet_t *pkt); + +/** + * @brief 计算 CRC16(MODBUS 变体,多项式 0x8005,初值 0xFFFF) + * + * @param[in] data 数据起始地址 + * @param[in] len 数据字节数 + * @return CRC16 值 + */ +uint16_t ble_proto_crc16(const uint8_t *data, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* BLE_PROTO_H */ diff --git a/apps/usr_le_code/bleproto.c b/apps/usr_le_code/bleproto.c new file mode 100644 index 0000000..89992ad --- /dev/null +++ b/apps/usr_le_code/bleproto.c @@ -0,0 +1,529 @@ +/****************************************************************************/ +/* bleproto.c + * + * Copyright (C) 2024 四川迈科创智科技有限公司 + * + ****************************************************************************/ + +/****************************************************************************/ +/* Included Files */ +/****************************************************************************/ + +#include "bleproto.h" + +/****************************************************************************/ +/* Trace Definitions */ +/****************************************************************************/ + +/****************************************************************************/ +/* Pre-processor Definitions */ +/****************************************************************************/ + +/****************************************************************************/ +/* Private Types */ +/****************************************************************************/ + +/****************************************************************************/ +/* Private Function Prototypes */ +/****************************************************************************/ + +/****************************************************************************/ +/* Private Data */ +/****************************************************************************/ + +/****************************************************************************/ +/* Public Data */ +/****************************************************************************/ + +/****************************************************************************/ +/* Private Functions */ +/****************************************************************************/ + +/****************************************************************************/ +/* Public Functions */ +/****************************************************************************/ + +const char *libbleproto_version(void) +{ + return BLEPROTO_API_VERSION; +} + +uint8_t bleproto_advdata_encode4bind( + uint8_t data[31], + int8_t txpower, + uint8_t manucode[BLEPROTO_ADV_TLV_LENGTH_MANUCODE], + uint8_t pcode[BLE_PROTO_ADV_LENGTH_PCODE], + uint8_t prodcode[BLEPROTO_ADV_TLV_LENGTH_PRODCODE]) +{ + bleproto_adv_t adv = {0}; + /* adv1 */ + { + adv.adv1.length = 0x02; + adv.adv1.ad_type = BLEPROTO_ADV_TYPE_FLAGS; + adv.adv1.ad_data = 0x06; + } + /* adv2 */ + // clang-format off + { + adv.adv2.length = 0x1B; + adv.adv2.ad_type = BLEPROTO_ADV_TYPE_SVC_DATA_UUID16; + + + adv.adv2.ad_data.service_data.uuid = BLEPROTO_ADV_SERVICE_UUID_TOBIND; + /* spec_data */ + bleproto_adv_specdata_t *spec_data = + &adv.adv2.ad_data.service_data.spec_data; + + spec_data->version = BLEPORTO_ADV_SERVICE_SPEC_DATA_VER; + spec_data->business = BELPROTO_ADV_SERVICE_SPEC_DATA_BUSINESS_NEARFIND; + spec_data->rfu[0] = 0; + spec_data->rfu[1] = 0; + spec_data->txpower = txpower; + + spec_data->manucode.type = BLEPROTO_ADV_TLV_TYPE_MANUCODE; + spec_data->manucode.length = BLEPROTO_ADV_TLV_LENGTH_MANUCODE; + memcpy(spec_data->manucode.value, manucode, BLEPROTO_ADV_TLV_LENGTH_MANUCODE); + + spec_data->prodcode.type = BLEPROTO_ADV_TLV_TYPE_PRODCODE; + spec_data->prodcode.length = BLEPROTO_ADV_TLV_LENGTH_PRODCODE; + memcpy(spec_data->prodcode.value, prodcode, BLEPROTO_ADV_TLV_LENGTH_PRODCODE); + + spec_data->separator = 0xFF; + + memcpy(spec_data->selfdata, pcode, BLE_PROTO_ADV_LENGTH_PCODE); + // spec_data->selfdata[0] = 0X30; + // spec_data->selfdata[1] = 0X30; + // spec_data->selfdata[2] = 0X30; + // spec_data->selfdata[3] = 0X30; + // spec_data->selfdata[4] = 0X30; + // spec_data->selfdata[5] = 0X30; + } + // clang-format on + + return bleproto_advdata_serialize(&adv, data, 31); +} + +void bleproto_advdata_encode4heartbeat( + uint8_t data[31], + int8_t txpower, + uint8_t gwmac[6], + uint8_t manucode[BLEPROTO_ADV_TLV_LENGTH_MANUCODE], + uint8_t pcode[BLE_PROTO_ADV_LENGTH_PCODE], + uint8_t prodcode[BLEPROTO_ADV_TLV_LENGTH_PRODCODE]) +{ + bleproto_adv_t adv = {0}; + /* adv1 */ + { + adv.adv1.length = 0x02; + adv.adv1.ad_type = BLEPROTO_ADV_TYPE_FLAGS; + adv.adv1.ad_data = 0x06; + } + /* adv2 */ + // clang-format off + { + adv.adv2.length = 0x1B; + adv.adv2.ad_type = BLEPROTO_ADV_TYPE_MFG_DATA; + /* mfg_data */ + bleproto_advdata_mfg_t *mfg_data = &adv.adv2.ad_data.mfg_data; + + memcpy(mfg_data->siot, pcode, BLE_PROTO_ADV_LENGTH_PCODE); + #if 0 + mfg_data->siot[0] = 'S'; + mfg_data->siot[1] = 'I'; + mfg_data->siot[2] = 'O'; + mfg_data->siot[3] = 'T'; + mfg_data->siot[4] = '-'; + mfg_data->siot[5] = '1'; + #endif + + /* HEARTBEAT */ + mfg_data->flag = BLEPROTO_ADV_FLAG_HEARTBEAT; + + mfg_data->txpower = txpower; + memcpy(mfg_data->gwmac, gwmac, 6); + + mfg_data->manucode.type = BLEPROTO_ADV_TLV_TYPE_MANUCODE; + mfg_data->manucode.length = BLEPROTO_ADV_TLV_LENGTH_MANUCODE; + memcpy(mfg_data->manucode.value, manucode, BLEPROTO_ADV_TLV_LENGTH_MANUCODE); + + mfg_data->prodcode.type = BLEPROTO_ADV_TLV_TYPE_PRODCODE; + mfg_data->prodcode.length = BLEPROTO_ADV_TLV_LENGTH_PRODCODE; + memcpy(mfg_data->prodcode.value, prodcode, BLEPROTO_ADV_TLV_LENGTH_PRODCODE); + } + // clang-format on + + (void)bleproto_advdata_serialize(&adv, data, 31); +} + +uint8_t bleproto_advdata_encode4reconn( + uint8_t data[31], + int8_t txpower, + uint8_t gwmac[6], + uint8_t manucode[BLEPROTO_ADV_TLV_LENGTH_MANUCODE], + uint8_t pcode[BLE_PROTO_ADV_LENGTH_PCODE], + uint8_t prodcode[BLEPROTO_ADV_TLV_LENGTH_PRODCODE]) +{ + bleproto_adv_t adv = {0}; + /* adv1 */ + { + adv.adv1.length = 0x02; + adv.adv1.ad_type = BLEPROTO_ADV_TYPE_FLAGS; + adv.adv1.ad_data = 0x06; + } + /* adv2 */ + // clang-format off + { + adv.adv2.length = 0x1B; + adv.adv2.ad_type = BLEPROTO_ADV_TYPE_MFG_DATA; + /* mfg_data */ + bleproto_advdata_mfg_t *mfg_data = &adv.adv2.ad_data.mfg_data; + + memcpy(mfg_data->siot, pcode, BLE_PROTO_ADV_LENGTH_PCODE); + #if 0 + mfg_data->siot[0] = 'S'; + mfg_data->siot[1] = 'I'; + mfg_data->siot[2] = 'O'; + mfg_data->siot[3] = 'T'; + mfg_data->siot[4] = '-'; + mfg_data->siot[5] = '1'; + #endif + + /* RECONNECT */ + mfg_data->flag = BLEPROTO_ADV_FLAG_RECONNECT; + + mfg_data->txpower = txpower; + memcpy(mfg_data->gwmac, gwmac, 6); + + mfg_data->manucode.type = BLEPROTO_ADV_TLV_TYPE_MANUCODE; + mfg_data->manucode.length = BLEPROTO_ADV_TLV_LENGTH_MANUCODE; + memcpy(mfg_data->manucode.value, manucode, BLEPROTO_ADV_TLV_LENGTH_MANUCODE); + + mfg_data->prodcode.type = BLEPROTO_ADV_TLV_TYPE_PRODCODE; + mfg_data->prodcode.length = BLEPROTO_ADV_TLV_LENGTH_PRODCODE; + memcpy(mfg_data->prodcode.value, prodcode, BLEPROTO_ADV_TLV_LENGTH_PRODCODE); + } + // clang-format on + + return bleproto_advdata_serialize(&adv, data, 31); +} + +int bleproto_advdata_decode(const uint8_t *data, + uint8_t len, + bleproto_adv_t *adv) +{ + return bleproto_advdata_deserialize(data, len, adv); +} + +int bleproto_appdata_encode(uint8_t *txbuf, + uint16_t size, + uint8_t packetlen, + bleproto_appdata_desc_t *desc) +{ + int rc; + uint16_t txbuf_size = size; + + /* + * appdata.data[] 在 SMALL 模式下约 2.3KB,供 JSON/OTA 编码中间缓冲。 + * JB 55AA 透传可走 bleproto_appdata_wrap_raw,不必经本函数。 + * 此处用 static,避免在调用方任务栈上临时分配。 + */ + static bleproto_appdata_t s_enc_appdata; + bleproto_appdata_t *appdata = &s_enc_appdata; + + /* header */ + { + appdata->header = desc->header; + } + /* data */ + if (appdata->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_REQ) + { + switch (appdata->header.serviceid) + { + case E_BLEPROTO_SERVICE_ID_DEVICEINFO: + rc = bleproto_encjson_deviceinfo_req(&desc->datast.deviceinfo_req, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_AUTHSETUP: + rc = bleproto_encjson_authsetup_req(&desc->datast.authsetup_req, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_AUTHDELETE: + rc = bleproto_encjson_authdelete_req(&desc->datast.authdelete_req, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_RUNCMD: + rc = bleproto_encjson_runcmd_req(&desc->datast.runcmd_req, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_REPORTCMD: + rc = bleproto_encjson_reportcmd_req(&desc->datast.reportcmd_req, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_DOACTION: + rc = bleproto_encjson_doaction_req(&desc->datast.doaction_req, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_OTANOTIFY: + rc = bleproto_encjson_otanotify_req(&desc->datast.otanotify_req, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_OTADATA: + rc = bleproto_encjson_otadata_req(&desc->datast.otadata_req, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_RUNCMD_V2: + rc = ble_proto_pack(&desc->datast.runcmd_v2_req, appdata->data, sizeof(appdata->data)); + break; + case E_BLEPROTO_SERVICE_ID_REPORTCMD_V2: + rc = ble_proto_pack(&desc->datast.reportcmd_v2_req, appdata->data, sizeof(appdata->data)); + break; + case E_BLEPROTO_SERVICE_ID_OTADATA_V2: + rc = ble_proto_pack(&desc->datast.otadata_v2_req, appdata->data, sizeof(appdata->data)); + break; + default: + /* unkown serviceid */ + return (-1); + } + } + else if (appdata->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_RSP) + { + switch (appdata->header.serviceid) + { + case E_BLEPROTO_SERVICE_ID_DEVICEINFO: + rc = bleproto_encjson_deviceinfo_rsp(&desc->datast.deviceinfo_rsp, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_AUTHSETUP: + rc = bleproto_encjson_authsetup_rsp(&desc->datast.authsetup_rsp, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_AUTHDELETE: + rc = bleproto_encjson_authdelete_rsp(&desc->datast.authdelete_rsp, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_RUNCMD: + rc = bleproto_encjson_runcmd_rsp(&desc->datast.runcmd_rsp, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_REPORTCMD: + rc = bleproto_encjson_reportcmd_rsp(&desc->datast.reportcmd_rsp, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_DOACTION: + rc = bleproto_encjson_doaction_rsp(&desc->datast.doaction_rsp, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_OTANOTIFY: + rc = bleproto_encjson_otanotify_rsp(&desc->datast.otanotify_rsp, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_OTADATA: + rc = bleproto_encbin_otadata_rsp(&desc->datast.otadata_rsp, + appdata->data); + break; + case E_BLEPROTO_SERVICE_ID_RUNCMD_V2: + rc = ble_proto_pack(&desc->datast.runcmd_v2_rsp, appdata->data, sizeof(appdata->data)); + break; + case E_BLEPROTO_SERVICE_ID_REPORTCMD_V2: + rc = ble_proto_pack(&desc->datast.reportcmd_v2_rsp, appdata->data, sizeof(appdata->data)); + break; + case E_BLEPROTO_SERVICE_ID_OTADATA_V2: + rc = ble_proto_pack(&desc->datast.otadata_v2_rsp, appdata->data, sizeof(appdata->data)); + break; + default: + /* unkown serviceid */ + return (-1); + } + } + else + { + /* unkown msgtype */ + return (-1); + } + /* failed to encjson struct */ + if (rc <= 0) + { + return (-1); + } + + /* datalen */ + appdata->header.datalen = (uint16_t)rc; + + /* appdata_serialize */ + return bleproto_appdata_serialize(appdata, packetlen, txbuf, txbuf_size); +} + +int bleproto_appdata_decode(uint8_t *rxbuf, + uint16_t len, + bleproto_appdata_desc_t *desc) +{ + int rc, rlen; + + /* + * V2(ble_proto_unpack) 零拷贝:bytes_data 等指向 appdata.data。 + * 若用栈局部变量,函数返回后指针悬空,usr_run_cmd_code_v2 会踩坏数据。 + * 使用 static,保证 desc 内指针在下次 decode 前有效。 + */ + static bleproto_appdata_t s_appdata; + bleproto_appdata_t *appdata = &s_appdata; + + /* appdata_deserialize */ + rlen = bleproto_appdata_deserialize(rxbuf, len, appdata); + if (rlen <= 0) + { + return (rlen); + } + + /* header */ + { + desc->header = appdata->header; + } + /* data */ + if (appdata->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_REQ) + { + switch (appdata->header.serviceid) + { + case E_BLEPROTO_SERVICE_ID_DEVICEINFO: + rc = bleproto_decjson_deviceinfo_req(appdata->data, + appdata->header.datalen, + &desc->datast.deviceinfo_req); + break; + case E_BLEPROTO_SERVICE_ID_AUTHSETUP: + rc = bleproto_decjson_authsetup_req(appdata->data, + appdata->header.datalen, + &desc->datast.authsetup_req); + break; + case E_BLEPROTO_SERVICE_ID_AUTHDELETE: + rc = bleproto_decjson_authdelete_req(appdata->data, + appdata->header.datalen, + &desc->datast.authdelete_req); + break; + case E_BLEPROTO_SERVICE_ID_RUNCMD: + rc = bleproto_decjson_runcmd_req(appdata->data, + appdata->header.datalen, + &desc->datast.runcmd_req); + break; + case E_BLEPROTO_SERVICE_ID_REPORTCMD: + rc = bleproto_decjson_reportcmd_req(appdata->data, + appdata->header.datalen, + &desc->datast.reportcmd_req); + break; + case E_BLEPROTO_SERVICE_ID_DOACTION: + rc = bleproto_decjson_doaction_req(appdata->data, + appdata->header.datalen, + &desc->datast.doaction_req); + break; + case E_BLEPROTO_SERVICE_ID_OTANOTIFY: + rc = bleproto_decjson_otanotify_req(appdata->data, + appdata->header.datalen, + &desc->datast.otanotify_req); + break; + case E_BLEPROTO_SERVICE_ID_OTADATA: + rc = bleproto_decjson_otadata_req(appdata->data, + appdata->header.datalen, + &desc->datast.otadata_req); + break; + case E_BLEPROTO_SERVICE_ID_RUNCMD_V2: + printf("runcmd_v2_req \n"); + rc = ble_proto_unpack(appdata->data, appdata->header.datalen, &desc->datast.runcmd_v2_req); + printf("runcmd_v2_req rc = %d \n", rc); + break; + case E_BLEPROTO_SERVICE_ID_REPORTCMD_V2: + printf("reportcmd_v2_req \n"); + rc = ble_proto_unpack(appdata->data, appdata->header.datalen, &desc->datast.reportcmd_v2_req); + printf("reportcmd_v2_req rc = %d \n", rc); + break; + case E_BLEPROTO_SERVICE_ID_OTADATA_V2: + printf("otadata_v2_req \n"); + rc = ble_proto_unpack(appdata->data, appdata->header.datalen, &desc->datast.otadata_v2_req); + printf("otadata_v2_req rc = %d \n", rc); + break; + default: + /* unkown serviceid */ + return (-1); + } + } + else if (appdata->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_RSP) + { + switch (appdata->header.serviceid) + { + case E_BLEPROTO_SERVICE_ID_DEVICEINFO: + rc = + bleproto_decjson_deviceinfo_rsp(appdata->data, + appdata->header.datalen, + &desc->datast.deviceinfo_rsp); + break; + case E_BLEPROTO_SERVICE_ID_AUTHSETUP: + rc = bleproto_decjson_authsetup_rsp(appdata->data, + appdata->header.datalen, + &desc->datast.authsetup_rsp); + break; + case E_BLEPROTO_SERVICE_ID_AUTHDELETE: + rc = + bleproto_decjson_authdelete_rsp(appdata->data, + appdata->header.datalen, + &desc->datast.authdelete_rsp); + break; + case E_BLEPROTO_SERVICE_ID_RUNCMD: + rc = bleproto_decjson_runcmd_rsp(appdata->data, + appdata->header.datalen, + &desc->datast.runcmd_rsp); + break; + case E_BLEPROTO_SERVICE_ID_REPORTCMD: + rc = bleproto_decjson_reportcmd_rsp(appdata->data, + appdata->header.datalen, + &desc->datast.reportcmd_rsp); + break; + case E_BLEPROTO_SERVICE_ID_DOACTION: + rc = bleproto_decjson_doaction_rsp(appdata->data, + appdata->header.datalen, + &desc->datast.doaction_rsp); + break; + case E_BLEPROTO_SERVICE_ID_OTANOTIFY: + rc = bleproto_decjson_otanotify_rsp(appdata->data, + appdata->header.datalen, + &desc->datast.otanotify_rsp); + break; + case E_BLEPROTO_SERVICE_ID_OTADATA: + rc = bleproto_debin_otadata_rsp(appdata->data, + appdata->header.datalen, + &desc->datast.otadata_rsp); + break; + case E_BLEPROTO_SERVICE_ID_RUNCMD_V2: + printf("runcmd_v2_rsp \n"); + rc = ble_proto_unpack(appdata->data, appdata->header.datalen, &desc->datast.runcmd_v2_rsp); + printf("runcmd_v2_rsp rc = %d \n", rc); + break; + case E_BLEPROTO_SERVICE_ID_REPORTCMD_V2: + printf("reportcmd_v2_rsp \n"); + rc = ble_proto_unpack(appdata->data, appdata->header.datalen, &desc->datast.reportcmd_v2_rsp); + printf("reportcmd_v2_rsp rc = %d \n", rc); + break; + case E_BLEPROTO_SERVICE_ID_OTADATA_V2: + printf("otadata_v2_rsp \n"); + rc = ble_proto_unpack(appdata->data, appdata->header.datalen, &desc->datast.otadata_v2_rsp); + printf("otadata_v2_rsp rc = %d \n", rc); + break; + default: + /* unkown serviceid */ + return (-1); + } + } + else + { + /* unkown msgtype */ + return (-1); + } + /* failed to decjson struct */ + if (rc < 0) + { + return (-1); + } + + return (rlen); +} +/****************************************************************************/ +/* */ +/* End of file. */ +/* */ +/****************************************************************************/ diff --git a/apps/usr_le_code/bleproto.h b/apps/usr_le_code/bleproto.h new file mode 100644 index 0000000..b917a12 --- /dev/null +++ b/apps/usr_le_code/bleproto.h @@ -0,0 +1,53 @@ +/****************************************************************************/ +/* bleproto.h + * + * Copyright (C) 2021 四川迈科创智科技有限公司 + * + ****************************************************************************/ +#ifndef BLEPROTO_H_INCLUDE +#define BLEPROTO_H_INCLUDE +/****************************************************************************/ +/* Included Files */ +/****************************************************************************/ + +/* api */ +#include "bleproto_api.h" + +/* thirdpart api */ +#include "mjson.h" + +/****************************************************************************/ +/* Configure Definitions */ +/****************************************************************************/ + +/****************************************************************************/ +/* Pre-processor Definitions */ +/****************************************************************************/ + +/****************************************************************************/ +/* Public Types */ +/****************************************************************************/ + +/****************************************************************************/ +/* Public Data */ +/****************************************************************************/ + +/****************************************************************************/ +#ifdef __cplusplus +extern "C" { +#endif +/****************************************************************************/ +/* Public Function Prototypes */ +/****************************************************************************/ + +/****************************************************************************/ +#ifdef __cplusplus +} +#endif +/****************************************************************************/ +#endif /* BLEPROTO_H_INCLUDE */ +/****************************************************************************/ +/* */ +/* End of file. */ +/* */ +/****************************************************************************/ diff --git a/apps/usr_le_code/bleproto_api.h b/apps/usr_le_code/bleproto_api.h new file mode 100644 index 0000000..f36f92d --- /dev/null +++ b/apps/usr_le_code/bleproto_api.h @@ -0,0 +1,303 @@ +/****************************************************************************/ +/* bleproto_api.h + * + * Copyright (C) 2021 四川迈科创智科技有限公司 + * + ****************************************************************************/ +/** + * @page bleproto_api_guides API Guides + * + * @ingroup bleproto_api_guides + * @addtogroup bleproto_api bleproto + * + * wiki + * ==== + * - https://gogos.mkcziot.com:13000/iot-device/cziot-sdk/src/master/doc/wiki/ble/mk蓝牙设备开发指南.md + * + * @{ + */ + +#ifndef BLEPROTO_API_H_INCLUDE +#define BLEPROTO_API_H_INCLUDE +/****************************************************************************/ +/* Included Files */ +/****************************************************************************/ + +/* system */ +//#include +/*#include +#include +#include +#include */ + +/* group */ +#include "bleproto_packer.h" +#include "ble_proto.h" +/****************************************************************************/ +/* Configure Definitions */ +/****************************************************************************/ + +/****************************************************************************/ +/* Pre-processor Definitions */ +/****************************************************************************/ + +/* api version */ +#define BLEPROTO_API_VERSION "1.1.4" + +/****************************************************************************/ +/* Public Types */ +/****************************************************************************/ + +/** + * @brief 应用数据描述 + */ +typedef struct bleproto_appdata_desc { + /** 包头 */ + bleproto_appdata_header_t header; + /** + * 根据 + * - header.msgtype:请求(0=req)、响应(1=rsp) + * - header.serviceid @ref bleproto_service_id_e + * datast取对应的member + * + * 比如 header.msgtype = 0(请求req), header.serviceid = E_BLEPROTO_SERVICE_ID_DEVICEINFO + * datast 对应 deviceinfo_req + */ + union { + bleproto_deviceinfo_req_t deviceinfo_req; + bleproto_deviceinfo_rsp_t deviceinfo_rsp; + bleproto_authsetup_req_t authsetup_req; + bleproto_authsetup_rsp_t authsetup_rsp; + bleproto_authdelete_req_t authdelete_req; + bleproto_authdelete_rsp_t authdelete_rsp; + bleproto_runcmd_req_t runcmd_req; + bleproto_runcmd_rsp_t runcmd_rsp; + bleproto_reportcmd_req_t reportcmd_req; + bleproto_reportcmd_rsp_t reportcmd_rsp; + bleproto_doaction_req_t doaction_req; + bleproto_doaction_rsp_t doaction_rsp; + bleproto_otanotify_req_t otanotify_req; + bleproto_otanotify_rsp_t otanotify_rsp; + bleproto_otadata_req_t otadata_req; + bleproto_otadata_rsp_t otadata_rsp; + + ble_proto_packet_t runcmd_v2_req; + ble_proto_packet_t runcmd_v2_rsp; + ble_proto_packet_t reportcmd_v2_req; + ble_proto_packet_t reportcmd_v2_rsp; + ble_proto_packet_t otadata_v2_req; + ble_proto_packet_t otadata_v2_rsp; + + } datast; +} bleproto_appdata_desc_t; + +/****************************************************************************/ +/* Public Data */ +/****************************************************************************/ + +/****************************************************************************/ +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************/ +/* Public Function Prototypes */ +/****************************************************************************/ + +/** + * @brief Get library version + * + * @return The string of library version + */ +const char *libbleproto_version(void); + +/** + * @brief 序列化请求注册绑定广播数据(广播数据长度为31) + * + * @param[out] data - 序列化数据缓冲区, 固定为31个字节. + * @param[in] txpower - 蓝牙发射功率[-127,128]dBm + * @param[in] manucode - 厂商编码, 现在固定填0 + * @param[in] pcode - 平台pcode, 比如pcode为AAABBB + * pcode[0] = 'A' = 0x41 + * pcode[1] = 'A' = 0x41 + * pcode[2] = 'A' = 0x41 + * pcode[3] = 'B' = 0x42 + * pcode[4] = 'B' = 0x42 + * pcode[5] = 'B' = 0x42 + * @param[in] prodcode - 产品型号, 比如 宠物喂食器 CBFD, + * prodcode[0] = 'C' = 0x43 + * prodcode[1] = 'B' = 0x42 + * prodcode[2] = 'F' = 0x46 + * prodcode[3] = 'D' = 0x44 + */ +uint8_t bleproto_advdata_encode4bind( + uint8_t data[31], + int8_t txpower, + uint8_t manucode[BLEPROTO_ADV_TLV_LENGTH_MANUCODE], + uint8_t pcode[BLE_PROTO_ADV_LENGTH_PCODE], + uint8_t prodcode[BLEPROTO_ADV_TLV_LENGTH_PRODCODE]); + +/** + * @brief 序列化心跳广播数据(广播数据长度为31) + * + * @param[out] data - 序列化数据缓冲区, 固定为31个字节. + * @param[in] txpower - 蓝牙发射功率[-127,128]dBm + * @param[in] gwmac - 主控设备mac地址(子设备绑定到主控设备,authStep会下发主控设备的mac地址), + * 例如mac地址 "11:22:33:44:55:66" + * gwmac[0] = 0x11 + * gwmac[1] = 0x22 + * gwmac[2] = 0x33 + * gwmac[3] = 0x44 + * gwmac[4] = 0x55 + * gwmac[5] = 0x66 + * @param[in] manucode - 厂商编码, 现在固定填0 + * @param[in] pcode - 平台pcode, 比如pcode为AAABBB + * pcode[0] = 'A' = 0x41 + * pcode[1] = 'A' = 0x41 + * pcode[2] = 'A' = 0x41 + * pcode[3] = 'B' = 0x42 + * pcode[4] = 'B' = 0x42 + * pcode[5] = 'B' = 0x42 + * @param[in] prodcode - 产品型号, 比如 宠物喂食器 CBFD, + * prodcode[0] = 'C' = 0x43 + * prodcode[1] = 'B' = 0x42 + * prodcode[2] = 'F' = 0x46 + * prodcode[3] = 'D' = 0x44 + */ +void bleproto_advdata_encode4heartbeat( + uint8_t data[31], + int8_t txpower, + uint8_t gwmac[6], + uint8_t manucode[BLEPROTO_ADV_TLV_LENGTH_MANUCODE], + uint8_t pcode[BLE_PROTO_ADV_LENGTH_PCODE], + uint8_t prodcode[BLEPROTO_ADV_TLV_LENGTH_PRODCODE]); + +/** + * @brief 序列化回连广播数据(广播数据长度为31) + * + * @param[out] data - 序列化数据缓冲区, 固定为31个字节. + * @param[in] txpower - 蓝牙发射功率[-127,128]dBm + * @param[in] gwmac - 主控设备mac地址(子设备绑定到主控设备,authStep会下发主控设备的mac地址), + * 例如mac地址 "11:22:33:44:55:66" + * gwmac[0] = 0x11 + * gwmac[1] = 0x22 + * gwmac[2] = 0x33 + * gwmac[3] = 0x44 + * gwmac[4] = 0x55 + * gwmac[5] = 0x66 + * @param[in] manucode - 厂商编码, 现在固定填0 + * @param[in] pcode - 平台pcode, 比如pcode为AAABBB + * pcode[0] = 'A' = 0x41 + * pcode[1] = 'A' = 0x41 + * pcode[2] = 'A' = 0x41 + * pcode[3] = 'B' = 0x42 + * pcode[4] = 'B' = 0x42 + * pcode[5] = 'B' = 0x42 + * @param[in] prodcode - 产品型号, 比如 宠物喂食器 CBFD, + * prodcode[0] = 'C' = 0x43 + * prodcode[1] = 'B' = 0x42 + * prodcode[2] = 'F' = 0x46 + * prodcode[3] = 'D' = 0x44 + * + * @return 小于0表示失败,大于0表示序列化后的长度(31) + */ +uint8_t bleproto_advdata_encode4reconn( + uint8_t data[31], + int8_t txpower, + uint8_t gwmac[6], + uint8_t manucode[BLEPROTO_ADV_TLV_LENGTH_MANUCODE], + uint8_t pcode[BLE_PROTO_ADV_LENGTH_PCODE], + uint8_t prodcode[BLEPROTO_ADV_TLV_LENGTH_PRODCODE]); + +/** + * @brief 反序列化广播数据(广播数据长度为31) + * + * @param[in] data - 反序列化数据指针 + * @param[in] len - 反序列化数据长度 + * @param[out] adv - 广播数据结构体 + * + * @return 小于0表示失败,大于0表示反序列化使用data的长度. + */ +int bleproto_advdata_decode(const uint8_t * data, + uint8_t len, + bleproto_adv_t *adv); + +/** + * @brief 序列化应用数据结构体到txbuf + * + * @note packetlen说明 + * + * ```c + * ble4.0 通常协商的mtu=27, packetlen = 27 - 4 - 3 = 20 + * ble4.2 通常协商的mtu=251, packelen = 251 - 4 - 3 = 244 + * ble5.0 通常协商的mtu=251, packelen = 251 - 4 - 3 = 244 + * ``` + * + * 如果报文数据长度超过 BLE GATT单包传输有效长度,需要按照文档方式分包, + * +----------------+ + * | 1 | 第一包按照packetlen填满 + * +----------------+ + * | | + * | ... | 中间包按照packetlen填满 + * | | + * +----------------+ + * | N | 最后一包长度 = totallen - (packetlen * (N -1)) + * +----------------+ + * + * 需要按照每包的顺序发给对端 + * + * @param[out] txbuf - 发送数据缓冲区指针 + * @param[in] size - 发送数据缓冲器长度 + * @param[in] packetlen - BLE GATT单包传输有效长度, 查看上面说明 + * @param[in] desc - 应用数据结构体描述 + * + * @return 小于0表示失败,大于0表示序列化后的长度 + */ +int bleproto_appdata_encode(uint8_t * txbuf, + uint16_t size, + uint8_t packetlen, + bleproto_appdata_desc_t *desc); + +/** + * @brief 反序列化rxbuf到应用数据结构体 + * + * + * 如果报文数据长度超过 BLE GATT单包传输有效长度,需要按照文档方式分包, + * +----------------+ + * | 1 | 第一包按照packetlen填满 + * +----------------+ + * | | + * | ... | 中间包按照packetlen填满 + * | | + * +----------------+ + * | N | 最后一包长度 = totallen - (packetlen * (N -1)) + * +----------------+ + * + * 因此接收到数据 + * 1. 函数返回等于0,以为还有分包数据还没有接收完成, + * 接收到下包数据 append 在buf尾部,再调用函数的解析数据包 + * 2. 函数返回大于0,表示收到数据解析了多少字节,需要将头上对应长度数据移除掉 + * 3. 函数返回小于0,表示收到非法的数据包,无法解析 + * + * @param[in] rxbuf - 接收缓冲区指针 + * @param[in] len - 接收缓冲区数据长度. + * @param[out] desc - 应用数据结构体. + * + * @return 大于0表示解析成功, 返回当前解析了多少个字节, + * 等于0表示解析未完成, 只接受到分包的部分数据, 需要继续接收数据 + * 小于0表示解析失败 + */ +int bleproto_appdata_decode(uint8_t* rxbuf, + uint16_t len, + bleproto_appdata_desc_t *desc); +/****************************************************************************/ +#ifdef __cplusplus +} +#endif +/****************************************************************************/ +#endif /* BLEPROTO_API_H_INCLUDE */ +/****************************************************************************/ +/* */ +/* End of file. */ +/* */ +/****************************************************************************/ diff --git a/apps/usr_le_code/bleproto_packer.c b/apps/usr_le_code/bleproto_packer.c new file mode 100644 index 0000000..b1c6159 --- /dev/null +++ b/apps/usr_le_code/bleproto_packer.c @@ -0,0 +1,3054 @@ +/****************************************************************************/ +/* bleproto_packer.c + * + * Copyright (C) 2024 四川迈科创智科技有限公司 + * + ****************************************************************************/ + +/****************************************************************************/ +/* Included Files */ +/****************************************************************************/ + +#include "bleproto.h" +#include "system/includes.h" +#include "inttypes.h" +/****************************************************************************/ +/* Trace Definitions */ +/****************************************************************************/ + +/****************************************************************************/ +/* Pre-processor Definitions */ +/****************************************************************************/ +/** + * @def UTILS_BITFIELD_GET + * @brief get bitfield value. + * + * @note MUSTBE `s`<=`e`. + * + * @param[in] v - The value to get. + * @param[in] e - The end-bit number. + * @param[in] s - The start-bit number. + * + * @code + * uint8_t ctrl; + * - bit7-2: type + * - bit1-0: id + * type = UTILS_BITFIELD_GET(ctrl, 7, 2); + * id = UTILS_BITFIELD_GET(ctrl, 1, 0); + * @endcode + */ +/** + * @def UTILS_BITFIELD_SET + * @brief set bitfield value. + * + * @note MUSTBE `s`<=`e`. + * + * @param[in] v - The value to set. + * @param[in] e - The end-bit number. + * @param[in] s - The start-bit number. + * + * @code + * uint8_t ctrl; + * - bit7-2: type + * - bit1-0: id + * ctrl = UTILS_BITFIELD_SET(type, 7, 2) | UTILS_BITFIELD_SET(id, 1, 0); + * @endcode + */ +/* Helper bitfield encode/decode macros */ +#define UTILS_STATIC_ASSERT_OR_ZERO(cond) (sizeof(char[1 - 2 * !(cond)]) - 1) +#define UTILS_BITFILED_CHECK(e, s) UTILS_STATIC_ASSERT_OR_ZERO((e) >= (s)) +#define UTILS_BITFIELD_MASK(e, s) \ + (UTILS_BITFILED_CHECK(e, s) + (((1UL << ((e) - (s) + 1)) - 1UL) << (s))) +#define UTILS_BITFIELD_GET(v, e, s) \ + (UTILS_BITFILED_CHECK(e, s) + \ + (((v) >> (s)) & UTILS_BITFIELD_MASK(((e) - (s)), 0))) +#define UTILS_BITFIELD_SET(v, e, s) \ + (UTILS_BITFILED_CHECK(e, s) + \ + (((v)&UTILS_BITFIELD_MASK(((e) - (s)), 0)) << (s))) +#define UTILS_BITFIELD_CLR(v, e, s) \ + (UTILS_BITFILED_CHECK(e, s) + ((v) & (~(UTILS_BITFIELD_MASK(e, s))))) +#define UTILS_BITFIELD_UPD(oldv, e, s, newv) \ + (UTILS_BITFILED_CHECK(e, s) + \ + (UTILS_BITFIELD_CLR(oldv, e, s) | UTILS_BITFIELD_SET(newv, e, s))) + +/** + * @def UTILS_MIN + * @brief Calculates the minimum of x and y. + */ +#define UTILS_MIN(x, y) ((x) < (y) ? (x) : (y)) + +/****************************************************************************/ +/* Private Types */ +/****************************************************************************/ + +/****************************************************************************/ +/* Private Function Prototypes */ +/****************************************************************************/ + +/****************************************************************************/ +/* Private Data */ +/****************************************************************************/ + +/****************************************************************************/ +/* Public Data */ +/****************************************************************************/ + +/****************************************************************************/ +/* Private Functions */ +/****************************************************************************/ + +static uint8_t hex_char_to_int(char c) +{ + // 将十六进制字符转换为对应的数值 + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + // 非十六进制字符,返回无效值 + return 0xFF; +} + +/* utils */ +static void proto_bytes_put_le16(uint8_t dst[2], uint16_t val) +{ + dst[0] = val; + dst[1] = val >> 8; +} + +static uint16_t proto_bytes_get_le16(const uint8_t src[2]) +{ + return ((uint16_t)src[1] << 8) | src[0]; +} + +static int proto_strhex_to_byte(const char *str, uint8_t *bytes, size_t size) +{ + // string转bytes + if (str == NULL || bytes == NULL) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + size_t str_len = strlen(str); + if (str_len % 2 != 0 || str_len / 2 > size) { + BLEPROTO_T_E("Invalid string length or buffer size"); + return -1; + } + + for (size_t i = 0; i < str_len; i += 2) { + uint8_t high = hex_char_to_int(str[i]); + uint8_t low = hex_char_to_int(str[i + 1]); + if (high == 0xFF || low == 0xFF) { + BLEPROTO_T_E("Invalid hex character"); + return -1; + } + bytes[i / 2] = (high << 4) | low; + } + + return (int)(str_len / 2); +} + +/****************************************************************************/ +/* Public Functions */ +/****************************************************************************/ + +int bleproto_advdata_serialize(bleproto_adv_t *adv, + uint8_t * data, + uint8_t size) +{ + uint8_t offset = 0; + + /* check params */ + if (!adv || !data || size < BLEPROTO_ADV_MAX_SZ) { + BLEPROTO_T_E("Invalid parameters"); + return (-1); + } + + /* check adv2 type */ + if (adv->adv2.ad_type != BLEPROTO_ADV_TYPE_SVC_DATA_UUID16 && + adv->adv2.ad_type != BLEPROTO_ADV_TYPE_MFG_DATA) { + BLEPROTO_T_E("Invalid adv2.adv_type"); + return (-1); + } + + /* clear */ + memset(data, 0, size); + + /* adv1 */ + { + /* rewrite member */ + adv->adv1.length = 0x02; + adv->adv1.ad_type = BLEPROTO_ADV_TYPE_FLAGS; + adv->adv1.ad_data = 0x06; + + data[offset++] = adv->adv1.length; + data[offset++] = adv->adv1.ad_type; + data[offset++] = adv->adv1.ad_data; + } + + /* adv2 */ + adv->adv2.length = BLEPROTO_ADV_MAX_SZ - 3 - 1; + { + /* adv2 length */ + data[offset++] = adv->adv2.length; + /* adv2 type */ + data[offset++] = adv->adv2.ad_type; + } + if (adv->adv2.ad_type == BLEPROTO_ADV_TYPE_SVC_DATA_UUID16) { + /* uuid, little endian */ + proto_bytes_put_le16(&data[offset], + adv->adv2.ad_data.service_data.uuid); + offset += 2; + ///////////////////////////////////////// + // spec_data + ///////////////////////////////////////// + bleproto_adv_specdata_t *spec_data = + &adv->adv2.ad_data.service_data.spec_data; + /* version */ + data[offset++] = spec_data->version; + /* business */ + data[offset++] = spec_data->business; + /* rfu */ + data[offset++] = 0; + data[offset++] = 0; + /* txpower */ + data[offset++] = (uint8_t)spec_data->txpower; + /* manucode */ + { + spec_data->manucode.type = BLEPROTO_ADV_TLV_TYPE_MANUCODE; + spec_data->manucode.length = BLEPROTO_ADV_TLV_LENGTH_MANUCODE; + + data[offset++] = spec_data->manucode.type; + data[offset++] = spec_data->manucode.length; + memcpy(&data[offset], + spec_data->manucode.value, + BLEPROTO_ADV_TLV_LENGTH_MANUCODE); + offset += BLEPROTO_ADV_TLV_LENGTH_MANUCODE; + } + /* prodcode */ + { + spec_data->prodcode.type = BLEPROTO_ADV_TLV_TYPE_PRODCODE; + spec_data->prodcode.length = BLEPROTO_ADV_TLV_LENGTH_PRODCODE; + + data[offset++] = spec_data->prodcode.type; + data[offset++] = spec_data->prodcode.length; + memcpy(&data[offset], + spec_data->prodcode.value, + BLEPROTO_ADV_TLV_LENGTH_PRODCODE); + offset += BLEPROTO_ADV_TLV_LENGTH_PRODCODE; + } + /* separator */ + { + spec_data->separator = 0xFF; + + data[offset++] = spec_data->separator; + } + /* selfdata */ + { + memcpy(&data[offset], + spec_data->selfdata, + sizeof(spec_data->selfdata)); + offset += (uint8_t)sizeof(spec_data->selfdata); + } + } else if (adv->adv2.ad_type == BLEPROTO_ADV_TYPE_MFG_DATA) { + bleproto_advdata_mfg_t *mfg_data = &adv->adv2.ad_data.mfg_data; + /* siot */ + data[offset++] = mfg_data->siot[0]; + data[offset++] = mfg_data->siot[1]; + data[offset++] = mfg_data->siot[2]; + data[offset++] = mfg_data->siot[3]; + data[offset++] = mfg_data->siot[4]; + data[offset++] = mfg_data->siot[5]; + /* flag */ + data[offset++] = mfg_data->flag; + /* txpower */ + data[offset++] = (uint8_t)mfg_data->txpower; + /* gwmac */ + data[offset++] = mfg_data->gwmac[0]; + data[offset++] = mfg_data->gwmac[1]; + data[offset++] = mfg_data->gwmac[2]; + data[offset++] = mfg_data->gwmac[3]; + data[offset++] = mfg_data->gwmac[4]; + data[offset++] = mfg_data->gwmac[5]; + /* manucode */ + { + mfg_data->manucode.type = BLEPROTO_ADV_TLV_TYPE_MANUCODE; + mfg_data->manucode.length = BLEPROTO_ADV_TLV_LENGTH_MANUCODE; + + data[offset++] = mfg_data->manucode.type; + data[offset++] = mfg_data->manucode.length; + memcpy(&data[offset], + mfg_data->manucode.value, + BLEPROTO_ADV_TLV_LENGTH_MANUCODE); + offset += BLEPROTO_ADV_TLV_LENGTH_MANUCODE; + } + /* prodcode */ + { + mfg_data->prodcode.type = BLEPROTO_ADV_TLV_TYPE_PRODCODE; + mfg_data->prodcode.length = BLEPROTO_ADV_TLV_LENGTH_PRODCODE; + + data[offset++] = mfg_data->prodcode.type; + data[offset++] = mfg_data->prodcode.length; + memcpy(&data[offset], + mfg_data->prodcode.value, + BLEPROTO_ADV_TLV_LENGTH_PRODCODE); + offset += BLEPROTO_ADV_TLV_LENGTH_PRODCODE; + } + } + + return (offset); +} + +int bleproto_advdata_deserialize(const uint8_t * data, + uint8_t len, + bleproto_adv_t *adv) +{ + int rc; + uint8_t offset = 0; + + /* check parameters */ + if (!data || !adv) { + BLEPROTO_T_E("Invalid parameters"); + return (-1); + } + + /* check len */ + if (len != BLEPROTO_ADV_MAX_SZ) { + BLEPROTO_T_E("Invalid len"); + return (-1); + } + + do { + /* clear */ + memset(adv, 0, sizeof(*adv)); + + /* adv1 */ + adv->adv1.length = data[offset++]; + adv->adv1.ad_type = data[offset++]; + adv->adv1.ad_data = data[offset++]; + if (adv->adv1.length != 0x02 || + adv->adv1.ad_type != BLEPROTO_ADV_TYPE_FLAGS || + adv->adv1.ad_data != 0x06) { + BLEPROTO_T_E("Invalid adv1 data"); + rc = -1; + break; + } + + adv->adv2.length = data[offset++]; + adv->adv2.ad_type = data[offset++]; + if (adv->adv2.length != BLEPROTO_ADV_MAX_SZ - 3 - 1) { + BLEPROTO_T_E("Invalid adv2 len"); + rc = -1; + break; + } + + /* adv2 */ + if (adv->adv2.ad_type == BLEPROTO_ADV_TYPE_SVC_DATA_UUID16) { + /* uuid, little endian */ + adv->adv2.ad_data.service_data.uuid = + proto_bytes_get_le16(&data[offset]); + offset += 2; + ///////////////////////////////////////// + // spec_data + ///////////////////////////////////////// + bleproto_adv_specdata_t *spec_data = + &adv->adv2.ad_data.service_data.spec_data; + /* version */ + spec_data->version = data[offset++]; + /* business */ + spec_data->business = data[offset++]; + /* rfu */ + spec_data->rfu[0] = data[offset++]; + spec_data->rfu[1] = data[offset++]; + /* txpower */ + spec_data->txpower = (int8_t)data[offset++]; + /* manucode */ + { + spec_data->manucode.type = data[offset++]; + if (spec_data->manucode.type != + BLEPROTO_ADV_TLV_TYPE_MANUCODE) { + BLEPROTO_T_E("Invalid adv2 service_data.manucode.type"); + rc = -1; + break; + } + spec_data->manucode.length = data[offset++]; + if (spec_data->manucode.length != + BLEPROTO_ADV_TLV_LENGTH_MANUCODE) { + BLEPROTO_T_E("Invalid adv2 service_data.manucode.length"); + rc = -1; + break; + } + memcpy(spec_data->manucode.value, + &data[offset], + spec_data->manucode.length); + offset += spec_data->manucode.length; + } + /* prodcode */ + { + spec_data->prodcode.type = data[offset++]; + if (spec_data->prodcode.type != + BLEPROTO_ADV_TLV_TYPE_PRODCODE) { + BLEPROTO_T_E("Invalid adv2 service_data.prodcode.type"); + rc = -1; + break; + } + spec_data->prodcode.length = data[offset++]; + if (spec_data->prodcode.length != + BLEPROTO_ADV_TLV_LENGTH_PRODCODE) { + BLEPROTO_T_E("Invalid adv2 service_data.prodcode.length"); + rc = -1; + break; + } + memcpy(spec_data->prodcode.value, + &data[offset], + spec_data->prodcode.length); + offset += spec_data->prodcode.length; + } + /* separator */ + { + spec_data->separator = data[offset++]; + if (spec_data->separator != 0xff) { + BLEPROTO_T_E("Invalid adv2 service_data.separator"); + rc = -1; + break; + } + } + /* selfdata */ + { + memcpy(spec_data->selfdata, + &data[offset], + sizeof(spec_data->selfdata)); + offset += (uint8_t)sizeof(spec_data->selfdata); + } + } else if (adv->adv2.ad_type == BLEPROTO_ADV_TYPE_MFG_DATA) { + bleproto_advdata_mfg_t *mfg_data = &adv->adv2.ad_data.mfg_data; + /* fix "SIOT-1" */ + memcpy(mfg_data->siot, &data[offset], 6); + // if (mfg_data->siot[0] != 'S' || mfg_data->siot[1] != 'I' || + // mfg_data->siot[2] != 'O' || mfg_data->siot[3] != 'T' || + // mfg_data->siot[4] != '-') { + // BLEPROTO_T_E("Invalid adv2 mfg_data.siot"); + // rc = -1; + // break; + // } + offset += 6; + + /* flag */ + mfg_data->flag = data[offset++]; + /* txpower */ + mfg_data->txpower = (int8_t)data[offset++]; + /* gwmac */ + memcpy(mfg_data->gwmac, &data[offset], 6); + offset += 6; + /* manucode */ + { + mfg_data->manucode.type = data[offset++]; + if (mfg_data->manucode.type != + BLEPROTO_ADV_TLV_TYPE_MANUCODE) { + BLEPROTO_T_E("Invalid adv2 mfg_data.manucode.type"); + rc = -1; + break; + } + mfg_data->manucode.length = data[offset++]; + if (mfg_data->manucode.length != + BLEPROTO_ADV_TLV_LENGTH_MANUCODE) { + BLEPROTO_T_E("Invalid adv2 mfg_data.manucode.length"); + rc = -1; + break; + } + memcpy(mfg_data->manucode.value, + &data[offset], + mfg_data->manucode.length); + offset += mfg_data->manucode.length; + } + /* prodcode */ + { + mfg_data->prodcode.type = data[offset++]; + if (mfg_data->prodcode.type != + BLEPROTO_ADV_TLV_TYPE_PRODCODE) { + BLEPROTO_T_E("Invalid adv2 mfg_data.prodcode.type"); + rc = -1; + break; + } + mfg_data->prodcode.length = data[offset++]; + if (mfg_data->prodcode.length != + BLEPROTO_ADV_TLV_LENGTH_PRODCODE) { + BLEPROTO_T_E("Invalid adv2 mfg_data.prodcode.length"); + rc = -1; + break; + } + memcpy(mfg_data->prodcode.value, + &data[offset], + mfg_data->prodcode.length); + offset += mfg_data->prodcode.length; + } + } else { + BLEPROTO_T_E("Invalid adv2 adv_type"); + rc = -1; + break; + } + + /* check offset */ + if (offset != 31) { + BLEPROTO_T_E("Invalid offset"); + rc = -1; + break; + } + rc = offset; + } while (0); + + return (rc); +} + +int bleproto_appdata_serialize_len(bleproto_appdata_t *appdata, + uint8_t packetlen, + uint16_t * outlen) +{ + /* check param */ + if (!appdata || packetlen <= BLEPROTO_APPDATA_HEADR_LEN) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + /* data total len */ + uint16_t datalen = appdata->header.datalen; + + /* each frame max payload length */ + uint8_t frame_max_payload = packetlen - BLEPROTO_APPDATA_HEADR_LEN; + + /* calculate total frames needed */ + uint16_t totalframe; + if (datalen == 0) { + /* even with no data, we need at least one frame for the header */ + totalframe = 1; + } else { + /* calculate frames needed for the data */ + totalframe = (datalen + frame_max_payload - 1) / frame_max_payload; + } + if (totalframe > 255) { + BLEPROTO_T_E("Invalid appdata.datalen too long >(%u)", + (uint16_t)frame_max_payload * 255); + return -1; + } + + /* calculate total buffer size needed */ + if (totalframe <= 1) { + /* at least one frame is needed */ + if (outlen) { + *outlen = datalen + BLEPROTO_APPDATA_HEADR_LEN; + } + } else { + /* calculate size of full frames */ + uint16_t full_frames_size = (uint16_t)(totalframe - 1) * packetlen; + + // clang-format off + /* calculate size of last frame */ + uint16_t last_frame_size = + /* header */ BLEPROTO_APPDATA_HEADR_LEN + + /* payload */ datalen - (uint16_t)((totalframe - 1) * frame_max_payload); + // clang-format on + + /* total size is sum of full frames and last frame */ + if (outlen) { + *outlen = full_frames_size + last_frame_size; + } + } + + return 0; +} + +int bleproto_appdata_serialize(bleproto_appdata_t *appdata, + uint8_t packetlen, + uint8_t * buf, + uint16_t size) + +{ + int rc, offset = 0; + uint16_t data_offset = 0; + + /* check param */ + if (!appdata || packetlen <= BLEPROTO_APPDATA_HEADR_LEN) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + /* totoal len */ + uint16_t total_len; + rc = bleproto_appdata_serialize_len(appdata, packetlen, &total_len); + if (rc < 0) { + return -1; + } + if (size < total_len) { + BLEPROTO_T_E("Invalid datalen:%u, buf too short(%u<%u)", + appdata->header.datalen, + size, + total_len); + return -1; + } + + /* data total len */ + uint16_t datalen = appdata->header.datalen; + + /* each frame max payload length */ + uint8_t frame_max_payload = packetlen - BLEPROTO_APPDATA_HEADR_LEN; + + /* calculate total frames needed */ + uint16_t totalframe; + if (datalen == 0) { + /* even with no data, we need at least one frame for the header */ + totalframe = 1; + } else { + /* calculate frames needed for the data */ + totalframe = (datalen + frame_max_payload - 1) / frame_max_payload; + } + if (totalframe > 255) { + BLEPROTO_T_E("Invalid appdata.datalen too long"); + return -1; + } + + for (uint8_t i = 0; i < (uint8_t)totalframe; i++) { + /* pthis_frame_len */ + uint8_t pthis_frame_len = 0; + if (totalframe == 1) { + /* only one frame */ + pthis_frame_len = (uint8_t)datalen; + } else if (i + 1 < totalframe) { + /* full size frames */ + pthis_frame_len = frame_max_payload; + } else { + /* last frame */ + pthis_frame_len = (uint8_t)(datalen - data_offset); + } + + /* header */ + bleproto_appdata_header_t hdr = appdata->header; + { + hdr.totalframe = totalframe; + if (totalframe == 1) { + hdr.frameseq = 0; + } else { + hdr.frameseq = i + 1; + } + } + /* serialize_header */ + { + /* buf[0]: version[2:0] | datafmt[3:3] | msgtype[7:4] */ + buf[offset + 0] = UTILS_BITFIELD_SET(hdr.version, 2, 0) | + UTILS_BITFIELD_SET(hdr.datafmt, 3, 3) | + UTILS_BITFIELD_SET(hdr.msgtype, 7, 4); + /* buf[1]: msgid[3:0] | encrypt[4:4] | reserve[7:5] */ + buf[offset + 1] = UTILS_BITFIELD_SET(hdr.msgid, 3, 0) | + UTILS_BITFIELD_SET(hdr.encrypt, 4, 4) | + UTILS_BITFIELD_SET(hdr.reserve, 7, 5); + + buf[offset + 2] = hdr.totalframe; + buf[offset + 3] = hdr.frameseq; + buf[offset + 4] = pthis_frame_len; + buf[offset + 5] = hdr.serviceid; + + /* datalen in little endian */ + proto_bytes_put_le16(&buf[offset + 6], hdr.datalen); + } + offset += BLEPROTO_APPDATA_HEADR_LEN; + + /* payload */ + if (pthis_frame_len != 0) { + memcpy( + &buf[offset], &appdata->data[data_offset], pthis_frame_len); + /* offset */ + offset += pthis_frame_len; + /* data_offset */ + data_offset += pthis_frame_len; + } + } + if (offset != total_len) { + BLEPROTO_T_E("Error calc appdata total_len"); + return -1; + } + + return (offset); +} + +int bleproto_appdata_wrap_raw(uint8_t *txbuf, + uint16_t txbuf_size, + const bleproto_appdata_header_t *header, + const uint8_t *payload, + uint16_t payload_len) +{ + uint16_t total; + + if (!txbuf || !header) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + if (payload_len > 0 && !payload) { + BLEPROTO_T_E("Invalid payload"); + return -1; + } + /* 单帧 this_frame_len 为 uint8,超过 255 需走分包 serialize */ + if (payload_len > 255u) { + BLEPROTO_T_E("payload too long for single frame"); + return -1; + } + + total = (uint16_t)(BLEPROTO_APPDATA_HEADR_LEN + payload_len); + if (txbuf_size < total) { + BLEPROTO_T_E("txbuf too short"); + return -1; + } + + /* buf[0]: version[2:0] | datafmt[3:3] | msgtype[7:4] */ + txbuf[0] = UTILS_BITFIELD_SET(header->version, 2, 0) | + UTILS_BITFIELD_SET(header->datafmt, 3, 3) | + UTILS_BITFIELD_SET(header->msgtype, 7, 4); + /* buf[1]: msgid[3:0] | encrypt[4:4] | reserve[7:5] */ + txbuf[1] = UTILS_BITFIELD_SET(header->msgid, 3, 0) | + UTILS_BITFIELD_SET(header->encrypt, 4, 4) | + UTILS_BITFIELD_SET(header->reserve, 7, 5); + txbuf[2] = 1; /* totalframe */ + txbuf[3] = 0; /* frameseq:单帧为 0 */ + txbuf[4] = (uint8_t)payload_len; /* this frame payload len */ + txbuf[5] = header->serviceid; + proto_bytes_put_le16(&txbuf[6], payload_len); + + if (payload_len > 0) { + memcpy(&txbuf[BLEPROTO_APPDATA_HEADR_LEN], payload, payload_len); + } + + return (int)total; +} + +int bleproto_appdata_deserialize(uint8_t * buf, + uint16_t len, + bleproto_appdata_t *appdata) +{ + /* check parameters */ + if (!buf || !appdata) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + /* 检查是否至少有一个完整的包头 */ + if (len < BLEPROTO_APPDATA_HEADR_LEN) { + // 数据不够,需要更多数据 + return 0; + } + + int offset = 0; + uint8_t msgid; + uint8_t totalframe; + uint8_t frameseq; + uint16_t datalen; + uint8_t frame_payload_len; + uint8_t max_payload = 0; + uint8_t recv_frames = 0; + + /* 第一轮: 扫描所有数据,验证分包的完整性和一致性 */ + { + int scan_offset = 0; + bool first_frame = true; + + while (scan_offset < len) { + /* 解析header中的关键字段 */ + msgid = UTILS_BITFIELD_GET(buf[scan_offset + 1], 3, 0); + totalframe = buf[scan_offset + 2]; + frameseq = buf[scan_offset + 3]; + frame_payload_len = buf[scan_offset + 4]; + datalen = proto_bytes_get_le16(&buf[scan_offset + 6]); + BLEPROTO_T_S("[%u/%u]=0x%02X(%u),msgid:%u,datalen:0x%02X(%u)", + frameseq, + totalframe, + frame_payload_len, + frame_payload_len, + msgid, + datalen, + datalen); + + /* 基本参数检查 */ + if (totalframe == 0) { + BLEPROTO_T_E("Invalid totalframe"); + return -1; + } + if (frameseq > totalframe) { + BLEPROTO_T_E("Invalid frameseq"); + return -1; + } + if (datalen > BLEPROTO_APPDATA_DATA_MAX_SZ) { + BLEPROTO_T_E("Invalid datalen"); + return -1; + } + + /* 分包一致性检查 */ + if (first_frame) { + first_frame = false; + // 使用第一帧的payload长度作为最大payload + max_payload = frame_payload_len; + } else { + /* 检查msgid一致性 */ + if (msgid != UTILS_BITFIELD_GET(buf[1], 3, 0)) { + BLEPROTO_T_E("Inconsistent msgid"); + return -1; + } + /* 检查totalframe一致性 */ + if (totalframe != buf[2]) { + BLEPROTO_T_E("Inconsistent totalframe"); + return -1; + } + /* 检查frameseq连续性 */ + uint8_t last_frameseq = + buf[scan_offset - + (BLEPROTO_APPDATA_HEADR_LEN + max_payload) + 3]; + if (frameseq != (last_frameseq + 1)) { + BLEPROTO_T_E("Non-sequential frameseq:%u!=%u", + frameseq, + (last_frameseq + 1)); + return -1; + } + /* 检查非最后一帧的payload长度是否与第一帧相同 */ + if (frameseq < (totalframe - 1) && + frame_payload_len != max_payload) { + BLEPROTO_T_E("Inconsistent frame payload length"); + return -1; + } + } + + /* 检查payload长度 */ + if ((len - scan_offset) < + (BLEPROTO_APPDATA_HEADR_LEN + frame_payload_len)) { + return 0; // 需要更多数据 + } + + /* 验证frame_payload_len */ + if (totalframe == 1) { + if (frame_payload_len != datalen) { + BLEPROTO_T_E( + "Invalid frame_payload_len for single frame"); + return -1; + } + } else { + if (frameseq < totalframe) { + /* 非最后一帧必须是满包 */ + if (frame_payload_len != max_payload) { + BLEPROTO_T_E( + "Invalid frame_payload_len for non-last frame"); + return -1; + } + } else { + /* 最后一帧的长度计算 */ + uint16_t last_frame_payload = datalen % max_payload; + if (last_frame_payload == 0) { + last_frame_payload = max_payload; + } + if (frame_payload_len != last_frame_payload) { + BLEPROTO_T_E( + "Invalid frame_payload_len for last frame(%u!=%u)", + frame_payload_len, + last_frame_payload); + return -1; + } + } + } + + /* recv_frames */ + recv_frames++; + + scan_offset += BLEPROTO_APPDATA_HEADR_LEN + frame_payload_len; + } + + /* 检查是否收到了完整的包 */ + if (recv_frames != totalframe) { + // 还没收到所有分包 + return 0; + } + } + + /* 第二轮: 解析数据并填充到appdata结构中 */ + { + /* 解析第一帧header */ + bleproto_appdata_header_t *hdr = &appdata->header; + + /* buf[0]: version[2:0] | datafmt[3:3] | msgtype[7:4] */ + hdr->version = UTILS_BITFIELD_GET(buf[0], 2, 0); + hdr->datafmt = UTILS_BITFIELD_GET(buf[0], 3, 3); + hdr->msgtype = UTILS_BITFIELD_GET(buf[0], 7, 4); + + /* buf[1]: msgid[3:0] | encrypt[4:4] | reserve[7:5] */ + hdr->msgid = UTILS_BITFIELD_GET(buf[1], 3, 0); + hdr->encrypt = UTILS_BITFIELD_GET(buf[1], 4, 4); + hdr->reserve = UTILS_BITFIELD_GET(buf[1], 7, 5); + + hdr->totalframe = buf[2]; + hdr->frameseq = buf[3]; + hdr->framelen = buf[4]; + hdr->serviceid = buf[5]; + hdr->datalen = proto_bytes_get_le16(&buf[6]); + + /* 复制所有payload数据 */ + uint16_t data_offset = 0; + uint8_t frames_processed = 0; + + /* clear offset */ + offset = 0; + while (frames_processed < hdr->totalframe) { + /* this frame payload length */ + frame_payload_len = buf[offset + 4]; + + if (frame_payload_len > 0) { + uint8_t curr_frameseq = buf[offset + 3]; + if (curr_frameseq == 0) { + data_offset = 0; + } else { + data_offset = (curr_frameseq - 1) * max_payload; + } + + /* copy payload data */ + memcpy(&appdata->data[data_offset], + &buf[offset + BLEPROTO_APPDATA_HEADR_LEN], + frame_payload_len); + } + + offset += BLEPROTO_APPDATA_HEADR_LEN + frame_payload_len; + frames_processed++; + } + } + + /* 返回处理的字节数 */ + return offset; +} + +int bleproto_encjson_ycmd(const bleproto_ycmd_t *ycmd, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + uint16_t i; + + /* check param */ + if (!ycmd || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + if (ycmd->arg_int32_count > YCMD_INT32_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_int32_count"); + return -1; + } + if (ycmd->arg_string_count > YCMD_STRING_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_string_count"); + return -1; + } + for (i = 0; i < ycmd->arg_string_count; i++) { + if (strlen(ycmd->arg_string[i]) == 0) { + BLEPROTO_T_E("Invalid ycmd.arg_string[%u]", i); + return -1; + } + } + + if (ycmd->arg_bytes_count > YCMD_BYTES_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_bytes_count"); + return -1; + } + + char *str = (char *)data; + // {"cmd_id":xxxx,"arg_int32":[],"arg_string":[],"arg_bytes":"xxxx","t":"xxxxxxxx" } + + // { + offset = snprintf(str, size, "%s", "{"); + + // cmd_id + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%" PRId32 ",", + "cmd_id", + (int)ycmd->cmd_id); + } + + // arg_int32 + if (ycmd->arg_int32_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":[", "arg_int32"); + for (i = 0; i < ycmd->arg_int32_count; i++) { + offset += snprintf(&str[offset], + size - offset, + "%" PRId32 "", + ycmd->arg_int32[i]); + if (i != ycmd->arg_int32_count - 1) { + offset += snprintf(&str[offset], size - offset, "%s", ","); + } + } + offset += snprintf(&str[offset], size - offset, "%s", "],"); + } + + // arg_string + if (ycmd->arg_string_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":[", "arg_string"); + for (i = 0; i < ycmd->arg_string_count; i++) { + offset += snprintf( + &str[offset], size - offset, "\"%s\"", ycmd->arg_string[i]); + if (i != ycmd->arg_string_count - 1) { + offset += snprintf(&str[offset], size - offset, "%s", ","); + } + } + offset += snprintf(&str[offset], size - offset, "%s", "],"); + } + + // arg_bytes + if (ycmd->arg_bytes_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":\"", "arg_bytes"); + for (i = 0; i < ycmd->arg_bytes_count; i++) { + offset += snprintf( + &str[offset], size - offset, "%02X", ycmd->arg_bytes[i]); + } + offset += snprintf(&str[offset], size - offset, "%s", "\","); + } + + // t + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":\"%" PRIu64 "\"", + "t", + ycmd->t); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("ycmd:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_decjson_ycmd(const uint8_t * data, + uint16_t len, + bleproto_ycmd_t *ycmd) +{ + int rc, i; + + /* check param */ + if (!data || len == 0 || !ycmd) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("ycmd:%.*s", (int)len, str); + + /* clear */ + memset(ycmd, 0, sizeof(*ycmd)); + + // {"cmd_id":xxxx,"arg_int32":[],"arg_string":[],"arg_bytes":"xxxx","t":"xxxxxxxx" } + char value_str[BLEPROTO_APPDATA_DATA_MAX_SZ] = { 0 }; + double v; + + // cmd_id + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.cmd_id", + /* v */ &v); + if (rc > 0) { + ycmd->cmd_id = (int)v; + } else { + BLEPROTO_T_E("Error parse cmd_id"); + return -1; + } + + // arg_int32 + for (i = 0; i < YCMD_INT32_MAX_COUNT; i++) { + const char *ptr = NULL; + int n; + char key[sizeof("$.arg_int32[65535]") + 1] = { 0 }; + snprintf(key, sizeof(key), "$.arg_int32[%d]", i); + if (mjson_find(str, (int)len, key, &ptr, &n) == MJSON_TOK_NUMBER) { + char vstr[32] = { 0 }; + memcpy(vstr, ptr, UTILS_MIN(n, 31)); + ycmd->arg_int32[ycmd->arg_int32_count] = + (int32_t)strtol(vstr, NULL, 10); + ycmd->arg_int32_count++; + } + } + + // arg_string + for (i = 0; i < YCMD_STRING_MAX_COUNT; i++) { + const char *ptr = NULL; + int n; + char key[sizeof("$.arg_string[65535]") + 1] = { 0 }; + snprintf(key, sizeof(key), "$.arg_string[%d]", i); + if (mjson_find(str, (int)len, key, &ptr, &n) == MJSON_TOK_STRING) { + if (n >= 2 && ptr[0] == '"' && ptr[n - 1] == '"') { + ptr++; // 跳过首引号 + n -= 2; // 去掉首尾引号的长度 + } else { + BLEPROTO_T_E("Error parse arg_string"); + return -1; + } + memcpy(ycmd->arg_string[ycmd->arg_string_count], + ptr, + UTILS_MIN(n, YCMD_STRING_MAX_SIZE)); + ycmd->arg_string_count++; + } + } + + // arg_bytes + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.arg_bytes", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + // strhex to byte + rc = proto_strhex_to_byte( + value_str, ycmd->arg_bytes, sizeof(ycmd->arg_bytes)); + if (rc >= 0) { + ycmd->arg_bytes_count = (uint16_t)rc; + } + } + + // t + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.t", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + ycmd->t = (uint64_t)strtoull(value_str, NULL, 10); + } else { + BLEPROTO_T_E("Error parse t"); + return -1; + } + + return (0); +} + +int bleproto_encjson_deviceinfo_req(const bleproto_deviceinfo_req_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + char *str = (char *)data; + //{"t":"xxxxxxxx",gmtoff:%d} + + // { + offset = snprintf(str, size, "%s", "{"); + + // t + { + offset += snprintf( + &str[offset], size - offset, "\"t\":\"%" PRIu64 "\",", p->t); + } + + // gmtoff + { + offset += + snprintf(&str[offset], size - offset, "\"gmtoff\":%d", p->gmtoff); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_deviceinfo_rsp(const bleproto_deviceinfo_rsp_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + /* check params */ + if (strlen(p->hwv) == 0) { + BLEPROTO_T_E("Invalid deviceinfo.rsp.hwv"); + return -1; + } + if (strlen(p->swv) == 0) { + BLEPROTO_T_E("Invalid deviceinfo.rsp.swv"); + return -1; + } + + char *str = (char *)data; + // {"hwv":"%s","swv":"%s","prototype":%u,"hbcycle":%u,"longcon":%u} + + // { + offset = snprintf(str, size, "%s", "{"); + + // hwv + { + offset += snprintf( + &str[offset], size - offset, "\"%s\":\"%s\",", "hwv", p->hwv); + } + // swv + { + offset += snprintf( + &str[offset], size - offset, "\"%s\":\"%s\",", "swv", p->swv); + } + // prototype + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%u,", + "prototype", + p->prototype); + } + // hbcycle + { + offset += snprintf( + &str[offset], size - offset, "\"%s\":%u,", "hbcycle", p->hbcycle); + } + // longcon + { + offset += snprintf( + &str[offset], size - offset, "\"%s\":%u", "longcon", p->longcon); + } + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_authsetup_req(const bleproto_authsetup_req_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + /* check params */ + if (strlen(p->devid) == 0) { + BLEPROTO_T_E("Invalid authsetup.req.devid"); + return -1; + } + if (strlen(p->authcode) == 0) { + BLEPROTO_T_E("Invalid authsetup.req.authcode"); + return -1; + } + + char *str = (char *)data; + // {"gwmac":"xxxx","devid":"xxxx","authcode":"xxxx"} + + // { + offset = snprintf(str, size, "%s", "{"); + + // gwmac + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":\"%02X%02X%02X%02X%02X%02X\",", + "gwmac", + p->gwmac[0], + p->gwmac[1], + p->gwmac[2], + p->gwmac[3], + p->gwmac[4], + p->gwmac[5]); + } + + // devid + { + offset += snprintf( + &str[offset], size - offset, "\"%s\":\"%s\",", "devid", p->devid); + } + + // authcode + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":\"%s\"", + "authcode", + p->authcode); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_authsetup_rsp(const bleproto_authsetup_rsp_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + char *str = (char *)data; + // {"errcode":xxx} + + // { + offset = snprintf(str, size, "%s", "{"); + + // errcode + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%d", + "errcode", + (int)p->errcode); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_authdelete_req(const bleproto_authdelete_req_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + /* check params */ + if (strlen(p->authcode) == 0) { + BLEPROTO_T_E("Invalid authdelete.req.authcode"); + return -1; + } + + char *str = (char *)data; + // { "authcode": "xxxx"} + + // { + offset = snprintf(str, size, "%s", "{"); + + // authcode + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":\"%s\"", + "authcode", + p->authcode); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_authdelete_rsp(const bleproto_authdelete_rsp_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + char *str = (char *)data; + // {"gwmac": "xxxx","errcode":xxx} + + // { + offset = snprintf(str, size, "%s", "{"); + + // gwmac + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":\"%02X%02X%02X%02X%02X%02X\",", + "gwmac", + p->gwmac[0], + p->gwmac[1], + p->gwmac[2], + p->gwmac[3], + p->gwmac[4], + p->gwmac[5]); + } + + // errcode + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%d", + "errcode", + (int)p->errcode); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_runcmd_req(const bleproto_runcmd_req_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + uint16_t i; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + if (p->ycmd.arg_int32_count > YCMD_INT32_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_int32_count"); + return -1; + } + if (p->ycmd.arg_string_count > YCMD_STRING_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_string_count"); + return -1; + } + for (i = 0; i < p->ycmd.arg_string_count; i++) { + if (strlen(p->ycmd.arg_string[i]) == 0) { + BLEPROTO_T_E("Invalid runcmd.req.arg_string[%u]", i); + return -1; + } + } + + if (p->ycmd.arg_bytes_count > YCMD_BYTES_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_bytes_count"); + return -1; + } + + char *str = (char *)data; + // {"cmd_id":xxxx,"arg_int32":[],"arg_string":[],"arg_bytes":"xxxx","t":"xxxxxxxx" } + + // { + offset = snprintf(str, size, "%s", "{"); + + // cmd_id + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%" PRId32 ",", + "cmd_id", + (int)p->ycmd.cmd_id); + } + + // arg_int32 + if (p->ycmd.arg_int32_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":[", "arg_int32"); + for (i = 0; i < p->ycmd.arg_int32_count; i++) { + offset += snprintf(&str[offset], + size - offset, + "%" PRId32 "", + p->ycmd.arg_int32[i]); + if (i != p->ycmd.arg_int32_count - 1) { + offset += snprintf(&str[offset], size - offset, "%s", ","); + } + } + offset += snprintf(&str[offset], size - offset, "%s", "],"); + } + + // arg_string + if (p->ycmd.arg_string_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":[", "arg_string"); + for (i = 0; i < p->ycmd.arg_string_count; i++) { + offset += snprintf( + &str[offset], size - offset, "\"%s\"", p->ycmd.arg_string[i]); + if (i != p->ycmd.arg_string_count - 1) { + offset += snprintf(&str[offset], size - offset, "%s", ","); + } + } + offset += snprintf(&str[offset], size - offset, "%s", "],"); + } + + // arg_bytes + if (p->ycmd.arg_bytes_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":\"", "arg_bytes"); + for (i = 0; i < p->ycmd.arg_bytes_count; i++) { + offset += snprintf( + &str[offset], size - offset, "%02X", p->ycmd.arg_bytes[i]); + } + offset += snprintf(&str[offset], size - offset, "%s", "\","); + } + + // t + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":\"%" PRIu64 "\"", + "t", + p->ycmd.t); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_runcmd_rsp(const bleproto_runcmd_rsp_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + uint16_t i; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + if (p->ycmd.arg_int32_count > YCMD_INT32_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_int32_count"); + return -1; + } + if (p->ycmd.arg_string_count > YCMD_STRING_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_string_count"); + return -1; + } + for (i = 0; i < p->ycmd.arg_string_count; i++) { + if (strlen(p->ycmd.arg_string[i]) == 0) { + BLEPROTO_T_E("Invalid runcmd.rsp.arg_string[%u]", i); + return -1; + } + } + if (p->ycmd.arg_bytes_count > YCMD_BYTES_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_bytes_count"); + return -1; + } + + char *str = (char *)data; + // {"cmd_id":xxxx,"arg_int32":[],"arg_string":[],"arg_bytes":"xxxx","t":"xxxxxxxx" } + + // { + offset = snprintf(str, size, "%s", "{"); + + // cmd_id + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%" PRId32 ",", + "cmd_id", + (int)p->ycmd.cmd_id); + } + + // arg_int32 + if (p->ycmd.arg_int32_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":[", "arg_int32"); + for (i = 0; i < p->ycmd.arg_int32_count; i++) { + offset += snprintf(&str[offset], + size - offset, + "%" PRId32 "", + p->ycmd.arg_int32[i]); + if (i != p->ycmd.arg_int32_count - 1) { + offset += snprintf(&str[offset], size - offset, "%s", ","); + } + } + offset += snprintf(&str[offset], size - offset, "%s", "],"); + } + + // arg_string + if (p->ycmd.arg_string_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":[", "arg_string"); + for (i = 0; i < p->ycmd.arg_string_count; i++) { + offset += snprintf( + &str[offset], size - offset, "\"%s\"", p->ycmd.arg_string[i]); + if (i != p->ycmd.arg_string_count - 1) { + offset += snprintf(&str[offset], size - offset, "%s", ","); + } + } + offset += snprintf(&str[offset], size - offset, "%s", "],"); + } + + // arg_bytes + if (p->ycmd.arg_bytes_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":\"", "arg_bytes"); + for (i = 0; i < p->ycmd.arg_bytes_count; i++) { + offset += snprintf( + &str[offset], size - offset, "%02X", p->ycmd.arg_bytes[i]); + } + offset += snprintf(&str[offset], size - offset, "%s", "\","); + } + + // t + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":\"%" PRIu64 "\"", + "t", + p->ycmd.t); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_reportcmd_req(const bleproto_reportcmd_req_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + uint16_t i; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + if (p->ycmd.arg_int32_count > YCMD_INT32_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_int32_count"); + return -1; + } + if (p->ycmd.arg_string_count > YCMD_STRING_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_string_count"); + return -1; + } + for (i = 0; i < p->ycmd.arg_string_count; i++) { + if (strlen(p->ycmd.arg_string[i]) == 0) { + BLEPROTO_T_E("Invalid reportcmd.req.arg_string[%u]", i); + return -1; + } + } + if (p->ycmd.arg_bytes_count > YCMD_BYTES_MAX_COUNT) { + BLEPROTO_T_E("Invalid ycmd.arg_bytes_count"); + return -1; + } + + char *str = (char *)data; + // {"cmd_id":xxxx,"arg_int32":[],"arg_string":[],"arg_bytes":"xxxx","t":"xxxxxxxx"} + + // { + offset = snprintf(str, size, "%s", "{"); + + // cmd_id + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%" PRId32 ",", + "cmd_id", + (int)p->ycmd.cmd_id); + } + + // arg_int32 + if (p->ycmd.arg_int32_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":[", "arg_int32"); + for (i = 0; i < p->ycmd.arg_int32_count; i++) { + offset += snprintf(&str[offset], + size - offset, + "%" PRId32 "", + p->ycmd.arg_int32[i]); + if (i != p->ycmd.arg_int32_count - 1) { + offset += snprintf(&str[offset], size - offset, "%s", ","); + } + } + offset += snprintf(&str[offset], size - offset, "%s", "],"); + } + + // arg_string + if (p->ycmd.arg_string_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":[", "arg_string"); + for (i = 0; i < p->ycmd.arg_string_count; i++) { + offset += snprintf( + &str[offset], size - offset, "\"%s\"", p->ycmd.arg_string[i]); + if (i != p->ycmd.arg_string_count - 1) { + offset += snprintf(&str[offset], size - offset, "%s", ","); + } + } + offset += snprintf(&str[offset], size - offset, "%s", "],"); + } + + // arg_bytes + if (p->ycmd.arg_bytes_count > 0) { + offset += + snprintf(&str[offset], size - offset, "\"%s\":\"", "arg_bytes"); + for (i = 0; i < p->ycmd.arg_bytes_count; i++) { + offset += snprintf( + &str[offset], size - offset, "%02X", p->ycmd.arg_bytes[i]); + } + offset += snprintf(&str[offset], size - offset, "%s", "\","); + } + + // t + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":\"%" PRIu64 "\"", + "t", + p->ycmd.t); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_reportcmd_rsp(const bleproto_reportcmd_rsp_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + char *str = (char *)data; + // {"errcode":xxx} + + // { + offset = snprintf(str, size, "%s", "{"); + + // errcode + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%d", + "errcode", + (int)p->errcode); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_doaction_req(const bleproto_doaction_req_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + char *str = (char *)data; + // {"action":%d} + + // { + offset = snprintf(str, size, "%s", "{"); + + // action + { + offset += snprintf( + &str[offset], size - offset, "\"%s\":%d", "action", p->action); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_doaction_rsp(const bleproto_doaction_rsp_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + char *str = (char *)data; + // {"errcode":xxx} + + // { + offset = snprintf(str, size, "%s", "{"); + + // errcode + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%d", + "errcode", + (int)p->errcode); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_otanotify_req(const bleproto_otanotify_req_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + char *str = (char *)data; + // {"id": %d, "ver":"xxxx", "size":%d, "md5":"xxxxxxx"} + + // { + offset = snprintf(str, size, "%s", "{"); + + // id + { + offset += + snprintf(&str[offset], size - offset, "\"%s\":%d,", "id", p->id); + } + // ver + { + offset += snprintf( + &str[offset], size - offset, "\"%s\":\"%s\",", "ver", p->version); + } + // size + { + offset += snprintf( + &str[offset], size - offset, "\"%s\":%u,", "size", p->size); + } + // md5 + { + offset += snprintf( + &str[offset], size - offset, "\"%s\":\"%s\",", "md5", p->md5); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_otanotify_rsp(const bleproto_otanotify_rsp_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + char *str = (char *)data; + // {"errcode":xxx} + + // { + offset = snprintf(str, size, "%s", "{"); + + // errcode + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%d", + "errcode", + (int)p->errcode); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encjson_otadata_req(const bleproto_otadata_req_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + char *str = (char *)data; + // {"id": %d, "offset":%d, "maxsize":%d} + + // { + offset = snprintf(str, size, "%s", "{"); + + // id + { + offset += snprintf( + &str[offset], size - offset, "\"%s\":%d, ", "id", (int)p->id); + } + // offset + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%d, ", + "offset", + (int)p->offset); + } + // maxsize + { + offset += snprintf(&str[offset], + size - offset, + "\"%s\":%d", + "maxsize", + (int)p->maxsize); + } + + // } + offset += snprintf((&str[offset]), size - offset, "%s", "}"); + + /* dump output data */ + BLEPROTO_T_D("data:%.*s", (int)offset, str); + + return (offset); +} + +int bleproto_encbin_otadata_rsp(const bleproto_otadata_rsp_t *p, + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]) +{ + int offset = 0, size = BLEPROTO_APPDATA_DATA_MAX_SZ; + + /* check param */ + if (!p || !data) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + /* check buffer size */ + if ((size_t)size < (sizeof(p->errcode) + sizeof(p->id) + + sizeof(p->offset) + sizeof(p->len) + p->len)) { + BLEPROTO_T_E("Buffer too small"); + return -1; + } + + /* serialize errcode (little-endian) */ + data[offset++] = (uint8_t)(p->errcode & 0xFF); + data[offset++] = (uint8_t)((p->errcode >> 8) & 0xFF); + data[offset++] = (uint8_t)((p->errcode >> 16) & 0xFF); + data[offset++] = (uint8_t)((p->errcode >> 24) & 0xFF); + + /* serialize id (little-endian) */ + data[offset++] = (uint8_t)(p->id & 0xFF); + data[offset++] = (uint8_t)((p->id >> 8) & 0xFF); + data[offset++] = (uint8_t)((p->id >> 16) & 0xFF); + data[offset++] = (uint8_t)((p->id >> 24) & 0xFF); + + /* serialize offset (little-endian) */ + data[offset++] = (uint8_t)(p->offset & 0xFF); + data[offset++] = (uint8_t)((p->offset >> 8) & 0xFF); + data[offset++] = (uint8_t)((p->offset >> 16) & 0xFF); + data[offset++] = (uint8_t)((p->offset >> 24) & 0xFF); + + /* serialize len (little-endian) */ + data[offset++] = (uint8_t)(p->len & 0xFF); + data[offset++] = (uint8_t)((p->len >> 8) & 0xFF); + data[offset++] = (uint8_t)((p->len >> 16) & 0xFF); + data[offset++] = (uint8_t)((p->len >> 24) & 0xFF); + + /* serialize data(data 为外部指针,不再内嵌数组) */ + if (p->len > 0 && p->len <= BLEPROTO_APPDATA_OTADATA_SZ) { + if (!p->data) { + BLEPROTO_T_E("otadata_rsp.data is NULL"); + return -1; + } + memcpy(&data[offset], p->data, p->len); + offset += p->len; + } + + return offset; +} + +int bleproto_decjson_deviceinfo_req(const uint8_t * data, + uint16_t len, + bleproto_deviceinfo_req_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + //{"t":"xxxxxxxx",gmtoff:%d} + char value_str[BLEPROTO_APPDATA_DATA_MAX_SZ] = { 0 }; + double v; + + // t + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.t", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + p->t = (uint64_t)strtoull(value_str, NULL, 10); + } else { + BLEPROTO_T_E("Error parse t"); + return -1; + } + + // gmtoff + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.gmtoff", + /* result */ &v); + if (rc > 0) { + p->gmtoff = (int)v; + } else { + BLEPROTO_T_E("Error parse gmtoff"); + return -1; + } + + return (0); +} + +int bleproto_decjson_deviceinfo_rsp(const uint8_t * data, + uint16_t len, + bleproto_deviceinfo_rsp_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"hwv":"%s","swv":"%s","prototype":%u, "hbcycle":%u,"longcon":%u} + char value_str[BLEPROTO_APPDATA_DATA_MAX_SZ] = { 0 }; + double v; + + // hwv + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.hwv", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + snprintf(p->hwv, sizeof(p->hwv), "%s", value_str); + } else { + BLEPROTO_T_E("Error parse hwv"); + return -1; + } + + // swv + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.swv", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + snprintf(p->swv, sizeof(p->swv), "%s", value_str); + } else { + BLEPROTO_T_E("Error parse swv"); + return -1; + } + + // prototype + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.prototype", + /* result */ &v); + if (rc > 0) { + p->prototype = (uint8_t)v; + } else { + BLEPROTO_T_E("Error parse prototype"); + return -1; + } + + // hbcycle + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.hbcycle", + /* result */ &v); + if (rc > 0) { + p->hbcycle = (uint32_t)v; + } else { + BLEPROTO_T_E("Error parse hbcycle"); + return -1; + } + + // longcon + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.longcon", + /* result */ &v); + if (rc > 0) { + p->longcon = (uint8_t)v; + } else { + BLEPROTO_T_E("Error parse longcon"); + return -1; + } + + return (0); +} + +int bleproto_decjson_authsetup_req(const uint8_t * data, + uint16_t len, + bleproto_authsetup_req_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"gwmac":"xxxx","devid":"xxxx","authcode":"xxxx"} + char value_str[BLEPROTO_APPDATA_DATA_MAX_SZ] = { 0 }; + + // gwmac + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.gwmac", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + int nr = proto_strhex_to_byte(value_str, p->gwmac, sizeof(p->gwmac)); + if (nr != (int)sizeof(p->gwmac)) { + BLEPROTO_T_E("Error parse gwmac"); + return -1; + } + } else { + BLEPROTO_T_E("Error parse gwmac"); + return -1; + } + + // devid + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.devid", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + snprintf(p->devid, sizeof(p->devid), "%s", value_str); + } else { + BLEPROTO_T_E("Error parse devid"); + return -1; + } + + // authcode + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.authcode", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + snprintf(p->authcode, sizeof(p->authcode), "%s", value_str); + } else { + BLEPROTO_T_E("Error parse authcode"); + return -1; + } + + return (0); +} + +int bleproto_decjson_authsetup_rsp(const uint8_t * data, + uint16_t len, + bleproto_authsetup_rsp_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"errcode":xxx} + double v; + + // errcode + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.errcode", + /* result */ &v); + if (rc > 0) { + p->errcode = (int32_t)v; + } else { + BLEPROTO_T_E("Error parse errcode"); + return -1; + } + + return (0); +} + +int bleproto_decjson_authdelete_req(const uint8_t * data, + uint16_t len, + bleproto_authdelete_req_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"authcode": "xxxx"} + char value_str[BLEPROTO_APPDATA_DATA_MAX_SZ] = { 0 }; + + // authcode + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.authcode", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + snprintf(p->authcode, sizeof(p->authcode), "%s", value_str); + } else { + BLEPROTO_T_E("Error parse authcode"); + return -1; + } + + return (0); +} + +int bleproto_decjson_authdelete_rsp(const uint8_t * data, + uint16_t len, + bleproto_authdelete_rsp_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"gwmac":"xxxx","errcode":xxx} + char value_str[BLEPROTO_APPDATA_DATA_MAX_SZ] = { 0 }; + double v; + + // gwmac + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.gwmac", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + int nr = proto_strhex_to_byte(value_str, p->gwmac, sizeof(p->gwmac)); + if (nr != (int)sizeof(p->gwmac)) { + BLEPROTO_T_E("Error parse gwmac"); + return -1; + } + } else { + BLEPROTO_T_E("Error parse gwmac"); + return -1; + } + + // errcode + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.errcode", + /* result */ &v); + if (rc > 0) { + p->errcode = (int32_t)v; + } else { + BLEPROTO_T_E("Error parse errcode"); + return -1; + } + + return (0); +} + +int bleproto_decjson_runcmd_req(const uint8_t * data, + uint16_t len, + bleproto_runcmd_req_t *p) +{ + int rc, i; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"cmd_id":xxxx,"arg_int32":[],"arg_string":[],"arg_bytes":"xxxx","t":"xxxxxxxx" } + char value_str[BLEPROTO_APPDATA_DATA_MAX_SZ] = { 0 }; + double v; + + // cmd_id + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.cmd_id", + /* v */ &v); + if (rc > 0) { + p->ycmd.cmd_id = (int)v; + } else { + BLEPROTO_T_E("Error parse cmd_id"); + return -1; + } + + // arg_int32 + for (i = 0; i < YCMD_INT32_MAX_COUNT; i++) { + const char *ptr = NULL; + int n; + char key[sizeof("$.arg_int32[65535]") + 1] = { 0 }; + snprintf(key, sizeof(key), "$.arg_int32[%d]", i); + if (mjson_find(str, (int)len, key, &ptr, &n) == MJSON_TOK_NUMBER) { + char vstr[32] = { 0 }; + memcpy(vstr, ptr, UTILS_MIN(n, 31)); + p->ycmd.arg_int32[p->ycmd.arg_int32_count] = + (int32_t)strtol(vstr, NULL, 10); + p->ycmd.arg_int32_count++; + } + } + + // arg_string + for (i = 0; i < YCMD_STRING_MAX_COUNT; i++) { + const char *ptr = NULL; + int n; + char key[sizeof("$.arg_string[65535]") + 1] = { 0 }; + snprintf(key, sizeof(key), "$.arg_string[%d]", i); + if (mjson_find(str, (int)len, key, &ptr, &n) == MJSON_TOK_STRING) { + if (n >= 2 && ptr[0] == '"' && ptr[n - 1] == '"') { + ptr++; // 跳过首引号 + n -= 2; // 去掉首尾引号的长度 + } else { + BLEPROTO_T_E("Error parse arg_string"); + return -1; + } + memcpy(p->ycmd.arg_string[p->ycmd.arg_string_count], + ptr, + UTILS_MIN(n, YCMD_STRING_MAX_SIZE)); + p->ycmd.arg_string_count++; + } + } + + // arg_bytes + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.arg_bytes", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + // strhex to byte + rc = proto_strhex_to_byte( + value_str, p->ycmd.arg_bytes, sizeof(p->ycmd.arg_bytes)); + if (rc >= 0) { + p->ycmd.arg_bytes_count = (uint16_t)rc; + } + } + + // t + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.t", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + p->ycmd.t = (uint64_t)strtoull(value_str, NULL, 10); + } else { + BLEPROTO_T_E("Error parse t"); + return -1; + } + + return (0); +} + +int bleproto_decjson_runcmd_rsp(const uint8_t * data, + uint16_t len, + bleproto_runcmd_rsp_t *p) +{ + int rc, i; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"cmd_id":xxxx,"arg_int32":[],"arg_string":[],"arg_bytes":"xxxx","t":"xxxxxxxx" } + char value_str[BLEPROTO_APPDATA_DATA_MAX_SZ] = { 0 }; + double v; + + // cmd_id + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.cmd_id", + /* v */ &v); + if (rc > 0) { + p->ycmd.cmd_id = (int)v; + } else { + BLEPROTO_T_E("Error parse cmd_id"); + return -1; + } + + // arg_int32 + for (i = 0; i < YCMD_INT32_MAX_COUNT; i++) { + const char *ptr = NULL; + int n; + char key[sizeof("$.arg_int32[65535]") + 1] = { 0 }; + snprintf(key, sizeof(key), "$.arg_int32[%d]", i); + if (mjson_find(str, (int)len, key, &ptr, &n) == MJSON_TOK_NUMBER) { + char vstr[32] = { 0 }; + memcpy(vstr, ptr, UTILS_MIN(n, 31)); + p->ycmd.arg_int32[p->ycmd.arg_int32_count] = + (int32_t)strtol(vstr, NULL, 10); + p->ycmd.arg_int32_count++; + } + } + + // arg_string + for (i = 0; i < YCMD_STRING_MAX_COUNT; i++) { + const char *ptr = NULL; + int n; + char key[sizeof("$.arg_string[65535]") + 1] = { 0 }; + snprintf(key, sizeof(key), "$.arg_string[%d]", i); + if (mjson_find(str, (int)len, key, &ptr, &n) == MJSON_TOK_STRING) { + if (n >= 2 && ptr[0] == '"' && ptr[n - 1] == '"') { + ptr++; // 跳过首引号 + n -= 2; // 去掉首尾引号的长度 + } else { + BLEPROTO_T_E("Error parse arg_string"); + return -1; + } + memcpy(p->ycmd.arg_string[p->ycmd.arg_string_count], + ptr, + UTILS_MIN(n, YCMD_STRING_MAX_SIZE)); + p->ycmd.arg_string_count++; + } + } + + // arg_bytes + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.arg_bytes", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + // strhex to byte + rc = proto_strhex_to_byte( + value_str, p->ycmd.arg_bytes, sizeof(p->ycmd.arg_bytes)); + if (rc >= 0) { + p->ycmd.arg_bytes_count = (uint16_t)rc; + } + } + + // t + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.t", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + p->ycmd.t = (uint64_t)strtoull(value_str, NULL, 10); + } else { + BLEPROTO_T_E("Error parse t"); + return -1; + } + + return (0); +} + +int bleproto_decjson_reportcmd_req(const uint8_t * data, + uint16_t len, + bleproto_reportcmd_req_t *p) +{ + int rc, i; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"cmd_id":xxxx,"arg_int32":[],"arg_string":[],"arg_bytes":"xxxx","t":"xxxxxxxx" } + char value_str[BLEPROTO_APPDATA_DATA_MAX_SZ] = { 0 }; + double v; + + // cmd_id + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.cmd_id", + /* v */ &v); + if (rc > 0) { + p->ycmd.cmd_id = (int)v; + } else { + BLEPROTO_T_E("Error parse cmd_id"); + return -1; + } + + // arg_int32 + for (i = 0; i < YCMD_INT32_MAX_COUNT; i++) { + const char *ptr = NULL; + int n; + char key[sizeof("$.arg_int32[65535]") + 1] = { 0 }; + snprintf(key, sizeof(key), "$.arg_int32[%d]", i); + if (mjson_find(str, (int)len, key, &ptr, &n) == MJSON_TOK_NUMBER) { + char vstr[32] = { 0 }; + memcpy(vstr, ptr, UTILS_MIN(n, 31)); + p->ycmd.arg_int32[p->ycmd.arg_int32_count] = + (int32_t)strtol(vstr, NULL, 10); + p->ycmd.arg_int32_count++; + } + } + + // arg_string + for (i = 0; i < YCMD_STRING_MAX_COUNT; i++) { + const char *ptr = NULL; + int n; + char key[sizeof("$.arg_string[65535]") + 1] = { 0 }; + snprintf(key, sizeof(key), "$.arg_string[%d]", i); + if (mjson_find(str, (int)len, key, &ptr, &n) == MJSON_TOK_STRING) { + if (n >= 2 && ptr[0] == '"' && ptr[n - 1] == '"') { + ptr++; // 跳过首引号 + n -= 2; // 去掉首尾引号的长度 + } else { + BLEPROTO_T_E("Error parse arg_string"); + return -1; + } + memcpy(p->ycmd.arg_string[p->ycmd.arg_string_count], + ptr, + UTILS_MIN(n, YCMD_STRING_MAX_SIZE)); + p->ycmd.arg_string_count++; + } + } + + // arg_bytes + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.arg_bytes", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + // strhex to byte + rc = proto_strhex_to_byte( + value_str, p->ycmd.arg_bytes, sizeof(p->ycmd.arg_bytes)); + if (rc >= 0) { + p->ycmd.arg_bytes_count = (uint16_t)rc; + } + } + + // t + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.t", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + p->ycmd.t = (uint64_t)strtoull(value_str, NULL, 10); + } else { + BLEPROTO_T_E("Error parse t"); + return -1; + } + + return (0); +} + +int bleproto_decjson_reportcmd_rsp(const uint8_t * data, + uint16_t len, + bleproto_reportcmd_rsp_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"errcode":xxx} + double v; + + // errcode + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.errcode", + /* result */ &v); + if (rc > 0) { + p->errcode = (int32_t)v; + } else { + BLEPROTO_T_E("Error parse errcode"); + return -1; + } + + return (0); +} + +int bleproto_decjson_doaction_req(const uint8_t * data, + uint16_t len, + bleproto_doaction_req_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"action":%d} + double v; + + // action + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.action", + /* v */ &v); + if (rc > 0) { + p->action = (int)v; + } else { + BLEPROTO_T_E("Error parse action"); + return -1; + } + + return (0); +} + +int bleproto_decjson_doaction_rsp(const uint8_t * data, + uint16_t len, + bleproto_doaction_rsp_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"errcode":xxx} + double v; + + // errcode + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.errcode", + /* result */ &v); + if (rc > 0) { + p->errcode = (int32_t)v; + } else { + BLEPROTO_T_E("Error parse errcode"); + return -1; + } + + return (0); +} + +int bleproto_decjson_otanotify_req(const uint8_t * data, + uint16_t len, + bleproto_otanotify_req_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"id": %d, "ver":"xxxx", "size":%d, "md5":"xxxxxxx"} + char value_str[BLEPROTO_APPDATA_DATA_MAX_SZ] = { 0 }; + double v; + + // id + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.id", + /* result */ &v); + if (rc > 0) { + p->id = (int)v; + } else { + BLEPROTO_T_E("Error parse errcode"); + return -1; + } + + // ver + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.ver", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + snprintf(p->version, sizeof(p->version), "%s", value_str); + } else { + BLEPROTO_T_E("Error parse version"); + return -1; + } + + // size + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.size", + /* result */ &v); + if (rc > 0) { + p->size = (uint32_t)v; + } else { + BLEPROTO_T_E("Error parse errcode"); + return -1; + } + + // md5 + rc = mjson_get_string( + /* s */ str, + /* len */ (int)len, + /* path */ "$.md5", + /* to */ value_str, + /* n */ (int)sizeof(value_str)); + if (rc > 0) { + snprintf(p->md5, sizeof(p->md5), "%s", value_str); + } else { + BLEPROTO_T_E("Error parse md5"); + return -1; + } + + return (0); +} + +int bleproto_decjson_otanotify_rsp(const uint8_t * data, + uint16_t len, + bleproto_otanotify_rsp_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"errcode":xxx} + double v; + + // errcode + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.errcode", + /* result */ &v); + if (rc > 0) { + p->errcode = (int32_t)v; + } else { + BLEPROTO_T_E("Error parse errcode"); + return -1; + } + + return (0); +} + +int bleproto_decjson_otadata_req(const uint8_t * data, + uint16_t len, + bleproto_otadata_req_t *p) +{ + int rc; + + /* check param */ + if (!data || len == 0 || !p) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + const char *str = (const char *)data; + /* dump input data */ + BLEPROTO_T_D("data:%.*s", (int)len, str); + + /* clear */ + memset(p, 0, sizeof(*p)); + + // {"id": %d, "offset":%d, "maxsize":%d} + double v; + + // id + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.id", + /* result */ &v); + if (rc > 0) { + p->id = (int)v; + } else { + BLEPROTO_T_E("Error parse errcode"); + return -1; + } + + // offset + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.offset", + /* result */ &v); + if (rc > 0) { + p->offset = (int32_t)v; + } else { + BLEPROTO_T_E("Error parse offset"); + return -1; + } + + // maxsize + rc = mjson_get_number( + /* s */ str, + /* len */ (int)len, + /* path */ "$.maxsize", + /* result */ &v); + if (rc > 0) { + p->maxsize = (uint32_t)v; + } else { + BLEPROTO_T_E("Error parse maxsize"); + return -1; + } + + return (0); +} + +int bleproto_debin_otadata_rsp(const uint8_t * data, + uint16_t len, + bleproto_otadata_rsp_t *rsp) +{ + int offset = 0; + + /* check param */ + if (!data || len == 0 || !rsp) { + BLEPROTO_T_E("Invalid parameters"); + return -1; + } + + /* check minimum buffer size (errcode + id + offset + len = 16 bytes) */ + if (len < 16) { + BLEPROTO_T_E("Buffer too small"); + return -1; + } + + /* deserialize errcode (little-endian) */ + rsp->errcode = + ((int32_t)data[offset]) | ((int32_t)data[offset + 1] << 8) | + ((int32_t)data[offset + 2] << 16) | ((int32_t)data[offset + 3] << 24); + offset += 4; + + /* deserialize id (little-endian) */ + rsp->id = ((uint32_t)data[offset]) | ((uint32_t)data[offset + 1] << 8) | + ((uint32_t)data[offset + 2] << 16) | + ((uint32_t)data[offset + 3] << 24); + offset += 4; + + /* deserialize offset (little-endian) */ + rsp->offset = ((uint32_t)data[offset]) | + ((uint32_t)data[offset + 1] << 8) | + ((uint32_t)data[offset + 2] << 16) | + ((uint32_t)data[offset + 3] << 24); + offset += 4; + + /* deserialize len (little-endian) */ + rsp->len = ((uint32_t)data[offset]) | ((uint32_t)data[offset + 1] << 8) | + ((uint32_t)data[offset + 2] << 16) | + ((uint32_t)data[offset + 3] << 24); + offset += 4; + + /* check data length validity */ + if (rsp->len > BLEPROTO_APPDATA_OTADATA_SZ) { + BLEPROTO_T_E("Data length too large: %u", rsp->len); + return -1; + } + + /* check remaining buffer size */ + if (len < (offset + rsp->len)) { + BLEPROTO_T_E("Insufficient data for payload"); + return -1; + } + + /* 零拷贝:指向输入缓冲,调用方须在下次 decode 前用完 */ + if (rsp->len > 0) { + rsp->data = &data[offset]; + offset += rsp->len; + } else { + rsp->data = NULL; + } + + return offset; +} +/****************************************************************************/ +/* */ +/* End of file. */ +/* */ +/****************************************************************************/ diff --git a/apps/usr_le_code/bleproto_packer.h b/apps/usr_le_code/bleproto_packer.h new file mode 100644 index 0000000..8ec82c1 --- /dev/null +++ b/apps/usr_le_code/bleproto_packer.h @@ -0,0 +1,702 @@ +/****************************************************************************/ +/* bleproto_packer.h + * + * Copyright (C) 2020 wanshijie wanshijie@126.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ****************************************************************************/ +/** + * @addtogroup bleproto_packer packer + * @ingroup bleproto_api + * @brief ble proto packer + * + * @{ + */ +#ifndef BLEPROTO_PACKER_H_INCLUDE +#define BLEPROTO_PACKER_H_INCLUDE +/****************************************************************************/ +/* Included Files */ +/****************************************************************************/ + +#include "system/includes.h" + + +#if !defined(BLEPROTO_API_H_INCLUDE) +#error "Only 'bleproto/bleproto_api.h' can be included directly." +#endif /* BLEPROTO_API_H_INCLUDE */ + +/****************************************************************************/ +/* Configure Definitions */ +/****************************************************************************/ + +#define BLEPOROTO_RAM_LARGE 0 ///< 大內存模式 +#define BLEPOROTO_RAM_MID 1 ///< 中等內存模式 +#define BLEPOROTO_RAM_SMALL 2 ///< 小內存模式 + +/* default 內存模式 */ +#ifndef BLEPOROTO_RAM_MODE +//#define BLEPOROTO_RAM_MODE BLEPOROTO_RAM_LARGE +//#define BLEPOROTO_RAM_MODE BLEPOROTO_RAM_MID +#define BLEPOROTO_RAM_MODE BLEPOROTO_RAM_SMALL +#endif /* BLEPOROTO_RAM_MODE */ + +/****************************************************************************/ +/* Pre-processor Definitions */ +/****************************************************************************/ + +/////////////////////////////////////////////////////////// +// trace log define +/////////////////////////////////////////////////////////// +#ifndef BLEPROTO_TRACE_ENABLE +#define BLEPROTO_TRACE_ENABLE 0//Jim +#endif /* BLEPROTO_TRACE_ENABLE */ + +/* trace default printf */ +#ifndef BLEPROTO_TRACE_PRINTF +#define BLEPROTO_TRACE_PRINTF printf +#endif /* BLEPROTO_TRACE_PRINTF */ + +/* trace strfunc */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 19901L +#define BLEPROTO_TRACE_STRFUNC ((const char *)(__func__)) +#else +#define BLEPROTO_TRACE_STRFUNC ((const char *)(__FUNCTION__)) +#endif /* __STDC_VERSION__ */ +/* trace file line */ +#define BLEPROTO_TRACE_INTLINE ((int)(__LINE__)) + +#if BLEPROTO_TRACE_ENABLE +#define BLEPROTO_TRACE(lvl, ...) \ + do { \ + BLEPROTO_TRACE_PRINTF("[%s][%s:%d]", \ + lvl, \ + BLEPROTO_TRACE_STRFUNC, \ + BLEPROTO_TRACE_INTLINE); \ + BLEPROTO_TRACE_PRINTF(__VA_ARGS__); \ + BLEPROTO_TRACE_PRINTF("\r\n"); \ + } while (0) +#else +#define BLEPROTO_TRACE(lvl, ...) \ + do { \ + } while (0) +#endif /* FLOG_TRACE_ENABLE */ + +/* trace log error */ +#ifndef BLEPROTO_T_E +#define BLEPROTO_T_E(...) BLEPROTO_TRACE("E", __VA_ARGS__) +#endif /* BLEPROTO_T_E */ + +/* trace log debug */ +#ifndef BLEPROTO_T_D +#define BLEPROTO_T_D(...) BLEPROTO_TRACE("D", __VA_ARGS__) +#endif /* BLEPROTO_T_D */ + +/* printf log slient */ +#ifndef BLEPROTO_T_S +#define BLEPROTO_T_S(...) +#endif /* BLEPROTO_T_S */ + +// clang-format off +/////////////////////////////////////////////////////////// +// ADV +/////////////////////////////////////////////////////////// +/** ADV Data Max Size. */ +#define BLEPROTO_ADV_MAX_SZ 31 + +/** ADV Type: Flags. */ +#define BLEPROTO_ADV_TYPE_FLAGS 0x01 +/** ADV Type: Service Data - 16-bit UUID. */ +#define BLEPROTO_ADV_TYPE_SVC_DATA_UUID16 0x16 +/** ADV Type: Manufacturer Specific Data. */ +#define BLEPROTO_ADV_TYPE_MFG_DATA 0xff + +/** ADV service uuid */ +#define BLEPROTO_ADV_SERVICE_UUID_TOBIND 0xFDEE +/** ADV service spec data version */ +#define BLEPORTO_ADV_SERVICE_SPEC_DATA_VER 0x01 +/** ADV service spec data business */ +#define BELPROTO_ADV_SERVICE_SPEC_DATA_BUSINESS_NEARFIND 0x01 + +/* ADV TLV Type */ +#define BLEPROTO_ADV_TLV_TYPE_MANUCODE 0x10 +#define BLEPROTO_ADV_TLV_TYPE_PRODCODE 0x11 +/* ADV TLV Len */ +#define BLEPROTO_ADV_TLV_LENGTH_MANUCODE 4 +#define BLEPROTO_ADV_TLV_LENGTH_PRODCODE 4 + +/* ADV PCDE */ +#define BLE_PROTO_ADV_LENGTH_PCODE 6 + +/** ADV flag value */ +#define BLEPROTO_ADV_FLAG_HEARTBEAT 0x00 +#define BLEPROTO_ADV_FLAG_RECONNECT 0x01 + +/////////////////////////////////////////////////////////// +// APPDATA +/////////////////////////////////////////////////////////// +/** APPDATA header len 8bytes */ +#define BLEPROTO_APPDATA_HEADR_LEN 8 +/** APPDATA version */ +#define BLEPROTO_APPDATA_VERSION 0 +/** APPDATA data fmt */ +#define BLEPROTO_APPDATA_DATAFMT_JSON 0 +#define BLEPROTO_APPDATA_DATAFMT_RAW 1 +/** APPDATA msg type */ +#define BLEPROTO_APPDATA_MSGTYPE_REQ 0 +#define BLEPROTO_APPDATA_MSGTYPE_RSP 1 + +#if (BLEPOROTO_RAM_MODE == BLEPOROTO_RAM_LARGE) +/** APPDATA OTA data max size */ +#define BLEPROTO_APPDATA_OTADATA_SZ (1024*16) +/** APPDATA data max size */ +#define BLEPROTO_APPDATA_DATA_MAX_SZ (BLEPROTO_APPDATA_OTADATA_SZ + 16 + 16) +/** APP tx buf max size */ +#define BLEPROTO_TXRXBUF_MAX_SZ (BLEPROTO_APPDATA_DATA_MAX_SZ + 1024 * 2) +#elif (BLEPOROTO_RAM_MODE == BLEPOROTO_RAM_MID) +/** APPDATA OTA data max size */ +#define BLEPROTO_APPDATA_OTADATA_SZ (1024*4) +/** APPDATA data max size */ +#define BLEPROTO_APPDATA_DATA_MAX_SZ (BLEPROTO_APPDATA_OTADATA_SZ + 16 + 16) +/** APP tx buf max size */ +#define BLEPROTO_TXRXBUF_MAX_SZ (BLEPROTO_APPDATA_DATA_MAX_SZ + 1024 * 2) +#else +/** APPDATA OTA data max size */ +#define BLEPROTO_APPDATA_OTADATA_SZ (1024+1024+256) +/** APPDATA data max size */ +#define BLEPROTO_APPDATA_DATA_MAX_SZ (BLEPROTO_APPDATA_OTADATA_SZ + 16 + 16) +/** APP tx buf max size */ +//#define BLEPROTO_TXRXBUF_MAX_SZ (BLEPROTO_APPDATA_DATA_MAX_SZ + 1024 * 2) +#endif /* BLEPOROTO_RAM_MODE */ + +// clang-format on + +/****************************************************************************/ +/* Public Types */ +/****************************************************************************/ + +/** + * @brief 厂商编码-manucode(manufacturer code) TLV desc. + */ +typedef struct bleproto_adv_manucode { + // clang-format off + uint8_t type; ///< 厂商编码T, @ref BLEPROTO_ADV_TLV_TYPE_MANUCODE + uint8_t length; ///< 厂商编码L, @ref BLEPROTO_ADV_TLV_LEN_MANUCODE + uint8_t value[BLEPROTO_ADV_TLV_LENGTH_MANUCODE]; ///< 厂商信息V, 现在固定填写0x00000000 + // clang-format off +} bleproto_adv_manucode_t; + +/** + * @brief 产品型号-prodcode(product code) TLV desc. + */ +typedef struct bleproto_adv_prodcode { + // clang-format off + uint8_t type; ///< 产品型号T, @ref BLEPROTO_ADV_TLV_TYPE_PRODCODE + uint8_t length; ///< 产品型号L, @ref BLEPROTO_ADV_TLV_LENGTH_PRODCODE + uint8_t value[BLEPROTO_ADV_TLV_LENGTH_PRODCODE]; ///< 产品型号V, 平台定义,需要转换成ASCII码值 + // clang-format on +} bleproto_adv_prodcode_t; + +/** + * @brief 请求配对广播的Spec Data结构. + */ +typedef struct bleproto_adv_specdata { + // clang-format off + uint8_t version; ///< 蓝牙广播协议版本, 当前1 @ref BLEPORTO_ADV_SERVICE_SPEC_DATA_VER + uint8_t business; ///< 设备发现模式, 固定填写0x01,表示靠近发现 @ref BELPROTO_ADV_SERVICE_SPEC_DATA_BUSINESS_NEARFIND + uint8_t rfu[2]; ///< 保留 + int8_t txpower; ///< 蓝牙发射功率[-127,128]dBm + bleproto_adv_manucode_t manucode; ///< 厂商编码 + bleproto_adv_prodcode_t prodcode; ///< 产品型号 + uint8_t separator; ///< 分隔符, 固定填写0xFF + uint8_t selfdata[6]; ///< 自定义数据 + // clang-format on +} bleproto_adv_specdata_t; + +/** + * @brief 请求注册广播数据. + */ +typedef struct bleproto_advdata_service { + // clang-format off + uint16_t uuid; ///< 服务uuid16 @ref BLEPROTO_ADV_SERVICE_UUID_TOBIND + bleproto_adv_specdata_t spec_data;///< 协议数据(24bytes) + // clang-format on +} bleproto_advdata_service_t; + +/** + * @brief 心跳广播, 回连广播数据. + */ +typedef struct bleproto_advdata_mfg { + // clang-format off + uint8_t siot[6]; ///< 固定填写SIOT-1 + uint8_t flag; ///< 0表示心跳,1表示请求回连 @ref BLEPROTO_ADV_FLAG_HEARTBEAT or @ref BLEPROTO_ADV_FLAG_RECONNECT + int8_t txpower; ///< 蓝牙发射功率[-127,128]dBm + uint8_t gwmac[6]; ///< gwmac "11:22:33:44:55:66", gwmac[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66 } + bleproto_adv_manucode_t manucode; ///< 厂商编码 + bleproto_adv_prodcode_t prodcode; ///< 产品型号 + // clang-format on +} bleproto_advdata_mfg_t; + +/** + * @brief AD Structure1结构. + */ +typedef struct bleproto_adv1 { + // clang-format off + /** + * 表示AD Type与AD Data的总长度, + * 此处取值固定为0x02 + */ + uint8_t length; + + /** + * 表示该广播数据代表的含义 + * 此处取值固定为0x01 @ref BLEPROTO_ADV_TYPE_FLAGS + */ + uint8_t ad_type; + + /** + * 固定为0x06,表示LE通用可发现模式,且不支持BR/EDR + * 表示蓝牙设备的物理连接能力, + * bit0:LE受限可发现模式。 + * bit1:LE通用可发现模式。 + * bit2:不支持BR/EDR。 + * bit3:对Same Device Capable(控制器)同时支持BLE和BR/EDR。 + * bit4:对Same Device Capable(主机)同时支持BLE和BR/EDR。 + * bit5~7:预留 + */ + uint8_t ad_data; + // clang-format on +} bleproto_adv1_t; + +/** + * @brief AD Structure2结构. + */ +typedef struct bleproto_adv2 { + // clang-format off + /** + * 表示AD Type与AD Data的总长度, + * 此处取值固定为0x1B = 27 + */ + uint8_t length; + + /** 广播数据代表的含义 + * BLEPROTO_ADV_TYPE_SVC_DATA_UUID16 - 0x16表示蓝牙服务数据(bleproto_advdata_service_t), + * BLEPROTO_ADV_TYPE_MFG_DATA - 0xFF表示厂商数据(bleproto_advdata_mfg_t) + */ + uint8_t ad_type; + + /** adv data */ + union { + bleproto_advdata_service_t service_data; ///< 蓝牙服务数据(请求注册广播时携带) + bleproto_advdata_mfg_t mfg_data; ///< 厂商数据(心跳广播,回连广播时携带) + } ad_data; + // clang-format on +} bleproto_adv2_t; + +/** + * @brief 广播数据总结构 + */ +typedef struct bleproto_adv { + bleproto_adv1_t adv1; ///< ADV Structure1 + bleproto_adv2_t adv2; ///< ADV Structure2 +} bleproto_adv_t; + +/** + * @brief 应用协议报文头(8字节) + */ +typedef struct bleproto_appdata_header { + // clang-format off + uint8_t version : 3; ///< byte0[2:0]:蓝牙BLE应用层协议的版本号,当前为0,根据业务演进递增 @ref BLEPROTO_APPDATA_VERSION + uint8_t datafmt : 1; ///< byte0[3:3]:数据格式, 当前只支持JSON字符串格式,取值固定为0, @ref BLEPROTO_APPDATA_DATAFMT_JSON + uint8_t msgtype : 4; ///< byte0[7:4]:消息类型, 包括:请求(0=req)、响应(1=rsp), @ref BLEPROTO_APPDATA_MSGTYPE_REQ + uint8_t msgid : 4; ///< byte1[3:0]:消息序号, 单调递增 + uint8_t encrypt : 1; ///< byte1[4:4]:数据加密方式, 当前固定填写0,表示不加密 + uint8_t reserve : 3; ///< byte1[7:5]:保留 + uint8_t totalframe; ///< byte2 :总包数, 范围 1~255, 0x01:不分包时,取值为1, 0x02~0xFF:分包时,取值为≥2 + uint8_t frameseq; ///< byte3 :分包包序号, 0x00:不分包时取值为0, 0x01~0xFF: 分包时, 取值为从1开始依次递增 + uint8_t framelen; ///< byte4 :该包数据长度 + uint8_t serviceid; ///< byte5 :应用数据服务id @ref bleproto_service_id_e + uint16_t datalen; ///< byte6-7 :应用数据总长度 + // clang-format on +} bleproto_appdata_header_t; + +/** + * @brief 应用数据 + */ +typedef struct bleproto_appdata { + bleproto_appdata_header_t header; + uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]; +} bleproto_appdata_t; + +/** + * @brief service id定义 + */ +typedef uint8_t bleproto_service_id_e; +enum { + E_BLEPROTO_SERVICE_ID_UNSPECIFIED = 0, ///< 保留 + E_BLEPROTO_SERVICE_ID_DEVICEINFO = 1, ///< deviceInfo + E_BLEPROTO_SERVICE_ID_AUTHSETUP = 2, ///< authSetup + E_BLEPROTO_SERVICE_ID_AUTHDELETE = 3, ///< authDelete + E_BLEPROTO_SERVICE_ID_RUNCMD = 4, ///< runCmd + E_BLEPROTO_SERVICE_ID_REPORTCMD = 5, ///< reportCmd + E_BLEPROTO_SERVICE_ID_DOACTION = 6, ///< doAction + E_BLEPROTO_SERVICE_ID_OTANOTIFY = 7, ///< otaNotify + E_BLEPROTO_SERVICE_ID_OTADATA = 8, ///< otaData + E_BLEPROTO_SERVICE_ID_RUNCMD_V2 = 20, ///< runCmdV2 + E_BLEPROTO_SERVICE_ID_REPORTCMD_V2 = 21, ///< reportCmdV2 + E_BLEPROTO_SERVICE_ID_OTADATA_V2 = 22, ///< otaDataV2 +}; + +/** + * @brief ycmd 定义 + */ +#define YCMD_INT32_MAX_COUNT 32 +#define YCMD_STRING_MAX_COUNT 16 +#define YCMD_STRING_MAX_SIZE 32 +#define YCMD_BYTES_MAX_COUNT 8 +typedef struct bleproto_ycmd { + int32_t cmd_id; + uint16_t arg_int32_count; + int32_t arg_int32[YCMD_INT32_MAX_COUNT]; + uint16_t arg_string_count; + char arg_string[YCMD_STRING_MAX_COUNT][YCMD_STRING_MAX_SIZE]; + uint16_t arg_bytes_count; + uint8_t arg_bytes[YCMD_BYTES_MAX_COUNT]; + uint64_t t; +} bleproto_ycmd_t; + +////////////////////////////////////////////////////////////////////// +// E_BLEPROTO_SERVICE_ID_DEVICEINFO +////////////////////////////////////////////////////////////////////// +typedef struct bleproto_deviceinfo_req { + uint64_t t; ///< utcms时间戳 + int gmtoff; ///< 时区偏移,单位秒,例如东八区(8x60x60=28800秒) +} bleproto_deviceinfo_req_t; + +typedef struct bleproto_deviceinfo_rsp { + // clang-format off + char hwv[16]; ///< 硬件版本号 + char swv[16]; ///< 软件版本号 + uint8_t prototype; ///< 协议类型,固定填写为4,表示Ble设备 + uint32_t hbcycle; ///< 设备心跳广播心跳周期,ms + uint8_t longcon; ///< 是否长链接, 1=长链接,0=短链接 + // clang-format on +} bleproto_deviceinfo_rsp_t; + +////////////////////////////////////////////////////////////////////// +// E_BLEPROTO_SERVICE_ID_AUTHSETUP +////////////////////////////////////////////////////////////////////// +typedef struct bleproto_authsetup_req { + // clang-format off + uint8_t gwmac[6]; ///< 主控mac地址, 设备需要保存 + char devid[36 + 1]; ///< 主控分配的子设备id, 设备需要保存 + char authcode[32 + 1]; ///< 主控分配的子设备认证码, 设备需要保存 + uint8_t paired_flag; + // clang-format on +} bleproto_authsetup_req_t; + +typedef struct bleproto_authsetup_rsp { + int32_t errcode; ///< 返回码,0表示成功, 其他表示失败 +} bleproto_authsetup_rsp_t; + +////////////////////////////////////////////////////////////////////// +// E_BLEPROTO_SERVICE_ID_AUTHDELETE +////////////////////////////////////////////////////////////////////// +typedef struct bleproto_authdelete_req { + char authcode[32 + 1]; ///< 子设备认证码 +} bleproto_authdelete_req_t; + +typedef struct bleproto_authdelete_rsp { + uint8_t gwmac[6]; ///< 绑定主控设备的mac地址 + int32_t errcode; ///< 返回码,0表示成功, 其他表示失败 +} bleproto_authdelete_rsp_t; + +////////////////////////////////////////////////////////////////////// +// E_BLEPROTO_SERVICE_ID_RUNCMD +////////////////////////////////////////////////////////////////////// +typedef struct bleproto_runcmd_req { + bleproto_ycmd_t ycmd; ///< Request Ycmd +} bleproto_runcmd_req_t; + +typedef struct bleproto_runcmd_rsp { + bleproto_ycmd_t ycmd; ///< Response Ycmd +} bleproto_runcmd_rsp_t; + +////////////////////////////////////////////////////////////////////// +// E_BLEPROTO_SERVICE_ID_REPORTCMD +////////////////////////////////////////////////////////////////////// +typedef struct bleproto_reportcmd_req { + bleproto_ycmd_t ycmd; ///< Ycmd +} bleproto_reportcmd_req_t; + +typedef struct bleproto_reportcmd_rsp { + int32_t errcode; ///< 返回码,0表示成功, 其他表示失败 +} bleproto_reportcmd_rsp_t; + +////////////////////////////////////////////////////////////////////// +// E_BLEPROTO_SERVICE_ID_DOACTION +////////////////////////////////////////////////////////////////////// +typedef struct bleproto_doaction_req { + int action; ///< 0:重启, 1:指示,收到命令设备指示灯闪烁或者音频发音 +} bleproto_doaction_req_t; + +typedef struct bleproto_doaction_rsp { + int32_t errcode; ///< 返回码,0表示成功, 其他表示失败 +} bleproto_doaction_rsp_t; + +////////////////////////////////////////////////////////////////////// +// E_BLEPROTO_SERVICE_ID_OTANOTIFY +////////////////////////////////////////////////////////////////////// +typedef struct bleproto_otanotify_req { + int id; ///< file_id + char version[16]; ///< 版本号 + uint32_t size; ///< 固件大小 + char md5[32 + 1]; ///< 固件的md5值 +} bleproto_otanotify_req_t; + +typedef struct bleproto_otanotify_rsp { + int32_t errcode; ///< 返回码,0表示成功, 其他表示失败 +} bleproto_otanotify_rsp_t; +////////////////////////////////////////////////////////////////////// +// E_BLEPROTO_SERVICE_ID_OTADATA +////////////////////////////////////////////////////////////////////// +typedef struct bleproto_otadata_req { + int id; ///< file_id + int32_t offset; ///< 要读取数据的偏移 + uint32_t maxsize; ///< 每次最大读取的大小 +} bleproto_otadata_req_t; + +typedef struct bleproto_otadata_rsp { + int32_t errcode; ///< 返回码,0表示成功, 其他表示失败 + int id; ///< file_id + int32_t offset; ///< 当前读取到的数据偏移 + uint32_t len; ///< 当次读取数据的大小 + /** + * 固件数据指针(不内嵌大数组,避免 bleproto_appdata_desc_t 膨胀到 2KB+) + * - decode:指向 appdata 内部缓冲(零拷贝),下次 decode 前有效 + * - encode:由调用方提供可读缓冲 + */ + const uint8_t *data; +} bleproto_otadata_rsp_t; +/****************************************************************************/ +/* Public Data */ +/****************************************************************************/ + +/****************************************************************************/ +#ifdef __cplusplus +extern "C" { +#endif +/****************************************************************************/ +/* Public Function Prototypes */ +/****************************************************************************/ + +/** + * @brief 序列化广播数据(广播数据长度为31) + * + * @param[in] adv - 广播数据结构体. + * @param[out] data - 序列化数据缓冲区, 固定为31个字节. + * @param[in] size - 序列化数据缓冲区长度 + * + * @return 小于0表示失败,大于0表示序列化后的长度(31) + */ +int bleproto_advdata_serialize(bleproto_adv_t *adv, + uint8_t * data, + uint8_t size); + +/** + * @brief 反序列化广播数据(广播数据长度为31) + * + * @param[in] data - 反序列化数据指针 + * @param[in] len - 反序列化数据长度 + * @param[out] adv - 广播数据结构体 + * + * @return 小于0表示失败,大于0表示反序列化使用data的长度(31) + */ +int bleproto_advdata_deserialize(const uint8_t * data, + uint8_t len, + bleproto_adv_t *adv); + +/** + * @brief 计算序列化应用数据长度 + * + * @note packetlen说明 + * + * ```c + * ble4.0 通常协商的mtu=27, packetlen = 27 - 4 - 3 = 20 + * ble4.2 通常协商的mtu=251, packelen = 251 - 4 - 3 = 244 + * ble5.0 通常协商的mtu=251, packelen = 251 - 4 - 3 = 244 + * ``` + * + * @param[in] appdata - 应用数据结构体 + * @param[in] packetlen - BLE GATT单包传输有效长度, 查看上面说明 + * @param[out] outlen - 计算返回总长度,每个包都含有包头 + * + * @return 成功返回0,其他值表示失败 + */ +int bleproto_appdata_serialize_len(bleproto_appdata_t *appdata, + uint8_t packetlen, + uint16_t * outlen); + +/** + * @brief 序列化应用数据 + * + * @note packetlen说明 + * + * ```c + * ble4.0 通常协商的mtu=27, packetlen = 27 - 4 - 3 = 20 + * ble4.2 通常协商的mtu=251, packelen = 251 - 4 - 3 = 244 + * ble5.0 通常协商的mtu=251, packelen = 251 - 4 - 3 = 244 + * ``` + * + * 如果报文数据长度超过 BLE GATT单包传输有效长度,需要按照文档方式分包, + * +----------------+ + * | 1 | 第一包按照packetlen填满 + * +----------------+ + * | | + * | ... | 中间包按照packetlen填满 + * | | + * +----------------+ + * | N | 最后一包长度 = totallen - (packetlen * (N -1)) + * +----------------+ + * + * 需要按照每包的顺序发给对端 + * + * @param[in] appdata - 应用数据结构体 + * @param[in] packetlen - BLE GATT单包传输有效长度, 查看上面说明 + * @param[out] buf - 数据缓冲区指针 + * @param[in] size - 数据缓冲区大小 + * + * @return 小于0表示失败,大于0表示序列化后的长度 + */ +int bleproto_appdata_serialize(bleproto_appdata_t *appdata, + uint8_t packetlen, + uint8_t * buf, + uint16_t size); + +/** + * @brief 将已打包好的 payload 包装成单帧 appdata(不分包) + * @param txbuf 输出缓冲 + * @param txbuf_size 输出缓冲大小 + * @param header 应用层头(datalen/totalframe/frameseq 由本函数填写) + * @param payload 已序列化的 service 数据;可为 NULL(payload_len==0) + * @param payload_len payload 长度,须 <=255(单帧 pthis_frame_len 为 u8) + * @return >0 总字节数;<0 失败 + * @note 小包场景(JB 55AA 透传等)用此接口,避免 bleproto_appdata_t 大缓冲 + */ +int bleproto_appdata_wrap_raw(uint8_t *txbuf, + uint16_t txbuf_size, + const bleproto_appdata_header_t *header, + const uint8_t *payload, + uint16_t payload_len); + +/** + * @brief 反序列化应用数据 + * + * + * 如果报文数据长度超过 BLE GATT单包传输有效长度,需要按照文档方式分包, + * +----------------+ + * | 1 | 第一包按照packetlen填满 + * +----------------+ + * | | + * | ... | 中间包按照packetlen填满 + * | | + * +----------------+ + * | N | 最后一包长度 = totallen - (packetlen * (N -1)) + * +----------------+ + * + * 因此接收到数据 + * 1. 函数返回等于0,以为还有分包数据还没有接收完成, + * 接收到下包数据 append 在buf尾部,再调用函数的解析数据包 + * 2. 函数返回大于0,表示收到数据解析了多少字节,需要将头上对应长度数据移除掉 + * 3. 函数返回小于0,表示收到非法的数据包,无法解析 + * + * @param[in] buf - 数据缓冲区指针. + * @param[in] len - 数据缓冲区长度. + * @param[out] app_data - 应用数据结构体. + * + * @return 大于0表示解析成功, 返回当前解析了多少个字节, + * 等于0表示解析未完成, 只接受到分包的部分数据, 需要继续接收数据 + * 小于0表示解析失败 + */ +int bleproto_appdata_deserialize(uint8_t * buf, + uint16_t len, + bleproto_appdata_t *appdata); + +//////////////////////////////////////////////////////////////////////// +// encjson 序列化, 成功返回json序列化的长度,失败返回小于0 +// decjson 反序列化, 成功返回0, 其他值表示失败 +//////////////////////////////////////////////////////////////////////// +// clang-format off +int bleproto_encjson_ycmd(const bleproto_ycmd_t *ycmd, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_decjson_ycmd(const uint8_t *data, uint16_t len, bleproto_ycmd_t *ycmd); +// clang-format on + +//////////////////////////////////////////////////////////////////////// +// encjson 序列化, 成功返回json序列化的长度,失败返回小于0 +// decjson 反序列化, 成功返回0, 其他值表示失败 +//////////////////////////////////////////////////////////////////////// +// clang-format off +int bleproto_encjson_deviceinfo_req(const bleproto_deviceinfo_req_t *req, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_deviceinfo_rsp(const bleproto_deviceinfo_rsp_t *rsp, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_authsetup_req(const bleproto_authsetup_req_t *req, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_authsetup_rsp(const bleproto_authsetup_rsp_t *rsp, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_authdelete_req(const bleproto_authdelete_req_t *req, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_authdelete_rsp(const bleproto_authdelete_rsp_t *rsp, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_runcmd_req(const bleproto_runcmd_req_t *req, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_runcmd_rsp(const bleproto_runcmd_rsp_t *rsp, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_reportcmd_req(const bleproto_reportcmd_req_t *req, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_reportcmd_rsp(const bleproto_reportcmd_rsp_t *rsp, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_doaction_req(const bleproto_doaction_req_t *req, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_doaction_rsp(const bleproto_doaction_rsp_t *rsp, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_otanotify_req(const bleproto_otanotify_req_t *req, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encjson_otanotify_rsp(const bleproto_otanotify_rsp_t *rsp, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +// otadata 序列化请求走json,回复走二进制 +int bleproto_encjson_otadata_req(const bleproto_otadata_req_t *req, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); +int bleproto_encbin_otadata_rsp(const bleproto_otadata_rsp_t *rsp, uint8_t data[BLEPROTO_APPDATA_DATA_MAX_SZ]); + +int bleproto_decjson_deviceinfo_req(const uint8_t *data, uint16_t len, bleproto_deviceinfo_req_t *req); +int bleproto_decjson_deviceinfo_rsp(const uint8_t *data, uint16_t len, bleproto_deviceinfo_rsp_t *rsp); +int bleproto_decjson_authsetup_req(const uint8_t *data, uint16_t len, bleproto_authsetup_req_t *req); +int bleproto_decjson_authsetup_rsp(const uint8_t *data, uint16_t len, bleproto_authsetup_rsp_t *rsp); +int bleproto_decjson_authdelete_req(const uint8_t *data, uint16_t len, bleproto_authdelete_req_t *req); +int bleproto_decjson_authdelete_rsp(const uint8_t *data, uint16_t len, bleproto_authdelete_rsp_t *rsp); +int bleproto_decjson_runcmd_req(const uint8_t *data, uint16_t len, bleproto_runcmd_req_t *req); +int bleproto_decjson_runcmd_rsp(const uint8_t *data, uint16_t len, bleproto_runcmd_rsp_t *rsp); +int bleproto_decjson_reportcmd_req(const uint8_t *data, uint16_t len, bleproto_reportcmd_req_t *req); +int bleproto_decjson_reportcmd_rsp(const uint8_t *data, uint16_t len, bleproto_reportcmd_rsp_t *rsp); +int bleproto_decjson_doaction_req(const uint8_t *data, uint16_t len, bleproto_doaction_req_t *req); +int bleproto_decjson_doaction_rsp(const uint8_t *data, uint16_t len, bleproto_doaction_rsp_t *rsp); +int bleproto_decjson_otanotify_req(const uint8_t *data, uint16_t len, bleproto_otanotify_req_t *req); +int bleproto_decjson_otanotify_rsp(const uint8_t *data, uint16_t len, bleproto_otanotify_rsp_t *rsp); +//otadata 反序列化请求走json,回复走二进制 +int bleproto_decjson_otadata_req(const uint8_t *data, uint16_t len, bleproto_otadata_req_t *req); +int bleproto_debin_otadata_rsp(const uint8_t *data, uint16_t len, bleproto_otadata_rsp_t *rsp); +// clang-format on + +/****************************************************************************/ +#ifdef __cplusplus +} +#endif +/****************************************************************************/ +#endif /* BLEPROTO_PACKER_H_INCLUDE */ +/****************************************************************************/ +/** + * @} (end addtogroup bleproto_packer) + */ +/****************************************************************************/ +/* */ +/* End of file. */ +/* */ +/****************************************************************************/ diff --git a/apps/usr_le_code/lib/libusr_le_code.a b/apps/usr_le_code/lib/libusr_le_code.a deleted file mode 100644 index f33797959f0a70c829a8ef4f73d50dff839087f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 257070 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
7jrkzF_F>xfn{V7w@(B#`s*6K>TQX)Pkh zNqU1B=W4prAOxe!^pK6qtX@V}C0$C;ZKS~9Iv2V>?S$s>;W4=Lsw33#Gl{y@Nn?rcmkzh7b=bK#d%9H&WB3S;MdhEZ@F8_sg0UOA0zDVKyClrn=fWlEx4(Glfd_QvvHKC?KtbQ_DgL%>uxc1sXOgD7#lmf_qT=?DlLH^t7~u1a<-y*}ZE@w>a%nFXGhfW5!PR^| zQ;hH$rA7J7EwX3J-;HP0X&GpQPpI2)75d5;xri*L{?rD!hF%231YwG&D>s$ zeF6D{bIT_~qhJ@r&d>!F#TigRt~^Dp0@RpqTfUW41&VFU+)>N~Y6Y##Eqt;Uz)K0; z93yz6u8t!^@D$7WrU(RcCbr9HVi+AjJAszZ7T`Zvh3x?03&xOs3@yNgpjC@2|7!sM ze=>lIeV8ctlO}>+4*xe+fmo$e7}ELg0t`O;UcUK*6!Q@!ODKU=Q)lzbC`CA0fozn?D&df23p# zr?BscFo4_$yc(gUiXwoxrLSl1LE5~It`LQA9`T6JBo?zFYJwO{9qc_NzyL&j5YP04 z$>1)@TzOBJlr~StJ&*T3p$P6Z#WWs{z9pYB@ZV7OzoD$wIR8T^Tj5Jw5tTOmj@AQ} zU^*m&bd_l^=N2Bb#T}AS1|B+78oC%H{Ifc`2nN7-;stpZtCUC+<2wh4)q}s`;7S3m z#FR^Kmho)QNrwbd+RMNt|W0I(_yOTOY zCYD_2e2zsZ=pX)@Z<$I}`5E>~wH**ts+G5e5lD;K`o?z%Zw-2I<8uPbu(tuHDN=j&48!+;Aie6S> zWA(myK$sz@(QckkG_U&Yd;rm@9=jHqn>`vQbp7yOqTX0o0P0OIpxy*q6<)FwPePTU ztPl0bYn>!;%egg_`XF90v%fq9xg*oam;m z>xM?*e#}BPgaRx~P1|EGvkkakdnSSJRArCmM>l=?EbtxR zuZ*n89<7dUy6~*)BzR9<_NY9%>Cz-MYQcFxjYZK#St&Vop+SptA5T)_3sn0TqNdy@ z)i|(XkNG02U(Xt<;2-s3fU1I#2r4QnO3Aegjb5DlL2;kSH+w|Otw zgts}a`#auNJFpUOOZ=6*&CAFJZ#%Guye;7zdE1fOZcwGJaQd>e)oE$D+oy=*M$2~9 zQpL@c(`OJBfZO%Igy9o~7(TLCO9Fpz18>6a`8*EZD1(37_~*v#(F5Ec{0oz1sjA@jG(2#IOs?rW;5nhl3_$wqk<6|o2r>Kv;*C1uEA~9n z5x*>U))UXy5%&;o))CK%dsR>TsE+tsl6hL<{rnCcaXZGBI^yRD2Q6_g`(YjNq|#kF z;xFXth%YsJtS8>3CmwiqfmSUm7>{+tKNq-YiN9_CosPJbFi%H3YfPt>EoS37;_tgg z=p>&#sv~}Z!HL)E{40)|mUxasw1=@q=N>(JwPaQs5dTe2T%srLKcZ8MF+)F~pNsU8 z+YRU>Pwjb0FFB!8%NEnqdg4tw;z|DfI^s9>Sm-4e=p?^mr_vGk5Eko+bM(Z`{?HR2 z(h=_uXwa!8e$PXl&d&*S1Nyez7dqm3!b%BzqZI=%R0kB)!Xy08bi0axs%q*3K;YQ|CapaZz7;AEH^C+Rv8&kiGbe%~(lk_5 zGtI?Nh)eySsP6#$*b{!)MUn-uDH`1t=9d_++CHiPUT~Q_GTk0WA;ci90I%%{e+SOP zWgZ?0-%2G26?}nPfgRfyW+;3^=8EbC)?z{g-{4k4)F(6%5JB(_ZUt> zAB|EuP&G=+ZWo2b7ZSiFV5frnfdUVYgw6#7RR#BG*v;iBIBhZ)$c0&>3quQnTv^)k3itF`NKpK7ihVzydtl6aE{J()VsRFoq*FyU*ebdw17^ zfXY_d-kpGX2%Xok+YYnB4URPIKq3nryG>L>c3~7lc7L114ya@A)XKaI)GKuC4nSu@ zbnI3@XY|VK0M$b0#c&yV_YH^-ofqKIp6~+bD|DWRN5Z#GL0^G^x_66KsYg=7&JSuF zLn9C!lz9MthNx$EV-mY3li2++iCxnqc2_5{TQ`XvScU;RdlnybO{$MSvcilyKlfeYc=+nqzJ0&Fmm%rSTTKuvcyI$~ zln8K^c^KeKQ2}R4;^tinI8%k*6}8@bk9+UG=KYG>^aD}TWwq1)mg)T}b$ZQ`>8~Ge zB4MXmC6DYV-lAGd@3Ktq@)GaLX77F7-c`fUtm#z?Vh3CVa?&N<`z)u|W_s@jq8ivL z?>I@bm3Kl}cdB=48FhMNluv6(6EQVm%hZIdsi%FXMz>9^mN0?_Hz;n3t~D+)+FOyZ zrRM$Uoas@8RbP#4e!Z=tHOS^(!qzX`;IS$^2isenemD5{jlnab3d=ry^5m=cSQmZ@ ze&cR-s8gFOzvorc^?3iLclY33(Hn?vX~<>!g^S~QTNoE+FZ%w{LAf!!C zyT!@UjtlTcKwt*Dsgk_o$f-E);&p!4>iT@Ey*zB555My2jqKz&zn{N)$@!D7U$0Kj zKDBGY>$Tagr*39UgugfIdbf`tYvP)fYgN-;4S|XyQ!Y$>|N7P2b513hC7?K#o-)%j zQfZdd(qsASN$k^jZeeQXiSON3#l0_|n3#I^-nXax+)l?$^J%}a)gsa6>Z#=2Z(Md; zRoP~8!3f56^A6|LZ?j$^8z-N+7kJ}j^n+{CD39f7FaL4n-lLA+&d#hkliEbP5c7{A z`7tMtcW*YiH9N^%-`O|s6@fxprWB^WzOU%Wug?3f+m>7(b@jO%bJ}t{+ifrX1>Vmh zabi?J6ub8OU7O24-BF!Ba(rI$+onb|R6}I~-|i&+Kt|%EKQX>H2fO9Ag27ClPnNP| zB>LZLgBZtodjmNOO1uv_+FCU8t2`K)Dt@mpysAJAtL11^=(;?{YHF;<@~u2! zNf0g4c2TNn3El3-R>yM%-mF^31(s%s{P3!W9@URLDE=PtBGWE`?~QEwjoGFb3Pj5! zzOt=Z%eOtZbasYy&Z@eC=VYFyvh2m>F~)zz=SkQ7P&_|l?S||^zqSASkX-=Y@vi~C z0Es0RAU0+JyjD)e*1RGTtzwH`?wU0kn60gw3=lv5B@_rY_7B$L#^5*IeI%3wTt>tY z_%jIv2ygqJz|9Cl(-V@?^VbCiq-U<%l43KdJ@Ly$a2^eKjZP9^_V3Y){sR!oUkCRoD6qBvv4sIx7+{P{!t(e(02fhXm;CQS zjA%e#NDQB6VWjAQQ`Qt|;aGHY0J>;^(eljG2$u3c17rCszRCX)z>EgU$pB|&%fCa8 z@z>z0pmnK{rT#}iDlkrkYxUjL0NNRhzWR(BRp_a9>eGhCq9LHZfO`19ghHf&9U@&7 zVrG$OMKGYnfGD;-Z*xW#f)oL}HR`^v%grj(N&CNnPK7)Gz)A;6MH&i1Ty4wROacTI z6>i#)y?q;;$=I}=m%kZie70UhLpZ4H0t1CkzM#f@(j#&iUPVYZU6&_c_@BW`O#-em zpf%|{63)^hVLc1j%AoZmi6))bV4&&5#fw@Hu1VdWeBqhxpW>n=v_-EcEfm-!+WM0^ zy4q8dXzEYu+v$=0CX+Ng|E$)N3F})M$eLvU6KF6BhNu66)~3HSiM-)yoh$|zfRiMj zbbeiyA$Xj@rAhQBoi}7a&O<;1G>{(JYR^6c)lPlsuf(-yHx&{&`MA%7OhAe9^Ilfe z3tV^B*U9^N#IO94F~)j-_}G9SdHAsrKQ{T75chjnR0Z3eC5V8QfAC9)i5^zB!`XB2 zp}>#cb1W)h3D@J`x*OcEzn4W7;EDMCyt9&YIO_#x8{n)M&Ti)0z@-E@>jP)o;H(1M zCPG`vD)@-R4+(yB;Rj_kvpTF zQRLO8hBP_`rvSU^fU_(N@2W4Y*Fs=@t!{LEzU;y$WNKjy zAJjk}p0IPG0tf=K5BDL5fxPOa^}VJ9A_9F6nG5X0j0`V;WDr4$1ClkQDiUS!g9Jh! z@gvk^=-iwo!DJfb7UY$X%mrzEhZZNQ#4C$06B$Gm+1Cj+|Mo9^RHdXAZ#=C(M_E64! zt1M0r`xH~Dfqi;xOW666CI1g)x;Zqu0Vf62oA>k#pv-`ABB47}0tHkqPphhIqbRiv z{5_i~e%c0OVoC~v2N13ZJQEJL`BR~@SVSJTw7ocQ0Y?BY1ZppM*7vy(e53%C;FqPk zxum$mcM_jxsyFc|$gtS!xQU_)P9mMzbrp6*2sVe_9a1A=i9{r#$kuxmE<~7C7P*9i zI9Z2s+?t3d8pdfWD#a8PNnZ_~Lx_M7zo*vC_mLal%{?g7eQvjV1l23hbbZ%*=S)kV zFUkWv1mN@*>QOO`)QLn=KWaRNPlZ84WALZJ7{(aI2YV6h#lb|?RY~ecSSn33Wrd_( zbzx_#F{lHP6tYELh13q%G~!WEmdI=SBj-G-GZ>4)wEOjr3sC94K~i5Gzq1A064@qK zGIFfi<8#tj|JoeBm)QJD6;p@?q){$Ie^ojRn7DtlSw8}d&ZpbaU|hafjo zhs!0G)SorpIbaO47eG&xm2Uzv0|Ss5rfHEGAg3mPZezbLJIF5VYAJsUGk(xDr9x7# zT!4TaZ~`tTJIdl;YGLlZg=CfNBr|@wWkD-Ywg!BG_G!DWO?#Q@(~PJ$#Uiiw3s41Wq3mQA zHZ-Wg6aZv{($A4yjDyGA1kr-{I{7|ob_bX_REN6~9}yvdkrwzIsr3ONN|`wU_M~ECW=@N?{@pVhwOKWyzaF-&!U2)(CI@ivdUuOJFVDow4;sL zCML(D``zA)pa*eD{m34qsvf~6k=N-a%?Oex7%4Nfx(2CFYFagmyv{T^|DCEq!Elrr zZ*E1^f@J{7eo6h4Jv$~qwcx*@A4{1z!w%nB_~dJlE-rh^E^KbqBVAON)Qii>q2MFZ z#fhjHowYMgAD?md8iFfrVdN}vd!W-oqolsA0xeFhg>@pYIm0L~A|*^r>68LC5t_S# zL^J{wpt-+Enma-$$fpFF9uxU(^6yzZg-8OfV6!&3*}tcWX+ZBq1A0&N7ohngH=D;L zHg{`n?l#*5TJ7Y_+7vk}AJ7VqTXL9I<- z&?b^sQ-R;R1%7R=v)=Qc^}c`4*sQyj0L<`bq2IgY!8P^42js!9`k)=$pA1Yx0P6~l z6IePS3J}3}octe%ygsD%Y9>EALvOO)9?TR^lbmZl#4b36gUdC^37GM)DWDM!5!UG_ zfE~&EwKiUn0N`J^Jc3y?l+-H|c6FM&}q#x(dn?TFuVtEB!QkWu6Jv>};Jk=NTmhQ#mT zY(O%8vI|Fn3})Mb#$l735~Kl7nozp?CG{;pK#SjFHcM7{jBKpN?|D*E-!^5}S(X~V z2jG|bO)n4ZzkJK|%E&vu?GDiQVbpY)&VI|u%_uC#vCLk^-u^PN2R2Kgcd8aoCz9j(l z-;`Zek5Vs-1rK-RX6jLDE3F^gx2wYp&iSe_#_o%}{^{ldE=8V(Y{h_8mLF*80c$t_ zu}y&$Q*&oWX}xOSu0AtwLIn*$rJbeqBl~uZu)GP;3K($Kjf2TITLJUv@-5|M)1ECj zYdCD2T+w$#b2eGbDgXnvnAKU}cgkYc=>oqq1#`~&_lV~_#7S1noz+PmGy|$g@!C0X zuV~J;+b~Uvw?H$sIR4aT=*dj5V-i|!o|r0t1;Ywpze3ecjN>kX$Z!-S*P!F<$0uVi z;0>3FRKUf$r#;^YVanThzDGccXxdsD3OuXT2geS>0Zfu#MP6>Se(ehRK;*>>beW4f zK?Cby=U4_jrnr-X(9i35<@^4W`#oQGmVFHtK)ZjdYGprP@d_Nkxc^dGFER>y zbW{eEeEi|I=f zU^*fa#z$zG(;Pse-5|(TM%dp=!%iIz3-tYy^d27<_GtBwtEOP+GQVXx+4hvJ@Pff5ol8QS>;R|{5wHnT)5{hxS00w(GUS@oGfcZ}&=Jd= z?m)3tj2_FJ94oL9c@KC`Qbj%;0fNuR!Cz8D1}xxmI_$ERM#6HMQj9P2mGo~qA=ooMmzdtqm!+Ilffq48w{H_1RDVrg`PytV z=zLkStnwS;H_eiNIZPJ+&HYaY)##r>WmKSPu!Pi66gjPCS>d!Ai+YIDYF3u|%8NY? zpq#DToZ&gdZqO}h%$7OV=1gR`i{KCjQ5a|%Lh4#9RO`A9HYA|}ZR-=DVo-D zXdOznR%%+G2}+&*{8s+LJa&&R%GZUc08O ze!`sNMyNfgxU7C`VTD5LwUd2h@jpr{svrhY`_r5ZuaCKI5gNPdalE?N>2m%-5TxvD z5C?oij}s}&t(vcU&*6a?u3lfPkQx6*KrLJKQjjRC9IqgWlG7b{?thi!DI$mKz?7V* z`EHr++65qahAjSWpl%4L?({`|8m9TKk~cgQ2RYoj14rO)i+GHNID~a7MZ6>Urz-gi-jRp|I08f?4KflS zIB8*#<|$Etb64S$HbV<)1kgrsxLHnBV;pHd4N#BZYB>BGD|N{CmOK-+$*WDkEK*#h z00uqVXO5*i>56`)7~f|Dc23GA3`1d;;CCV+VAx~hR`assvF>p+|UkO~{C|G_N_ zNP-}(6EMYQKn;mRv4<0ah&k(vJZ8+ z&w)Q#Wh-Ko3E74AP4d0=7wL84fP0mzsZ=>okET8*h(c=4)JWet;}uX*j(6x{^h~JG z%t_3SvN_l0R&gZ2xC$yXE0)yXkA(`&qN7PykiIv0WKluPqDFo6Z0XVe*2!R@NL zi}a*gGy49<_}5zyxT)9}>h-s0P07B-&9y|lkcuNqy}5sj<05cVly6g1{Y137UVrBF zsTVzbm(a1$Hj(&Jiq{AJkkkU;rsbKX0Thcu0B$NBO8kVraL8lf?T&p!*3qPrRdilr zw%dI-aheY|qa>f<9S|ufwW^2wBvoB&PHrDz@g=#JK(+f2qnHe4<$Ip>5jdYnwrkYCvLWOUsE0*P0;c=Iq$8EN+tY%e0EyS5`ub_GpEVzx&3l{fU%9AVJ^+;&;fD&eBWVyx&q;#AEMew`*f}f|J zG?vs$`U-BR2|4{j<&v0vUA~ zw$I~CiWSVOP}0O@0zR;WZFh4ew(aY8!?qtOa*cTu9>}sYlF^~lSgTR^NKY;*bhk#K zs4+Q({sYKa@ll*wPFDmLKnjf%thTnxkb*oW{JwACHR$sE%*zb6#NAzn45)lvM%K(B9Sn1!Sdez{8n3fX=vQ5SYU36yTnsdUwGMJzfp+?S-?33ts3f^vi5l$5^pS& zQIh+j7b>MmN+9Lxkd35>3?xMs*89A3-wwX`HQ}|~6*tvOa(PRO~c3_Ir zRbySjG8o9=V9PXeAZOt~cAA-@Ryrw*Mx)clqzflND>V+A{;HMneY^6FZ=qJuY7Mlq zIR&&5y~#i;(VI{!To3b?o2T^+Pur2{`!bYUQdRNgWH$%MdGU>awi0Ha8&fpI&8Aq- zkF=f-1OFt}c987TOW3FA&5e?oI^%3^V*9s$1sFCHkdZx5(#$r2*|S_) zjh;VS_I%^H6^-X$V4O4Z6M=(8Lo5NKdQZamyKd0sjJW&J+mR3creDX0LZ7d|dVup@ zUrAyU`Qf0au^$`)+;y0}UCxC-QER9hq|u8S$kI%xAN}kdD-8#&4jw+GH`Kw>GoB(E zYj-u4ecqY+cbEqrR^K$w937r{vjjh3>i(R&$+)v|!!%Q|!|83^1h2_G$(HD0NI7%` z>|V8l;^5_P{R1vBSiM&_D8X|RGW>?Av+lV0eFNj$$Tocq$Je$D$9Gg{ny+)gt!4*! zCS)1O0l445G><4jHt9=2og~`bbUL`Z3>QZ~@J9Ty2%z%h@I_D28@yJLO;Nkewn&?T z3>#?wa?mNzsRx#@c518O^vA5a-=&vU;^19i{Sc+?FqGC;0@gdRfP#z0k|DqEN@jrx zaZZ&kr~0V|VbO~2Q`L#NDcjDAITkH?yFcFlTKD^ z&If`9R#9#AF=#Q)W4qoe;^;d;rH&mud-zdTGsV6U)N-)m=Y+{F)|=hlX!0jI6RZ(u zFk&C7U<9HoJwDL6*yu{p2jEObtP|D-r5+B770ymr3sqX5Qli8;tH8z;^@t>OR>a`s zQ-WY9xbds?Q=8kh8m3l-U*n#29gOv|*bOqMQ(NG6a|XqVkQ&ppE6Cb;{ZX^xu!tir z!x|;}Rgi5Blvtov;%M3Dab|;QRRj4|;2C0IyeE(HkaO-;n`tPZDRfb@uG+*wXX0u` z>d>?~I5Xoc?$?PomWWF?$j|3>GB)(>*#M(F;~L`wdJ4e-91Ar@L0Z*#oQX08jin35 zfQ&h4tRay-6dBxWpuXO{r24WPGjGq%a}Qp9ICwR1%(Vz8_7qdmKX$<2Xs18prM7dq z9asURTEAoz3ZOsDF$MFA7c6&D*O6ZK7U*%XCQ#|ADtRC3CKt6__561$WoO@-C+F*d z6MHS_gJcyP`A5^dFNE`NuAQemJP&yCDW$8>6{_arY+)S;97Gq+8*;wy@A9C_`CgYZ zbYCX!@}$C}IdF>ZngAN(lB4p%E;hE52HE))@{@Im=YEv0=+i5IsbEjY zULDVePXOK=G2Mo%Bb5IQggZN%V4Wl^Lvdlq6R;u)SSq#qxR4V2yB1h}7k)gij^+4? zBKWpp$yrN&%JmCuUW8`peWI%&FE1N$O!{xfv-`@#(D*Fb*oOM@jR3I_DHpMR&Ay~C zN6Oz}W7>SAtSnc67{6oVAs6b4Hv%f6QRZ5toNtMZm$+f$L~NX2iH-d&pmBy*$?h8g zgJk0dmLTVnoQsWjkd4!@F=rn(&c?<=zDT(>1}UFJ%462pc$jRw3LDGG#@k8Cvr4LO z1U!l11*~K!u`$P9(wAo!=MG{BH_+WrWEWS7vrd{GCmRP-Ye9@y6>RU-|7Gw9A{oAF zaj7xa{A4kQ%!fF^cQPMhE@citegs}yE!w;$b9+(tmNofXi`L|2`v=2ohw!o?f%slY zDaPIhw&xzA(m?Ox?4GdVbKYP7gw`A-|BJrX)<}YDL)Bqp2 z^!E2Sw^`=H3n{jopHyw=mpGB+fuhYarBn@Zf%(jPJ|)Y2xW(%r9W}GA@@(pfy^V)1daOCNB4#Whdr83MHaEqQfVuVSmszNm)ORM$ zewVfSXj0MJaQm55ukfXp@%97laJytiUFAPOJjH7l=|yn6Dlxk-^z$~id>HLFbCy|1 zm(~v~gxkA8+IMaW6nz(3Sg)gjG?2-scR=#O05lMK+WNA9$^%wlAn9(3+UW-?8yYVl zSS008dQ;LUJr-V3dW|JNI3;_@G_V@TZxtVz@69c=gV8zfSu&$NvcA>q6g&(a;=i*c z>Ybt`?`+L{XB8c?YnDkIB@yGr66{UlToJ_R>@A|{3osxkXl9tNqD(6!bUT#ui(1tA zszS6kZN93v(h;`8n*GE>ZZ2Ny6s7|$UB0T&jj7)sW4@|qrQzkR*MPt+A|CTqs!GT{ zc0w8yo(0s49u9y7#5hA# zn+O@$`RRyGV`nkOB?bnT{bI9&l7Q0?B~V}Mo4#VkrB z`gE-U28&gUV2CAKF4>;d>?u!P7-G!t;;tRyhIe6_=QQjlw8^jN(^3QZ8@t%ED43lQ zYO^yae;(m#o)4y&_8MQk;m9B`G1NUgt>4Y}%`H?WXW^+Z$J~4v&GZQ zFhAKzdkeIAE5=N(&Cb#j-XL? z16pkwH#65utT#7V=MGt~$7_jp1LU+K?e1YS<3qaX9M)C}hHz&)A@_B{lwk#S^JvNE zI`?+f-lI#M!>Rz5xY`cH(=dlM!SjW%byq>@^V-Q0IiZ+2nCW+`WadZ-q+?jSro%O@ zMNroaHXafWF*0T=z*zfiLOMM~hla!eW#YEx7$nBysZ;WYthZ8ZHbvUNC+sUfiT)4< zG{fp53>s12c1Ub;8uA(AueBcfcj=uB2j9ikchDoZ8Op28IBjf=ULixh$zf~!XAgK$ zigYOgXmw`|60fPZ(DLN!rJegutM;8~H96ghgOPQSAhSW_mYHuiTj43{jAWVdVF(wV zH!C!jWeXaiS0VLNUeg-A+1I(19VzJVhCmntYm!fS8y(bs2bFG^j!)THW7F7fvt44d z9fqkR$xfvorkOnqNGQ!=b&|t$Nda4>6Q;&EOnys;;AuncveA7XajHHZG{H}hDA%hS z-H*Hx!M!eLbwZi`55W;#j_#!2Qhvx~5XMfJeg=?pc;j5Z!GG9ld`gD$Qs94`yxJ_! z`BJZu%#Yy$6Lz(Ky1;#AJY|UwND)@bIHUD&g-YtWirDqldve9F*KH5HMQ)48+)sO8 zjvSq}zy$sC*nayl@N7&>vrZ1V#K_>qvvdyaUJbXn<{uziDg6JGLu)C217NFY_5d6=$q zv!i8Sq?z5wt@>OHsgdDLegQ32VE@i%6VX>u`n+!n|6YlDjp2j>Wbb3o(cZ(P<-yzw`J#&F^hY@B$4cg$Xf zi6_E|N4$AaKk+6w`>qQxQ`JyXUSgc>wf??&kT+d{_2x)nxxuMMvE zn3cRF2D2;(-AuqUK_Sfux_2-2V|*?}yb8h7)-M#82kGoVM346m{9>6O`6WbV?{VRq z@Jqm*@+iWr-k_LMLdf)a(x_-Zk^6ZNi@8BFJlf@v6K^tDL8jJT=M%wV9{YPR9_1B# zDOa6og~t}`VT$uzS7+5#2h6@G&F!RId=#UO9}X~v&_Ucs2NC@Pl=1}!VU7;s!=+_h zZJ4^)Eicl6gkH&oR_evF%Xt9wKlo-aYZ_+faIx%l?k^4hq9?r1J-iRIDNX^bX}X6+ z+g>p1Hn!cwiKUx@d;ey8X#q9I&eaPlMct&6NM2xHGC68n3{{DJGcm=q6JhA(-+I zkXelHIAfO%0sJg9Rtc5R_#ifxTXuZ{`4KYLkVNO5$nGSw7;`P8{fD}yVPi3wYakO7 zD(f!DHQd3*!c}w+*bC!-89aiBfWObmDd5(b-2YDLWemV~!<@nk+3K9amcq5Ve8Mi# z5uQ?;PssNam5#YhJ5k9IKSzZqO=!D!g_lPmozLBNd%lUVG2?In^=MF@g z+(8Yys1Z}XbTq)9hn8sEO!}Aw%<1_-$LMK-bZ{So%tduX06v~-XXn5TMlSvt!~o|$ z9%nTs2w-0I&bK;-DK1TJfCu9og;^sv!`#3WNTzV<31n`70ez*XSPU%w<4@+d5ds&Z zNP#;Fs?@`}gE4SBxHj3q8z<>*ot7a7FBl(zw|Z||cGVR&C8dtWkemMZS`xCUv9ric zf9{dze7FHS;CD&EyfeLtAB||QaFGkU{EgDjw1&PVlmO{liOl6L7SX&Am--_{v_g;FZc)m{`xDJo$3ZgcE7Rbhm z6x&`)kt9wGvqao9>kwfw1;ykC!P_{gd?h^35EgS_r(I({hhBr-4T!#g-Q_^PT!oeJ z=+CSq0XtbEw3-2(JZAxI<{ktM-V#Nk0QXabC_p9(U_Sc<-6{|ocL~?vyD@ZYd}E|b z_#A9Z=nzU=6~@D9GZ`KCEB3Qb!nWQc4rsJ4zMdeqn?XTX#Qi@v))6Mw$R@{X%6s&| zrIr{hToN!wvw7-Tni5_t7=zL89PFQC`u2(0Z9@kU~XRvkL!*+UMVT0 z0)8sIh7M~&BlwDVRp3g7-+28@p*=l<^qLtwct;()0hhk;DSz=~@gFo#0lcY3*ZYfR zpry&pVw#qC{*mGPYJc??MeqyQJaiiQMY-#?X#4{lBEO{}HISuC$PQQt@0^K_1lYY3 z2HxO@|KuP3U)65)YIZ40j@_Nr+9WreZvyS zDMC_k({%6#CsMsm_o{P>YrvVnJ8G2+Jt3!9cd@tO^%XJsiP^Oa2in|nAu0F{+&;3T zeqd?PyR4$4Ne?PtCtsnP0MOKuWj+UF>zA2++`H&^xVqzLQs#QoU@LxY!1P+LS;s3m zL_Ro{Zgebti`9;0H8_^6SJaMWEMX|ctLUH?Iu_~jsIq{ghizQZv7D`)anRVgi*zhQ zbxWF(##phWWBI_tJiB&*+OZTLN%i3_M#q9Ieu}DBT-!&W?^*@dYj99iaXqAF>D>D3bDNfi9=#sg zzBKIA^{^k@2zZ%KWJ8fJsi&V|p~nWt)d`NvkJ<@u0e)dYg`aR7e}yBc7F2JQ5+!td z-uEmD-8e){UgD6K6r6gdWgo{)`1bjuV7C{KmMo)gvrvHQ`H_SXzM3~3-qE8?g)IAw zfJl~+T|KX8PC(@N4)BcQee|Ij2aKa$o5rORi{QPFza??RVQ|agfJh=PyM#D+kxt>) z8F#bXV0YkaWUR>i!)BMhvqH4y_SMc%fT%zG_Hjm zkZW--_~Q`I1&74;C9oNdn+~r;9Fka+z_#O0&Ovw)7x~Ji_z-{0RlUb2BKc`m$CV_0 zrSo_2s>W_5W~3ng7kl2V6Cd*c>N)Em9R+z!0`r=DlQ*FQF5fkg_~9QxTYR@;#roC8 zAx-Y^ZDm3Z>;-HeP+%`mv2t$??8SJ9P1$(_UN3M6g*~Y12*1%&L9HXur6;<~q*!x! zz@>PddG7y1-IvEjS+)Jo<}icoC?cQ^D4>W6xB#xhqN1Rok>NgrKxi(2sAy&bg9c(|kgUDp7au zuffANQP`koUK~M`>Dy-!M9jwkEi|p1RSqL%N$zD2n$_wRZAFH>v3;8J8|76}{b~_b z?xlFw{^MN#SgDr2ZB)KhKP=kgQuUa+>c9wKF1C@>ZA5*dJp-lfw{CIz*l3rKl@(cC zHD2OTHZ@AoTZCgsXTY#hCaO|vSLLkw$g$lH^I|I2eoBj}$=#2eu^|E=6N>q6>I%sS zEB0gIgGT;--ThA>gooDAeP`S@UE_)dZ-|zpEdv$y?5{i95QS$wu>S={ z1hkyb!y~%$JT^9L4;O0*CkiP*xn_M&k9%iS7Q_mNhk4=otUC+bnl>*2M12 ze-`%IYc2epb>CUGQFlWgl1RqCtz}CfPd8xa8YJ=3=onZtM6yav{|R0Hg&}RWRxpDs z8J)-=yJhG8_p5ppY>SZ6bGbsvYYT-e+t`t ztwnj6?ra*hD-IIb&a%h-wTyxjzj!V@yg0M?W)H-Z@fUR+aF;F;wY@?YY1l>la@30? z-J@~0!}=d0Q%5BW-yF8!kgWKg8_{F5?RR9#+D?j{&pDXoU|5JD$Ar(hJ1&}b(@6IV z>am7w9)*#(`{INnEGBRR^RfC-BRAG4jB8X(jkfTOR1^`hCE<9iPWuW|&~^k(gE2{G ziw&1E-X7)_d9Qto!(HDjbMh{04Rmgqxo68!i5n%8nbp^)PZK5 zc+#F4NqVA@EOxD!EP?VidQH4dh>M!O!ga-5(woOaS z60p^hyUe1S^!|_Frj=}WzW3Gy$(J|r5PjZzi{>qAOCIUx&-4-hv9fr1-GjD$adoP# z-7>Jv7-E})jLzQfYJGb!pUtzDDUBt>{Kh?hmKiEVxf-KK6X$lBa*2$Ah4@Eaft-G-;{^oS%PNGJ8$yqeA}HU(s3nud;L&9Y-D zI^uRl+lS13O=;86J5A6#Cy?G5uUM9Ps;(`C6qtCM01OC0%qDPyA{%l#5zMdEpN*Zo z;?=?>N?gt`Jv1BWizAqRN0N34BzN)aEe0;$JU(E#Pit(GvUV!R3Wmnv# z0H~h8d-FGPIQ9@!PxzyiFGKaz6rIfbc;9e4jU2NHAe}pqfmK713GX#}ao3r5efDRC zkXosm%y}bW+Lm-_m;!*u+nnC0hYYw$UT}^m%+O9av8)lX)syHkEXJ6}hWR zCY&VwslbU4*`#p|P??(P+d9H}5zbJVIJi4O8Hi@12a#R1aYB3g5Z8{bnY^G;mY{-G z)i|MJptyba!CH}BXxLgplJj3U>Jp!0q(&)5K)rU6L0VC}CSnyY&bO|Hd?G{eL5>FE zyiNRS7&e;WZqr6zg1hkr7<%1{8%UQsN}O)-#tIkXD=Xe-uImRqYbboOn#DpTGrsoP z_w4r{+3)VX*zbsyg1d;NOq>pwVhz4JojHYA(XYsM$eY<7*dH#}Ni6(#((4r9*E|JM zJnX|N=6B>G#>bw@@NMWvvBmLiGl~69C%Vln-g&q#}HR8W^%r4LL=mMR74^;} ziRd=%!+fY+dKnIKuh9QnQ7u|sFqgi~E7DEO#qa4K<`(I4ipGu+^;ez+Mi$%Ule$-L zJB2t+TkKm7-zJjHvp1}Q`VQbHVmsm|YnZ7sL{XJ`DjlXyw530-3MExQjjpokjIRKb z!A|Dgew=In@e#~g_A!iQ<4oPU^_8gAL>U~Pd5HfR{ta0(i@uw(FYwfOd34tZB(Phm zLGfkVP{Pq-IpZp~3D}FEh+AgLjD@@{ZT<}HT)voF>dZ{DNsX_iI2|<>`y^-1M2);Y zGtw9INy-cfo1&dRhd%U9OPV)tw$%zZKPAKZ!CWnlx7We;IU{BM{FLMw3o zu-P>fovgmv4CV$aTmBS4-&wojq8;pEl?&Ub{Ioq9Y&zNPvtww0Xq<+LXpp%p?%^6B z=K~3TuT4NS&eQlJ5paqv zV1Gk`Uj;GG^Z?hZWCZNJu^=Cpi#DCCuHHUzxGpwjd(!E0pOzgVyLTfN=mCs?i7)~# z-4%DN9HV~*BOpeXObKueUyV>AjJ~a)vfL+eXUK`YXn$+`=7$|U0qhqBtdBp1{`+hk z^?nLsnJ~R`d)h}0KDr?elb;^2e%i}m-Muy{PrLxuz3pUmld*l&kncB)oukrp8#Aq2 z$^d7H<~hXth<2uTUw>irdP3=vu#Y1**PUOjAe3%8(cxt2{_&|Sr)})6c)#!3VD|u} zr|*G<@n0ND*FE2R!>Gj^N*8a*Dv7J@$ypd1&bCd-oW>afL_1*!xHp;^0wiI(#&0ft zaTPNJm}YJ~8GV>H1jLpn`29>n*ae7QbP;K&>_+tXw&^eph%nCwJ7CNBH2^XZme4+x z<2UOja-C%NC^{)SH%%te4QItp%Iw^61q>FGeO879f3iVB28&B?hHV)2*0SpQwd%EV`p5Uj@eFTHTRLURh8__&IHCI$o zD-#gNG>9GXt|GmOw6Mtw;h*FEGb5gBqM&P&!%&g}W}|0a zQ74_n0e0H;0Z_y+W7KnK=$MHYl8GOu*faq3hsBe~sn`Lx+9j0nhCFR3x;I{}%z(ag!4tcMRKbfv^We`uk3^L!g9VBzcy8{<+1_QaYS#;q*w0CE*R$7Hzl|Dq)LgY5|M2o*Y_t zj#joq2vQsbrt@@VoOr|*87bwzok=Gl*8xJEr7f6VFDBhKV)`r{g@b$slwjy%&xcT$ z4Km^mesm)bqnNT`j<)*#d!4IhRU83}vOW|{Ayp)iO#Wp@BOeoC_Gw?2&^0#evKfpd zJp?GQ>X28uq{rUHvzVh1c)^;wlUgu^_(wnG4ozd^=~D<9x`dg#qGemon$ z;$P3R@zbcYUl*XWHSXKa5LNacE=0rJ}_mGLQs+wMAze zuq{|Mdv;%cgJgdlKy`(;6F^OnD7E98y`gN(F2C!mLuM3aJ7|FX2lSXIrky`Xjt{jV z-KV0*TZ!MtnhKbyAZek^XR>-vSuifGDz{szEK}Rzp*i&(4@bOIv4j>)JK?v#J+uk$ z?!?;IY;9o=jfd`%+K}l;UIUa$s;w4n6}3Gh!JSkWSBeKh8yrs-xa#7?&6q?WO=8W& z?_+f@%p=Gc`9-~ID z*r4=nSToaJ#U|WDk`(jy`RYsER^3GTuD0lE@CWB%M+=koSK*gyUmco?t_8ooC}#| zS+r&Een^Y4w)bbv+2EUe{eBE)EjqcOd_8$=v#0;`W6rAr@|?UUrF( z>fM|t21{v?e}Ts)5^ZqQ@mEj01rdtBw}HdTmLfdK}! z4f4e5&qPjG{l(uR4|G`c;JlI9#mB+B=6%W1)K%n)^PYF17vPLxW{R@T}4692}(G33oJKD7qz-|WQ_LjO#sNRz`yD=Bk# zczeCZX!EKI#=DR4u&&F}-nbuwsGX`SSXYWgWU2U9_Dr_y=4r{jmmc{XyU8X5L);y6 zz>*LB-SF%qp}%8WzDA@Od9_qLw?o~u~MB8T4e)RPf= zUm&I&^U)sPhP>C=ySMyJx$DVBkdc|Geooh>kXY^dgHo~(@5@;=w&tPD2WsCe#P@z$ zTcrClsyaIV;Yg5NeRUUR5`5i`4u{v+Wj1d2 z6#U09;9ne$a41>r2{EXHIF89{xRv8A6+F!$Oq{Y&i6sjD27%hV zVvytaGx4NLE7zjAn$Sp$R520M-1>AH2S-2$29 z%#G-su2G$&&Rmm`T9hlMCK;+Iu1UAir@1CM8ormRZd{YoVWYVwUNw9#1yy`6-}U2~ zI4j0-O$OI}%?~7Az|BP+O-&-6BMSrBG56qqW?zs;%iH6<#io!lcgC#w^X5*clI!^1 zocFY4zQ3wIoVQDs`2(4k0^?1EU2^=cxSHOuOCrc6Z4;m`z~ug(z{apkehVZIXK&t5 zO=u4n`o5`qlk$CEy|{DY@IpA`7s0_Evm@lUgD3)n-VD>wTiUL;_&4lWmd=TrFl(BM zPA30?@EpLgJxdlK3n+b8oTd+;_@@be#tt?;S4{ogzYrfWdId(8Obc+m)KibFA+&!5 z{R7EgI1CT`Lm>HWop|I=&^>Hp!R-^3tF&8U-Y><+fQ<#eVL08ElhqGvFxw5!Z!VpV z`PA-;JJSbu_d|l;jTbRo*61D^x#8}-7?>pLizRaR#0&5+LI*@Y%E~|qr zp&lgu&c%DeaM(tzp1>{7kQt zr9Tj6&L}7x-4gGY?T_a+JLrmX+qF^Kt`}@u=&XbL9dH*$VN|L?1s)e*RU|k?T+fk# z#tM7v@2T-MT>D73k%O}I^JHe4y)=Ha;^QUCk7Z|X`JQ#*5Hq?!8WY;+rU$r|EbLRV zNoV_c-ZX;E;cOVJ0@|dHP4P+i-FJg){H;y#V@Zr;Hrw@0vFj z-t|spaZW?W!FgU1645*%CAaR2ld_U`0ft`$82%YxxD&we!0ABC4QQ|o4N6YQO7xqQ zksbQD<~=tJ4Gse>FF_A~0C@k726xb4&}L=T$`A~Z;-|QL5)F#cpuf~&OHR|0PyNUc zmfuvzvm(jC)@znOn-*|Kr8DLu-K zEWqN0tB|B(-?0NpmijUGISa6O;Tr*vC1L`L-_C4EkoZD??Rf<`Lk%^aO#_*O$$9fE7HNVDfy}M1cW`ggB_+v)sf{kXQ9Rf1Ew^TSo=laU9v4C zz@onj-vxH=L~L{TC+(6#>_TM{g0QngFGJ^xcgK~L3)>78f4i$Yiu)dr40x~8gVL9XI{%ym3k+DxJ=dXBq@|DtZud$gcChVj-OLRfO|z(mPx|2zU=B^~5@ zF}+6nCEKa8$eBTU6fJ6|1c%xqu zsYlJU3(;FcxW6Sg$NsEl<=fe$9+l8GCRgweTN1cHtfXJOn8GCvm!=x7t#@s*D>nCg zu9#quw?FHbSyRw&v#{TDY-Df$R2tdbf{omkT+%G0FMhra0-0AE=8@g1hZD?OD z>Cjv3)}5&=ZvZax+Tn}=_XvbJaW33COq@Vi_Lrl4b#)E&2#m+SN31zn8jLS}kEpz< zw2rzV18M*hbCk4ub%%Rw0@zAb;?dQwCVNd)k9OF+w>BbcB(=i`vg6brlujk(H&9lo zbBm5v#6+~CZ@ zoGaG90t5~}%tUvr^W}p}l*T$0F|p=p9`}fEvZr0=6WPF0eVV6z%@bVyCH)c$!3DkX zH4)s+YixAd^JaoG&o9VvW7+Ea)QFapHp3EZ22GLZtzgx{ukQG24BK6|g%ryJhAvus zQscoBvfA-1UGg*6FWyqW`9#R(e%0K_Kl^-!A6N1%7^znnx}xpn4mvrwIR`GamTl&`HVL$3|N}eU-Pbp3U~b4BHBV^-8Le~ zu-vm_-iyR%t{eoSSJCFGe5GBO<>|LH(&jocy@@tgJVqG&L#c~;drL4bx%yodiWOis zimQ98lt#?}QqCVt^CSbUk-4-UTxkd6*pY5X&&azyoGh<7UPO1lo>XrUy=~tWOsM+y zWh6tGnGh0l8e1q=<@TIwvP2yb&x0XxY_<%3JuIUQ)YvbQ;8z+(rZp?}EylZ+m#dZT zaI}wFWi}k$eRn?^+njY`c^gPHAv!g5%c2m`Uz;?g7S{GB>(#qI>ouEy62-ncTIbji zws`J~clA^@7PEI!=wNW1PK$)TTZB($mf zf_tQ9m=x^ulDB67_S-OxMRrfYyWhjF6BNr`pp~}oaxt;P03KG2^PwodVRiD=bn6#+ z&S2&pzHBJzoC+Gszz?PVu*DlId+(1pTjZ%o2+^Op9nu4~%3syRi`#h@r#wJ5MGxsR;SMG8A3B&BWnlM(^#(_BZef>!b4HvGvi=nzy%{Y#JR#FrSZ9q+t z8gW6F{?*-|ctgm5GV^v#EhRv1bEko}tC)Qva^?Tlx)e~l)?fv8ix~376*3A zx`-+sb)guJ{b^Mq(L*Wux+gJQ+z#K$;-k-wQ!gJMu>6DgHUP=@#&fQ2;N<1r95x-< zZ43urgU2QC@HPG%2VXa)P!rjZwIZi)$aBtNR}?*H9lIu{Jc3hLkUlPB-&Tm7{NYfoK7)D^3c0^4;mrIU6S}W=w&S9EoMLas`*m~A z`N*Ln@DmxPd;%a!h%1W#tVjYvH{!GsIfiVqxaaMY%h(-$aQE6~n5c zi73q4K@78kHKZI-{kgSLWwK(O_wz5gx7+6uKc8NKauXPWh1G$O@DzrG5n7;Nj^Ahm zqracv7kNP&uN$o{Ny00@EtRMBQ=|dh=Ji1_jei+{a_j4Xa(lPb$Vz6cS4w(UoRF35 zU9ZfY{jf%s(_snR!fl6!L=@WxZB`j75n(;RC?KS%im=0q$<4Sd+fa%j)h#cdw0Zh&=B4Zjft^ywh`Qs^X%*xl@fV zt4ADnEeUe1wC(gg0 zYFcVs(x;IA-oriS9yH6WKPhl3+E(s11Q>d&EB9gg5Uv79sm6F3C{~p$h%gs-NTtCZ zMlm#L_NVrCkKQf2v~mw-XeS>|`H+-~9AtCd*>rS->o<=`*^fCFOQ*h3N}n%Ha@24OMRmAV2nS8Yb~>kWf*D|7{$OyH`oP-Vdi zVPC2tKYJHjlU=WGXMVMHOefFz9=(z0O)m8#%-2mC5meXavPv)2LlAYc+mJ~51&jJ^ zX4kD;$2@l4W`D0t-;EHu#;+E9|3Rg0GX$O11aW-L2ET~jG*V%;6%ixZqtU6{%U4V- zrfcI}cF<~-^H2Da##9XeWdl*Gao6kvUaqv8T?II=2(s(URVr|Y{wA@LT4T}e4>d1e zAJ2fNbvC$*cjNDO-*EY~bfatd?#=Si{#i+b>|Zvzh6l$|?D0$P-4O+rkd+;>K^y{Y zhJvXZJFvtA3`?77FJ8zH;OCBtx4;-LGY4o#`T8Ni7|Y3s0CN3Rc4_&ZMJ2c1>)hKY zTK!YM>WIX`qk$12a2RXqs(~~#iRd}KuuGraxFF3PMNy$=uz=?^^01m{Hc|yYQkr`L zt0enQCp%UR({gPIuARwU>+Yt=6?-*}9Muh4frlJXdtK!=h+3_a|L*(^`Z@A4&E4GN zEb0+9c2_8ZbrjeE#bDivh&Fh@5qok}?hkU8oE5*PtAte@GRsJYiDqV) znC)IKrr1LpaskbDM@f)1?%YkatQ-~yTJw~Tq3jLrrv79%6~&h6AV#4FGRHlJ!p6Cq zx^;ZK$NV6-*|@1fcf%Se$@bC@t#RUC=aYG%(vL;AH}Ev0K4QrA)m0k^vCnm1YK=iR za5o-0j@|feT!|ZxW;cGsUOdSFm8fw=aUJNVY(9JuAjg;VXxWi-`fbqwnaR-dN`(!kVH5Vz#u&3 zS#_h;_6zYo+Do2PM|Der+eq1C<5nuKW>h>Lzn5G|=>RoA`70yNP(Yy;D{qR(ua1UW zqgESFDJxPSnJFYm|AmG&Mt54XN}w}hCRH*$$gpvRI{T!5|GIuAmgCVeFtoYiM(5pp5>{i{60AKwZWsWz=A$^bQom-z`Zhfk^e}Zmo2x1a= zA7uAF4aTv^)bA+r#AbH`F{K=OA@94ReirtnJ{hdFW%)zZ^1g-V)ll>EhuEQJ@Cr}K z$1GmLQ98&-ymK*@Dalcvx>1y*e;s=W3RojE_!6Vida{Iu4M*ppZ%5kU%wp-DfJ#xJ zviVvvRCbZOQW=uC_Slt6-8B`mI!_{xTu1$fwDc^E$No|sUECO0O-oGz%d*r|ZGc8K z{z88GEi6%Xse69JF!Z^W>$4OeCfuTQ0L4(dCeu6cHaZcmS}FE?z#}wxMqgkt29Ekc zEqMBspW?PKg=T8(^$PCmo>u+6<-YEC8b}&c3}1qoIFi|$?rRadFHfgMPIhp2o?a>5 zXpthv=H(LuYH%Zq#B&NWR;(l~sxy(xZES ziOn4Jaz720q+VMmR}sWzqG!uWRwjPox~rJ36#t0N~P6UhT>V@pv%uH^ENw zkF08_>Rg)JX#>@ZS(SrIXi(Bfgv*x{)8eOh_=6Uoo)#^Bv8E7m4_jshLFe7>K?k8- zML&;Us*~`GajRmA+Wrickj-_IB4x!{AEl27Qmu45q1RAZm5FvX(zZS{MrI@sK zb!C$^hM_(w2n9uIU63DD-Ai6TSs$b1xf+#2$lbQvTWnu7QNi5a^3hgVcY8N!nEL7Y z5AYx94u0<4m`3ms5n6PZyq2tv?Z_+NNWXCGKPSAVrpuu!D(-U4JqyOKonkM|3>2zW zWY~2^%m4x%XuCyv=@na$mNpx~KgR`{pDwP+2VQwaf}Jff7VeqWRQGAZGc|7mmFwrl zdfFf&Pkv*lTtCgHZ+Pygi+dR9K!yQ+i-g=+9gZ!Msx(@@S+_b&uxb8zfU+CaD?&Ug zX9w)vU3)#`?V0=thPa=7$T*#4mrE)lH_emL9ip{WqGI0~`M`nVC{NLxTzXn)vyO7E zy8tHu>!Sv#*2(Z?5ds4bqufP=Vc=x>^NNI*PU&pLSE(E*;C@t~Z0X*V;^iVB_=Bp` zum^Za&Rq!E7i9w6&!)yfj^ycXAs>3wFxmTiQxpYc8}M;%`l2YpH}?>H=y@BEG_y?s zLqfb;1n?mJX;mVTy_j(SD2Ds-7Pw!^{LC-nJ@YfWRnw6pqJ#`5DOVE%XLYBgWKZWS zZnCPI_=+-O(q_JtHhbP8iW7(P*cV6~4$dlj{Nf&cigLO%%wP$wdj`i@QKjtt%+&ADJy3m2HY@mNV*V(v(Wh(FplI^~QD zSC=u}7dC`VF#yp6ygtc`{j@Ze3N_#y5de0$O?kM=WkBy^a25AO8PLcgM+P*K#5*Q; zj!{MB+igxNkh~~a<%fKOV!*>yfQKqz&#icKpabAxKLi)2BMg}kXjfNSPqTTj@3`|s zS79u9MMtazI%K2c96@czxI$9tRSpY?!;ows4)fqhXKQW8VAmP+TfTHEesm-eMk3>{ zLR-odc9Ijq^Ts(EL6_8C#0T^8evMTtD-mPmJ?B|L)x$f;<*(YA7iVw@jeKMvJE+Bp%|p58eqNCnKq4p4Ao zj^|nq?vmw{*1xwiT3m{cp(Q_**N@VBf^x7fcU-jUvE2IiDWNKn>F!wn`2566khZSd zdWv6=VGgx2RIt*#K1!oOgjzuj)y_`C#lfyrtwJ6hM0q>Gu0=h?Z4hJY?i83I{~k=2 zv%(vXvW@VEk$83#ZxxWmM*oYv&}_NBC)hV|e6ct%Yp_d8zNxD&JsA~(L?V<* zp*SAcAzz?;nO?+JxMIiBnMJgD@uyXZMAz}Zt1OD~4y3n;@X!JH z1$G*x;$kqaV&@j2=mo`uj2bNGVpMJ%H^ABlCegKHyVdR{GPxzv?Im)Dw)V^c&oiwM zkC@V93^F%bMziRSW!={=u$$6@i{e1x*IVwT1f|-ztb{Y7ZIfwADNDpj)Vwlr`>Yj7ZE_Pl%v-Yo9lJdlk{6^1f^z> zd>lnT#4t0?U^xqgsBs~=Jy>4Xi!!<|MZ=TU$o#k%sx-va8|D^NR{p{Y2f&#Sg`sTl8x8(&%To)D;@E>sCv)9wtyYt{~k_You34nOTQjTW`(L^<)imn+>q zDIYre_HOzWsM5k*2i-cV%PsV^r43UZ?>8{3+f|ImtuhNSsKAvzzS3SBMSYcYP*e11 zxxj&tJ6j?Z(Hc&4F#0=?_%9n~PI>Mw*_VZlUR7 zyID#_Y6yBDn${uGoM`&Q7&A?uP;p^v+Kyzw-*~dL7w;;lNr9^Xfo$s!k{bX<6+T{tkiQ6i`)7$-Y>Xg?Rbvj$Ckt; zhww*}1?)f?SF)Mnp>33$;LYE=WU#XrgS91}4b@?4k$xOsU)-ehm#KII*zhWOiFK}KjTmusmAA|%N ztsL$Pegj>+CtnTZQiyI6Azc=Pl-QOb-FqdB-w@&^e)AT`Z;aqKvZ-=p)V{~~jpBfv z$wWpE&M*0`Q|?dWu~?tl`MoGvqfkRRs7(=Bb%{G}JL#!D;LLq=_7CygezYXBMUBYB zP#m&5@S>#Fm+{WU9jV&z+jE?x#bt;{%k87TP5HOrom>RY(vIm=-lkh3@#=dcBJv_S zpttOfasQGfrW5lqof4s@0Pnp#(t8pypYGg=RueEbF`qQ{C`Gv)iJ7Iz9RkG@Tp1&+ zvonc`bYBc8tsK*gYGKZA<@7MQs3VRS699L?F44Ii)4PAErlcoHKfMxVxfm}sbW~ew zIJYckcFmrRB&;m)WBhZ{=rP5hFH8l#riGE@jMcs+Mx`&~g%zRA1xCy*u5b6{bgUSK zkg-Z&#i=S=LZ93nmyL`tRI+)`ls_LRcJm_9F}kk~@R@&9Zjs5j@Hu z3WHkrR!-4qUBPmeU(z2A2wE=A+dd>u=Wv!d_!kYMd``$u_iCz%!b~k)0>{s#C5gNd)`RWKFt~$aIQPsc%4g3y1c$2rT?QSoojN-mE-(5aZeUfsAL1%sl&Tu|R*@ z&GGCqj%S-Sqdgss0{v}Jqs>jmv!!O9J%{7jag1lr;dpi}x#6nk$P0V(JUdmuv%kyV z+2wu-@$4%?o;{xN?7ke&{)<)dxMH`@%Cq|t&(6q`x%e{8C|k(0%ZX>>DKVby!+3VK zypx4z;}f3;#IxMTCLl$Zz7{AN!xHtWr?IwNJn;Z0hks z4Cx{-TjXFAr{Br>ZnW-39xD}qd?@g#@*Q!L zC6M~q^d{cEc;Vu8yLxcN?3GN;-t?BuDwGqP z%})zDOvMN>gUD!UGjlajaN63_%-kR0_a(^@6UHE0y5S2I6kMrdJ_+{#kF;H~;zXt- zB=k$uCqSd%f;=xpDVlW@^V8mbTY`iWjb&*jE3C-y_?%{4U%dqVIugSs(YTLx9&oMH zcWaQzR@75mt{|gpkcaFbtAr{N5s`BIl;PPx;MpT?h=i$ZOUKgiC;IROQnf0P=s4Ox z71wr1<#}s{_9dRTN}Y+f$}X(M^@M9Fv4GQopN(%q1vc|d*N!N0rbz3D9vI)8#PLn# z>pb7|{@MTtn~I-JL+OQ!DM?fkz#U*0bY}ESszUf8%Ed$m{1UafCarF?n1VJE1}+cT zd|EX|#&R0yjFNZ7&{|d!SpePWkc+75C@NCQ$a8R;)8Q}EZfh%Mg#7`MOPOxEnOd}%R_H$wz zPb126yl(NKhuQXX?OAL;1MLO2pM@R*+fPeBv(f8CZLt-Z30@s2eaMh)=Z98>a(1 z$*nY(A_yik%g+h~M|w91Ta<;>yYP z`w}F9-593YZ(da<^gi^Kecczn!&xn*yu_=TAoa6IJo~-wEb68+Lr(Ri;zQz@z9xxR zq(N0QS>ZiaKL5{R=r$n>B)qO7uWYTHwI-``CAGIuJ7Rtd4l@4`Zpl?O`t;mp;+B5#s_I}`LEbl&~a0g#Kx)6LwyDn ztdFNOxarclkEqZG-)KmP^rd2AHIEPVSy7sDBZj*7x1r2hvcdCg(}MVSy(ne7`sEn&~r5nr0!owV$$@4iVj0bxJkXkcdw44X0+#AT{$p9$DqG;y9*ess|{fC^>vp zBk$*p~#4V5R{2JpSLBvBIzAWG&>pJYGXJwFOV>C-u6@JaV zDbej(G8ui3aNy*G(C8~!tjO^o!J4tBa{XwXvzP{2Z(qzX#medY(bH{TqYk)chYI;9EVC<=(L4&hGhfgP8UY#i26C1 zeH+5uRrY@__c@pJO46qf;eTFNu%X=D$7~Rp3+5vG`4Jsq6Y7yDk_MnP)1#51&vJ&p z;Q80-3%rgn)RVI%N6#kv1H8TYb13W_skyuDso)vHmDkLvD|b&qEi1%_Bkj3#P9bM( zK48xoo3DKm!6iaJ91w&|=6FOqAE0k$?~02IrSR`w8%H(E7je1JaiRH`3Cft=kk2PAF8c=Qz{^-C;TuofmM=0U#q8WLp1UOTvxwdK;##be`Zc`p zMZd~j$>1~FU6Z%@Q&Bpg=e=<7Nf*}Gn6x7Ot zfFUSlI-~pg&Gv%S=((OXu*P_<;ex{#Z%2GMXD>%OG?k*{9PTDmGAmrH%CYkfNgh30 zFPR{@$zGA+jMYSZI2M;OoKf^Ep;?aX=!Bc}$}*y<7Zid+lsT%>MVHmvyDU+beM_%! z;Ug~`F8rXKcbV2;*p52fjeT`nxEr)SpEEwlBZ1rEmR&Nu4Jm&-)e2Gly11~_% z?1h|3OTozv$do>~Q5m@snbKdrh(hHRXh4zS&1leo$nc}5WR2^QDLoRI(jOsH`toOS zPMEgg`bXvwUO@Tc*< zk|$a6rSSo_d4D>0B6x2OQgL7*ML=jmR;;(*k@P4P@lWslm03u2j?$7oST9G)^q@MV zOcxit?UV{}ZIh5>`xv5ZJbM@UgjCSk6bf-$;_8`Rj#GlDtbLF`c=*t$MUE}mmA*qH66<(!@T%F zga^WN_@vU7&XFRUSy)%y+`6-n;8cv|+C1sgQmG3sn5>>qY5)30KUNsZoM~OmelAi0 zIz)!1qfiS{9g#(yv+B9Ag?%+JBQa5$FoOb5S8ib{LgTOW9{V6wD#+4qHJh3Z-jGWr zULr%w9w=2olv*0yEZ3SW3DwRADvv&g9I@XGLHmOuaWGsz^2!~EIP;vzAy8e}i_59r zo+S-3`Ra0C$Nq&Ii}_xfZG!74yCQ#XdLtG@f(41plG<8JVO?NRs1aPiFZq2*rD1d8 z)2iR|%>I-wh;u_e0h!tLEb8=Y`%5aS*u~0NRb?S=)0|oTwY|?2WJ;#n9);p@AfjWl zi)NuV5AKSv%MA`V;hcANJ2`u++Kj_pj}5IiCs$`25WmMl9P<$3cx)~+;uzehYV9x+ zO$|3vGn=+?tO@(9%(LGCiYKY^>0ySnhZrdwnY4k{--#~4LU|KebNSU+*7*(3S;``E z+1Ms-gc@p&5o);+3Nb>7XSct|BgmGrSaHt~*#qCc?-^%Kv5vnidf&3YzYAsAA?a;d zgljW;v+VC;-*l*6=j>NaqS{9#jrtk9>q|PAUGul7SVj|EmUZ5ba%VbqEnh3*{^~md$%#zEedm&D-eN?=Vl7*_TB(E%QDy znEC$8R@0FTmSk!+2eZ5C@m%XvB3mv|x8B}sI^9-d5B*HTwfUQN+P!D4FmO*zD7CTo z6|&3HiqP)j=*heQL)hRIs~^zdzY7UOvE*lpyL?33?ICyA zA+|8vJ)(jt${|-fQMfQ{Yc{*ZlJsmN(&okPR zdcA$2KE%o2lzN2t*0_U}olqvz%(uEmIyrofshpi_EO6C`iA=XIls+l@db2$%@bDgd z0t@vcwovYVVQCt_s9lG#oC~$RGuE@q7SKH7LvPlJu^#nMET52o&39~oFj}ZjW(`@k zdhp^cnNAc-zevAplc;R~-FStk`UyGMJgLUR-SVktyTe4Pk{vc*nrnH!#twcpdHCVM z4~qehua%>4LV|4*XCQHi|Ua%(2`Rr@mr zOLBdi{?57SB22exk^a3cyaV{Kr`j5Z_XZ{w9eO`i1OwdEr311aHR)Si<7~Yuv2!^jt=P`U|2M)t09pf>4Z5WT?-(=qaW}XS-F}#NjkKr$c@fhCI z<`E2^beuW?hQAfYWB51OH^A_BStr2ooo#pw|A2J@4By4(5ey%Bk~#r~j|t~7d?xz_ z7=COx%>)>}hs`4x{_aULhS%g_v7UzEE3XPL{F|Q(FnscB0t|oRF9C*k{Y!}92YCIr z7=G4r!tk~$=(RNr?_on2-iuya!|;P02*VGf*VZunPA|gn@6)RV!;kv^9K+{){&Wog z+mkVT^hzFKKx z#qjuG#_*M&{{x2KU1i1a)wVXk@CSt$UW3EdAI0#w%Y_(T(>+p584KXZ)?Ty zD;~%2!CnFkAMEv13~z74;tc}Q7I z&xHMV7(Vy4r($?EIEUfSgjq4Xhm8QkuRGQXhF^D#Fnn(ThF|;77``mSis9Ej6~nVR z@fd!tmj%On+E_7ssDlv0uN7eUPzNi9zjI80;j_PM6~kW%vtal;$DWGeZ_Tq{_;jyl z!|=O_ta%LYVe?cB-@=VR7=ET#YZyM$%Yxwxzk4c%XZxGO@Zk>6h~e2aJcjRNBf#+5 z;{pt?JublTEn!v+-;mKNhR^o0VEBsfo{Hhw9R3-@KOnN@F#KSL{|>`*^z=jw&j#l( z{9p$QhF^DFh~b+uTEXy58HC~63Nic;ht@Fs5C;o}Z_aoUhG)Bn$M6rrEEs;MgB8Os z^%7$EE;d#Sztl^B;olHo_|wN*#qjqstQh`{Ct>*fKdl)4ji+PyB*uq03_lo(kpL42J z4BwDx#qgV+h~YWHG-LRs;m?TSHIcyZVI^h^AD;#cA7M)vKB$B+yx%@ct+nDh3x-dR zBn&?`4H&*sImc3Kt@}P;c+v8|VR*wM82--xO+Wl9Glu^R;PR<)t(ht5^D?u{?s!XF zYq|rle0BKWv3$4m1JeIGW&tv(;W-MPf{}C+TG@r-vZ}M3F+8G|p zPZ?=I!4r320W`18~WuzVHk1Xw=NjOA-s zC&2O(AI0)j>=I!4dTu80$FpyM<*%?#faNDYisduEF=Kh%@@K&E;vWQ9-sMXnmfs@8 z@?kbsf4pm^gTNpE*ME=Yw=pdL&Oc%KIELlN{u7orGAzIMpRoMAPK4z%>D7YeNB@70 z<;7nL}K$u)M1U%P)8g%kTbDfaUQ4 zSbnSp%U=n949gD^V);Q9EFbaZKVbRbuLM|rFk$&Yrv?6a!w-*P`E5chZ}>rg{+h7-SOJ!Q@1L=JO_mkQzxPxu&*sEq z`2n3QSbmHE%fI~ymVZxx<=+-y`HZgxSia&$t5|;SKns@7`1+|>e$9LfmJjRnY*;>+ z$ePFUaZktch1>{)<%2u5hUEu#vS9hT8&AdZY=3iDe(SShd3Ftt<)0H``NppWSibRV z0hTunv|@SPf>yD-rjrHBKe+K!EYIff&se^O$d<$M#{Uw_bM*8?EYAk#u)NWV<(bjHLM*?#RV=^TiskbcJPFIQ-NR$~nt>K9zej-OlRF8q{6qnkPwphZ z^5p_7Kkj_1SboC-E0!;RB9_l^u=wN4p8?A=KEz>p52Jmj9%6EdQYu%g^cbBrMNOw^b}3 zPBY{D@#miz%QulU{YQU18;!^EpFD!)KWq)le`vw-**_AN_qSqs=Wm{ds-7UHGqVY8Kr{#(o77B7j zNBtzo72R`DkSm(}u?1HS-f&{J&69FPV=cJt*>gp6E()$#%4Itj7VW4WT$vuvz& zkMdsTuoSC%gb(Ii(TatS=jD{Rww5cpw^J**qL&s5(8F%-y1(U$e$dI9D|*?%nk#y! z6U!CN;B!T~|N8i{6gD5%uYV>uwn^RzRvX5t_`R))e% zBkO-58oBs)8X2{er;!`C%`{?o78)sEB%l%PZ2^s3yCR^G>cavWDR}jXG!pkeppo5I za=KfeSnD)$t5dlk{{Jx=$$LdWBhgC*G=dLi8mV6NtTb}JH5zGN^i&%8SwJKIaS+r7 z>qND|24(Uz@*js7stwj!K6T?&3ymoL^HdtCIxL`(YXTanZjDBq9j!Ey{ZtxZ_wp!> zTx*R+>i!or^7onp6aR-aLSn>1BkdhQBa=uH3FXMBHD(&QW2TXoXQGj8A&uM-(#SO- zjT{xy$l52;$e8~Lja+*=jRbanGL5Vj(#RSijjXZKNcOYR$binR$dTskr_xC8&K5bM z=;COVBf~l~Il|M3BG~aC<}NRt2%=15!W**$hdtFvMzFHbh^u#_X1!qq)mu55v!^>oPc+ z6H|?VagHCoAi2fmRB!KGN^)s_aikCG3*o1PzssdMk7^Mmhj`zN*lVuy5ig{<+DKOE zU`ZlnW@srK8By{+Uve~;h$}O0<{TNV+sNvCTstcb()em#a=9>SL-i=!(9X_lBveq3 ztIV?}Sn&;|IlR|7u3VPLs!9g^B1*DkmdxlCN1@wwHuaR;VXi89Z7^psprYO7ox?tq z6^~~5(uqSvIG}sLqUUc!%wsQMYIp8Yy{&^0I!2U}-tE z6!|`DNZoj)@hvAh|M2XRIytjIu(X`Kl}=me-0QPTEZB7Wc>y-HKU_#p$lG@rEg_X$ zq}-YOe^at~ks3K@d8V;CDt|cDQ8JZEdy51$ISnIFKR1zx7=K#Tf2|AuUCu9lT+TnC zwvd1?3Gm|jX(Z>D4Z)0ra{fdG&zBbeYL@eL#C@NJ7w;_*$oV_J2;{uvXA8q=e^Q3} z&|o-RZ6W1>C(8Nf{s(gY^p8TG)4DGF*&{sXF%`1=doYwJT zvykVQ>*N|=5%8SW@nVBO&Swkc{2flt&*yYu#gc!P^Wxi9IiD@$Ijz_$#;~LpK0k&fK|jgbEG_ByUXZInYx1vJ&j)kfLJ)kc|* zS&;ni+J))Pm2~3pTaLh&<(wSv*M;HBtbA7C7C*7t(?Wb{gd7itG!Fi^g{Ja13u&sx zhsp89?DQ>fUXCXbv(fGHLsY!bG zaw8**8rsl2mEArhEB>VSN8v^ZrM{(g39{P%>z1=x zWL-;WzeLe#G~A{^g!*1t74rLJvm}&cTJfq=lMAZTw*lSobx+bqqTaRXUDWbx!VwPm z)2c+GWC_(c8Nuq3ksLldE~rgRWJp(0@<9B;(S8*E|ED@4Ni*k8w~#%ACMd=qwHTfQ z#k^Cj${H3N(Oi|3TUT%t8u4mnO84X>f|CJbgC@M~q4T1zFZajq}Zt}BdUH6}>MBkIcNQd(&}FQR{m zO%tmzp(mw`9_J;S6M~;}Y?@kVAi>Y)0+F>lg0YCpHnzR!1C^)GyKGY!#+5g%f7OJ8 z-A_5SqY@OcE1NpsgA&&KhpOEyrNJ-*bvxUa(;>F6c4@(R?#R*}nvZ>1`D@M#s9X?5 zW!Pm`X%Yvs((Y?%`<`|@W-gqNE*9a+ce^C+wRoi-a;M+@Y#?wWy)3+qBl2ojcJ{*W z;{LHB`%W{C=~RD0|5KK!x}>xB_NmGWkqC_DjAQvks&2{35>!WazE<)t^tAFIUT6xR zI=(p(`T43pp{qvx9udqP&m+cWFI=Yn!bLrjGMz^cQ|Tv40?KT*(0dKu_ZH#AobC6d zFI&&0-)mU8L?MI`Nk8k8i;%mT7=YZ%4a?&30B5UZk#wIsy2S65)t#YoXrBq`$_<$L_+jptEyrpg(r~ck zG7V0<;Qfe=ZlBRcy2q7ddlySW8)-hM1cfguR}%*KMW0lp(@K4uBvAch{?xtYtIMZT zz{-}X<$Glbg)U+AJ{|9;;LJ^V-c6hH)?JqAOsS($G5cs6r}Ky)_}7S#SS0FKn^4h8 zan5noM0Ny`0t*`Xi*#0zC~_d3@ANy)5qF7?7}Y@UEAgJ&As+~LNPQbR=EKp~nfTTb zGv8tx-BTT;r+Oz7hILO>x(`rEys;Vb&pDP&l;q!_^V)y3Ne+rtw|Pm&-oJ!wAhte6 z;rf(x*j?!IF)b0DM90b@yjoaG9KJu*C&_(eaWu3znoSO~4X(qmK!=tEVNv4if^q6m zTM-s7`%ja#`p1zz=l=kZNh@H5uT#-ERY$l!G$s|<+_|2YAeQbtvKtSv-55=~QN!)V z8YQ-jW>}(mH%jgccjM`F+A7|DOK6x1+ybjL@P`V=VM-O522|jZ-Lg3fikC;Lh?2yA z5G8pNCFOIJWbgwe<^QQMQ&N7tkdorMeuh3*zNMgHv}AGsa5nGw`iSSSk9I@*lHn}D z949(I@RrTlKyh^z4t|Tg|Nq!~^SGw2?qB$10Kyc4jLKj@w1|iShk{~FLR3`L;E0Nn zAShVD2#7c*5D2zfgQCUOwn0&GMn!9@wPC15OD#~J)>_+wQcGK_sQ5gzk8ST-`y}C@ zRNCL?-aqbrU-XmYoU_lK*Ra2PueCT~B?Aq)V~qGaZ)C~9W;O-wN*Y%ILuGOaE_YPb zZ6;x>H$vXb&hb^FAAL~fdx?Z|Ax8Jzy7HL0*j#lWw!#ZlLFR~ zTAK=}g8~W}1)O6Ruy53FSb)%;IET3QQ~`qxP{7J=1>7VBY$PqXNw*+|1ueMQ)dC~4 zfB`SwWDRY@GSDYBbHW^|aEA$Vk2zs9WWsEg;*885r&w-Vt{w)n{QB?eQv)Gnyxc0R z1mb)3E%1l0>>r!$*0Ug-8TY=8a|p}TfGEZ}KfRR*Ud|b(QrVUy2NuHH&A%Jo!wFg; z@PNdfHjP68UD)iAt5DgD=;NSEI9Clb^$$tL6ly;yxO!Ygx^1~41YYI+Nxk0%wM4dK zGxYX<;#jmZyQ*YhAPndOr&kuRt>=#}X2}PMcT0o~cF0HBdkno8#D}6ArF-kFcLBU#{4E-+SpHfIs z17X$U!ktL;GQ7`-Cr$?oua!?!8VLrAA;b5Z{lmsUT9d9u)@l9W+OkeF#|{6B*g%hP zt#%Fl-;uSN34h)EU(;G`u*47Ih#Z+UXkeg>#$T=TA&xgV9;-75h9;`PkEME*5#CmC zSOZ>oRV&T`I#GZu=iqLmbH(?y}pJq~95^ z$K@wBbinQCfolSH{OvoznTy3(DThyH45SGq5FXb<-0RiNgN?yEo&hwLdo%0H59!1D?R)wciQGVip*1l0EL^^ZKvz1ieUZf_3( z-OOQ(7Id?gKQ#LxBo=2eeNpu;ge22IsIYtem%k5Qxp)&qN_3t?(8MR)1gMtNJrOFD z7Io|it}K5r+=cbWpOPYs-oXZUMfH+2?zmW|0ga#&e!WyoM=tr@=@s;~NJr*Nzio@* z0P{HDu{?!y^EyWV)ZiM<<~)3$X7;-iam~2h7F;v(mk*#`-PCTgw3-U%CIbumgPE7A z#+#00c^M*E)>e%;P~$8Hw<(=}ifKny7{_&Sa3|;=oAfr<2uFYqlqp|~1Mn3>r^u=i zJM80Hg6?#IWr3O}$0woT;Shf{Q{-6v6?#wMT5TmUivlDNO+{;K=Rl%>2{9O`GB>w| ztPV*?>3N-~y~~}-QFnL(tO#|7_DwWWlLUcN-*4#mWuK8C7VYgOlaVK}jF-0H$mfl% zKz+%GLKU>qlYbzBs)|zdSpu2V8H|;nf{R^Y^!)Oq+b2_F<3MJYNF;}cU`JI5(z*q$@wK|}kx2F+!Ux}o1sQ0(D>A_zbYa#{p(y4JyN2Zr(mVN#SQ zg#i^$oH&5{8ZSJ|92~jRIue2-Yx#BraiF94UMvj-m!!d((1u3a{MV?_eQ>qdW)QUF zYLOXBV*$iRDiHDHOFxlYOYQ<$lRu@9qCO(LX)@tWE^zl+Y#f6{o(08hT%$8YES(wMdK9A_5U zc>*W%od)?74&jIy`C+UmmTBh+LL=oCuz{bWlle)$SGfHA4{0E{37pJ(+Zj0EWS;YS z#-!J?H^092^6M5q^Ro`cXcuxJic=p5r$|9K#RQ1MaYF(>Dq;c2x@SVNGO8v3q_sOn zpkl$+)HiRyk5c&Ivj=`ira{M1!<{!aJdg#}%bKMhyX>>9KwJnTtK^VvDKk(M8OY>^ zFG&bL9PD+HL`tj7OMoO!=4@Uf+0-~r=K?>h^|T}Wa1!#v#ev8V zKL>sYxBH;CDnTT&z|ewRt7*Uwljw_T4+s&6{BUvLa*US9o5bLUTk;oSVGjj_AAXC! zL1*b_2mJ8Ot$LX>Kn6T0C8`ud)d*SO4DpWe#oUwnY3w&d)Y;F}TWX`BBC0@Fgkk7l z+II_FfnP$vaqzp-V10ph-cLGaE#!`ZLKR3*8#>^4+XS)u0KYzs5N3#X1uD6cnOA$k zXfBYXDROS!vUr;SJuE6Dx?Er;rm(q}=Cxs$Dx3~r=e6S^0mxQe3LqBxThyWtq_u&f zBhYe(Xe4OzBvHK{7+9siYy3+wkmE_6v>MqyiO?JMa>^^S_P|}gR~Y3K%x}kX^iGEe zJAA5~GEz=?py(KsBfxSDdl5$aiZ^wjT#8knDrk|lNWJ+D$Y*)10>EZilqwOSSl}9r5o_s)-K~iP$keL{ zlJGs`gd384E8^@t03vobiH@wGo$OqrW zEd}C1^`PAfCTcdrZdsKW-5z?w6YBj6SZRm8#`dfukN^hsj`i4!gQS;{fKZ1aAA&96 zyo+xL1$qg$+Z7D4t^)JkFv1cz(c5YQjSpBu37!)*+bvS)c8DBxaM5NGL{DLoLbv1~ zg<#miK&~!qcN}3HopwnrpU4;#&ah~FFMKlz!7?9ez#NoxGAOHEOXk6#@NrPSC4<7p zL1__I&bXYj#_izzx^ugft%vtdkexO0}U|`V+{j^%@d)D1Z&MftQGWm z;b6d8;Wz{Lh5RLRoR7}fZP<@kEBJD*blp2|e8uGC#Mh-@!wEr&@6IT64>f|GK)s}Z zj|z>tXvZ4oc{gM{BOb3%?u7V%4`ai3pfl3lZTs5&G1{}hPKQI z`LQYF7k_FF@p)V^V>j5!>;oZ3!cT#cpQVIz8CSSwxn|aq zJ8Bo=OuH+*0bk~Lf3lbCHdYX}i>^E;Oeb-Mjr>xiv^o#$Z-sF zHnns%syq%xuw`w}1;!i9>4Ugxw4Kus)(_~m7j9nzmA>W?Uy01TOz{|)dAlop(f(^6 zxzs??FThaY>^?-`(EIm^RYcB4l2arqlbns+F0I7)PJ6*Qy&ec-){H<5`5BEN_#%v3 z#limi6j0=U8Sd>*LJUr^`v~<}S}lD40g*vfENFIRRXT|vEXIy8hoh2BvAcrFFLQGh zBAfS|x8z4_wZn1E5zv>Hs|hzuNET^OPKpKxlFlKNUHlP|yd5gi$0o@u8)tVJeJHag zl|bhxcOdT#u2DhfXs6v926Up4)_4}zqfyW60(2wKxF?FQ#2Z6|KY&?t3&XK&%~^u zelA0?H}(r52DM4DtG%iUkkc=;n*^Sgxe$rE@pb7*SP4w|VL(BNL!y77t>2)J3N=cN z(9>9zqq+*~$k(MXzk^v}p#R(Krjm&M-??A#gx3FY(+BXBZm~A5!9C_45$rD)1+F1G z#z_$Jj_B-CNJH!Fe~(Xr%5Krw8(iUh#e|uV1M2LSxHPFIoxNqMNoT(>*raYN`ZLt+ zA*gQeB#}r#-8KwnsN2$+40XHkK*MB9{dVOF;B+mjf5vvTSIu#PKf5Da0&{O)aAPaml+u*G3I*#a~H%W@M&vWN6h-w;d!X5-Yv=D;E!jhAB_V6f zdqG+{8&#eLz3i@hBX=)OS7#ul+Elx3NMr>Os360WRTxlPLSIztOz6SE!OP!8ZF#&% zNyQ`R0IxM6l1K9y^Deg zQt))c+>z>+70yNqufAL)F;# z!o%M{y-?VYNTRJK6(xEm4GMHt0Vu%xmIei^5fsS5CqgS!H9i=;o`fPM4C#rMFr<5g zx0#_4ZW>WFvKoMX-pu`Ivay1t8 z$m@FrJZc@x#fWOI9G|zudY;%{0&5uV8aqh{ixt{+wrMu#4c5Uq2*|;oQfQ6|Men^L z#&<22sVCUy+0el#O)UNT8iUSWB4|?77(LRNdEM;y4$|4x z0dH(<(`|$)eQ#zU>f^LLH*7V&&~IZLEzkdG29BcLXg9t~o}X=G%JZ}iY_=SEPt-T? zK_@407A?=uI6u*j>)}Rh6^7F7vBX47X2I2Jiq(pg@Q-K- z1Q*e-lct*V>!XJ(`t|%-`hh}qtXaR#HtE+Rf@uA^Y^q7WZa-wwuU~T8JKmuks?gws zX#H}bqBUs|ch@LqwqOeAzj6C#2JUcon>Gkx?~2LClr&La>+u$W}vYnbC{^1LF#Ip9ki^82t}QiGn!yW%}5A%GI}pN@Tg@I zyvQYkWJ6;|=4^(MY-$|s5KHH7wX;LzZz5txBZ4B2a`&KEgWG-38~rDQN+tVpKSISj z5vf$Nulh@qG#nAM`~phDiJ~qny_4oDzYum%V?J&Dnq4D=E!>v!TWSB_;9y02Y-yYY zJ5rBgzhkf|QV`k{655oNFm>&-E!}97up}XqHX(UEb_|m?qkuNOb58-CcgySDSU#Na z5GCpns=g)f*VM>E)uH&1f$la03jEwjkgi%3Gi*y|t6mSG}W11z)RP(wMQQBOD+{Ua?1 zjyD5JGy(BL0#~@N#ncUSE&c!rjRe>Ooy4zx07dq{M{pKbGzPGC?jbuk=Wbq9eNaTO_A12MtzVy^} zi1iLTsvd;QIZCN6RmOsU(o=lpE}O= zHlO;6neF+4FdBn$6C~mea>|pl1|36A2?Zs^BXg~X7*ak@ zz&4Rm;1HPlD8TmY_#hX(+aHRL3{(6D&LBCIEx{b_&e(m8Bv=%*V~^V#^Tsz!N#1@s zcjOUX;-Q!;bI_?3_@nyDu91!h+hOAX-7rcZ$_N|QXBvdb7Q6rluxpzJ7jHgf_ks&T z5{3Kc1n!vWwn&C7Suo{y`JnP3?dJsG-xXgPGi$aQ&enlxN$}RF-Q|Z(EIAHXa?xxP zOFjuKdG8SuOHKrqoHWK_57Y4JOl5q2Ht-%ZXWoa_c#^NxUMznLh`)R`!wq z2S35N6ulh6jRtr#o**&J1c_;eDFPzn@T@`7ACJh@lf@^G!lyBWUARBYb`UQ`?eC%; z2Ky2IrEtXd`gMqb8`2J@GK+WEvO~^6#HiCdY;%`CspD!q)`Cp2e<@5-;waiJG*rC< z#|h=2_LYO&nUS0;_gWk#Y0ig79l3ZmcP^x19fe(w<~gvDaoiM1su>brf=E(t*~6ed zC6`<@);`m>RL97^goRqYtaif3E~O%uDm$O)+Ugi?zpThmyW!3YareVT;)OR&!5=Qo z*P{+RpsG|Mi!y{QAaQhUs9Ji_F* zu%#igx#Mj7XoAa^aCmS-fz2QV;2jOKOAA5987lzqXr{)VB5S&ET+@w;-gXLkD~Sjb zjvN|9V#92Nuxo|8A^Dx=$lP=4&956lB(9TZ!_gCFUc#~W{9xs1KKOTM?@`FXrbvMJ zh{gn~C(gARgy;7NtZwyo!tp&n93@ztN@wQ>>;haxL11;y zxf(cBwM%J4Q=E8sLTyJ_U2?p7A{iR8A-u4J=*oTeV{aWbjqu&W#@aqAi4C|QX$?xL zf_HIM;7)7aCnR+n8dJImTBPh1-&y-T6=*#KvdMPD!*4w@cn8{mN&o zJfg_u%~V5{^o4BUN$W8uT_QrsQuF7aK-lG-m6a1#%nCC}&lZm~tWPC<(DF27hog5m zXv9gB;V)wVm)iI3c8moXo{usdDE2?xoB5|!m)JwcniPKumtM3y4I>ceS zsW{d&_V??9KA(VuKQ3ikdf`%vIxnUcl`u7KiK7B=Txx|UE+2eVMG+*@^6nK__R#Or zIFn>!u?$X`pF5&Ag65_|;h$#czY?o|fth0q$+1up2H;dZYh%FP2iA%rYZurFW9@}o z4eWIRD^%7nJ`b2RvretW@e#S4!VEi};BU}9j>xMl7@?$}3ZK=}8zW;O1@_f1L9;4= zMgzP+(->Ah(MMtDxgG@e1v^I&^p2WoxUgfL$n}A>$tNlyxsY<8L+D1aHg(oOqiT;d zz@EOTQ4MsXNHcPE>zsQG~3Qk%p^W$SFrW8ix(9Bn`hQFj)12Ha-km ze~cURA}`gM3=!&&Dx2RQ2&(28p+SBpq@P1}RaMZt>Y8;t>`Ls%W_;j~}k zqWs}018EmxcV?%|x4o{h>R(~Cpcc^~ICa1Z#Zw0t?7&c2?3j%w1wXJ$YCyCk)QSU1 z%&L&KZjv14*avpDcs!vFAl?(wCMei6Jql}OBLT-eBEJAVNdqYY|1rv`xSvW-D!)_6 zc@%vX7y%58o2zkbxAd9Xk-$eAr5B+6x`(Ddo9*^S9D9jvA+%v(KKmUADsW@(tKyzzo!mqdd*ZVCCh?*Ox*;Vah%n^S>f z&GnmyXfF#-ZA6Xw){hQ677lqKx4S;KbUrcxdEN|9HS8#v+2r{3(l>yY@!RZj0^bip_&&TSaMW+jL)!_{ z6_0>?=3n1d?(~xw5DnQ`z~Z-gA)t4S2wsF?gF5f+&+%aPBs$;iyzP% zxe58pAYN=<66{;H=RXnV57&Poc7#_qx%FGXF7RTXp&;tZtpuwv705x?KsVh=$X~LP zO3cGE#-N6^Sm;mbMb;a|AK{@XC*00M`g;;Ii>x;?alOHk6mCDu!oHTQ#hnhG7j?G zO6OSy7jy2Cm~VQwR^vNxyjy@&eTwbX=^|!{OGH$GSMF?V7L+=s9Y(+EJ!lqOv|(cJ zbH0XZE*&sp@&Mje7@eg2JLJSW_T<(FLL6sF$PFxjgru`f+H)GU)d-kI zGv*l5ZA;Pv6KP@G2PS4SUzHTjZj8$}XBRXd0&($nx?O3;o#b3tA3`rKv3(opE;+vy zi=%1AJM>kIz8g0uaF%7>8=c=1@%-rYIM3%M%^x-LnD2g8O05=D%FRR$f|BwBs)JIEjk zb3xqeFz%JqI98=1SQUGkeHtwuMpywi`xKvysW4hy6FWAEp%2+ht+qhK!MvzEiAbn( zznrHS0Tl73oA)BiZq65+Gmx*%2kppOa65X|?C3;1rxsU(=hXJN85Y6rcNrQeWz0#* zu?=tGKo7pGWl-e7;#nQvlK7epUHTT1lXvN2jPw*s~ z5?g#0pU^^}ByYtH@!0oOIR8^KJJ)VCChP z)QSZREl?fP>d%M7yWH%Mz0k)6>uQI!5;jG%JE6;mB{=N6C@&KO6uP`u$e0M~m?u&` zD2C>_6ZYWM2r{cGEO&}t4Tn3u7f}9VZ9$k<)$*XzC+qKoy_ss_!iJc8{;n_>XWep9 zU8JjdMdl$>R#PPd>#RqQW<_~XX}LIHkfsQYk`bl{yD2zVti-iB92geBv0jM*F#MUk zSxY}r+p)e|-Z$P4!d68EbJw3vF3C4QB&zem#-?2oZ}z{w3agAVzPO+u2NR2&@CWuI9H8|$jRWksX~qE>j$;})OmM@514z$z;Q-&x0USWT z;AtEndkBpKXjhqVfQsYYZ~(&zEM-BEONSW;C_6#p06RLm;{dgrOgKO%Nw6r$#o3+4 z0sN~?I6!RMc6L^1x16=-^fdjalW8wfShcIw}J98K~K>aZW4)E0i1`ZJR zJ_85PW-)MpNxv{~0GF?sIKaC@7&t)ITm}xHKf%BO1}89ZfW(XcTX2BNA&3JsIVs;KH$9aRBbk-f@6hl8qS$$R5)65!2z;|FmM3vHw+vA zF3dQ<^<#e*2Us`6j014no{s}~E@Vi6-f;lWh0nnOgv`AD4i1o;)dLPt_<655Kw>%* z2T&~R!U1wuS#W^H_j|b9%-B{FpdE<&eLN z17r?i-~ilT7&rh>Y8UYaBpHzuOH5xOu!69AG8M@o(S& z8fMLU#sRJ!?-d6)JESKZKz5Ib0~DQS-~dJEpTz;%j`xBCl%9Vs4v;;!HyohwMi&ln z@_es2z;)7aGY)Wm$n$W3ljnQD0c7_rI6&^|E*v0dF5&?B&*A{w`&~GI_WpBlfZF>O z9N@wEXK?`aiNA>h7#8-50~Eez!2xo!|1J&ytbu_8cqVk=0Ez3L#Q}r~&&2_R3D3g; zuFQP~2jD&G4F{;7`!{ibt90(q!2x&=d&L3vuJ0KK;B_!@0QLq34#3{fg#&;Z)*BA+ z{SXEYAp4Dh1N_*91Nd*~6$c<)P2&J%Czv?Ek4zlk$01!fKw$?12PnGuEDivrcHsbU z(S-w)pXe0_XeI43;{c@>d&L2YPjuk`aS6TR09VL7FyjD;8!R{g>%%S_pq=0VQU(sd z-q0@(*QU?uFDMz z4gf*5h4)B2M%BS&waltemQ1+S$54d)+8y-+g zN`Z3$aikPD7f^S~bS_|?d$)4|t$R#(z@F2X0L}%hGT{NIuA1yYPS~6&5^TTrdL@pJHi6lPw32M^f0wFf+a7w+EMxqw_96Avg~+=U11ea(Ug zC_nBM52!q8!2_~`5f1?Kbkn(jip5Mk;BMS=@qnDTp78*GCLXXM`0wHYk--c+K-S;B z>s$a(YLh0wh;@ z#RHy@hMVz#vf$_80g@{{-~rmf_U3Z|dyBj9fXVX^57_)H9v~ZRZ#frGJJ|lO&joOU z?9Jx_G*_O*1IkYQO+27&aj$p)&)402E@1Drzl#R|Yhd63isUXlAb01pctA?>bMb(b z^(Y(Kj2Po*=pMwV!&T;SUTtM~Cp7DUfICsXm0RLSKJivcf z7ajm=SZ{d1@n8lXpbfU~Iu~#%*m5pFx~o?_fOIvD2h^Qn;sK|E8Rr5{1$W^Aym{_j z=K}mbc@__VQoHZ~xah(I>QD8G2OJ~qGUEZEpY)0coIBNp2N;rj#RKBWJTT({xw|ZQ zK+z{%c))wXcrHN3zythW?-dWAC#@L|$e#B+Jb=!QJ{Pda{n>K?dtdJX4_H6%@8SX6 zA@;qV3uv#f-~mU2{{jza3GVG&0Ej*Y9fltPq>^c`9eX9pNpfKyXctB1Z z0}m)%#lQo;m}kZVc-aIGc&iH!xXZ)?xMRBTfXdTxGJ|F^2 zn_Sz;_Ou_JA5%?sf@6o8>;%jG{?aDbZz#hiS2mwvle?voVJA2^)wDo81TJuzn#^{B za6-Q909gtDT|2?%KL0bDTv@bzuQs`bmA%^J*3#LSY;rYCUABVU$@UDJTzJxDldEa6 z*a`*?eaa>mUSrq__Gpva*o3x%>E5(WuJpjOHo1GrJ@b*8zhRSG_=Y>fCyU7@*O=O? zP449R44d4-x7~@OE@G3r7N5{Ixi1av%_dhi)gIk{piOSe=S-X2&Pt0-Zqm@dw8>2# z`m9Z^Y@U6OHn~PWnC9@O6oL&dd_QID9_zW5*t!Rx(J9*BN+Au6lgWsWJ#_!^jO|QP z&vCU*BVAp4nyxl4Fws@tG!tFzGt*VWU(?l6CS5f!>8k7ugRbVi%%H2VFBx=|bmAY; zmC)sXMpw1-?AJ4FaC@|MH=Hr{X6r7SPqH!5Rm&G$bfsOuq$_yRMOQ6fSm-MKX}W^f zdZVk*FOjZZGtm|M#ItmjNbda;y8568x@v#9SGxM58(lRr=xV|O3tg2BP3gtfy>^j> zt|T|43|n{oS&`Y+{qSGY)sY_Ps&-k=bk+8Jx-y&MCjAfT3dNP#hs2IAkggO9;7&Jr zm1d%=Nh?fr^`n`tI{%ukjxp&fegQ*XT{z32tIcT)y4wCVgRTND{3E(j{153$_HyrZ zb?$7hbX7}dW1=g`*IjhQO=Hp(Jn5n<$=4RTs(hNR;I-c9s_1LtL&8K?)$cw_SN<39 z-anzM_8#a;xT06ON<`{`1G~hBL}3Smu9~{&>Zji2RgQ(OO21{wtMYd(@~Zu>>FPlb zbR}EgGhGS5*bo1dLJG`EP+rmgrqHYZwb-12vSC&+=)#{F2V}%mU$a}r2PU^u-qHQd zw|=GFPTlF+3yOohdff|R#LL0GAd?Tq9lyFSv*q12)1n#MSv427J{`jW-3vcZwzF^S!5iDTAY!VD&xh~uh;v(DX z*TJyyEqReGd-*jqjb9FCjK9}$SsvB!qX;YJdyT->P8LBsuM8vGlv@|Cns$e;c~|BHGGFi@I#(w^$|M`SXXw z0|y5Un$YrblRfxZe4+F%7~0)JH>GC3v+3N$es9%+S^M&LSbaAa!3p_k@qy^!f<8EY zeQO{&^vxHdg-p(3Vj)Akzo6|CTF6X`Cl2bOiG@tVR(0?MX@J3&(<~@{D^w_F=GE-D zibsx^bHRsgoa1TY!?x6yc(3dH57ZN^A;c>+#N8lzrp+oHdHnVyI1cXS!EoGnHIdX| zotPNT;DLWIoVj(8cHCzSK+jF;l^QS;+NDI#P3%kPxNkmM7&_lZj$rcTIDz)DsCGI? z&g{bnS~nQZfy-Ii!Z0*Mc9eU!-fzlgj0=z$uZXjFAT0Fs@SOu^*l7n-i}Ds97=R9@ zyqVR!UX9h%ZAigt`k5R|nX9QF4yM{yYEq$^8scE;V{E`%lT^W_eK1DBlXJrNa265Q zkt(mMaSAZ-dvIHN%hG^L3y7bRW~XD=0E5W{k2C<@BR+E#UJ7bn6WA5pVK|qxjwkcA z-RUI0S8TFiyZJ8d#PR|$<$pOubOKHJCqkDTeGi_9UD1^PdVCwWVGO0QOuc<08cIpo zXx^mz96r!)&ea2!Dnt81&9xb%zahd=)Hb9=i0Ycu!DeF{n^` zCCgLw7_==jgyH4nniVk|2_XIm^UT2tHrUf%PNt&aNYM{$7ckMtL(gEcxADoC9mLTh zI*sgz*C%0ZUqU-AYMa`jS^-vI9r4jg;HWSh>MHkm1soO5AN^uzt4zN`GL07jwraFp z!WaAcmxEWOYxzQp^vio3n70f~6~thw&|+{=Pa~!Zau2qO#7_gq+ol*V^U=r9F!{cr zdHuNMr;}BA3iJz(e@bDGYa6o;*7~dt_8dQ648G6A3}QV%2a8zvK7(*`XqY3 zzEc|W+d>Lpl2M*?^we|3T>(_VTST=v_L{{*L?WC7(Z;L6LxgUC3%Ij|A7A2+PWbV{ zAow99`cpSQd(GU1APSs*XePSR*Z3QF^ZWK|yRZlAY4vD4uHuu}rIjYpTYlcaz#f{l z@gy4~Gn-mE8&%XPVkUl{&}R+$+2dY7`h1=E^Sqy1393@wUgFQwcwX@fxTrA@f1bwk z?GH@;JTKUPjh3&~w23yY&y6j@!Z@_wY#}NnU~wONAL%nRa16L}J}*bjPLB#=rpOCo zwnRmXT&#^g_^^Y1NLy0Ny>1H_jzY(2Fyp*2#DSk0RTjv&@jiOoEM%b{=(gNcv2`8@ zK;bZk090Eof}~FOi6UqA7$!SU3&5&z#B%Z>?dD5?0`M|EDN}&SWOb|6t0=f=1KyK$ zX#MNwI|0p3&%p;OT5YRA(Q3}+gGh{7R2XM_y)-YNk3VQ0B^Qifd=G(_sxk1=pkd&= z2|NQ4Q%*hYckB`E!t6t=92EN@lKTMbXzN^s!Tw0>Q2B5$h`(Tn69Ui@SzB)(gASv#=rtj17(8jn0|Usy-5!zX$&>ckc{R5O&2R5_ z=<3;iG`OlRSyP&UQR=>gXtNml4!wq&6^{A~BBDezQ^9-^r9e+>Tu)5%w(>l!Ini#p#|@)3Cv<}Ja+XQCI z{gzOtMzBo&w-jhDnD%>ALMe#fqdgx8I%vN~w!mxXm~`k9u^{*y#H8~8BTJZzVlIGhyVT7-5p|Q9Bxwy#dz@KKfLcx{Pkv zQKd%;@X|%_2I?(pjC-O9+G%?ed8vGV*vWOsOMy+D83v~i#XGMg{Reqz`HbD#5B2py zx!~fc-QzfMaU}hNeVXu6^;iZktwUZ4@seKW9Y$XIZxb(_g}n3^6E7{F5w!F24f|7s zm&*4;$jY;XmpaOb-{Ynbc3gKKI!lm4@*$EJ1#S~)E;1^Xu&fB;yqEYl2VyV!H`^E7 za+y|zO4<=yaUP-Xmre6;*|26!mm{`O#1UImL@TmNb2cZR%4VIMkD-0X9W~Fz5A^8# zOBJAi>1X@^y^npO;G(_#f2zS9peoOgFwXx}AHc<=R1Z&-|B=>A7F$(=KYZ0N62!z) zDpFFkzqXt95c{N-(t=~I`}vcQ%)r%3+d4@s#vFR>C76R7hE+nPT`4WB_;OzZt^2n{ zLe6Y=IK?M|bw@JBR^G?XGbCDeE-_6eBFM0$JCk(KjRgAom+rP7gn2t9>FQbo!vmSkB zLwLL9Dj+<+-*OLhE3eT*hz|wUxI!zuD7QJxl~V#((vp4=Y*0GRvaL`LOoz@dyr{4{ z%$+5N^|s${?0)4K18uk(9l&>3P3_k7eZ$1ibbTLe`hRHodxE15`!w zbXA(LD#3Ai*z~etN1>NV)3;m{LZ$9DRXMKDeF1u?(ZhAN#eiJekw1Y*1+x5ZIG6vr z0A*1r!WXcH-up|wY$v!}sFh5EOv{JmbZeb>gJuSfdR7I5LX-Vu|8}=gpOIjIrZ>NU zO)tCH4&6_huKA7ag-zE+c0$vokFBP6Yx?0~a%j5I1)DxAA;;YG=nbWOY0%{Ww3`()6ugMcbS#$ELH8 zZWvA1v*$z8PYhe$t?4-%{7BOe8o*Oa?a%hv-DY9ommb$~7Pj6(?>|4=A2a|teFWrm zVpti}%}cDXdKcQKNVq=B)<6kCE$|>}%73M05Mh1AO7wj3Z>v%AfreKU5X|gLkNFZZ z3c2x6m6K0Zr4XxP%#AyTPA>k7ohu(-0qW0P`%D8I<@HI?jF<+|ZE`jsiJE=$FG^6M z0|+tk{43hFq1@gTn%y6V9RZvhi=yFix?qU}P491bl+9r;Gup$0?1Xa=F(O@+PXdL1 z`DGS;^b+P9vq^{r?O$*j!_&3Pf6U(@Y>O+FD8r8 z@s!v~Xyq+u5nr?If^yVWV9S?`9ek0$l|qVwa6HruzQ8{zx6E58t80yp5Etr8w^(U; z^FD5wGPCvT57N9Zju?OOgO}eLl&1Cwu62pn@yJQ5kDm5uj7vkf%M8V)fU#F8&wW#|fnV5uf&V-sS=gds5WV_T`zc-04DHwql_ZXAKb5^q;hC4c6+9 zsb7;yes@MgF4Gp&VV%EwcQd)%`Zay|UJU*$4|q3ipl;aI zqD}XgWMD;KJTdK{cs5;+=_9Zn+idF%NxRmW>PFwYA0xgU6Rr8(nf29WQrHLYyUnm( zUL}nz8Xobdn76rg#3tEDs5~Snt`6A#`4g)qhY88iGe<T^9YJB<4@)wf9F|zNIqN zqCJu$V*M$OmT4*b?are{44v{(r~M3pOEpX@R%6-fx^t}g<_X1WdUBPEld`CzgtX^} z%1^O*8y9(fip~4stk3vQW`zEc>sqT7X{V3K`xkk-@lE>axo^nj+g?{m;`fi2j<(KW zj`le5RGlp5+M|h0i$~+oyz^*RCQheck1u^k!&+dOPwEjL4rf8Np|xj8f;A@+wK)qd z&tble3s;N$V2*Q>CVX{j)|Sr%TiO+WL=Ct=&(>qv?Ppl==4RpiydPtvVvPNr^%KtY zpWUragG6}`u-+G^E%*%U{qvb$$z}T2^yRzDk{5KFik4Pzlf>AH6Sr>+^6A@dnqeG2 zoMF^RAI?dE%Y%tc`!e4?Hr(&)MZM5JYOfA&JIrJj6+s z&*L_^UImKI9+|m;Imw_#AB%%)WiW1mZ~nQs`+G2R9BQR8wb1q}Ti>yEs$k-pZ8xi)r9leaLT293O$$pl#a;GM`FEpDsaX*TXT}A zk&whmE0ggmgngAW%#`(_tBas$uUkLs_1!99F24-}z zoFHe?9I>YpzPm#%N2WWG%OBhcTYQ|}hg?1yMJ`vJSfbx#8hkS&G7Wyl_b+#ArKRJb z!vz^S6Z_xW>Vm?%Z3TZLR}<%!gg#x8)$toXUl*e&KanEf(AfmJ@|M$)Ep zFzu$VHQ2cO1?16%f#lJN$S;BVNb;Y5%dYEN(QRHi|BDAauxYWp@LHsEQ%rs= znW|HV?Amy(8@sSf7H56IFE_eP-%TjKhkqtg45%O4Jq%d_cj5)P%zQRNpXZYcyZ)98fEl#A>#*ohR+rmkD{OKMHfvHi8G$xy0&Oy> z!j(BTYxKGdPn)$0-TD;HnjGC)l?^=2fHcuI>!RVeZd8dbM{lz>g_Eh)jY`p}3^rL* zy`T4WrSBD%u+Gotj-Pj}#^*|@W1F9Mlg8(U!n<1I15c?;o=tX%E`#D_2kO?8=tilb zP~FOC9ej=w+2lZ0LK`C{zEkQ=Rr`Ef>R2!FtuDo^np=FDs(ji?y<1BiuV{RlN`0uc zdfnPU8-+@@8fv1~sZw-M$($TrmdXb1uSv1DqoQqA zi*(THp6Y+I#pj0D=ciJiW>SA_2(#w+x=N=~J@0jeZ&Qo+%~J2WQpXOca0@iYv7Y67 z!{Ae`@oivyF9gT0b()$Z&zQp%ijqequcUy}O^gwN? zZ+(^b6^ZW+C`;1+3d_h*8+0nYE;A6uC2yr#x0Y&E@~OSPH(XAGd z(NySG^K8~yhVr_ChqGoJ)kmBrv}VPP^289;e>#c1s3z;ZJauy3d!BRxYaz=;?tHtQ z|4``ky@=moM#fqAtp-m@}piLrcyITn+J99D&a&{O2%k8V+|Yyzr~34q*}1JM|%Db&HV4h{4|5p z_dAuh@O~`+& z;NKywfUzGIik76_AaEo~*2jypV@) zm2jX-!y7DZFgJKck)&=q3-j|!(B;WzY;!0Dm5=RV;^l+{FP^TI2jVyv;+ zsL>VB9GE;X=$496Rs8QoPB1|2WGem$bA}8SbZ=|dOd}%&^C7$vvx>X`J4%1=$G0{TNNh{ZXlQ<24N}Jz&e#jk1Dfz_c@ZUqSfH#npr#y3 z05k2O3P#E)O~h}PJEkIr&%+^Q&lPw~dj3&6{|*_=yX{Eq77Fz$)Ka?~wQMS} zLKDX}+z^X670U~E-@(abW26EU>~at&8lP`ld~XWKagk z9iGifkP9iCwLD#xLO042I5SA7E*X@I%pbTnuwUT4O(5WCM(XEX_Y@odYXM}VhI)cz z^hC*G^ltU@zESFZgYx~>&*!Gb7kRkB`?{acjZ#NQaI@6$dZ{;XY+C$*$g5ZWMg36{ z$#rW}KyE_)NkbSlC(`q#pEvOTo|})am*}#>ZB`TEz5yg=&hvyCw8GE3ndJ*ry829K z7Ony5j20Z6NvsC=w~0mE|^>B3*`_l+^&Dznv0-R?yl4BL%_>#>JOfQ^HY~ z+pI3psUT&~Lis1s^LJW&>ftS;_w^Q(q?Vx^<@p*FOr6o>P{OaoA;kTxlbRK6lx?XQ zQKPWe9bnfP{g-OK5dHFB{ZFaTm8FJO{XR zHOsddm|KhYGa7G+4RG3(aYXaa0WDp((x6)n%t4@AE3yII6_oZh(Kf(~C?_h{w_4(R zwbZ+<)bSF__a~NK7eN)S1;rcGc++~}=?d%xUhNGk@wZj@Ry~Lh&kYTkP2f2)``_}hfCZR@gNM)E$1gySBR>(lGH)G zX(DBKi6U3-=~8e|J;zksSgcp^aM#G!Jz?)pjkAoau`C_V|+VG}8TgCNv zID6tvp_cWcl9zYvP2u`)xVJf+P)|-++aSTWHrC&8t9~BDQ#1g?OKlcX)I0DU=FhVe zCgpQo0?M@Taa&ueckL6S-(s)jP8GD*db74$QLcJxYJ}dNnx$t`S$Zy2td~%gdKPs} z?@V>*hfuCXw$z9sH)>W9hsrAAQN=|;RHcN<%(dJNb^}d?L`}5+g*>(?FZrzd&%j6p51=~va+XrhWPSn{nIJBz!GpAi%Vc}qTZ=6=-K*4Eyx-+O2>CYR54era^uVOL*h z(~HHk^IzlN2+G$*L)uk4tE0+lYVs~lZt-hU-j*oGw^x*&l)_6n@_EjYk!?r&;7i4G z_}k#0QxSjukOHWkF52dd+wHvCefw?~e7o#{l7W2Q#i$c}GPD0Fl7|F(paz%>fhz)S|F)ad-l=pMw8)72A~qw^NiF{`=I2 z7Rt<-M-&uUGXI2fv1Hyi(UN%pXG&M*DVEH4ID{0LGk;q$s4H_lBlGQ+%s*E2F=u|q zbDAY{8E2{`^VD)i=EE$RAC|me$$aEpOXlxczir9 zzgxQH0IMtW;q{ixZLK$%GB2@9VGRn}^HGk?aK{KsolaF*UW*y^#PV8%b+M&RAM4Ex z*6SR}IN?B0in>SEVjMN{Cu%L?2r1a=BY&O`8(470i3}4(Ijm`?SOxGvhz|=QGbpYR zf8k%0?^vLF94B4GI_Hvnby3w#_NJeC455lg?JV9HXJt+LRVm0)qbB1 zgI`cMcA-)Kg9k+w!Y^#AqRs_^e#zh$B!=Sp+6but_yvifGmANF2eA4P!v(Z%Effbi zkgI~oAD}ZK2f8z{IJKoXCm)K1sp#Tffc;+lMHhD(io>3#i%W&=UHnBCSI(kLCg@n4 z9Bx9_is3P|?mpBUxn0weEMiU?Dz?dV7;tt%Mw~0GOH4Fjb&9%+{!18la~80In2u zGy90C6!?XI2IuNlSI25q=abp*eDdMlhpV3qdOaa4YyPwX-S-`H7mVF>J|XS#lV7s8 z4jjdEodav~IyQ@13onNGmI(Vgg92bXXyPSq)I(2JV4;7YHY`w69r$X$5|*LFSuG7L ztPa#m2bWY2Hb{fYtM{r09w_wRl~SG@yLV^yn>&T?H6;x^yMM{+)@K9N1SxH+W*Q=%`-k-r8;zVbjN?~ z=F8~uh$=VWV?0p!E1*fzw+s_np>goEq`g>=Rb`+|I5aj+Yci?NdNHm zqqT7{qxSu4`HS)CkM|ZhQ)}Z2kN@KJ+2gnFYs8*~($ehRuWj5Rx%Z+^(#9{pNqR*- z^7^YyNkzw7XM8$u@2U7dcDc+MS~8@mkpb|WBmKur;J>>x%K-s zZ%y_1u}zxa6rX=FH>^YW-j$4B7aX_AElmkH=RdY~$-8^5x-}LYs9H2@*C*d)6r~UA z_u=K_qT?fXe4`NUDqr~aXEi^Zik_QsXz%YAb4M499`uUCg_Z4>lw3vmZL$9f?+YtE zf2yktYrj${Jf36|Sw>A_agx$0oC6r31jVG(b7rK?;B}KmguLL{=nO zo>x6*Y+ck?z>n&4wgC9Pj%Nc41{I+2Dh}*4sD!${_0EWwwTPV3MNR+{x6Ap%1+}Q1 z1MpF;^j#v_jLTX8p#T~K5DH=3c30S(SeIh+BEtGMFRBsUMsz07CcD!LvR$p#CBRz3 zQ3xOXkWgBG0SkUur4x87;#^HQtfCQat{Db} zKFJ+(jRRP-!^f#QRh%wEm6rwB3Tz>G>aq;FzE@{D-2d4lpF@dYjh$qb$A&KSI2ep^ai zYZY*s(m`W@S>VbK<>d+;3R)a$&v@IndfGm47@ruJTk0Lt5Iwz;D1*2~)<;o0$+bJ!bke{F*vdk}!Qn!uaXaNu}_3 z51jP*o0+DNcM=vSCS+wUPflN)xGE_*M+NOjO<$UxoQYqn(lb-xcajjZO-)KxWu;_h zK~F5sTJdsndQy56^g&F#Ds^~RJe<*u4+)81o|+g>8X8X;7@wNHcsb-7pP7}El)NM) zB{OwxazfVf70?rw%%R%k27EK`C9POR&HyK8Wvxk4W+|5^XD2ULWh7^-lrS7gP~|1* zbQ4q=@M^-6^n|QcnaLTLHa$5f3%^$^S(25#LYa}FT$7p(&3hScybNz6tLG5*a^9vF5~dO6F%m z=2~oyIYCD9%2hBrkm-^PWm+=%fVr9uGgO(DNq^#XYEo(ve$!duFX$j_pgE(|^wg|0 z*=l9R65$+rQp%DOS1lDzN?)?Vd}l>kda7zgR&sh4nF2FY(^Siog;B$zh71wTl1e1X z^ff}&3g~_4cAP3IWpd)OjO?rxnQNCMLI*8LgAV;ivqYJ(G$bWknXo)HGbaaHPa zn1~6c8%xrYQZp0iIhn0ozB)B2%QTDWDF7uxzrqk?hNXug@unqbW-6B^W3v;WF7!kE zW$py%MmQChngwcYF|AvQa+`P7@N$>(2k^d)*doliEy@+x`~r7NYj{!*_W?ZLF!mHTxnmpM-&b#)%?)XEAJ_=j z!{B=*eSM(;u0tE$Tf}f3*698{eh+VS{{g>8G`jzU-y<8{f5GojjqdIE{bHm0WBd+> z?`HbC-C(oWO*6gHYN*3vx2kFMHw&+`X2wP>2oNvOvQsEYAn;Rv$$ZMVoEjjh39wEJ zO{ZXEjk*AeqcOTcbV#zm=s>AKPsG^9qCO*HvKri9W>E%E*I>m>ZQ(lZ=-)aXG{kUt z!5{8GHf45ePQs>t7#stD^2m#=?3nS6X+=)r1)U1RFmZbxlp*X>W<_<{i{QK% zX~EXXfs$_-PUVJPr34>`a}>hig7gRODrb)Y5;m(cKd_Ta!P2J=W{J_ED!zU(m1Iz} z~6b>L(aMUjLXNy2syqiS=r%QP6dMPSVp@84PbxqB&?kx~w? z^Il?|86VgQ^08)M?sQq2v)x(=MUu~0P*@!gIc;O{|PXKfdb$|s+ zBLmclB%Qfni*y+zg`8#oaxas^ItFuAp0OHy4Qf*}P&z$6E!KV&3zIjv#4Wg6!77&2;i<%hQxjbaKjN!GaPQbdD&`rQhDrer2)!4xj9Iw6Wju2mLCX zy-u0C4>B}&pV&(QzeUa*m|aj)>^Zt$h3s#QWEVKXLxiB-p00SuZ%YZjTz+o`FrZZd=l?q%l6w?6#(SY!}Xl645 z2nyz)X0VzLW=2H?NknEfTr@;9Olr~Cb}(wGb^Bqt#qL`Dzvp>p0QdL%`@xxa-uJxE z^*raC=iDCuj|D*kAwizenixsGwqQ{zKkM$IS&yY3{qnJ16o+^5I9`&U!9UhBe0apS z>Eippir1lTSL)`hoF1>CpFMY=LrK7}geR?uPYzv7F5%8dZG-T=t*`o0$*uuc3vFho z;BAups)9wWK|0zUqrZ>%HI<(KwS)@x<5cb1pl>pQ{$>dJ#{f0*oFv~pMmN5Z9rCoX zMs9dFv0I<`Bwlr5ZOZHMiWYfMo955}^)zYIZSYC~)&2EoaeUo0n)EgUzMxI-l&AGc zeK<1%7D@9%Vssh7XsDDmG@z-pTVMJt;6qt$TYJ3JBCnrOTQw`7d#nH<7ZWk;9{gi; z@V(UFUkt(jstx`RkY?779A^WDAq0@z?)4QeV~%NKybp<}Dg3yrA&`B#or)~|!@cks+72=j@uw(ixv&s7>C2O4z8-NA7qTMoC zZ-CLNGvx>BJCC+q%T~k=cle%k;s-CyzQj zgU1p!#;=&Xccyyn#(;$Z%i=3LJ1c3NrVn0RV{#yzVWpVk)-^b+9>ohHXDWW#6?6Ta zKuW93HW+h-{L0jbzDZ0sl|GLWc+`2#~GQ2j_ z5<)rgi)--W>dI0Ys3=Y)`e~BRV``>)YSxOWIa{aZ9-g{c-Oe?Xt?N#ZamPV2+g6+6 z(vN0Xf}neOQMR#4lApTIuueuq_Qb{Trcz(YLLo+{`1l3K6Wywz;>bJg^5TX4!bRN* z`3;9Qxi+d>Wi`Gu(k^d*sek_>fV9z%_>+VC7bQ=VU6RK=*-JEH*iqGPCV-Ud+LI&2 zrz%TMnMzI_Ejd+Pa_U6MDRaWfGYKc_N>0^x$lFih-EP(W{iiDF$B5%H{8)PO4F80F z>?g3|QzHP=^*XG>pLOw*=#k|$E@r|GgZB=D|Bh(S5biZ$cj0S>Q~zCE!bw|o2`B3l zPBx(2*QqE~{ee>_=*J+{Sig+b`1;^}{s%P-PnI(O9*ZhWyzj7@=Tbi9O=2;I&2cb~3FV*u5J7W|(T7z~s&H%f$%mDK; zdu{CTit50mbf@$>JTrto(jeY+ri4`IT|CP06{rmAWANsLwe2W+hHgUb1UX0))JFDK!o31fL- z@N!bOs(U&8*gu^g%R$GbgG*6+-*Su~f#59-x3Dc}H_hh1guu|J8lCb7iVcUDfKl%- zy{XhqawmiseuLL5Jzk4^h9G0a$p6ze^Vh9YgyjYw{F(VrfuH%oKfN&Qzy7M(9c$_G zmJ-`vU-9PQ;^iZyn_CS2d}(^)@<>*WOTMYB+is$tR0(%VETefSY z_zUnuAq}!Xy2<{mM*jvh#&PUUk6<6*l>c*HC8_%Z-n>x!6UVZE8EB`oH|NNWX7b_$0vFYjyr#EyUY%2F`WLhR(pLN44q7knj-{gFZYQNg#upF;jrra`4#o?t^kNj58 ztM#FU`&u&PMKgQe3FckG>PCe_@kzrnjIzIeo$hGEMfa(|BdZ4X;a4LX6Cuo$lNt z?3LWpktI)xHJ@W890up5p&m1juqgdekV3rm3~VE3%+H=oGiU zT;*uoh(s9wvlsw>0>CT){1E_m0Py)`032~NO2a{6-KP!I#)ow$Y!8}O*@nzhB}$*l zl6gJ9z@@|LeB~;6b|8E)Z0$k;ax7?M#@3{|_C0j>BXd7uAt@zQiSciql(dR}wUdAK z^60BAhR1B7=PHly6@SG$#WR(&@kKcnYW33bCC?aX+wv10As{`(X}i_avM@lrU4!`7 z%n6cH++um93lhe-w#9ixV7H2B*ht--l-F`z^(M>I$FzMGY)MIUwMat&&u;h+4`LLQKA0`w`W!RkO#|xTJw$ z);J^&TO1lRL=6usYaeaK1ZsvjMuU)BqgUuTL5^?90vTGq#^;*Eme#7uB>fKx3+^5m z`4`GYqKvxIGt&?iZEnR}^tFBN$*}-*JGhw#?-4lK@@h1czBWzoydd+EzNPflX|^03 zKKpmGj)Vj%8u^Ztrzhs>WEjA|5?oi{woBVOG;+y zLPfUJ9ZCJ|&!S?tzxSAIsRB&K+cf_tcPF8=^=SzbijaRuM(2!smFhj=){yRSCf z);ER2JdE!#3!OGzNUs#6%%TQ#)P}PLn9u-4Sy>jBK1dDdn4_CK+JK?>z0`moxR)Mm zz~ntTL>+HQOQHrCSpzZy<{TE}INm{N%Cgcd{eUFF#eyGU(x;~4$C6;m@fIeH(fFH% zT7*n*9m)|_XQ!BTZJeq+NP~H*Za*B0&b_r4`R1^Vp)>=AC5TV;8WG{Nd;P$^X&gHZ zJ>zmQic*y&=G!OA@v!19+9?QbYT+k}N9zJrf)LTsvTbQ!NUF1Sca-RLcSeT4?%2Sl zCJb4?v1SaJhbz{P%a6)iqAS5PnX0mj`KU}k*@C83XBVAd_rA&J+BHI&pt%u+qHd2N zM8o2g2t9i^r2r?qKU}Ti_O0gr-c}JdbLxk6KMRjXE3efa@wYTpaY)&)#Whnk8xgj{ z(p>cQM6qwTi|UH<8!i11`Qt}s;dbO*+0Oah^sPqnfGw_1YY5)#L1`gQh{AgSZ?(Zc zK3l(QngEIN;sQG?1!%2qLJwM!B8_Q4?+jfuKq=B#{OGU_VO~+^nJyl-lM2&4K_O+sN9jm-a6$3Mfmv-w;8a5c1*2eO{Q}Ns>!Q zLZg(vS?jSGFdglu8RgW2=yp6Z*uLzH;5))WP}~J;=LzKgih@FCk&0zbr2IBX{o;f4 zd`6XoaCH$Yai6qUrYj^32KNLi{EZEw{)y0||G3DV3P>pKo zyguNykYI;@9wkd7j)5pSIH%1u4c|nF8Lc~}0cw0}_|jizZu3+ZTI|c#688zfqO6;0XHFq5uHq`H=x%PN>l#~O1tH&oa9aA%n?YS{P9|1(`R=3wVfB<@cC{VE zCRg+X2g-RmDB>ef&K9Da<-5)QmW0jiaYTFWXkE4nk*Xt#F#F+Z?W5cd>2?KOTX8)} zvKRwtmNtj5$H{Yv;juwKT6zeLaGRSUeFxvfwSFER6k}>D9sKa)7sb1B)fJQUNeq8~ z`w>sgL?P~u^Z+DNPuYAXZSh(JioNJgNpKIoiDjDF2F50S^%-KM4W7F-M8&}Rwm2_+ zj5{a(N>d@-v`2Yea8Yus$EB&reER*84v(YD()NCK(AKmw4%K+Y?cr7_Dcy_i9z{MK;aMfb^Sq9Ma&4%=-sM6PwFhreq+l&hATQGUmwh?ye!Htt!IbemSP zoiZ;aES(i4SQ&Q40f19$B`QH4HxK}F&^Ec7o2plrrk2z7Fs|K2FBfZ7J;gsN*yE|y zqkMQMl*AEfld^Oe-y{nhDbfLBWP?KRuJR-0nIM{-#cIMBdQuk$HY1$kH&()(I99^D zxuryXnKUsDUmGe;q*SbA;apf-EyR#*0)pcQ!;bG(kG{mD18@#YiXoEtKtqSeJn(^a zwNa30^g_KVgIjBL_mt=zb_$3AvQ;VsTvUiS3 zI0dSEj1?S9TP&^4)`;el1aT}a2|v<`XPHW=lw+Tvl*4Ys4Q_`_{I+%C9bpiIN|sWN zV<9o2)%zYwA!4c9|IK!;*x>>Z%T^EP?}3`T(ayApK?lC%71HV{J*>5@6T1Y&h5l5u z6$9E(%!HReF-gE&`XLi*rcHebi}Oc|3-xB2-;3P7rCeI4=;I>M`+CuNJ*{85TkrKF z<3Nv93qIxkQ)qR#PIaBK6iLi1MM=AhV4)rj8t{C|5@T@)ibhtQj*Bt2^en3}^K!d@K8ZLJfS%kd|f%{F) z0v}-XIr$co6cu8N^gYS5Y?ZxrmqfSXTvIzOY_`h677FCEed=i$iPIwLv5YzmfgmaP z@g{!swg#B6#06dze@7{orASF8nbhb!Mc`T&f)ql4dRv5?V{Zv4(L_XiU=J}({YBw? z{r=+3w0gLTDLZYeCof=?h%fma8<$q4eDm5Rv)|LYi-vVR+YNR5UX^Y%`{kMavdvys zG%vjBSG@UxVRMIJv(M{>A>*rl^w28JHv8e>G{5593x?bdL$1$hJe=mYQR$awiOE*d z!%zH*f05`sr3*KhU4*egeJ2FB8jyrR&=+AJiublq0@U`+Jt9m+xiApq#7_SF&l35o z-!~7$wj*-#!=se7fAMQHY;O#>QU)QXGPY4*1N)wqFRh76W0GKmD5zgBACs4pYH~?w zFu3ADbz<~qm5A-XRic%96OqiAGp zrQVA2`{QG5ydi?7y{9c@zD2Xi+W5%DSg$XW@;n<|F2)`ypAfpW;b2>=|CiN*FDnIK zHVD3K5`1Y9eAyycSa@C|$@2{RBTq~d)`)?Ovvv(CCEqF}TkrOiPI7um-hcTRXxIFk>fuBH@4IX2lt7ijWhkNJfpN}Lyn*R#5US0IRl<}t*^jS% z#V4gpFqoQ=sEyqqR>E@zf7Qkg9|3ZIdYOOWo8_4C>m)k^*J;bOlD}&k zIZ9;VGbb}+TXsSAM%e4KHf@A~-BUbYQ{iZQ&-L5^9kc+Ko?o2^HH7V-NPru>y`;<3N?`t0MHb-{7k#96#K#!BlYN1gvOu!`tMdqRZx!P zyRU!z(-#x|ic@fPIkz%Dvgj`AV$7cNpy|oE75eds)rn?xzBY5|k+im}fZ;aYGIqQb z6@B0{0W!>8Yf9X|zx6=v#*}G0awZD8nj8*J0SuHVmku{aOTG}Sl4txhuK|rqIU89J z0X3bHgipRlQHozCZ4U2J1v}`iYtw8_y2R zx73HG)imdx+t98ED$<4?mkE%Zd2MLDyt65B*FYj==*-A&Y7U#};m*li@QF@)YJXlwUIr}Gx_^pJLenL6$vN_XiAM~le5AZ=A=tg zj9ul)=_P4J(4F+q$ z-|h03-h+SXclm1YYD+@7Z#||VO33vo?p%!$R`ZIeZl)s2fxhQYZwqS#-+$zkB_UCR5v z`3@I9(Va>H?!=uJ=%N=Fuh2ywE?Pek*B(FQ7`G(Yv*d?`P?YXXR-C_Itt`5aHfW$% z+7HE(KNL?(klg;$gsHNfDY%%QggdX|;!q;)G|-(w-1!9;Z$mlt((}oRJIWulUJvxo`<|v{r0O* zj}H5`5gK-_cP{P5t4D`DZ){@h5Ny9b27!sOPu;d*zKp{0WvFImXX`VaYLHCwjWo9H zhiY{3?SMVlQa;yL8h~1Zb1`nTFYjKydK|=V9>Q-tM9+-${{`XT{{Y0_$Cpv`|EuMg z8N2_?ym`J>i5uF+h6j=swM`hv-m;c@Q@NXMZL`d%@cmPxMF%Ds@LhHUi2A7U&nv9Y*%@$ndqqX08?FY)uynY!+3rI{*eXM6W~sAH4{ zEKdRKPmhP3fb!?|s zc8GfqLX4vYKj`=}^fcS{GBVb6b#aL=8PJgXkB^b1yRP3-qbuX90qeE^A7hQS0QUDC z<6(z*Sk^&1?9dBi$a>e6hbVp!;-BNo_}l-dzLXaxVPQNsacp5&+ILZ-29=a#ApTRj zhB{!^^cmQ$KyakglFv<_jvbEb4;a$>L{2rOfj&-+@|D{Q%tcph27-XUK449j=RKL$U!c&;VOsxR`t`O(UW0VB1p%YEJkT z$U61>7I?7}=#R(pm7p$#pe~9HD?wejSlK50@YuFY>QgrqRlZ3Qen)BSuJx^JD3+K9CmY*?rw;-sG zMu5&Kp%Iw#c5XJ*&_Y+z?S)0Z3T?~2A)=@r70aY8ZSnS|+bHUA*+0>=;K5p=hh>y( zmdN>VS7y$}2%U)ZGXoD@?7oH#RUS)!7$KVA0JY5iz*j{lbQ^#K`yXE*I_lnuRK1y= zpK_&QOlWmq8{3PMO{gR30@;KL-N-_-$TDuqH6c&KnXP00+y!Iwx8oVWx{YwOv5xPj zYt&U99WKh_GQI;V6khZ=Im#@T-Tn{|WqZuDHnl&{H6T7b`GO#flbsV^gY1x5_B|(> z$oGAkTHm7 z<~S~-2O50Pn#{_J^VOXXQ9>9qUh+6PieI_Hj{zY)>hn*0+0V$a;}<64;FLwAt$CHm zU&6uYMTDe9Fg={U6#m6%Aq8<%NZAW2D#UP)u5Czq)?XNh+BZjNVVyGnNySOR&wt`k zXK7gg%pctjr0I!O6negiAy{O= z2l10mpJKITd%QUb)aBrmF;p};lmozR&H(9K;_uM)e<76pP4? zkQIB`RdvOjP0FF1i+8wHUJ0@s$7a~E2qO=e9Uwq;-g$X+phF`Qi0BXPe-d>D=i$RH z+*xFeq#D)c@G2%o7&eE{qAq@{mnOTA7zJiLWP@WDw7Go*oA?iq$(@CT$+0N|fhX#- z>=Y$0nqCy8^)pl%|KcbFD_WE`G9H4b7cN=@t&EySS|At{uE)$AU0pR_?yp^5Y>x+Yv%PPo!XurmN# zH&ocBDBWakbIom2m2PU$&XGm~>8{Ct41^g_M4Ih6F<1Tw%eg!ky2KMVeeb*SQv9_% z`Ce&@B_29(o6x=gyoR=8A(52K{y9Qa;O_>(tIJo7nnx#v2 zd{m`48Menh+`C)ZOs1P@kcg%J-aaPa)OydLb(-}77+f%%Z<()kBisAy8*K0<$*a9&%1c{^pQdF?UXlvRVWm@ySWf95zI&-1W)ny{*sy#+{A)UI#r` z=nbzOJ;*s-waIyY(kl6sNii|!GU}W!)KxrE!d1(mbkbbfR5905!IV**3-Y6-JK&$# zlEBrx9yw%&va3&JPVTDfjuI-EDoF!H8B{9G1Fb7<-&DG;D$y_j6s2mkO`Oz&7KYD!v ziBRfwnOoc-#2#NZzi%xZGGrdJunO;3w}Isu!bmgk+GWYcDvW@{o*~^atmge!Z0oQ1 zbcI$mr)&o+KvVppV4uG+o%fBXRk61{s^N-J36AQfd{H2ox1g-SX}O)#_c_F%3&HC$ zm`>#=U85oM=4d1hdM~oS^=iFF?GZD|0%F{7wJKsTaFFuuYcU77f|X%q z(Jif45|*zzn0EX^l)Hw4YiHVG2Z^(d$ zg@b)Iw`x=Qvw5LnYnFb?evSIk^d1=n_C&cS>y~kG zQVGZ4u~KGb20L(-%f*L%Yntn-8aPf@T%`^_r;fOxj*8I^eF6M!yol2(D;(PbTP}(X zZeO7w^%Lf-y<6Sx3)` z!<%SLsBDTP?WC=m(KS&Ld6b@+o$?6jE5RaZP4skznMT({hI1A@t7~Hongj?UlC#>J zCX3egG=eA|lvAU8{AET%KvP1P7+Ae3pkz%QOsA(8TlDaM5{2p{5RCc`wCkK3Rloy( zg&{(2DG@YGl-~B*oI#J`4bGS0*Aw^2qvZJE2)kB_#|Mg8^@Yx~BhAyu-cfO^WY;HC zz-myYo-b4Mj`MlGOu;N3!ed<;4>43Ks8(^gLKz7*0aIvIDu`>${_H4Lr9y^Pxn+`+ zjh7F+N*c6}u3eRkEra;^fT;v*)q?|wpEnyWb1RFmXhN3+9TuGR?+8&~T?EWc0t2Pv zLJUry&f-FF#AYmxCY;;pn5M_$K*uBrNrSR-io75^*JT0Ehk@r)xECTt6H2;{X`K?X zuV@@zekZ#@xZzB$6aLhpxOuQLCk}9Z7IBX2UC>}D^;Z{97_L?lxRWO={CsEQNLigaf50E7`lxD} zM-Ji)pEJqn_ZNr3(-wZLcgfSXtEsdoZ^pi3a|3%}aEZ9#HFzBcm&2PX?iDgO-o)MJ z*7{I=O>@d2yl#Q@XbBu?vtT`X*$n{QO%8kDs0(9I=t6S5h4m-+T776kO>>JIj57UL z!e!y84&JH1(t)7*;&co3-6F>JDi-4H|q|@V05x?c)eML`rg^({AX~=+--|SU{Bh8HgfTq2~=h|$~=wg zmG&|%i!?8Gif(Z;B+YkE{Z5|4H@v3aMhVTEoF{C7P3y}hhXW@tKt;!!Q>tiy9!an3 z3AUdw&2dU1;bOH-s!F(=j4nL&qX2oT@aG8bSaqh z5xXr1Zo|s#TX8TEO@-U=zJbT?cTA3zKX-fDG?;&QPh$)7XXcJ=az1@&QbxAk?~wTg zG7(*2yno$Ua$r{QCZcLqYzH&jXfV?#R?WPTfffo_o_KHM%}iX-@vp#<8{$G=a=M&3 zFdOm8>>K1+4PzBvfgy?{3>d56Sq)>Aws&v9pgiS2caOwaqJ&mn3i*^7y)Zk*E&Hu8 zkESE$M9ej4A=&gL$n+yH%e8KNy}7UAJcu{~FuT9-dL|WkljqY`wx_TqPvq8F$))p0~0r;9kF>gC0z1_ugeXYWiFR8e&VIOweVhO+=?JM)6k{12OX>fmCa0&dd zUn3^}%X^o$L9}Yu(B9C#t}OQ#Zk$uZPg zJV2cOwbrXR8|mX#iCL7=6AfnQqHNGUjDCNtm6}CsE$2KXoI}tCaClBghXH2 zt$}@3<_A1-Z~=!+<^VO~_UywwXoOPw6ndvGJFT`jESS#(20;?-xdcH6ySSV?A_%zU z!-XO;^O>phMSH?Y26<9$L^{Ys1#R4lR)&k#TD5ewfb^jPw|;D5Wp0Vtdgr2@Z5;V0 zvymCjT`4iQZ$RU24?x@BuTaojl`m;RCq=hOeNz+>E;98-%YO7ZC%%kbMUdxr*#4hH zeqj3H{~hqb2aV5Mp#01()=L#(TN^D7@KC8-vSOazC5+r^G8e?PZinb{+KEJ$>M2R+ z#oeT2sVZ~|iPxr2{LZ*S)JbdQ$c0LL6`F>iY!j?4T9k9NtX<9RH+%w#CL+y|j^}BY zh<(N6@Y?M$2d&%{5FzUEB{@a-@Ps3Rh+G>`f}agYk3B8@{2FQYX(~5K9Q`c2ZxiDJhM{$e<0EiwVlk22-j+V;aZ#jx3CnI6}ml=12h~G zreR8dsFJ)j{%xNE9b5#XmHiv!YvJN&)`ZZHVyM<|K-tV*jL<;P*5P?P+ND?tqX+hx zBgi&T=r+Fyoe)fToqik=@8n|2(n*_fH>kH8lhF~5er(WOD2s`~a=tX+1u1Z(Fc<4~ z3$5D%wr&+2OdUJi*aHc)a5df*1Ye=SPAS8&So%Zi@<6OvAEN-xvQCm`!db9|%4x_% z1BLVS*wnfQKYEq(p7jGc;C)(6{Wq>5PhLnze}c21MnI0qBWXF3us>GQxnH-xmS;)p z%uuw3F0^WP0{@IMnw0WGDiiv{O(5`wEUC}8!-E31xcGP}1py3AIv$8A_`!rTzr@e( zX&OI>eO%mvew^i|4bIife_*I2q0|!N!VW#$?)JTjmdO5{Cc1@Xn&P$>dZV5t0llt; zf=?mjPbi{EZV6Gc^TvkST{dGrhFZ`t^Pzclfz}@fa^P0|@A5o}*}MVRzW6>lP?rCD zC{Kc^BGUTLfxb}`Fg{BapI+Eu$$@Wn$`DqYabduhg34$Y0{x{?@*X654O1HZ5`rIa zLN8)Z?&8PVB*MKd!8`QxIfp<-t=f|4jQK6W&*~rOp^H*MyN2Mc1W891Vvn9o4y5J( zQ8@Ht_!lxq>i-nxV!q;*m18wk1WxcoK7z-P?eBP{3nIe{CI-qx~j>FYV-@m2<*ceQv@$w4ilr+t@e-&JtR_hp+(PDl8k*fhi}5pIBmrwh z!#4U{7>{%n7(jieKQxM0Op-qmE+{jlR#ESoCMm8%;{GX5XV{e(m3Wiiy1=CG`JDwddQ7cDJ0x z9TcZf_8*Rv3j&Hh_^=R<5v|q2mg&r}Q~#>@PNMkk5XN+<;**VgFB!=>H(|XBU9#(L zry5E9U%Z!hTar?b1IG2(yl~NmUDv(XP)~N<4Vgy6uqq+JWjFk(3mI0)++Qm`3G$;U zg~c^GH2u?2F>?B8Dn0%=J&A`plwPuVd&Z<#d*#!A%fjDNLd$#WFU#)|FOJZF5Zw}XDIBIRW< zd0xMfT6(b0t`Ry@{>;5kKEflnotVM;OYZ+w*}umsdlZ#Lw9<*K^@hrWeNeka4NB4j z@U5b6dx`b3J@@$%i>SoK6URzy3_VZ9-Isn$6|%?sJjXwNpFQR=ialoS=~t*oENzf1 zUX&Sgg_CZjRaGC|d#Df6fiKYDi=^(wdA#PcFuuW?mSEGq4Y8Tq3`+p{X4TPyeQAYy z9muN9o>%U^9xVu7mPNi@(nUbXRbR{e8*g%PTk*nM11Apl@zSJ=G7Y?O!0+>$~Qa>WssrCKF9Z;csjyL2E>l zh%TmWkl$I7XGibCtbS^Z*HS|7TH>Fd7q%_V%W?b)=&U=~y0>GW)j|U0~@>u>CIirt%YfntTC_gs`inPpGuJ5is zZg*-5{Fj@zBh@`Xbzd*+&B@Pk{1$a94{A|@xvhB8+_WKkMVU)RzUE8Pu#y7CO4=Vd z{TC`GaWPfIKc3cW>urNMRRH0mVqSH{ymEXZT3LMm(TNQEKcT6na=l^ddk(aiF zK4l3>0Twb_soekeNBw69uTzXlV!(T(62Gow;esJnP~QSVa7#y zXK^(e8NQ;`fz`j|t1Nu>fsSb>#G%~RC{laeCIlH*=^)Ix8wur>6g#^Y&kRaNVw?8 zzmvn=l9#z9JGmvl?wZ_n8S`MO(q-xOS)QfPcO7e$lWnbDtC*KAnJY`!cts`E4v`1{$dSnQK(YCq?7m{_qs{WY z1U%q2`PfZ$NgNlRE?FTP$+@C)68tsKHzkCs2p2zdF1x5y_jI%ToC4Jeyp26K)pz0= zW~c94C;Khy3g;@Q%k%9Bfvp3F$?iFqtt(TmY?cozOkKw^f`q9z)&21%D2bnyEzZ8e zd1znD^IaB-l10l1ICnn>;Ar>zmya2yQk(B2K%*=K%x;aPm z7>%br?p!^leN^}nI=Ga^`C&(lvvpeQsXX5}(ulX$v65fVql2$`ly@DgecZUVQBZevQpreQMwio%)s*A6i-Nlp+;$+&Y*yEM!(P7WdR(3}uzEGL5lb1&c6 z*BaQ^*vxDthjW3dIvUIm-4AZ0)@$My$Ith z?8Cz3x)%j+4I@-DE_s3pu2Fm@hqY}JO%Mf=g%8|1>?l%#>BS{WOafshCkCGKHq{kG zM#A;JxPgBeN{)LT-=5;JbE*eZ{Vt~Z)9P7()x)nJ#GlyKPl?zS*JJA^^XaGaM(sou z>JVRLn5WH7lww;yCoW_CXwF9V4BD4U-zQiqi)~9Kv1V+kgdG`MD!4zkRJuAA@=N8& zyQMX3NgOyFT|=t^>mh_Gyg4ZNr^^1aCRwq7!aDM!wN}x4P)KjbO0}cRIX%O_=@uG% zjwBACOzKqK7`k^voS@(cO^FXe_iW=hbnlL< zp=Yo5FbuU}P(n>5&>fiPZPfKhCs!%UNEpibY^-Fhb2gQ1j$F6h^?o1#h5)F{w5BqzK74rV5-9_JpLR2G^4*xB`gNbgu=#fh<82VfA2sx z;|&>w*<(2Mgm$7*KhcI$Ere4g$?4r)z$xU%`7sODHPFLdYS{W`=x=CTN_l4brBYsMkg?1zNMjn+reAPQBpZXAs@+mtBCLaZ4Km$lP&@o{eVv-nuB zm*1)PSvxhq7a4$H$E@#)j&hHKXUh|<%(Ep9o-KF7dCwME&AeQ?PyPULou>Ho3r+!~%I}+O)iNS~jgDi|@2xsHd5mBNbxI>h89T8Ls!XK9; zP!!lqz=&(p5i|7(j6824`OZt}u;F0Q^&6f{R*YDqrpf}>V?BP1_4q7U(QmK|l_E|` zggB`(r0F>{8!=Fu0WgepdK&@Dp)CASAP-3`xFjGA{kA{)r`)4)6O z*n^rbEb}WF)v;`GW}aR3ypM!EsOjy~Qbzij|3tr$1w6f>V+;2(En+Z-H>%^tn96%5 zzA4N{!rm0Yj5Rh+<~}2yV0lg!)i}n<-ceIacuqF#Q9quOy>F%><7e>BGd8m1KI5^m zO5bOd#?N4t+6h<4j3tboFivJzw&2lt;$-y!q2#8}$oN$K83S=7EvcK%Z7q>&7L&3u zcZxIfpQpSQM(pg-gTnfZ;QF9dgOB;IR!r&O*fWA8c0l|_`LD+&VgqB3m*H`Q0c{?1RI9_GRQoP5#i?6U`iZx8x@wZ7Q$(S6 z0~R)qk!&8VqeauZwhkoBVPSbGd?`r?qewrFyr2&c-(!vy0WWVtw=^mL;JJU;Tt-|w1Ipq zwlTE^&d6Gl3Lab7nCkn74uSZbJTa7BS|Q1NS&jW4gY41ejTP$83iXg~cv*c;zSx6+ z51S5lRUdIB72Ey64Br=Saz;Y0z3((scG-=1oSb&U!=4ASSLPfW(4*Y8$6}AA`X2d$ zk_MHWD%aku>y{|$8PMC~>dPXuMIKCg9w8Ev9qCj=4mGaa3TbJ?b77kA3%=1|9h{`q zGNFyOxxZz}S1rz7EH_Ch`0ob+8D-0M43Vs5xI@>FogRB0;`3@@4wSVoWTH(it(PeS zj%;Gg9RC+;vQw(rkKDYKIU-p<>}uu9_Nl|;ZK(`Qq@S@^bqe`!wZXpCEY5}%ikToXSN`c3>X+nF2nl=gC`Vi6l@Vx`yJ%2A z0foLxcNuq#3mNSyk*Qf*OZZIZCC(7KhJazn?;K z@AR-2B8z=#M_I^r?%T-UR^Ub(+wwTt*gioUTaELID|xufP0$Wrwn+tyO|}s~g6lE4CO5kXG1+#1+H}YDu`L+8=>^ucB12@*28+uTy~qM-)i z*a-ed$o668?g95mh20nTA6F|oj`(a>d?$`=;ooM?r=l(z3@G*93K-ir-{C!<-{yBcqY$$~ zAdkpcfW`+aE@LaBoI|YbPM1%&JjGxJkAJI34b!VUL6yO!+=vG8TvuCs7&}}(1K{3) zPf%IeY8Ihp_=c`Q(VsYJf(1w60}AwZpY}4em$I#(m)407he5V6`-ptRqCe>NgRWa& zP~nzHHWBLF;bp06?0(#30<>Vb>j|}f`p2Xl&U~U*pJU&sJ8tzM!U(M_hTyq!njDne z`NE_u?hi=DO%3i4T%}DVOSJNhiclgx%1VHC=cK9`%OH3WRcxFgLP`pGX%(j8BTNO9 zvw6_j?z#BBFFE48MO1H3>3MkESm=swo7>x+xf!a=4nA3zX4>`)I7wKh$k4OVTLuI> zsjF2Sw)dP2>nB~z=v)dEV^N*EBYM)!P7FLSDs=mqh%m#oH#|s{6gX9 zXQN{s4l8hN-;aTctuNS=kK0cHK1{&c(R+ zF_#}b^L3&4CzQm_#dt^rPJmd5XP3ga6FG7}s)q9?oNKKwBF!ukTMRq<)%&c-*;s@< zDhPCWH}q>-asSMxCDCy`0}el5cwEI(`qSQNuU=rxnckQ+)XPE#taj<`$^I zp0js3zlU6W$@|vrb;%;$Db(8I^-CuFRLNPi^_FnG{XU#UN9cZD%Bp?YYc6H4ljI;F zLC(G$IBN3wY_WlaZ>Ng!5l78hzi4o01h=F97PnR4Co+Ke5sL53;;UaYo9h z{zu0%3x;cUW)VFzQ!}=WjOgW=1;aHvv*^C!H|F~u?L3Py3*iII;-@(2m>Yg~8{31t zTKE97xNr|{`2LzC=u~E9Z7mKrCw-PsCmGxC@h=B!2V67Sf|;kysXX35!fKaTy8fKYW86rZ!9FY45F!xH`ixrc>s zn7L8)Ur{lc@=S$%W$hH~O@!;;V%bpHY;^JLW-T8OO%q7$X8qYJ!H@tk1LBWNZJqXz z_>@Ym+-L~5Bw(bh&VklZRfEAbgK%Wn$b#1%LLsCh91+5kbi&Jjfv7w)U!~Se@q>Eq zUq;+qse}@|YUVbTTL0uAiymEHJ=UZ?1I@j(aF`}CghJBLq|@tM52JgW1hI!zGcQ6N zElE&`Va%(V+0)5&&`iXwKYSRbP2o%;XU`g7oG4IsaFlBH*}GdMb($Umc?)@1efNce zlZ1N|BiF>w-?$`8!ph`@m-Kz!53795!VI`vJ^PyIh(Ip4()O+6qd8MYHT%6HrmXy~ zkTwOyefS`;;KP8)BKY~ZkVEWx*CV9P)=}Qs%=MFx2xM{&Q_?!~3OOCN`jRfQb>?UE zqr=b4e(20G8m8F1fA}TnE;SRNyG&W-*AM;v%q@DgCbbm<_v=<^n8?SkkL_&s8peWo zhsx%n8Ns&)C=OQoE?wJJ47{%bCZl;DPYxsVajOD8tO*~dgu!k+0XY3}0B&zD5^G&@ zly>{{eG!|&pA_WzA^H*@WBt>*+yndJYO=-_l!=#8f(mF)m22BmReMJ(!QW}ed?$Md zR|6W_eJX0$I_nXTQ$}dazsPe+{4}+xq_c1#+N7kbdp5i3NzofVp zr|fm`N!c@)I_n{yl;4C81*H5aoVaSjH;=F4`B?~Hcsp8$7^$Zo;Ac4O%`P8QY|+*z zec%$$;zjIs0MiwIuhaPo(tkl`|1HHcfTe86^a~tq}HB0~9KDje|?gsY#gXOI}AL2h4 zczm&c8A=YTp5~LcfkHGP4g7=gaFYMFfAX@3-Trw5d(=6w4Y7Gkve|FyNR~h(r}GhB z-z|i`J40=cAS1tyuMqL^HjwVUf(V3gudG>=k?th=(|I+nZCQMvr>Nf zXyxS-@~*K!&Y@f2?eBxPKSP0V9TW&Rh&zA5oh#1&_a5;bGa*6o^iE=5b{l4f`T-jz zvjFOxwh&Ra+c2w>AFyGv0MKfd>D{$Vo$Yd?am8jAUF8`I5O!kyp8k^Ic5aZ*9UAG}2r zhGW`Q+^i4$kCJ%@$OD=j?({7YFt35z=1AQq3+2o^U=ohGn*Z|_(&KP7b8{>D8r{Sc zZrXVV+5KG8NMGaYdw9CL# z7jIn>#S}GGr=7GEPcR+B293ovkQM-+d#=`*DQa5c7~`FWl1O%H_-Wiy9O4xI7ZP@5fIivc@_MWor!%r<3Mur|eka@smW4f3T-<9tc`YTU^?2msfi z9Ax~YS)SYg->5Jhyl*Bn!Q;khZT6KyhqvF!?i8loROe~y^L)>1AQ0eRhLYpH0VDDh zKgD8FNe?lY8kQZL(kyWJ!yJbd8i&V|Y*DD&DF7A!C_cLs-!?fyNOA|QFod6u$`5}U zs8H6~`H+(0O%YmcmCxdJr8%(5LVj3qj+Y-!rb2#zhVpIekR_U#j<|J<>O=*Gt9iP8N8YIi*HdN@{#8K#A{nyD1NfDsq`;&4o&v0 zwt`+X01>GDk05AMcQ$hPEZ=}V)BtE_KL)(uYE$W7v(6nb=iCx*NBJoTN1TWD;M^3& zOK4ATa(d;Xczr8kKBu8QNJ{GP-2$A8v+>sH?Kr4Lq0Xo{b>Jz*>E=4$55pJ*p?soOaajK^7s<{P)CO+kp}GCr1)-=gzoVOYEo)v4zXgNbBj z;X-B>M#LtWg%Podgq^mC%^Fq<YDn22h%u1tLi<^sN(?q9McG2=SCCnm5A}$#);N z?e-|Rr#W!!le?f>D3BGq&<65UEn?dXtj#EX*teWC1be-cM3eD4|HH``w?!S|VBz z7IeSzA~mgx5+GV6YG137`J`HTpHwpqognrg#BjMI*(G&1Og4IFxg@_uPNe&E=!2}m zvA7u?R7g23%zLX@kn|H&#wC3VLPTjwHziM%qnHc!F%2cMZ-fCG@frU8@C?d`g%Wqv zHy3FizjZ{A&aMZ7bg+Ycm_kgp#P(8CD*R}Jd^2kr4S?$x7^^wQKh5Wd)oh`VC8S+X z;$JxN|M>bAxTvb_|1-=mGx8ijc?ik?qWC}uPy{rc;bDYkh-RW@4DylyM@2-l9A1L? z2n-cXYcMr5DpWLlR0E=+prTn9jSP&8ywt_=cD-G1_y1k{%$b4i{r&ya%;C&B`|PvN z+H0@9)_1M-H7;;*LEqC)9bH zzl%CsUyvYeFtA9nd_Iz_o&__l_~H;ow`(%`RS{}`h07a7M$Vg3`@80GDXahIT`&=4 z`!A>x83YnZvt%#=!0v_Rl71z&8Ll{^~wbU3Tad+lYI=8g18w8L5N&a&i*|EVdMuW z{w97BCeorzQlazS{KQUo!g!KSZ_E`7VS+|i+UnQ66=a}7G6_ZO5C3e;o?DTmL9Y{E zw9{$6qu958KK3o%nt}^{KC%THMV|{WPu=k5r%?jeR{XHNGWHgBXeIM+%G}W&iz?=n^r#{QU7gdJ&Thh~I0R zdaZ3R2Na%UFJfiQUW{HC8@*2xBc%z%id|a|FD&B!SJCCnMA1~IvYvK(0 zbbi)MqFEz~*iP2QciiyEhA(cB$wb~qLPyfOaUD!18|i4#I<%u5(Smm5BYOYbP8hX= z#hKdC0!Bc8JPX67v{>OtGpzVpDRAx@l2X;nyTJs->%w4fi_BH-|C1zR$)JjRCx?Lv zwiQq^DEu*nEK5yK5v<-PLR*fe8mXy+YrdzZ)|aEH`8zhkXX*(GG!>MOI}R89L``k#M^nCQ z_YVrc!?ZX;j}iSK#ZR1TjAojJj8Xkjibe$lexF>pH0?;x*J9*=gDP_uB$eV7J2$ZGBm9P=9Ro( z92bTEd2C{=Iuy8m=&s^nwO4lg*xpQQ?Xu21Nx1&!3D;kFWw*$_5PEE_2AL0}q4Ky? zWaby$&hqF|DgjO(AxSw2qnREFwq8*jy1LtEs8!)W zt3%66sO74ydbGUGsgCxo^iQkgxWD1sMZ#ly$sUA#FVnwg;+W~*Hqo+S_<`T&>W~2} z_ocKI`Kc){C9h4(N=>792?4**InPnqGGO1!bnO0XyiIRII*(uYB&60oZgg5o+EU-* zHt+hFpLuMr4`A>u2Y766aoYr(@4ZNBtxrl@bztrfp&SD;%s+MUXTtW|=!r8KdREIEBFzmNOJ*clhfzDi&v|H|tLm)-{!j_Z?rNZj*7SE; zl@$xVr5Bkmym43$IeNlKPlq7uIN*n3Z<~|Un7{|u9i*kbm~@j3={g#V3JmTehY{yZ zA>1{+P}ZXVR@~Ui)l&|GgX?Z~rE<1*wBCO5g>({$d3N_7myuj4^d+VBZUu6j+1;=v zsr$*Bjb^)oj{~zSbQHdgt~gG@2iXsXy;0Q@Nm;&>Z9`HW-KLW%8^`8j+8bI?T+!))~Ai)OYf z|Je8f2}^gz=JRx%XY<)18Xj1V+fy)jPN%HT(Ppn$PP2OX+SPD3=cKNCY0Ziash|~X zzrSLBtGq3Ct%Xk5EY@Ae{6>za&Zj;0$bEm6Wou{fo_z%nNNw-&2qfPSTb$p(5J(D- z_X4lUrm_j`yT&1V)r_*nxVr8Di`nW$iIS!KqphlT zFpek(@S(Q>DJIlVJ}cN@WuC*w)nvwr8q3U|T_*g1cE$>&*{2&lVH+qmKmH%ncBbdaEDkJgJVK0_Vz7t0pr zlh05ng5dk=P{6lia}k^9J!vhq)dd$*(F5mtO z%-_{*axUN7=v+Y#N48)5Z7=+=(;4v`24?oGD3IVB3a0 z;0$p*QV1S^3A01`@W{4nvoO%Lo^tzVE4Yr7L1qS9d!yt z_+jyY9-zd$iw53LI)U6^u00m)%NzI|cHrrD_$Duy$<{j{re#eITLsfB^WtBy_tpvX zLJfQGJhz5M&wLTwQ%cGP$OZq`KBc~{ z%d|iQpaN+kAMtEZi2CgVp!Phuz~G+oml?4X3P?Iq5)c z<)!5e{OzT*oR>(SgH1F2PSX)2&7(M(`8W9-ePdCg31FNSOW>ip+*`RBw!7~PBdD%b$Aat|2|Og1d+US1#mAue!T>xI z&|cB}Jhp&7M!~18RmT{F*XsKIs3o040UAR@P~QC&TMzK(o37z}zv+DLr4e_mYWXB@ zyGenze zTm>QKHE8iEJesc*AZc!cHOXYxAbm{m`iBnc2l#``-f7T17S3LaeSo3y+0K@$o$b$l z({c5iu4nJ|UcKA@?6;4veyg9`TXwCteD04`*M2lydw^X+9Wz4r^Sgv}R`+~5D_8}* z9KxAc0>iBwJOi*>^JxS1``9G}H)9D5$vO^)0PNODyxkf+08{rhc8MMp$rr5fudOr#@jPJ%sqgbAwFSwM#EFQBm;#`Ys&c_CMONg~SMKEz7l=^}AN ziSlo#M0qC&&I8o80hqOaQ*hK9Lkh=?L~zt}=sfE|@CXDCKyU{HXHZJ22odb0>Qti1*#5(3Q})x0f7tFmjGndTaZ%N^+AiIdO6BR{Qv?- zl#jZ*6`k}#+PxVdcnt(4mn9nSW*k&pmQ+teq*nT9v#!+>e;3kNJh6i?ax9t^R?_1M zDO{+KOkNURP6AtRJB4(pW|P&uw$f9ND!{ydPvh_%S>69sBWV2JWl3gGQF&258u@dP zq1mejP(+REDr6u#+UJn67PxlcEspB>JbywD>MO-CD^uozhFj>Zy5#BM>J=N2uWbeQ z-a~T^`U~u~XVnZPNZT#K`pibkD*+8u#dqwiYA{835^eP;vDKc0#WGXM1uu11qqTc; z3A<-I(!F()5zOkyv3Ia!@q4-(q#>V!fPZwmMk=fk$o*x-;UT9$()#(Vm_wE zw5!^(Hh@&zZnqrL1{v-Ji=%$*{d57-w`HxqLxe|+{>Uf zl^^}yeASS=d}|%+hJY_)SM{l7v^AO>vxTfaDl-1=Tiq<>pHSFE|4mO^aUqPs?2WW` zgDXVy+mLcef=JSN^GiFB8E<5k-B};I1zfXHE?oGqoGy$CXQh9-23}~UU`x3MWkqP$MaDUjEhQ?x)J8u0FVqAJ*M5SgbV#^lrrA%4*l<9|vDRmA{5mV~H zeJuLy)&+t#s$^PR+jYyM-%j<7auLJ)BzeIiT`_+oB?gM|KobpCbAhpso_iD}8yH%s zV*cBIrF4HuhNq#^_Ei2Og5V4#2q|xhs~36N!5mjG-=U9lji;TyF0;T=bilh4JCIEo zvJiB`yA3@ey<^kUIorv^7Ehh(Gw+}tIzFUX-NwTKvv5~7DZkg- zCoP^qAgKPMsTOW4FC#847|HfD0!%LBs5lFj?Ue}gI=VbvuXW{K?@eC6_3*@z=k(C* z-no~CLD2V5dX%zvj-{c#fFH)D?m@2ENTC?=Pjub8&xge%eoT)}6q%sNsZz6yY;~DA z(yEsm#MAoIJciH05H=mVVHvei_i5COGD2*6-!lzEv&|J@7+$L$^9WUhF%0hG1JXHh zQ8`D3*M)PgnPuc?&9}I?Xhu6z)QRsf8*U`XI>0zoO=saV18C>*v@C^z%0#PO=tvZ? zZiPwxwlf-Kuc&}cY);g`%Iq8lmTQz>Md=Kcp}i_zsub)!@mpBkmnWj@xMArze2~3o zcZAVAHt~FAm?zz25wcLMHo_V67_{{fxim0EGx{-Pr>7c-xjgja*YtH%1axtZeE z=+em9Bn+xHm#!3rPm%p%#9#%ABg znc}Y@yAF-T#Jd}JmdUP!MhK&>%!B4xcyBb>Xf`d{G99<^j>QK_$gX46Om;1(2Kl{X z7ILdGj68`ak28_qdjN71cUg^LVDqFQZ&QN;#nKkLTL;bDrL+1RKgxD9G*I;;95W{E zQU_iUm{hpYko|+22^c!CnS-CPW|%d~(#!%4HPh7vUT-oEv#K73fpJT~brWh>d9c-| zkowc4A62{jnhB5g}n7;vRTV(evV-)R4%ocsWa>FK?^FcV>M#&?f6m)+Qu7L58FjcJ4D_+ z&;ztDB>aqJyWlx;Txnm(E=SVHqO}y^`{FYwZZoA*6qpY|_u}&*W_7Eqqzp|vhWb3f z*9AggV|p~dI3rY`bXnGS`*HQF*y^;^;&L{C z+d=;7DGPZ4Pu|ay6Na70UwxiYa7p6+4_;|Uh{0db+8$i z$=+$8+1mXZKA3VT%vP&YJ4;T#i*jJsrTb%?WeBwwqc=K+*abI0+O*MKB-A#jEm5PRP+nVg3Fp(S%rQ z8t6l(n$3gL?K+wUy6obIxY&_aFM;KS;p=t{@o_yyM13Z1D_iISFLOaCva{@7LmGYQ z_(blINe1xOyv2KdhKJRY z9L|S#^-*Hv<(MyX#?J<-Mz7LxOS_z0q-yJx1z_YA>sN^TO1q=)`x434I8P{YY|iUP z6n?yj1WLW3UNk{X^Qmg#o96i>SyoRwDx>T7)X_>D3iF8@K@o0YffKUzHm_+Ai1EdP zn^Gt~SeE~-s*hbpXp47)%}}-^ppTz}MKWqkDGZW3bk{;ICy&y7y|RLtom6E$kS*{1V7z=UNya z6MJDGlTU>b1zXa+tu{84Zor5yYApah=OtRw8yDNQnN_kw8rrUjmo-CD{zrlTd6i69 z(r+x55p9bLE&qq+k*pCfGZ423wB3GdQdaz1#a)s)ih6OXd7rX{#tmIy+xBA#mqpJq{RC&x~IsZ;WxMihb0$s`PU&<%{J>1-{m^9aWn{B5pQF z?@Yhh|Ky&2OS8AvXZcU8ZdzZS6p&E&#OhbuYm*{AYnUKMhGb_#0?_U^B^T4zkA(q=da0(gMV5j$K~N$u)QNB&t!X# zmcS)svSDfk)4Sl4+WH^Z-X9CLcZ5q^&<`@a!@&L@0b&gUJLe-9*r(UW$h)O5ux};< zJ8xa*t9`E$33l}x1M{GLz_7qk0d=JG8A!JTD?gm6g}s|uzV+=E%eUy6VzPWQQ+c%9 z-eM}REM}&1lW1uk-`vQzC&X<#MGm1Tt>%7~v@$uqN(2=!u%U=16>DF_hhdLr(F!ID zeh2X^DD>@CWaD2gMUsaz?GC>xhGIz#iev!=B_0Uh?u?vjw4Kg@P1)vG`-(}@g_XJAZKk>mf&+7n&7BZ`-142Qf`N382?h%%77lUhP z9}o(1gHIt;%)3We23@d!KnmO=hd^74S4EYo;G$t;fiLz?t3;GDj)XCQEfmK=i;xOI zNRRaP5}XrE8y_kNyj~R0LMej<>;V%rf-S9hJhSg8Z9XD#BVZ4h=5JmW`8;K1h3}&g zM*({&M2UlJ$7YIB@POyr4X$`V z{`rt2%k)pHK- z^QQMt=DET29?P?jM}3K%$@HEA(|bxRZ+ch$3OjVvC(K99d92H@^*CnBOz$n*qL#F^ z^QLqvSLbnu`KYa0va*As&N0(Fm8(n3fKR(hG6Vyle^HNjTZ1E5-_`fM?i6lLlky(F ztajJQDX^Lkj)BfNf*5F1Z9C-pFvmcfFxh~!@R;o6^7YCBJD=c`ai!8oX^oeSjR(*! zNXrGIw1J0gqpD8iPwm`g?qbyJQ4}X#3Bz@vQu?Q=D^_tWK)RemX0AO5k9I`Pjk}a{ z;}ckt--V%g4h+QyV2fS^>+V00hiejS(RY!?4?f2+-PKX%dSfk3TZvtcuuuwf#FO2D z0TCZ3x(J6|G?uv~`%Qw=Cu$XmOA*6i7f)ciO%h*1PYAwhX&*_hINl=cV>^L3ysB_7 z-A)L;3XZlR*{WoTxmqImuEf&>7rN4H7c+pOX+)LPpawz?A+=rK1s}cjA}9wL8630` zK7E4HKia2C#X)~S$ZwbZfNgD8! zQ8r=b2khj>`h6zB#HQ#P_JD292s@6pUmJ#k#ra82HP#4?auP{}kug=P`M^jcQ#9~G zGX>xD2X%ufjt+Q@XS!s(?6fp+5kH@lkVY3()7Awp zIH;uoB?W*IlwL_n*S{Jz=N`R?@JBx}EKf9HdBU5e)5NzDP-mqg%H5I~{=S|Rx}u3a zq#-c81coLk%E>%Pr~oD&wk+2m4H9;I4q?RAeN6{&g(L9!(#pe4UO#VRwUZ1&UxCkBaKROI+UWK--2#?S&|e6LpX@M#m9ID7njl&X&iR zw>Z($SwZ@Ehl4aIkMVX$t7(fzI7XeLOqDMKpt-lYEQ^IXuvAbRV*Lo8jA*3|0lvV9 zHu*V+s|8ckeG$25O4lf9Qy}W2V{8gG-{XLa3BxFC%%CvFE;T_6RPo{<`Ax7$zmGLx z2$qXAs&a9?vNQ;UuLW={5~``x6ba=BImqic&yju`OevuZEUfyDdN^_&v#@F-3#(pF zVbvOzKk89I3ZPu`#({CuygmfXrCHxqIeRGOekOe=jA2dS zT3*Z6H%K73mfJk)W84Rb1A#sP!XR5MY|b*T9WTdWgs9&7)+PS{#> z5gii^0rkY(f-d4TD{X)j=X0i3?AeyGzhGj1;YhrmZ1YJa1IUt&*ffvvR znUK5iDQ*U)igqtxaeKVM`vEW|F7H4KY^Wf%HL z(#7W&Q|8fl>xBys$Bc*?A&WGjEDjFkQ5H_}%_fw^m2N^=q=d}k3YBvDZ*1%~Cp~|t zl>081m<|^e(n}>B#~F)0dymf1vg^p3GY~hMlAM*jHfxPGFEwR(27C9@;vD1V zo)xy!BNp0C7DrT{Os?^`;cO*SYj2ORp|GWcVCIm%isDX=pFuf=YGEV z;KYuoNlouki91y&@PGMxLo&98B?EwU$-k+!K59vG#WI%AbXl6)9p4>IETL(CaDTh& zss%_!Q|rM^=0YVaLWVfgdq2k^nD-VobeIC?0W~P``T$zmch23Z$4*zh%R?E?1In;w z0k{BAhCR3MB=6v%5BQT6cMe!d+$r1Bn7f^a!%2YOj=0AQ3rT})i=kX;a$ z%v8S&k2M*#FF5n4i&g+4Zzb?KaS~>w+_BnEx3G`DU3K-g!T#&|?O&g_?`+=Q*=GOE zt?l1*UY%&A=K%+83_XC_l>i*5Se-&WUI*$F;$=a%LyTdep!ywqL%LL2Gh@Rl55toT z6!rb(UDrLLJSQVx>M)Bnp$lQf_fWjD`i~4ln*R59tM-X9E9t^{Cvaix3S8*x??D%u zzz#^ggL~KUaeXMkiF*U=fEtNO=`0j*8uZE=37RY1uG!XODVG~)kbmb9>z~oAdp1q9 z0)|5Q`W~A&X~43`aipIt={VwyV+MpOX-F`f=;aj={Y8gTG39kk%!EgXCf^u*t0Lg#%Q z;0U+9Xn8wYP68U@?QJ+iXo#6WLo}t}?kT~zdoa$V<3JpWgO~Bs&6L8fi6;nZ>HUKkEW7_LYfXXD@>IJ0sgSQ@%^FzMQD(Z*tymwgb< zPZHKk23xf)^H|rLR?FrJkMm}wS>$jAwy>G)eA2Z*P?LJGN+O$NUc>d`91^khb)>pP zwbC)ft=B`qsfAr=m+otv76<5phYp?tMvL^X_**%bHBh z+isVP5*v8vL?^zWy9=#}oSuyDNJ1 zqd#CTpu^p20*$8v3uA(hW}R$wQzbNVsmQxmwsooUv6k}D7!Fpn}FocSJK9=!0Rj0se4IB zfK9s0Z$`fPU0ID}JyG(hP&Wu~lWrx17>sP8zA8(-sWSpaZ>;5gTa8=>hTf!(jU)=u^KR>l5dI{76l2 zoc~mGOrD3c?_LHYNZ%U*e(`P6z=>(t6Obc?Zs`v9iOnJ$|>O>0MX4WwXABM-3yMMXaLdm|SQ z96+_{W)6>&)il~+a31?jRaww7qsRcdsYVt~&(SH2Js8USwJXCk+cMk~GM<50o=Ii{1gfqYfWTk~R;J zq}z%c=(m5U*lwtkjv2%L|Y?JMR!4D(>y_g`1AI8zXq#^>5x3CIp8?TvkILU@ANI*cxE5nLO-w* zdF2u^^3FRkfZG=NehM;Kc|YSede`8#p6^~8)o403ry zFMNMA^TirKO)q>+@h6A`iiaci&}7hd>Z+_L;_?U0r{8ogd%CEg=C-BT``T!1Z%WZbTTR*K6+u_}0`Rm27!)^D|VVU;b<;>PyjvzPrg)|1Ts(jiW`_Kn_dk6ObSeS%Odh@u{iBJ=~M%Wc< z!dqe#hv73n9``!qN0!ud#*J@rQPG@cW}T)FGIu4+ z;4+2uG-tzd#@9ACKvCfn8mT>lZ{Ug#CSx3DD`mgA)GM&09o#kgFL=$hM&i7Mg;aO7 zol*;A`2VmLD|e1*f$55^UY>3ze_`BWv!|kc`#w3w!alsRa8Vfomb>GU+DTi9*G~8u z4d@OBXRv7VLJD$TI>c_Vw4?mO*z$|d0qwugc0Jv^Y1d;zDn&xGnx{H{6|RVKIGW^| z5`+{5NDP-qE}Hla#v2eYaj&5HGSN=*ymTvuf3!;S2{x$b6K*mSCrg~W_zy4niJWFHqhWqpR+I!|h>xTJjWzGIG*-NXrHEI$ zc2i@5x4R9EshtcM@aPF60+BBetmHF$A+;nMjy@C@gc~E{9k5}Z5EO_v%Li|imo5s! z%bs8ljHGi*qq^iD1qB~vh==9HOOp&XcOa#TMeb!qp(cx!#!W@{G+aD~Cvfb>$A1aJ z_Lyf4F=q$C?ztE% z&FtmeG`-V%gl#C?-LA~TLaz6srNiJKilVr?8*M3|-f%cFPw#63p^!OtIr42d($SD) zv+m(LDTUUcjIFEDEpIX$k)D*fLQ6*7GqzOOHtXnZ{Gl=OfBd*_grPVIe7EHFJZPDZ zjg%;2TP(xEo=%aW?k)6pX>s5OqMx|0I;jM$e91Uev=x^BKkkvvTY83Jp$ljk5~knj z9;L&p)tC;an{QBAXGs*<98XtD>+yzYJ=4zi`iao857*x{Eo#vjUBq4SZUj%5N$ZfA zyZl1|f9Q0+5Ukb|pqY;!)!D_zEAYkx{U?A4c++|L<{h}iG5;Y6@hA+0Wf$OoR19&~ zP+4c*Rm>Y6L3imXA<6AX`vGDB_6BDgL0i9|(*Y%BwoA!;d9MBuM59u=oFv$}$|}30 zhJH7)m*-t>GOytGCg;)McWiwXjd}Otg9e{zp|c$x#1B6M{_jgDrmC_|y=*kj9qM*K zaTEA%@M1Wn_;c@Gr^#`j6IjQoSLXeMs=aGF5g44sv zQ%jwzlrob&-GePcm@7an3)A7It^rwHxrqhOt2&M%S+hA8})LHj4HN!O4*9yy_RBEph87L?(#~PG5>d@%FD1;SeTiyv z%U7giZS!kmlB}LWS^PGU-yrNBqJV|@xH@Xm;tO4a$@(-n*WW7pfE^CNBB%4{S2Dm(22C( zH&6Or^q3nakAS}EJmQIisHVG`_kT|oz zvYLLA=8R!G8^e9r0Pf#i$%?n*X5B-jV|f7-9hNv&?0%9JZf+WIdGd6&Q){q)i(UbA zs^lG5`A_#GId_xj>CQdG+#PJPbw?kz)0J$;)V|kz$S!^^)W+hlr^bpaU4Sp}nfuHY zv;O!)HyMd9B-=xHs?cNyN9zc zJ|;KGchVGtR8UNRWDQ5Hq;%{5NkYT?a-i5>>Ve+gZ+>jAYHgS5Xw>gZ{%Ie-on&wL z+E>A$js7?Nz%Feo9+=QrP{(JA$Zj0IMe{BEN;&~Bpbbm`fThd;v0G2!^lOGgzszwrGT`c(->(609tFl)6Y)e^rxa0~z-)yKn)d{a z7kb9}jGTol)s2D_YTn57eci}38VD-CnN%Qy#IQQBlir5Th=m}<36CX- z2q!7z#cNY9&vPkU43Zyd0fp*>#Xo}f7`O5>ocmjn{H&KGO*lSemCB@O8Af-na>1iV zSWgmD##2c%*_nLt{}PEY^|ak`a#wUa9NLS>^TOzmC7WL;&q-@lIXGWKjtb3{y@3~= zlAJz~+Fq((c-roX0&$O`(N;@t2e(C25c@s~Q#ZI#9mm+HJ9`6*GAX}o*LEuU_?ge& z+jbXBCjd}!EW9Stp%t&Cwf6fGaYnQiN?^VrSx1Ik{L?BqE*f@pA@3}#y0F_{5dlbG zy>O^?X8a{liC6q(k^j75%;p|~-+A9ju@%v3Q&Lj%0X7g!;K4A?1qdeaMJ7DKAu@pj zPjHJ&;L0ytUxo?X3K8g`5uu8;1)jV|C4l#+Aac#4dlnBkd96C|N(~>?uDoEIJx}nc z?EN6Ht%lN3sFAG{#fIQf;S;Kvb5Zyr2n$|#is9`-3~!?+eCzMQxBk%-*co4j^ZhSy zzMsHz3tay(?*tjg0LMb^+Ih1)EIH0T=Kb#Dnn@~JC<3S?A&s8@>=DFG&*xSgb7UkP#83!lFkxr7{thdwI(osf_#Z_NmYcTS6 zt2Et>H78Z z;ZU>C$2xk?RG~_eVF&lUroAz~Acjb2JL;OG#PD~-+AozhaHL#>-dQTGi}WIAZr!}^ z%?wf;!5rRQw#;oy;X1@&rgv^MQXBHs?456>cis`O#>4P>9JO~4o78b(P<{k8C8P8y zTD6`e1O>hi>+P*Vg=`{Q*0Ja>t=HUt zV_{@g5-1R=cf~AhoM6aX)^*TQuJuY7ND<%{#CaGcDyH+yLIOdSzWK&1hZZihhGh;9 zjRZTIG^z5^8Pu&d&6oHpeTRa(*{=fXnsH%uqZ=`x`IeD7)9dpV|9>BhNTR+2tA zH(9JS*649o$j6;eLC=@#t)~Ye);r}+^4nFzE?h=kLMr$WUUWKX%x`k?!)=>0t=@Xt zk0m5HwJG~?{=7RokI>;h^5GHv(K`nZ@P0TI3Q*L~=bdNK(wDccI{~L!s8_(7Cxj1atwaPq)nkTRTN1aV3 zOXs+yHf|d#+O(bgM#AUw)p zjO@k?&L0!5@DPCXJ|qY=HyV}h`=h3jXq~pJz%*kY&fUd^oEfLiE7}z6SvY{zNS^(k3l5#8g;7*;-b=@kVr;_E*_>Y&qiG;wnQLOxx-k9uL7e_~O*jMk7 z`>l~?AAhdTiIOzA@6F5D`sz(v%I4pMa&yO@JH-IA?ofXp9|fR@=r2gKZ#<5&O*t%l#Q-Lma$6`oYWeevLO4mCPu1RM$`yF(4r_P3 znh+d)=d4M&Rvj39EDdMtpl8?GwbpNIJ#R;)aXZ>3T4DJ@1_!Q%0Vp0wPN?lv zQs_IZ8y^#97VE~_SU0ABRizBT!qRU9W|qJrSVOi9SVQLV#efd+AZuR}-~Vgi8gi>? z8_kKM?Ig5{OlwHL(h1ljJ za2kCZ3ZYXzahYshc#8Rp41DrBL6Sc)$$A!C8rWCBm!DG!V)VJsTexXjMMi;ZUX$vU z)r7v{f;#KP9Ydm4*`DO*va$r)Xnglrx?KN>Kd{_mBIP$duVutD1eh=g|Hq8 zBfW4(y_gv{>y_+|f*_5Bs9#iJOQy)kaW3RPHjvR+esnJFLJDXXQhqiX`-wt-+|g%> zG{S_jw4Dy0OqIWq5g=;3roXsxGQ0hU2gsVloGWeBpQH1#7aN$qNe=?+uJ0ja_sVjT zkzLeht!x@S&v1GkpHuk5q>>je7|}-s;O4T94b7MGi4;(9o2&^*ME$&MI$1m;eLP`* z!Nh5&;6}D`OQta^9p|u-)QBDv3luM4Y0UWLRq6T}*-w0*rxjPnLD;TTxiT{&)0oSt z!;oe^30Nw>G>B0|$G2pQU7efs=Au%Jk78_G3TMOddFN9!wDr_*LTCNH zl+F_<@V6ocT7B0Cz>Z^&pYZA5V^Arc=Ay(I7-$T5Tv6)6zlMw3uNZ;zgUJv z%Y&BTfTPjoWq62C_a}`uA3{Qd$GpnG@TNs)s$`U@cuuC=!+1r_%B$*!N*O+u^s?yK zc3O1f9Z%8QZ9p@$(RyN5==7Af1XH~LzMjSr4FxkO_;pnRNJUEn8t=#O1QeMxrrMczVlvpO(m_6rQCr~I|vI)|2=0eRF0N%aOX z(#q%SM3C7B2xFDZYu4R-C_)*p_X|MP(^Fsv5O)6!d?>;}ruSd?6fXmUtT_7=(9eSX z=EHa<&jXm=FJNZ1OI`zJmX(j_{EE=Crt%Tbuw-f5+=r$?fz{dmOt#E3Q}|6isZS4i z?CdsPbEfmax(JuJq;Mwgf9WsT7+e?0Vy3^4ZpCs>J|90a)mx3KDba~7 z4I-A>Y$uPX^^*7SdCO$M>yg?_%=DU8fj69+t+y(Mn)I{m)Ya*!xxp_lUzwe)g>HlR zmU~|&X&FAYbMN@tgPTud%NN+wmZnd-h88Lih?JDAXGzQErSo{&-jgXTY1yO1aoD+5 zuCSfCWTD--FG6!rX93xr{@8{x4M-1ra5sJhdNyG;a)7ng#~l5!vDPv)&qgyMr&0Frvd zO-ftjou~n0ILSj;E2##|0bYr!PC*Tr-dbL{@<$CAP5N4cq%iKyO2l2?p#0MJc6rC=OsIj7M(VR@Cm=l+osjWyv--@(xyB> z`5F7EY?ApQaLVar()Xkf(sZNKb5Uawfm!#{jia-g)3REQWVPMTY9F0_E6q&=Y_qwV z<2Sb{N-0y!qLg;C^jh^iQWWXkYF_YEdKmJv^(p!nC2AmI2N!_qLIEg^-CbluY=lZS z89|wG|Z~rvemu9U)B{``uiM zr*XLaV>$!8=r=2&(LIDr_zoIf3pBdDK!ipC5&9kyZmoO@?Y{z3!DEi`*D3CQTOO=#f{w4ekE^bZjH6D`;xpVm}gm*Nji3Ad&X+pFx$1Uw#-@~l&EjH?S+C3CIRZ? zFrFmTE#5RS5z>*Q)#}qSw!u}i4Q|ovBs5RynUFN;WzGAypi-|OK)cKNuCRQSLRW&) zv(ObEg4nd*rcUt!R5bS%#oT(`JPrj=5UO)K{_9-9+|K_x2P@ZqofAFR^IzvQ&-Kzd zVF}nT9^V(qcIlH%516}KYI#8VnU7{L|2%rY^V82ww8u6F{^Zi|*iF@ar-y|w`T)Xu z+JaNaRCDY0JUl0_#22`hM8T!su{Xi;J|c|tKH6M>pY>9X>UKlMeHylFfg%w|5wxozZJr7gN>x0Te8vP~ot0&|MN24xnH>tZ-nJtSxQ4Kolnl$WyC! zS+10draa>W?`3CVvsn)-p2jX!Ixt^BpYRBY!@>TYiaIgr_v<#>WZ~_hU^TA2Z45O& zHDcOq1vE{@A?4pA`dU8yYm`n2;w>*_GB-T4_myfIR@pmr42p=COf^YZ{r1N81$7+vG1{APJSx+XtVRSe`}d z$t#;HuvIDMkbkV~sef4>##lO>9-yOunUPKmco-FuvtmU0ZhHagm0V7ochcZeqG*XC zKkh3F=`7EW6^^0w>guE3!R3x=_#gq2`&A{~(GQT?$F)l4RJg@94>O7^w{MrR+jsI- ztUBTLAp;7AqbMg%%hoX#^)U^c?{!}qIOkAUyLyXG>54+tp#Yinp8UbA#BX`Eibd9q z_>G1}O)g{)*#4}>NT$MCJUcbRvYe^k>`l&)`~ z*I!_Q`?9A%?4F7N*SJ%ugwtK#Q1C!Tor?|xqG`8}hP-LFoau2L93)72)zjtpi;n|3 zHt*bBXFVi!I2geG=B1s)JQl}+pk8rT%dhCd30F?ozqrxW(w z+nU+4{DY%s5qIK)J%a74&4{nU%leD_+37@*<1hOBIZ4(Zu%PI!z`@dXAQmTP!xv$2?IY{d1`c{iXA% z3qK28nBx#9HH6YDjy~#7fIHrTDbh48$L?>-j%+ zi8^_xNadZsz*1dwLgFIjuCjrcb9Ifm7x(-(P%qqvBvLO3Kg*_T79w!O^20P;k6b!q zr_Svn(KFwV;X`>TlH5ebiqf4`f5%rNZ4qFsD71=If=<{U_J zuEib9xyE`D@p5kPefkLwLqV)n4e>^E-gEh6O6_z^VvV1QmEgRj5zU3}#fG7=Swu@q#7Gq{`|2?nsSSEK;vy-CwYzsKGo7vaH(Bu54Egbzl1kAFTUr z1`Bh&#nBAW`0Hrv?M@qKnzbiuQwZSc$WWm1f%fZNO!U@R+xyV=w*y$qR^x#5`|1gP}pM%`B8B8 zp?$yNy*7r#cL%-RwP>iGU{WN2lgChM(oOk8mZEiY-mYHaD`9*)h9Iril0iRbImY2R z^ccB?o*;yI>S?dn3v8ZycI2u6Vcpn}OhZ;$#q#B7Xx!M4{bm`mEpIDm$b3fU(vZaq zLlzPaL1&7P=R%VTNn+4#s{9o|de3iY)8dA6yMs%$h9=L6Pk9J8G#0XVw6CA;Rc*fE zP0J03)G?1r!RUOtVTN$SlTXnN`SGxtz{bO{Wf>arUW^aM;RDBm+d7Ya%Q)DHuhK8* znD}j)WyCmJo}zFt1r}OiOu4P`tc~Qs6mJcnu;nQt2UD~Tpx8Q-wc$IM;$K9O^c75m z`eAd6T+@8Qtmg+o=-yTv*=B7 zj(jOSEs_tL4jvV~{IzB8zdVRI@WQkENt|-@!G!j+m$_m65(tF4)7$M-E~5NV!8W8b zo7Atwa+2}oJW`oLS;)#{yhGkUQ(pI+uHq56{P@CccIp~7ArogOo5xRO8;onbbMLWd zzfH&V4(Qo)y@Eql*Qd0DuTPa!|NUR@64w&vv< zEu6%bmJ7qFY|D&36-MmYIOZ;K3p0hW8O@f9#}pmhz8H3Islw_6gms3!9ZHXwG_54t zQ0kJ=>Mp)YsgHuDrAl=p<)nT12y&qE%1PJo5=B+aL_BRho>ov!Zn&~K5ydg*ak=AJ zd<;-dQlOk=xnDjiY7LGCW^8}Td9rM z4)Bh_@wFnaOPW>IiFmfGz`dk;WH3v1)(7@!7+AxH7+6>*NcFfJNZitJsyGd*$NLmU zvt%~46c$DSX6~~5c-vKICg-Pvp2k8O@~D0=FW0dX_~~ z^MsCR?b$(f>&q!ULBY#6QL3IXfN_e20k)jWCkD_4{zeR-#33)zkAOvriV(doFUnO| z<>JY7+dYu+%Zl8)ANpIo3_aHSagAfPmY%GLINtnZf_VO`%3*ai@bb|g2KkAr6lBnC zp+PnIL$`cVMqTI*?58fMamed}5RrOgBb668I}7v6G%Ss7Y&&DT_!;ONf5Zo4nb2Ki z?9V5a@WnT0New#E7m7bzVd?1O64uex3}}4ece zpoc|jPG%lx!`9busW-9He@o5n#|^fFf96+_26StgCnj!hzod2OsQFdE+-x{LM-yDK z!X%R;Co>Oto$V%Sou(A8bzl(^CY)k>IQ`al?^}G8l7hu1+?z}t=XWU&-kq3+`P+4> z8qSx(43pxfA9+hV=|0Z)PC1;yAz2&VVf05M*an+zNXw-M(3)eu6PYohhFRKB)I4o=L=y8L_WZ4+;fMAmaToAeNpg zPfly?V)ed$_9ZL+OR+&GQv`TxIz;#~XQTXp-Y69no@q3w*XBpnv4k z&NEbyzU?bl?F(*J%!og>h|V)q5KCOqaa_h1t-WaoY@R#GhWyFOlPCm`HIic;Uige>k?4-~7CU~6)jm2qxMq8F z$F2c({1ZA_M~nFSK|%&2r@UOU-kYKdCsdILN#b*c2^oxv4~(;b@_cL={GrNT8RWFKz@0L@$$Wr;)4gnCou=aVW9ta{q3 zEU~PFdClY*TJoA@hYzjiZ1EF9CMJl%uUV~)HCQmncJMl~?;3~wMn#@4UE?zk6(`X; zMYrkLEnaPlqUnS;zRIyq#OgGAS>b7e2N?}8^N}(|99p$S9$AWDQRO*5s@J7uesPh7Iu zU$rMN%4L7+?(QM&sjW)uA5Wwjtk&KRUepL1IQ`QqIS!Z<@a{{Dcjf<3Sht$E8r7v!^URpxyzjOuDWMnh zMLj0oMnx*)*YFzvQ}9bR{04qsxc>9Lj0eDdyOX&cj<3A@dUwi|OIZw3QPL8~SCj5R zC6e;sY|Uy^B0uOAEQyEW!Fj?bB9jZ%Q+`@v1?&FGaI)?rR|Ngc z7tcsay%QO*b9StUA#NfJA}>l%B?f3dZV#gYJc9~s@P(mO44zR2c*cY8oe}(5)ry!8 zH&Gj-G4eE17!C51C?4E*&X5M|Z>{!70$j(GH$s2_euGLI=lfxm|0~S)Nie1V2wQsy z(k;lw!)(6{X8X?|;!QQ`S#9Z}@k|jG%D&&oS#0s_on^J9i^gxGS#P1pR9Ga!M3(=! z%Z61A+fR#=3i*$h45$iN&DWN0h^)eoj$`maD#1@jP!&+HF!vU2qTfE`m?F=@fUpLh z+Z9Q*rAMY*jZwR4UXXDrv>q{kOF|XIZ5)I3Ts~Tbe ze()$JGEjXQ!P|zZaVY4lq8h5|6wF0WbxjTQue-q&(7#j!1^%R(@O7Pi*n4X}XX1m; zk#2Jjx}9KRH23(+w({?5Xa*iN(KVYPzZBw5d3<*D_VRb+8*e_!TSfw4lp! zG3o)dObzEeyR6xmI--s0l`d3=EUAdn$v)cdgEmYzh2r+9 zW|Vl5TAu_+Gb=6)No)!~8nVeNSSx`xKQbV5EPd zS#Zg;utdi)CX9DH{0G=W@cEzr+9{aR(kORKotp(M--ja0lE0mJ90AbVDBR_@Ko3v- z$O?$ze$TXEZrBLWc1^vW!X2YsV&lp*Q_{tPDGl&yJs5y>hh3a?wh&tH?b(naaItipHy3n^I5|vd% zFcUuZ^rv0f?|Mg!%pksVx1NV?KM4dBM=Y=IM<7e_Xa5)L9UO{HZ_~HF#H8^y6LE*f zZ~2-kj5CgxZ(0M6*AHT+lajjt>8&g!j)}EifSX0`aQ+hmENi5!>*cM))RE(Bv2Z!9 z&QPM|snfI^YWvaanXAU0$&!o-f(X*-XELiP5PjFCh!K?+evHHW~6U!al3Fws4Qi&`@$uZKbH}M{Eq5 zTXrDuF+<;EPX5*W9kjJUR7LN9G|=!3R7`5*hGaK6MNn$?}R z5nB*RJ}b%P7t`VOP`!w5l`ky}Cx8muP~*G>C~Ly7XgLiztq=r$w1VR8PC^LIehR?9*HDp6=6oDx-SZV)-u$Y#gGmr22}pIMK*XJYQ@$ zkW5_|6-b9(cSr|Q|1_F*f-fCfI*RzXw|WxzrE-3>gQeon#H4T=qA^<4tJ964ToBFD_%ji(_Lyd= zx~lXhZ5Vg?iBYWnA#E71l!(4IPgMD6zF0jKd|5DeIaNLPX&jCRYz?3Ir@Qk7Pjp`klIGwz%%a6PtLP463!Kr9*2GET zrTR0p2KX*-;yO61*);&Eb+1nC|6j96d~j42Nvowf$nheUt13LHjUcpPSx0o?2&nwi zI)XhlAn|@aBj3|{rr}-B#LCxvZUk1oXZ!o4ip=Dxkl%?`E5N7WGRjVJJ@;UykbXk- z#!g(~og2mEFwvNK`Qhr$uLWLiZhdhjczMnO5@k!Rrg9qMWqtUBG@h45YnHWfDa4~z z_uG^J?GP`E)~r$hjW>{&MQfgK<9e9kpKU@X-{FhwTMSb6i@@Hkf!Cl6+<$Yq@{ z4{Fz>s!=2Uzr?dRa^kE}Xd^2JML@*zSGB&>MiAPtFjif7S=#@>SPy+41^J#N#_ESO zdYJPxRG{J0&xs^A)D^ZldApSufShYbJrHdy_N%=KE4PwTYNSbi#xe~E-4N=XI=ytDIpBxNZ&Z4Iuy>Ypc8j0(oc@4t{GH5??`upe&Qsdtm7 zOLxsKORB74C*5SB&xsx&k#0zB36#LD#4E&emOhrekN0l z9_GG^-iq_+nRoIiQb%Ra_o8LDekh@|E$t4X$g1msKH`*p=+2C( z#e8W_?p5sNmPk0*UY@3Xfnt}M*4-kTuTopp*7r}O)>*B&4M@eAERWNMrKjzd z=_W3bBJpQxt4>;QHp}Cda?zf^B2Nh@^+iSytW#9BB7GgZPC(7GWTa#?5wM8?@pFC; zov?$~PNi<>pGG$}KcsHxMp8F!`q9vBlzfk!{Tu54$KIRAMOm)@-0!G#1|bjr%*GRQ;j1q>G&8}hAHK2DW-SJv&6ay z-#(H4tsC+D^O1(*`TI6YUSwrL-Uei0!Q!k%hSKc3lIX(d!u-OgT>QW1o~Hv@y-T*$ zK>UAg+T6XtxV#iz#?Oxc6Ank)i+_|3m2CVn@HIYIB>iMaIlOf4J%zhW!SF{fYlD}| zF76LrF6}X3uvxn0dJnG_C_Dn0Lc^0!a$YV|7lfQG|Gq1W)c@2mciQ?EJi%GWK?m>b z*6(?pJ#|b+Ti-=ba5{V9^*)>SK?9n7^nNA z=XG6Oa(ui)Z+2sjb#c|CC0P9FD{8a@665UNPO5bFA zzfS%txK{yi6wY(yE;-$z?|QdjncjmOMvr$jaQBlby}EGDJecz>;_BgvrNKv_at?6V zu~b9?>5vR&p%HvumSBqm{<7D(wFN;+J%{-u`kX2v51_T8Aq?F5<1C=tqAwu4)^${v zFNl%Grh$=BFa4+*(5hnz={wCez z$$m=Y)JOlcOCm$hP?EpA4!SC2P#v5U_2Zlrl^m8B&?U1RwPjsD&K7jfOSOPB*h(Ie z8bJRBO~E9!28UR^)cw>T7%mlhH`76GBX`H*N1kpZa(Io1_)Cg@ARri6G~O8j)hs-z z7-&(PGd5p5qHknm)dDCa13&A5D(92Efr|H=eey^_;91U`ASm_G4|3Z$+knBd(;5zA;cTSfNobLYy89l%7fzx+$#B}Ek+0$Qg`fh&iTax@z83g}h@XOmD zBumKd;i;0vg~i6~Oo)D$6c=XZ78frrru%1Hwg~?i8pw9J;D3hmY~$Y7yw8XUHz1;g zn)J3;7O71A-}c;ZfBwlYlVwn6rlXGL`r&u4PY`lP7c;#VIhOH?-%Q}jyyySHD}D=G z&S~e~37*d>eyaxspKZLc^bI%)t~k=|JOb{x_2p{1w!wAjdp)_o4c^~lhxnnLgJ45i zX>Y3O5DdqFj%B^hC7O9H-Ubg&+p^Xvh3xve*Ubt=ycX}yq%Fwr9f2>}IL}t~GIcn| zgO_)?LloD=1Igd~(CdigLlfVW{k@ygu`CxyBq4#D|D?uZJTNm!;uPU|Ifj!E>E^QO z@Qp7|Cgr5n%i!gGqD1xx)D?wy2Gt69p`d-qtV7fDH|F584&hdtygq6UEv_H$X>I{{pwdw@KFk@^Bv4 zqF3O2Vmk?2%I~}bD>4$ch_4jUDQwV_Fs&GK#-dE7s6OE$T{x)q>XRQ#kg#tOVgAM! z+$ryF4bqL+VpSTyf&gR1L8sS$!-^&-Fswnz7pPa4IWf+ad^&&POYX8LGr$L2f&E3R zFZ>6Hufpu29S7el1jleTLJOEb8=24siff>eXxjZCmfYKkroFx^cSrfMFJtDJ_Z0*- zw1YaD+m2)-mV-v%J_eRy%JAf}!=VE}ryY*OAI}fl2d+KvnO`8AF|NlR$09hF`eeJ? zdxZZR)#(`}+k4UbE1cJuZ^F9&tjT$o%69GChS&bW zFb(wqf$IkcZb;bVE)0Z|X9t`-lfAz6IPCS8bp_9E!4m2xrO@RwM*}>Scrw{6uCAJ-AIdA#oJNRZJ{JC z+Zi$d&Q7?Kx}CjF>h!+bNZb}K;caOe$tFGdpPVfXukL5(6~wnUaMk{uZ<6?*!)XCT z6hZ^BOR-Py-%P0fFI>6KJD+xwm}&ocYL-*$Cbfq2TI}-y${@NDL_gdfXFJBtPdwpa zu=1rPWOCD_J6vIK`MEdquyS6;rQWczXN@#VXdV4#jg&0O&dx0<={_s-{4zF>t#hpt z*Ew+8c^+04IAP_U4?p32N1U5EN@;(g{or2;!o;>55W7swg|@!CmW2zr17Szip4xHy zofkTd*?sWZ_ebeMrwy7-8^26p;HSqSHp@SU-N*X}#T|;XLKi{zH*SEnL+mtntP>W7 zu3XwPugSxCI>>n+k`orT%pcryMmyazpO)2cOm#n4Mwi5_Gi-9hX{T-6Ko~$eg|?@K zgeiAFQUN%kBLYve5^!W?j}DB(IBAzWK0Xsm!3a>qC?WF7;FFF@U-N9usZ z|Me$tHb61nsY3>f|?O@cMXUBtQF zGEQbj%4@XjILUn%tu~z0?0;mZlgH+9czfm^c{0JH2*4wYFW`}x;8B+t@W@2)D1+e9 zU)o`IgpYyc`7aj?3Pl_bgRBzT=nr3CJ-GNAz@fXY^@Y<4k>H%{H$doy3kY@94}Zqn zd7;w=#UjC~kA*nDG~}ednKMY1(*OXomHxUhri%4`1rJwPyJT zznQH!*5PuBM@m5f3szyuT#6zQJ|5%&%yo$yx1p`y@avItvoVx2YhigfbJ_`KOy9sO z2d0B85N;biIlyqCH(=OFfT4yfhX)LrR9rR(d3$CzV0f6F-b;w|6YDs*&<|tI+3CHZ zlYgu8O_}aC@~iRcXtxQl*u$ZD^z_X=fjm3)zpl~!;ac;?yg3`>A6WOZg*pRUpZZ0@aKn8|4-Lxmwp+0M59xH!ji6dl1J>4&6Bb->$wb&MF6qGE+WKCVhSU8PkPcMs$vE&n?y(SM z$BwqdCTxox+Y~$QV(fU)D8mPayjKzf*LZ37-F2T-5O8+jv_HW!0@>#6lR2+x7z#*R z>6m{nCt{TR`}L0weE)ey4`&KqmcMtpPr_;a%RRn#-d4&b>5@o)`i9^D)6gguxL~y7 zX}C)W*o)Knl|b-PGPh%2fJt86A#wf17eLQ$JOtaksW6jUb6DIVzm^S}mo83w;10&$ z*tli4*Eer_Xvf zg=e^Gr!|3G~J&o=#Gp0eF-cqjIE98yQ+HU~YyYx&W$V6{-gLEcp7;qi~KzgPpZ$Qwn zH|MPTj(O_1#E9 zkuYSw5S0+9e@p(X&Nm!1QS&z;TfDmUT{r_N+4##Aj7ar??+b9Gybx0wojL|*DvzCc zetef`bi+H)NceT_MsoAJjtX!5H93@QSeodh_TOX4I*m(T+P-Fdmt{0lp+l`*e+9Mt zayw|nJM`4dUf~8QDmm2uy#5q2-tXU?@v*zlPghoq?!uiS7w${e3zi<$c?>^+L)T=P zBmD8?r+ax*Qt74uw9+WovPPm)oP+&Y^4^#2K&Yc(alT7*86? z)6-y6wKufZjSh`~9@{X2#`QFUkrtibNDNA~-VXaAP6=q#QS~`%8fpj&*kV3L>JHcy z%?!0(*D_?mKa;7%{q<95^<5m5THs9>Y!%9>Zv*vTs0d9*Xt5kOhy!C$|{&YfM-Ro!8U`0T! zF^_~;m=TP@*&ZEcPj1~6WSv7`?z^>4zF1a7>jZ@i;4CO@AKpAjx}TJNGp@qgnnwCUAejn)BHX1eM%@83xb3zzl^EY znnul)l*n*fbFD&iwjl6KXvUw1YZFsdBGk;&_M`CY(j%r=(2zbd1E+Y&$=-Ys=ae~c z{U}rJ6D>JC`WiQ{`tNRXgsnzuNo1Zn)NQ-{r- zuAO_&v#sxlfIE|w`}K(ByeN)<=dE&x_X6ec)J49jEDlhya4)eyRSU8eCpH?3ChWFa z7?_mP66YleSBZ88Os!n-)Pi|oR^0Aa;VI#ramhB#$@6?fekX~}jFd4~+WJNXTm!p& z(Kj?_cKvX@s>_Gh<2;=-w^`4G6JcnrEjybevCy%R#T+wuTg=d=nBj1&i0XMyS>#xe z*XG257wADfR8)5a%5mLnWPkeZZ)j0V%Po5U{1YJ2vNBb(A}L=|Z0H;BLaSmU99ex~YR;HU>`9qjdS6kuQeBldfWJmOJ`c z<*F}?Qck9)3MdVQWQC_#_BOtNdcC*mIN8fT=o#`gx#WqSGXJ8IZdLVuZIw%K+CMf| zmSHAcus}Jq%oezlj9em0&Vee)t4h;aXh`$uND{Y|yWr5jgK>;_@S z^3aswX@`U&Y&Nl%L{EA#ys~`raL#*V1MfZ3C6sRA2J3M(#vv}i7)^SRjqNhm6589w zk?=z~MMIrSykl`d#c)EPVr~O0=(b8kqgba%7;bbRwlxpEL(KANy^TK{BuQ@6ey!M@ zq$?Idc&y2dE@r6iC+94oc_oe&p`7hqMlI1Y8qNWQ>|C39o|;+qaD*Nj!Wl#IFl~ki5Gz_VhZ5o+X9-Z`Fx=HseGzp*!ZXdb2h- z&nU?Tep#l_2In)9EFRTxxWbZ(BR63UQb?t)WVcc};JeCt!xa$=E+|@S#%fqT3k@B znHPw^p(h5|asXd|a?Re7um5CfZ4g8UbC!4 zWJ}y!B;lEt`-mk+)XE;en{d5}zi9l&_*n~w4Uz@Vak+CkZptlhCqzr`oYXK{Owq+G zT1QeE6+|u;xPRJ`Hg~%q2yz~{a~hcncTO8($9!Pe@%+a-$o0}r&h-+!2gmMszT5xN z7AVP+v}E#|kDL{HbqRSFOQEOw=9xHM3M=VSs73pM6tyQ}M%<23`Ni&&*`X%^c?Pau zZRRhv#9f(3In#$3oFW9|OVe8tYt@y?z?hdLqJg5*{p4@~qUnNox*g)_Zi&ZQ5EHH$ zeUB3WXd|n8Z^9zUpL(Pf?{O#Y+DPkzDGa)9Mlg9OcN(ws7*9**vUBO!9K8P%?gV-$ zccQQKcn>RO*vq0KhMQgsP z70L)F+|?6Yi_b3PQ0#UNhiaXRPuwqYph(9k0b1kk<`HMAd|f6h$E8_M|PUk1nNHyUB8y1WBx2S-qoT_++}597%cW@te%NE zFOzTa)9Q~&Sxr8i>yf1Aw)8IEFLMLGpO5m}f@@i;FCEhKlgINu-IM!^Ij#F<^&=@@ zMAd3J53fu(r=Cpb)I(rVXBqScElPg9;}Te`cP+Qx32uvZh7{nFgSx_R`{5{(Tl;jj z=A;vaRNj)pyxisZbjSaK234 zqBYFt-De%UI6!V0%z6EHX_+_%J;lVRL!}spMM*gOJf{qz8y;F?;Q8FZY%B&3)iJ0q zohUdo#BY~28Ava~$#yexR{X7a9T4M*K`@#WL<`YYqJ{CpeTrCy8H~+6L!k`IDc>H% zCbx8^Ca9}x>ucPsvcSU~kI(G<>hH6UjFBX!g(ZBlc-T}sv%$E-%TU`KvHsM(^b+M4 z->_*7%FSVU(^YFa<0R_4C;juM?4H&{uO-pHNQ?4MeSD4!O|!yv4cc}RL9 z{GLzr^y#-B-^eQ0r_bT4`et`o&_Ci*etq&QKDfus1)rVULJj$5@1N=?NYZ=d^_$+A z{8Dk~Vpk+p@u~7uhkX4@T*mkMAK@~7^ePwpCEYvU&>FVHILlnv3ppbi{9oAZ)*eNQ$O`@OXM3; zr{4Bk&m>we1oa(9qcni=t-t6qJ-@ACBiHd6x06$2`%JdHEs93L2Q+C1;!;wxBWJy9 zP~S>!jx!uko;gaTMx|=fWZJtY#mO5!N~`+lLH+=-f+o%hPJh0Mm(sea=c zqtd>3F|H*?R~47?f<1B61hQiaKCbcK z-Y;}LR{YhNE}C7{zxINBI%gItkb*FYd%%hB&YqV4d4Ib<-J$M0|rx> z-zdM>u5Yyo+&&9xeq+AvH31^0+>wnx$!_?UUHfErwW7}%dDuO{tbh}{Fdk3X(e>dY z?`I}93R8V&=q-KMtQbjYy%T*-rd1qj{i}*XiNIpZmHp{7tmZN;+l44bmKbKJkWTOR(&_9yPH7o>A-TAiGhe)?Xrf5JNVk(h&3 zZue+Dj$S^mcGmTcE=`@hV)rjUbmy0**T+^TC}R~yG7zkY|s~Vo4M-+O7Yrt2#r&D|W@#NftRLoa<=5WDhzv6>? z4P5Zmxrey7ALnzyzkkIC2{dz`B_+j~1-Y4qoTVeOM=XwFRcz0Pf9AhO#l>+iqGLwK z@G*LiB(44YNC0qx+gWl|#H2a@7?!%1uDqGzh^ni5S#=UXQbxvb($9O zB#AO6VZFdAALu3v3=uz{WeQ3WCaw2O>Qu(~$IAj9_lPO3PObH4wG*n}U;<;3I#U(3 z>%yCLn(5}64$HUCi4!JBro-8FD+=yk%`naSreiW7ulwT}2`f0IR9v$wZWz56wJUzG zE3Pr#cUbR5YQ?v#_qTcl6Ro(xdf(D1E~>q+)p{>ty>IBeuj8#+fize2-dFJ4Cb_Nj zzNPkFq*rvcNxoMq?wAy&1gmIlt2TiVCv0y@v|8dDv%73mcn zcExR-f^jImRe9fTlXRg{mEwxZ8)aT%yirsqQ-{P&i5o@gy-j0xn-f~J@w=(0#QoYa zdqv|mS`v3_5*oB)c8k<#r}wvv{CbxHExDyse6Le1V!UtEDz2-&(Vay)#kE?+_iYj+ zxZWnYrc-pFgB^++ZIUbPic6}8x8FK;zqbDl?YP~dL~1|RkzOt5vR_v!uK%a(t=chL zEMqr{5_X#(LxcA>C88sCZU5cc_%_iP<`Sj=U1pTja`Zx}=wiIlr0;$=n9Yf8+W4BL zG3b6v)7Um`LXBwLevE)Q{njNka(k zxIR-Kbk-Eo7#e)0-s@0k&>6fHsH%+ms^olJ=YO@)*jRu8gv##F(KzegU^{mj%q^A+Jer62A>t!q-8s*iW$kW<9RZ#-WpX>ld)=9o~6_%Evb8W`x}GL z*L$4{4LWBCLGA7JUS~stE(?O05*ZGCP@RmbtE(!FmoAHxqS?mGzL^H;f8{m9e~^Vq$sUuhqOe+^#XOZq24Zry&M|U z*%5Tq9CB8VhJ`R?bs`gHrw;vc4&d(^a1|q2?{%poxD~nVL1#LG8_glzeHo>+GQ;}U zkmt<%Qkxf(Z!tikgA=t>=5OYBp;|sl*}_yiB;oERH=D()(wFhnEn;6GNymMuor%zg z9qI_~)Px*01YI@<->UcO>R&)r2(G2V-Q~JAyGUm+ON%>%ESegHP6D$pxQx1fMkp9}Nv^EZ3CEn=2~> zYv7O}n1-T*ntjL|a$Xa1RvmQJ5!A}{0(ITNHcVt>D`e8L_^M*;Q~2)LAytQ5Vpz=1 zCG^WVfWKYa)hlQxTFRD7OSs*=bUCeO*VZ<9MK|%wLunGE+T(x-8k)wQ)Arx19S5*) z7mWj$swH5RP$yE?G>vVsByMa1BplnR?GLa97=^dBlxEBs?HD}ob|f}i0Q&mZ0SIfx zT1De*J-9MV&??bwal{`RJ^+bsl&(W#B1vD>vkQe*d9#_ks2Z9F~Aqmg$c?zd2`IuS+^^X?)8?2>IhCbu1}fV1k5^YtO; z%z%MFE3&x2X1#dr62nPrxN)cFvF+}{h$+f)_cbiTOhP>|VJ+@#PK9_w z_;xG4E?Vnr{e9Ec)_7Vw)0EAPLAVgyG)#~qYaf&UT%_TarDA#5!s1n?&5AX3JHC5S zCi4+E7_~J)n70;m@VYmveFxoN_T>+&|JElTg}Xkg0J)^8!x$f=s1k*)XVe}Z5$>jb zKY>sZTvk_ZXOsh@nVd;pjA6Y&V5_X%n78g-k1CntIrh2Qoyy9VYwF!$)ry><ygrmtfrIo zDaQw+4}S9HsZ|NJqLsB4uVnxQNw)FpRv+ zj*1XcQ>ZdA8x6n%i+@HN63Ylp^n{NobXq?~pyLCKy>_9OiBTbhY8idYMTwdTa=ue_ z&JnQ;cv32s3Aj}!fNxjlJ9*h56!k~d#bb={4(&yC-ocE3#S?~ss*^K%YCnDSEwLC` zssNp1RGon${%nA+t_Ib4pa8r=@2(*~7-cnue$%_xaHgO~w6nSpM$mu|U)@A+6ZZ}g zsIEz>W`^O3ukI##M{28lbta*b!9kA+5vb1DI|`t>QbugT1B-tQCL9^zG)_%{%EYaV z*p6ni6w-#wV1#NE=WGcy>RHCslEWgQ8Ug;*r+8&U$TcP=Mu!j$?r+B=+KpvM?#nl_ z20y6Hga|ZJC+IyU?=WH$I>ypFt!qpuUI;p`ExR}9mCPSLx_4~E>ZY81PY-)vV>5muWYm38M?8|p zgwzEE`lQv?xDT3mNURmsFtrA;NfFMZ=w!L-H+)Q4k?%IjBh6uvmZZpv;>hatkHByFo)>-cHPYD;^^?x2XN`;ORDUmh1an)1sw-5dEq z(|7aN>|~#ty6wb{C*OMOfMM4!k1I|!*+Qy^t@!M_TQ`tz^>^2P{H6b(uQcOyNZGnz zqyI0BNz4hbE_cGGblmd^_3_JJx^4UANBRn@f0e4Y4#@ZTZt<4t+Uhm&|DHbMj@0v} zcXdSk6s_!(cU)7{l;gKWS`yg2k0H&&FK!kJYVHVH;4c_gt&O)f)g+m~=7DJ}lJoKl zh(coJNg<8I)CDb2xpB!X{ac9$9tZBdb$JcwgOwn`Q>%<16pSJwrdeDZL35q@#)Qkd8K089{}B zf&qVf*A2xkGgW2Es)_*waMZ%Ma&Q88fPijTX>1%R%LUSGQFK9-rxb9dx8l(OFCK zqYjQnk7XmlCV}%0ISJ<39CEH5%xdseLCAuD>G6=`Kv2@Ei*R;CUj?3gdENB1JGI_4 z0wGUvJY*=bUaJH|@U?~pcNrw<6_)tcrm+p$aTt($jftJwvDFBQm@<}OCSccOg>ZMV z1ttVW)UHz>YH7&})5-i}UJD2eo!C-4{j}yY*#%2-ax)9^v!Zf}^YJn& z+qfhut2icpo}pwB1xDm8iCS(=Y4Z8Gx=g*gSezipaMdSZ*g&H@-jp5 z0(Lgv_~hKIB@5X}g$owDVzgtTCZ|Oi^J8PUuca}=ojsdZQdSa`SyGyVmY^s2j?XOp zH zVK+MHOE4H;8u~KRmxI0pLnw~E%=G1;FTqfXqc1akIp|9;jN<6aOkWQA5)7v}`ZCj( zgT4eKD2~3&^yQ#0K@`Q&mpQ8SkbJd~*{Sw;PYK+Rmg2S3#_Vh#^jAi_7?8eNyqaO^ z86;ZS!0com-R??cXLfe=N1}FguP4#7ZOl$($orH?-p5TIDaTt)jR3d@XmVbb?eNw04<|p!A|mv%XA{)}XhH%14wjbGXhU8oeLBfLy<}h4_l<$q zg#E51^}ANy?^QRb^@#u@Tk8Uu#5rJ*gVRY;n9TWF=XwB+vBU{p&J4LVxYPh0I6hkXli~U17hy z=KgyH!FvUf8HF}!CkoVmWbThw8GZZh5Cz)={Y?JBNYj$4FE2J^fADlksNdLx z0s@#m%-g_Yq&4H|Q5*^UAYGARtkaGIm~Q}I-&B4n=uo{E7;T_0$crv>aF;Q#3^NdU zTzW>(2z2RUa`vADLsJO;WNRp?uTWRG49A45f+#}b_C8+MOk<#vY}P7ME6yG4W}c7Jl` z)1G8e*oSFZQgj;;@f{2%BH*6bJha)tNGmirkH>nxg4m!LGAJva5ys#;aB)<`6{eML z=1Gq7zrwWRaNg6A(0%T1e_)J;qK_{9C>kSiq#(v$lP}Tb?65?dE_y4b2Mh`IXuoT@ z=)HD4-qRS79u=>D(CkL%Q#3})it|fzdqxSm2>d)3V~ip(tIm0b!Gu*ucO@DWuT#(Y z!T3NehK6mCq9TE`W^G!%0-6#Wh0D$99=Y01Ym+_(mSGa`>uNJ`4X%0hFA%;oIo9;2 zaK=Bdr_ljd?Yk~f`zJ7Y3m(6U^ncWcY~aq~-}j+nDf(dS)dwhgE6epJk5V$0yrHWT z!Mu{LoB4}cdN3$x4OGvYn-(fyX%||-2JF;~7*-JjPgq(5Mfo7AO*34nMN})I$O7_? zvQDH_iXOU#i7|2QFY2pP??dE{XV3z`b-;Oees_n)|ENG&v1fS&Ys6r=#yecnjP<61 zQX%YnQ~3IdeEzt;IKDnk{{#r3du$)FXDs!KMJ2R-kc=BsC4QajLQ2MXv@U)#rkRD< zK-6N_9(o32+xC#LEh5AHfA<<&rBoE;FTvQ}={2_I;cOG4?m7qZK0lBTg9W|&MRYsX z`IwEul*%!+5!nASiu%pEu)2|MIk*3byx;)9B2axa*8JLDPiq?!@!PT9fw8Vjrs)=F zFx@rgA?VAa)3HuqnA1dBagpzD!lKco!Gpk3qX!fq*ZE@YhfBnLXs+Hw{vF6qYh$Q$ zt_VO8eA^>W;`Sf4*T6E&RN!Gh@uVKxOWMDAOz>n|z|s0PZywC8l@6hK(sys^tFpQd z+7iz0tE=i25$;HmWZ1E-sG7EVuU#K-RlObnAUt>ilbW?pY1=e>up2j4^Uwxvz6Yfe zH_fyeSkH2$8#iVyax%X#zqE9@aq+U;VxH8?1QaEbuNVEg$WXY1=xNY#IrO^_M4yo! zP^O>Q<~@75Xy`_f-DBKM4|O{Ljw!WE${2m^JHwVsXX~EFO^N$OL-(fu?p`qWY2ONS z&6{?Qvgy!-FVk22JrAap;~IpS5%6gol3|F0Pvb7dPJ2I?V(yjB&U+yXWQb-AYkI$0 zFs!tp!m9ucyd+8Bm18eqqS_BvhJBWR`oM%7Y`TVE>XXtpzr8@K1Y$FUghT# z^qtZpoKwZMd(^kL3si7f(L7Ccg9-Ba5}LQeo?Av%ARc0eU4BxdD0inja3g;v$K0}p zyz_$iuw6hF8U8oO^?V=Tm%trOL8QT@kXr}bh2mCd--BbZJSVu7>-Ca<+N7LbpOM@I z<=Ygb@IC%AiB|RGDI+6}iizlpV*1;(N{`P+P6=Sfs+6nA@oIDLN*bC%sFG@j0D6Vn z9b?G$<-U!+n#f^k_KE!>_0do~EttNa_73YHr{Up}HE4!Sx{poGm<@EfkX~l#=Akjq z*gVf-FHvLt1u#{w@9UEYr8L1{@(5Dj*F6y)K?J@i@Tqku+E;47$do`tR|g70iObYEoDH*VawQ!#ZH5b59?%fA;bKxL59aUbVwnW>Wau^!|Ok zzhe*DQXp?3V>{W&ylZHQE(R^p1yp_7Cum0*3O*?ltz;Is*O(P*A8R=)8N*~K_4SNr z))|x9J(SEBhs$6=R4&x00xZR-efGBHQq$B6DwOzn>UC}{_$PdBpxb>_$R z3P%DtP3Qu^?2bwrN!FqKV!SLGl0oal_0raem+df#@2{Cmn#6<`p+_9NM?Oobm@65^ zR1FtB($)B~MlPIxCZ<8dD&|&>k{vP}>ykJo6$D;p;|Y86@G}v=E?R=t=!(2cNsx zNbGYr&Zq;c$y!D?>zJ12mmMlC7%Kh9{3A2@MH{ zBZ$7l{kq;6hD35Euh^*$Ik+?IvS@bRi*$XiXz1RF@Z|w|xqTZP-CePJ+*}9sYXH)p zs$n5NrlIkfIKb`T4D(~}%t>hjH9g=2jOwMcTV8yM1N)aJ#=R>$Y3`F8f$ZPeJszC_ zu>Y98qR%8`PizoA@6Un#`jpM6-Fs(y7{GpmboTOfd9!`aiH6!!VW0Fl94mTRqVASs zd`cN$~&3!t0kh zHi^_X7`ha*Y`>W>dS%~oJ?rsKEeQDLvWQDq)BfqQazRj+5Zl0p%*+uHi=0=Yq31Nu zSPjtJY_9`!NUiU)nUtAdY!1TKy5ONv*+p~7JL?artpePMg{}NUWGgl}y9XP5S}?-@ z(jBMmd585V!+R-{oG$qM7K&84tXE%zh{ahJ7}5|ANxrtRA_Qd1DA zJ$Gv7N@1?4tRThePLbE=I1fT~vY+l+o*@I*8DxW+^w^kt4Pv>Bz3tNWD{%i zAm;4n%V((}Q#m;C^K#2lRKmFqzq~C=YWmqhY5uJeEn|k2=!r+%WC7kZYwMr+*~k9jV>*`ADP=HNclq@~ZS346K{LY*S#1V=N}Ag@F4{)88j- zQIkAWW`}9!fQ1ZT4YyIcGzrz1QOAHjKns%Qh1^EEQ`OIo(i@QTS06{OoY7&KKp5g< zFm&*JkA-c6__=~xV62Am{Av45td#e|ju3`%45gLwe%N`!Fg1QCYUp}eDe3~Ol=)7nYW?}W-NWxEyV3Ilyz+tq!$QQlXyvceQg$!SRl50owMRFz3l_NA6I|xToQ=)J%RB2ig8J%m+XbSz z`8E<=$>R_!=ck6+=%Sf<5p3qPbWQj8RSZCTNg1?Pd%-K1q;Iv;@~`9Z1l-GF#fJ{U zNG8*m8V0?R!1M`1m+Fo{a)M0}z=5d+GrHN29VRJ!OBW5|*bibAZ5{;HY?Fw6(GdG$ z7Yv8TvgFOEqIL|GT0aP^`y)YFoGI~}04y^i-3Wr5-mU@}V%rPg)n)I4`wg?Ec$6?k zkOv#=K_$5ssx^Pu2Enke$6eyuZy1{K2A(WLu2_dqc#4)b9(O#%~T$j zs2&9_IU}0AcLR@0+9&b2r+eVqFCyhyCe0w} zl3haXB3mZSbjez*TOw9=@3$T3v*yt?KmuYsO@EXhD!}#jD+m`odX#X5xj(;&RY3p* zMUNhVR*Zsb=ybOz)NV4446??H1u8fq7%i3&wYkFg4N#kE@=jU*s27QbcxrPcvUN?` zreI#+FvF^oI{}__$nfm?9U@3YW!7i}xaf1r$SkD3J83ssZZQMNSYMC3OeDi4os_5q z)8yuEHdI4)EcGWFP?`x(Ti4^MKwYQxBeSiyDHj^x^SB6U=wo0RCI!DP+ZD>o?TN=V;~`5DO)BSPNdoqWla{)qj-7kb zi=z>U{^0k5*o|Zev=?xQ!5vZ%|LEMa6|=Dij7aQ)A+y0754BGQ-*?>HC+m$fB!R7U zR0@Jtb&!Y(AYNQV8{+1WR}+|sAo8I;^n`1&DDV{_b~7RNhV?{w8Iu97OVu6+jfmQH zvYx1Xpi+aVQARe4wJ&)E!+A7p66;Adn~JgW*-lW5 zVTxFWxrX0|+qZ1ehE7@c*FU?bOga(ebIM!(*^6<0KC|pujMu=F@#-mu$1{HVh$kA9 z_x%Mg+=)G|9^O1HPTin<^JHq>ooO;{@_3be_WP-I5&nk;_}KMho3&Hc7bW@*`o*tN zsTlc-T@_H>cUbeMx?|RRGsSTy$Fu9(V**|L|6ASmJ4FMIg1|Mw+H3`lz@KN7XY`XvO-OX$NNdKbQTq0QHMLy*<{CY)j^7nsk1*&^vFtIKb@z`V2!KdHk@tCsn?K+$(O*^@;I0 zltqImw1{_}oZq0F)|qx3s}M_IT3mUoa(S|B>ZuI7X3~jF!Me~| z0oQsocl-}>yZA0TTN#^hV0ZlI-tw2;v`(hpJCbN!osPx2Y@HX9#Z ziKnJ316H{W^EV9qk~h}r(vz{y{WjT+@DCUDKLneh^g2)M2$QCCiHeD4dw*2D0{k=o?XvZVQN^g@xitSh zTaR79>HmM)dPD(<_OE7{cf71Dlhx3XTS@Yj0gR~Dl+S5RFWXx+%2>4m+5tH9fet)N zI^@OBd;ed>ep38CT_185ik&b_(2#{fFO!7-QHtCWx|;sJh!vccY*m_aU26Zk1?(c# zS|-Evl;|VwqEyb3$nSq`g-A1euQq<~e=_hy$l(11WZnq=et{unRQc0QV-mqGN4HR!|^}7a~hSe49iJHdNA(uG;I{Z+) z@79(tP_1V&m@#Mck-`S%GfB}LmtyVz+Iswt0uf>n@QXRmgN%TtGSIue;0{3X5qEhp z4;FMj&|ka7a_iHs_^C~TH4ig^PHqc8HSAxt^o+H7ll8vy@Vx*_5SXoz-kG*PR`Pby zSS(}Ct|YO0K`LPYavO#ZoW|Kj_^5T-AYfbdSR-=QeE-3AgMsnm4?2atovZ>cvEChR zv^%=G6fnnwH3T--w|2#qHU*3&Z?r2G*%jZy*nv0Pt5sar|EIld{-0vR5hi2`M4?`$ zfCBbSp6bZAXvq2CeI~%Z+q^^Ej-~QBv&A9l+ON9B=Vf;b zJKWEjy;-UAD}Sny&G^Eq->jVHC;o0Tn|?|x`12rnopA~qHgWGDS?kD9`yhG7k|~km zp-=zCy&Uo4!TT6y4GVr0_5VkG{LZEzL9!`9*qPW{8Nar67?b2)2Fg95{Blh?{F zCzW?9YX=FW;^s*M1Z5}H##iG~HmZJdBqgqkO#qWwm7I3^_C&E&*0w#&rduJ)+vp)2 z@fmr&`Yu^sinT^rJ9MyEe5{Y)$ogdU?nIy3;X}pNWA1`0zDer+aXz&#%D)V? zwae6jm4S?pROk^M*YAbWVNI%a^*McHfqy#gBR&|gAV!?DVM_&@!zjvMR;y2D$zz4W z`bnp)?7Tr!dGjjSZsH%_fy_J6^Cpp&y7Fu@q$jspt`!VP<6gXhKI*4VBjNV6zh-R8M1xHays zGu&W#TVlj9FoVCPsxMTENsxCSnPP(So*oBO_&ejs6D34IVurs?*(KFk$MPmuSnhtA z$I?Lm?O9y3p=WdE=Sy znzv*{D&y?(Gpf_wHV$uB8oT3Hsm^rAM_114j;~jp>yCe0Qq&!P$mZ!RzqShUY8lE$ zFQHdo;-Th32td(&62z*U;25ERTRWlXj#MUkCoh{PkeM(I+@~_Jff19^BTGT;Yw3(o zPfz&3v|%tVdd3q~Vy~Up*F^ntR;Tq;GZ6^UN7Z=*)uA;sb*Qcj)j@b{LIBmJLX95+ zBQ=F86aT^VQsWOXGD0=-V8Nie7vovG9ZzEX+iR0 zpFcSN^K)B%eE#bLA3wTq>HVi~eD}-TD{l-vyLJ3O-}`Gz*?}{^Z2qcp=NBIj9yx)z zT)#-w?*(=7{=J2gzki?m(WYPi@qwDNUD&tsjWN|vF8y)xlD?_)Uwh?>$8$AITk;Wg z>xk9g<&OS|vnAO5Rmg(3jkcC?)UO znE&XY1)V?E`#dRT)P2YAb4NP8rKy&TGf6Iq_*2hC~^vWss z6*DYsao~uk7n|Fuy?1>= z)z>*oH_6vet+$oUDV+A=vHaq{CeKzb6q^#yN_#i=?StPG{rQ}w<8f7BU;mI(r~ZX> zl9#@|IQpY|xvzZvci+I%uf2Y%6P6dYHW~B6?(kQ>%Wm60?8CPXe;fDF6JeIKw`0e7 zNBuZC$L*z2Wlz8Uul(2Fys~}Pvm@?T{!rVFZVt`qTYvJ=AAWkZ?;D?Y zHr$=!d*Ec)oxyJ?{+z6vGh)DjBj1ua$SX;?KVn6${xWy;4(X6}eZ;5Uz7~RaQTwh* zHZ|ps+L1iFHtjPTmhQJpeq45L(CWjBi+_6h74yl$m36NUvNV^iC_65E|Je2UZ~uke zyw<$`ddP=IGM8s4CT}1kl2+lG+jnlk@MRxDNb%>*8g%XGKd;}|JLT84-*n#kVuXlN z1ofJ4>&&O(`kC81ZuX0adht~5%1!bWf1UK!$-Fq>d&hF)Q|E`gb?jfe|DBfj?1XJT zk>Brm>&qNvV&eP_(~hYVo*ko^_LMStMANt57S+ukgoRrcxE<#;cLT4vVXgSjJO1wv zPd@&{?v&M^&&^zSd)52@e&WZ@*0l|LmL$I8y(Dg``ko*f3Vr`;uk!zny-ESV^M7Hl z66E#TWd}ibgG#zFz$yrMMsI)i{1?w=F_OH7MA^3Zwasd!+jQkDiKs+U9Ta(dojlb? zk`z0*b=j;53*t9z7@1*7*tB8rOEd5g{YVMEhku?ma?5FT?M1n4YvROR@d_(zO$siL zR-QJ@kw&M#WSG-@Dqxy$@HAicxsg+zC<=Xf*03*B5r>NczbNuQT+gN!E1UG}#F=rY zY9l6w_n9Uh_FID#m)%TpZgzg|(%cc*{~y`T{I;Gb9H{cPAx1;?)49bX@=`LB(o%FY zrln8IoS8CX_9Sge=A`7zSyN}~QnZsMOiD?9c!J~twNw6}>KQ4zX*1IQ@OAPujW&JKw5bm-7$?Y6r=?GtFy{|nCu`C* z4}ZxwDsy(sAG~(8>km_)aw;9~yXWPpEHXMOT^yyO@9f0|^IS1_9+8J$W@@KRO`kC> zt=D6wM*BDgXQXJ7UB5kX&e6eJdUD01?o7Vtl%prq`#m+z^_FX>vmQ#Voi;^B^W}Q) z3ir-GGkuzlmXj-uEBw1OQ?#=lNP{q?`R`eJ#m01-T|sa5kL{#>XH^9=v;UC%Kbuui z-tKBS6Diak8bWbr<;7D`VWpmcO#eZY6R+3p@C@IY> zUQ(F7C?|7K$-?}c+(OC-$6AGS{!2z^(4aH?uHf!HZ7}AK$mMX7qC`LbN$-xSspomuJ}_ueJ-MD@JxRf6j^wWT=M2m?Tx zxCaX89s59{-o^AJqLOkJ8?sAb{P;jYy(4=k=~YZm5-J78jD?F!^B0soP|O3d4+n0&**te}rWLkP{3WokL7nON>2Z z%op`wxCFM1e5?4ky+b8N%nTV>6fe!qAtOnCwt^F6?>1hv^MJfZaK` zAU9#7;#@OEPaGiTzPe#H>gsM(&(8f_R4)M68<3P37cK*W=uY1I9mWVChi2}9*O);v zyzMngl*}2MTSSW|hb(Avb23RWusiRAa5Qh((voFMITC@)eDkuh%V0l5U4u^bL-XVo zEnHNJ71aF_TaMd|JXEL01&d07U#L76nkgT<)tRw}|`2YNXeM z338|mZpD{mV@sop23TFuH;@&6i;bn3*uW*3PW(j(sF+Y9r^SRp;PwLrNIM*50Wff6 zFD$By>5f>sldL4OcsYl<_;iK;g>f~9lK(3cXG38bYg~+#kXsD?;7}Eq34B!LwY811 zq#3Yr7Q?(*S+6QrS2xPa{c6~VH}P@{UY6tKB-k`Zyn~3Hi176cm#u;Ea>NISXrt^D z0n6ox4^`#ust=;I)q4f@M;9oEXdoggXjO*6{hWKhyOSV5QuU9uwgfTH%#alNLcmlb z_VQ~Ou@aIdNZpR1L5SFpSd)5}!672UlFUC`(`5Ay-vy;Vs9>CdGN(PYO?9QI`m}=c zG!hRPnaVdV?cb`fzlfN25u-lC7jtTSP0JOC)$p2b4M8zt-|(0*41=0BLiLo2S<_tI z<;4}V&{@o&=&+kYrhN{>w2$U<#rcMp-jk6b^=nE^D%$oO!`Pg;tbXBJ(M2?<0ezzeecN2!EN5)c0@>{>CNVlJ z0>!`*=(@9*jGC4MoHva&j0silR{dP$ekoV^ZHWD7G;*TDRtp&$k^=9xb^k$?ju*w(CfS-I0FU{%9%Fb!1B2WMw-E z4}GmN=Cn6v{PM&nnP=AfZKD~7U3zHNBh2et1iy;GsnTLO=VpKOsjeYvG>ghuF6f#x zw#!?&7bC!xVN#-uyNBm9&-i+~%J|iRGWJ`zGPEgz@Gvdo6LwvjXBOl9*q~-hasW~5xUspjLnR%9S(do2;xNAuz^`& zkCulG-WoQvF>LsSu&5Z{2&KLHng_RBR5Z>yK{8igL|EIEV zk8A4K_TM|%Bq0P69%=xEB)lS`CV+~FkOv|nDq3rOn<#qFqlnh_P@j1qgf_@SYpJz| zr!6Y>pi&Q2YXgZI6}-WE&!LsJ5jnloYEQ7%;=R<~wRVEFy}y5cezsxu%vx(^*37JV z?3wQzm~xX){1a-XUr!QKW#t=^UXst~|2a@|>m>X?R{oTD^}RopThA_nYu|zbo8wsGWGs##8^p zBzRl)(x?7I(Yv38-fd*3uG9Pd+okXKdjGY*^skWot)8@P)6u{J?fQ4)6g8Nd&v}HM zNTBwC+M(7Edl?3(4z+Hz-`2wyK)G%`yS~H6vJ0sLVHafyiF(%Ohbza;ELxOH6yrB3W+4YOK4+mohAP1P1 zdYL4J**L;lCyM>R5q6rvafr{AAjd(Ow_l76dSGo07+=tsH(Dn%c8V<*r?Y+dma9If6=Ea)cjBTC$k?v36 zfs&hLlAX+o5y>Y-u_qm27Z_;CEKm_X=`aoXitv*wyg6D8O)aWjkGWt}QP$`=_J0$fj<8NNWyQ5#{X_@lkF4Kym|FOj5!QY~lU=o| z(Pt@$g4%XbMAQ7#alVz&GD7pf(Bx3T7a5zJF9eq*nX)F0aPvl=Y~wcDxu<%#9}RH-HpFc+`Fv9CbG~Wrg|-_PEORf~ zZ$JcQ>-op+`K{ibO7d-ObRBUNtQcK7|Eh@A5V!G20aoAsmPvbCLZ3jD5z-ksW<0XP z-CVF1_-#~?Lr`BZ7hThv=Sm-BToS?OA4?*WAQPzn@ie6ZhKe9uAQE%eWIb={$bam^ zA)R!UJ?r=VBJPFmXaO;wj`2auqz_s`w=hT-g*EzfzwfuZgBM|NH5S5A_zz*&07dZ{ zaAQh;Xqj}PB{YJ`B9@Z0eRAoDD^NUG*`t6G(+GvFyXL7aj*Hgw%^mqic|xMr3+r?+ z(DZxqji>U)gD5(Mf&A|~xEJamCfyjKv(|Cr(}A}>8}_jm@vhZ&UdODbm`y`2*3l8k z4y(z`-`eIaBs8>um-rE?)Qe;c2w>0!KGU4})K&Z`(EHJ8|5+ya-WnPU5oQAT$*db~ zoi{AmogmqFrkV5a$HniBNWN_~J)XbS@?7#kenj7lPqmY2$(?XpPuJ~E&~c~L)N_C) z1G6pYMK4Ix#hY}RaA@ESYE)d)K?N9gH}}M0nWW{%&}3$SyPzG9ttQ*etv%2LG$fct zg%C{R%mlY-j512#&@m2-K=6>&WWTv}nD-`8OA-l!>1PvCm{XzP68I5@WG7}C%113i zufTu^Rr1}tP`}Vi;i0-Ut#5=%175n910w1tTMB^&%U>N3Q8le!Kb5vEor1!S0V^|s%G{`xQ zUO&ntGY*Co(7i_jbrQX%$R!C4VaJ$g%b|`01L;R28Z<@|#6?e(12&Vxywl=$1iw;4 za$gOMSd?&oFhR<2axwp1^UbENrrBUSl1K|L{d?Pi2c0(|sgEJ4ojV73neL>PEil`! zHJ9AfQA7Y;Fllm$^laEu10iCdSmx-6s`&5e*5Hj|=Pfmxt~aCDCTvy@du~2-&i?@d zK*Jn+3$0;VCFaYfBHk`+u(jAJU$D*qX;ywjKR;y85KWO{Q~|6GD6 z;;0&7?31`bE@|isd#WNRjzO>ybF$3LIo>w=l7L_sM2kZ}jB#|tA{0YfK+M}Hrtz|= z8pPZn5<$!{6thV#`4Gey;Da~HSZ?N=02#P30Mh}vLIE6j#oD+nH#*`> z>fMOcd+OAGtxNqcW2!aEu=k=@O)qtvLT)r{C+Q-zMyd8%ylN^g z!+6~jaO!!0&8z0ntp9l);2ttqqZoVbUNw%(zXg2k;po`Y38FHFpL0AI3E1uMs_DFn z0mi!22lrB45owPTx(J;H3{%g4WLDrO{vmG;)U#f9SL~uaJWYGa>gqYnX1ZD+`R zI@39pX9VPFUoW$!Rs9U|v{EuuG84w{1Lbhw1_B1WYV5}MAU>-j?b(lM3A_)w$9yQa zYC`tvLlP+K4!uk*2ua}8E`$=ay2eDOha{}b)v6jto)cP?`#GsqHEUI;w5pG`ss1Kc zZ(~c-L!uW0>-b#NM{=u$g{-^=;FGJPLSoWGCfvsG=%COTDl{fCG$t`LENk+-NOq=)=htWIds<5K`9N&om*zA(=0VP>@z&GkIgJO?~4 zytYyEbPTD^Fy8%JZ5Thae`b$yCjC1XK$_gbJ-=iKf;V|(wb3!6C+!2C_#o=j=2|Mm zp&t)0PYzOQF(y|tQksn8TMVIy3FUss6bNc!h7?GZY?s1st&`=l@TGeE{o^AFaJirV z8)V_{Ao#-;&y%#44_6IH>iq6o_-ietMVT%pTs=Fz!kDRJ;4gqrnW!w6G1F9(X`&{e zgBs^zUgb-~0TMmyD7;GXb!=Jz#5fjsA$fWXlHLuFh=HKCXgus+uBB2;R7}88VS%Kb z523@BLEy^67)?}~j*$kx@U);Ko&f3*LJ2 z0eIKyW z;hXh@n7gbli2L7lW4ZN-Om5R{e%7(fO}YRtsiMoDI8qL9X~9bsBVY}&V|DJ6~^gpC>o${{O%JGZo(OD zMl%u=Cl5RMDKyXhOy(4wn{b_Pk4Qoj9w@DHdo`R%c2hadl_F!)l*OMXfeJ7|Ze7eQ z3k0W`L1(lUG@3K-o(%HbKnH_9eoFL%yvVtFk>#^H5O~*K^O}2Wuo^v+f^d#s4jAST@TUzFWly6bBbSwY>z+ z{Z{+e^sC|Auf?<=1F?U!wiqd3|1gG~fu90~gg#{O+G@a%aHbALPN`ou%aXCXKBNmU zBqCDZDBG~ae^^js&x9kSUz-+eXax2&Tg*&2Hf%`h3k)9*U_Y5esA2jMdc>})fo_u^ z#z8ZO%WlJ~#HnvoCNl^PKsuGzY?e^Y18`TaOPy$onHZl&(ljT;sT=*%{0YrU$#8}5 zptpGddf8A@%B_eQUpB0wax!)YJdhB-IuB~B1uE#nLW@J=#kYELOkUE;E8MrQ6pIfj zN)xB@>RMIDPHXRa2TFVcuZsh3)s6a69N1Uq%N&0S_Mi(huN4*MSru8)jp|U=(WM|k zR(p$Ed8IfqCfk&lJuoG^sU({-bt>;fuBzp9T`kBd8x>pT%baj3XCqw7JFV}aW(;}J zzlL*x<186U?~%1Syn$UAxx@sqpJ`P!p4R4x17pipQL=DPngg{40l!(Uv_H}shyg35 zZ!7a=2w)_U;O|nHRZ>8uQvhDU_{PgKVaqo^33h&`H9Y|*oJ+pQd8E!wTuWaK_&^qj zw}z)Rrf){N6G#W=Zy!3JvSrUHexfO;-&5#V&q()ef#i(NqE*N}e(+29W88#pIM$pJ z`HbXEbEx1N4>-il&k82b0`dWzlcr{-tf$6K6s*0Xt7RWAhg$-dS9>Gi#X$T02n|5T z;7x+HCAMmbg^kY5P^>AAz9G**=4@VfMaL<% z13Qy~d-*i%njyTkQy;wpE^*w~^bp)|a=D_!mWEd%#yB7qf>LitMR5mQRLiN%9A*mT zQXPoovlBF&U2BLFAL;aeksi0dOIiHr^2Hei#LGRi=PHpT)kN#1cNgNa}x2Ma8m z5gCNf43M!%G6%G&K(LXwrMI33a_GS25`T7=@VEL;EF&IiGthSTfA*Clq!`Fdeb`u4!DibzGX2muzC7XBp9ux6_)PxZI>Z>1#-i}bdj zfIDfMC8q(JN?UpRs&L^u0G~iDuT}0f``3|Ls6ly^?5FY!z=ihR)+eB(Fi-Z-{RUoJU>AM0L7X~*~?opKw{nbfP>tblJod$o{ttj&1mE46yZ5awqNBTE`OM@F0of|(Tm1DIL+FWm~obujaVr={qJH11WN!IXL>)tVz!Uz4k_z4 zwQC_2%^e$uloK99Lb~0)dh{JKtm$MV1Mgn{Q{9l}M8H*9llbRENJSB-P~o>r4&VnF zM#%775i5c<3zO#eYBxZT6>UuX8B^<4^&Wq`(bnVT&Oj6C(^}!TxehWwZF19=0K3L- z=OA|vfw(N2GB+n?Sbdc%-Ixz91*EPrqv(}(}eB`mT6Bn`5 z(lbI+8}+MtK>LG|%lyi2lU}a}R5sOSlUk-F93i>TlEe|~omsh1h!1GEwD_t|0)o3I z6IbGxFMESx%@+NH%5JZsY8e2GHG4BPZAzC9+SFWX^y*UchT!H2_!6m)iVtZLaky(v z_%q#2@Q!Rhn3CE`>VQ4(u;M+rXmg}KBL2Jq;Bt!0o$V7U{oiG3AbZJ%Uj)Ic6ehqQ znq3_(dESJ-%G|F3(c2q`#PO0H1T^2N%HbN8Eux~5AhYYt*&OuW+gf}mPwa``e zm)JelqRQqU>^qGk6(gETdFHlssGCbiLyR-4h6TZ8LLt=6_ShlT1cY`fkK7L76hO_9 z(WPT6{l8Og0Lrxz7HAP#0SjBfXZ~KcJR7tXaWrZZ>P7e>Eb81U$N|U;fx14vHDfV& ztzQIDbOq!=Uf-o?5iAf#oNqy8#@GDnzfd6as)0&0ZJ2 zZif~XBZ;dA+cl!t!2(pQu>4Bu6;LdgVbi0$2kXoO;lAI-S#@i@aDh1U)5~ z=_i?eU`|JIpf}N#Ppc@Ghsb5k0?PQZl)n`E3DeJc{p-x`e!x_o0sJ=NIP`Np%Bf$Y zausD`LA&9Lpu64&CGu?q@7r}|Qy2l_TFMiu%jM^FSg3n)egwndXMk4Pdo{T8s*oiPpiD{Hk>w!`&`(3Jb^?3Gp|aJ$_pG6^Tz({q?)K9|AjtOQRX8P2;QV(G)T1YZ|r)=jjAQsZ0Kn~b7E=Xl!%rdx;W{1}VAjCU$?vQ`wQaI!bv zaI(qREx`(L&asKs2~>0I`ilL!x6kn|30fV8$+3(vU&c47iMcC^U+1jMyP@DKeYONY z5Es@>WM-mdL8;>KIbO8C<BuqBHTU8_^{b7OMqt*Awih zcop^y(c3Bt{tP?ew!?NP*m?nz7?`&J>@@D@dEHF}`0x3!e~#W<2z(GYven7G%Lh3? zb|BS`c%wCTfhA^ZN1B3&f%WT57}XZc2E0+HIQ1H04Lugr3?SA}p=rS|0?)yuVIKg` zF~a%|E(G8?rEt*uco4wWEZD(u9$eGYAcDm~fUV*96V3?5w|XkR$X>DW<Idm z1Ozc`uEPZ67)(Ic+hQ(wWbFW1WXNFYc?7Y8VfpH+ZKW3V&ycbzdVWELonfcG5%>!| zzi>HtgL96yz~aNb0DVcGX+eMgV%SuX03Q%GbH2_C@O)LRhuKR3EHVJ*XkkQ$Y-IEvt;#6uBnGI@Kbo^F zHCsUJsTMhn^Roq1D>yplWeZH7R*N2}z{m6%mT`jr-xcN#X=i$*O=|A&y7PW%8+&b# zN^2uXv^L_gJKkhWqdWI6jKj=4t)YzTWR_Xj@a~bHpZbo#OS(sM@-==zoQ^oP zmWN>=XeD+a^^1Q{m_Y+-dT?CTf~IwGfE>pD0O{3%t_+?)goE@x<@=D+l2`3G&md2s z8#>!VMRq!dY`ZqCoXKcFd{xguE6=n>!uRCoh$q%SpzcJ zt>^~(oUAN_2}EWxP%Ix}+s9o|jBA7VUgm}|o@gq1NPs}(ES&>1Q$^ApLYPtXy(|f1 z6#H@p#vyuB?c3MQ0OcepEFqvMH?5C{iHQiXS+^gPMT}G$Wmqm&B}K_@jPsVKTHUAa zLa~d!gJRDkBkTVnGQKtdaV;5N$mua^H^GIJa*-wqXnJ%_x3mN#^_5aO8TE;n48toY z5*YS4dV=!yeY1|+tN0VowEA6oK3FUw0AXrRl&lagOaVC^Q7{|q(Q*1{IR$rba(Z5o zyam^hf@)POt6;wAYf^+^egtEFfQgqOw-)9s-wK;Voy@_qb+sy|SNfO(dah-h;PoU{ zEVWwUyw_u_y8u^^%0;`6xrLih1H1o~9HCjmmRZ=l;cf3o5Q#5Q7*ry&(?OOOgDl5p zAeY=xG7Si?yK~J^(5crDhZZr$*w|dD0gW$ zbhYzkNa4q)+C^hl8h95`zGFXXo*+TZ#;gN0gbKvzD2lb}f_ENZ~%X6Qu17WJG5KSGLH z`0+R}Ha`Y8jIHd~t^$Sf5sZdis_cuTv|2BOmS?*l!2z zx`E<-Xi{S{a7?yZ7{=fPKpG^gdvzr`Zn_d@mN;mzy%pjT0DgQX;K!eI2Iea=XF;5n zx$SC8fl(ZQcL3PmYRCZlV8!Ns5k&TqsL<6ZbpB+7QYP{`+Gd0r(ee&$x~vSP%(ef$ zHfHV>T^o8WGi-n{(>oCHh+V6(GpAI`2+$7?)IDmqD)Jv18qN=3i;at2-ZM2uZzl04 z9%r}rFo%QtByj)C3QALQj<>?4T2VZWvpVmFj7{>k04AgkhA2{MQtNu@{@VR$yy2f# z1laNLcLb*qH?BlVD;_n8B!?L5i?c#X#IWYdTpRS<3G1_2Au+hN`=W1sxO=@a3i#ux=9@K5WCJU}?8N(Z~2D$zNe!zgYv-jlCwF~gATbQu}P_2G-vIlS} zYZ&REa38lzZwmm14=|g-V%Jyvr$Av4fvZ8mnnEqs!Q4$zOurZWryxgJAIMU8S?3D@ z+AckSg8HlEF7qW}jo>c6Eaa~6B>};&T9u<#D0l>99m-5se2zD#LIrO8rxg*zYWN#S zrPE_MELZr}zzkSiORO(WZkvR~0*mX@3XN~N*UX(Fzc^eCcb5b1(xA(M^&4&_45vJs zUuno=7?IJqm2?>_k-aU-^YWB|1;;y`f|`qXM+S^gG<7iEf#uSVb6`C+c&AUQ)@zYB zp-~s8pX;?zN9#4LmTs9Ndgbnsp_pJLicqg$ zMd-xf0spp%V5cYi{eNQtE;9iZ;J$CZt@832OuyMbZ21vD)`pi*Df_6U=Ny1W$Gp8` z7!doC>#OB}Vm8?Z;DD~#MLNMKEu(1iyvkDHt)QpV@@WvjyDL5L4z3j7NbBa@RPZ!ilsIeeygOEeu>0q^Zq(M2 zqHLSz-ngY%h$J{UQ<=?C52Yt8Eo&E;VfWLvA&BUweE$+ClQlm^)32{>m5&0Hxr17`+6zhI?{N1DG0fkCIGy>3>qb`ZIlq>&1V&ZgujK-0DAv+1L$3RKi7?* z*kMEjMa0c3?>#q#rr(-;6Y%uXsNyxG&#WHI;J$w*zqEWUtu`9YFU`UGEIrZqu5dw7 z;!D~v(8RNOC*O?A1dV%&M@ZMfJ5%XIzrJM@=3(od4fN}iEo11{d&Tb1{9yX^d#u?6 F@xO<#N_qeQ diff --git a/apps/usr_le_code/thirdpart/mjson/mjson.c b/apps/usr_le_code/thirdpart/mjson/mjson.c new file mode 100644 index 0000000..75548d4 --- /dev/null +++ b/apps/usr_le_code/thirdpart/mjson/mjson.c @@ -0,0 +1,1066 @@ +// Copyright (c) 2018-2020 Cesanta Software Limited +// All rights reserved +// +// 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. + +#include +#include + +#include "mjson.h" + +#if defined(_MSC_VER) +#define alloca(x) _alloca(x) +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define va_copy(x, y) (x) = (y) +#define isinf(x) !_finite(x) +#define isnan(x) _isnan(x) +#endif + +static double mystrtod(const char *str, const char **end); + +static int mjson_esc(int c, int esc) { + const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\""; + for (p = esc ? esc1 : esc2; *p != '\0'; p++) { + if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2]; + } + return 0; +} + +static int mjson_escape(int c) { + return mjson_esc(c, 1); +} + +static int mjson_pass_string(const char *s, int len) { + int i; + for (i = 0; i < len; i++) { + if (s[i] == '\\' && i + 1 < len && mjson_escape(s[i + 1])) { + i++; + } else if (s[i] == '\0') { + return MJSON_ERROR_INVALID_INPUT; + } else if (s[i] == '"') { + return i; + } + } + return MJSON_ERROR_INVALID_INPUT; +} + +int mjson(const char *s, int len, mjson_cb_t cb, void *ud) { + enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE; + unsigned char nesting[MJSON_MAX_DEPTH]; + int i, depth = 0; +#define MJSONCALL(ev) \ + if (cb != NULL && cb(ev, s, start, i - start + 1, ud)) return i + 1; + +// In the ascii table, the distance between `[` and `]` is 2. +// Ditto for `{` and `}`. Hence +2 in the code below. +#define MJSONEOO() \ + do { \ + if (c != nesting[depth - 1] + 2) return MJSON_ERROR_INVALID_INPUT; \ + depth--; \ + if (depth == 0) { \ + MJSONCALL(tok); \ + return i + 1; \ + } \ + } while (0) + + for (i = 0; i < len; i++) { + int start = i; + unsigned char c = ((const unsigned char *) s)[i]; + int tok = c; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; + // printf("- %c [%.*s] %d %d\n", c, i, s, depth, expecting); + switch (expecting) { + case S_VALUE: + if (c == '{') { + if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP; + nesting[depth++] = c; + expecting = S_KEY; + break; + } else if (c == '[') { + if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP; + nesting[depth++] = c; + break; + } else if (c == ']' && depth > 0) { // Empty array + MJSONEOO(); + } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) { + i += 3; + tok = MJSON_TOK_TRUE; + } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) { + i += 3; + tok = MJSON_TOK_NULL; + } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) { + i += 4; + tok = MJSON_TOK_FALSE; + } else if (c == '-' || ((c >= '0' && c <= '9'))) { + const char *end = NULL; + mystrtod(&s[i], &end); + if (end != NULL) i += (int) (end - &s[i] - 1); + tok = MJSON_TOK_NUMBER; + } else if (c == '"') { + int n = mjson_pass_string(&s[i + 1], len - i - 1); + if (n < 0) return n; + i += n + 1; + tok = MJSON_TOK_STRING; + } else { + return MJSON_ERROR_INVALID_INPUT; + } + if (depth == 0) { + MJSONCALL(tok); + return i + 1; + } + expecting = S_COMMA_OR_EOO; + break; + + case S_KEY: + if (c == '"') { + int n = mjson_pass_string(&s[i + 1], len - i - 1); + if (n < 0) return n; + i += n + 1; + tok = MJSON_TOK_KEY; + expecting = S_COLON; + } else if (c == '}') { // Empty object + MJSONEOO(); + expecting = S_COMMA_OR_EOO; + } else { + return MJSON_ERROR_INVALID_INPUT; + } + break; + + case S_COLON: + if (c == ':') { + expecting = S_VALUE; + } else { + return MJSON_ERROR_INVALID_INPUT; + } + break; + + case S_COMMA_OR_EOO: + if (depth <= 0) return MJSON_ERROR_INVALID_INPUT; + if (c == ',') { + expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE; + } else if (c == ']' || c == '}') { + MJSONEOO(); + } else { + return MJSON_ERROR_INVALID_INPUT; + } + break; + } + MJSONCALL(tok); + } + return MJSON_ERROR_INVALID_INPUT; +} + +struct msjon_get_data { + const char *path; // Lookup json path + int pos; // Current path position + int d1; // Current depth of traversal + int d2; // Expected depth of traversal + int i1; // Index in an array + int i2; // Expected index in an array + int obj; // If the value is array/object, offset where it starts + const char **tokptr; // Destination + int *toklen; // Destination length + int tok; // Returned token +}; + +//#include + +static int plen1(const char *s) { + int i = 0, n = 0; + while (s[i] != '\0' && s[i] != '.' && s[i] != '[') + n++, i += s[i] == '\\' ? 2 : 1; + // printf("PLEN: s: [%s], [%.*s] => %d\n", s, i, s, n); + return n; +} + +static int plen2(const char *s) { + int i = 0, n = 0; + while (s[i] != '\0' && s[i] != '.' && s[i] != '[') + n++, i += s[i] == '\\' ? 2 : 1; + // printf("PLEN: s: [%s], [%.*s] => %d\n", s, i, s, n); + return i; +} + +static int kcmp(const char *a, const char *b, int n) { + int i = 0, j = 0, r = 0; + for (i = 0, j = 0; j < n; i++, j++) { + if (b[i] == '\\') i++; + if ((r = a[j] - b[i]) != 0) return r; + } + // printf("KCMP: a: [%.*s], b:[%.*s] ==> %d\n", n, a, i, b, r); + return r; +} + +static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) { + struct msjon_get_data *d = (struct msjon_get_data *) ud; +#if 0 + printf("--> %2x %2d %2d %2d %2d\t %2d %2d\t'%s' '%s' '%s' '%s'\n", tok, d->d1, + d->d2, d->i1, d->i2, (int) off, (int) d->pos, s, d->path, s + off, + d->path + d->pos); +#endif + if (d->tok != MJSON_TOK_INVALID) return 1; // Found + if (tok == '{' || tok == '[') { + if (d->d1 < d->d2) d->obj = -1; + if (d->d1 == d->d2) d->obj = off; + if (d->d1 == d->d2 && tok == '[' && d->path[d->pos] == '[') { + d->i1 = 0; + d->i2 = (int) mystrtod(&d->path[d->pos + 1], NULL); + if (d->i1 == d->i2) { + while (d->path[d->pos] && d->path[d->pos] != ']') d->pos++; + if (d->path[d->pos] == ']') d->pos++; + d->d2++; + } + } + d->d1++; + } else if (tok == '}' || tok == ']') { + if (tok == ']' && d->d1 == d->d2) d->i1 = 0; + d->d1--; + // printf("X %s %d %d %d %d %d\n", d->path + d->pos, d->d1, d->d2, d->i1, + // d->i2, d->obj); + if (!d->path[d->pos] && d->d1 == d->d2 && d->obj != -1) { + d->tok = tok - 2; + if (d->tokptr) *d->tokptr = s + d->obj; + if (d->toklen) *d->toklen = off - d->obj + 1; + return 1; + } + } else if (tok == ',' && d->d1 == d->d2 && d->pos && + d->path[d->pos - 1] == ']') { + return 1; // Not found in the current elem array + } else if (tok == ',' && d->d1 == d->d2 + 1 && d->path[d->pos] == '[') { + // printf("GG '%s' '%s'\n", d->path, &d->path[d->pos]); + d->i1++; + if (d->i1 == d->i2) { + while (d->path[d->pos] && d->path[d->pos] != ']') d->pos++; + if (d->path[d->pos] == ']') d->pos++; + d->d2++; + } + } else if (tok == MJSON_TOK_KEY && d->d1 == d->d2 + 1 && + d->path[d->pos] == '.' && s[off] == '"' && + s[off + len - 1] == '"' && + plen1(&d->path[d->pos + 1]) == len - 2 && + kcmp(s + off + 1, &d->path[d->pos + 1], len - 2) == 0) { + d->d2++; + d->pos += plen2(&d->path[d->pos + 1]) + 1; + } else if (tok == MJSON_TOK_KEY && d->d1 == d->d2) { + return 1; // Exhausted path, not found + } else if (MJSON_TOK_IS_VALUE(tok)) { + // printf("T %d %d %d %d %d\n", tok, d->d1, d->d2, d->i1, d->i2); + if (d->d1 == d->d2 && d->i1 == d->i2 && !d->path[d->pos]) { + d->tok = tok; + if (d->tokptr) *d->tokptr = s + off; + if (d->toklen) *d->toklen = len; + return 1; + } + } + return 0; +} + +int mjson_find(const char *s, int n, const char *jp, const char **tp, int *tl) { + struct msjon_get_data data = {jp, 1, 0, 0, 0, + 0, -1, tp, tl, MJSON_TOK_INVALID}; + if (jp[0] != '$') return MJSON_TOK_INVALID; + if (mjson(s, n, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID; + return data.tok; +} + +int mjson_get_number(const char *s, int len, const char *path, double *v) { + const char *p; + int tok, n; + if ((tok = mjson_find(s, len, path, &p, &n)) == MJSON_TOK_NUMBER) { + if (v != NULL) *v = mystrtod(p, NULL); + } + return tok == MJSON_TOK_NUMBER ? 1 : 0; +} + +int mjson_get_bool(const char *s, int len, const char *path, int *v) { + int tok = mjson_find(s, len, path, NULL, NULL); + if (tok == MJSON_TOK_TRUE && v != NULL) *v = 1; + if (tok == MJSON_TOK_FALSE && v != NULL) *v = 0; + return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0; +} + +static unsigned char unhex(unsigned char c) { + return (c >= '0' && c <= '9') ? (unsigned char) (c - '0') + : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7') + : (unsigned char) (c - 'W'); +} + +static unsigned char mjson_unhex_nimble(const char *s) { + const unsigned char *u = (const unsigned char *) s; + return (unsigned char) (((unsigned char) (unhex(u[0]) << 4)) | unhex(u[1])); +} + +static int mjson_unescape(const char *s, int len, char *to, int n) { + int i, j; + for (i = 0, j = 0; i < len && j < n; i++, j++) { + if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') { + // \uXXXX escape. We could process a simple one-byte chars + // \u00xx from the ASCII range. More complex chars would require + // dragging in a UTF8 library, which is too much for us + if (s[i + 2] != '0' || s[i + 3] != '0') return -1; // Too much, give up + ((unsigned char *) to)[j] = mjson_unhex_nimble(s + i + 4); + i += 5; + } else if (s[i] == '\\' && i + 1 < len) { + int c = mjson_esc(s[i + 1], 0); + if (c == 0) return -1; + to[j] = (char) (unsigned char) c; + i++; + } else { + to[j] = s[i]; + } + } + if (j >= n) return -1; + if (n > 0) to[j] = '\0'; + return j; +} + +int mjson_get_string(const char *s, int len, const char *path, char *to, + int n) { + const char *p; + int sz; + if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return -1; + return mjson_unescape(p + 1, sz - 2, to, n); +} + +int mjson_get_hex(const char *s, int len, const char *x, char *to, int n) { + const char *p; + int i, j, sz; + if (mjson_find(s, len, x, &p, &sz) != MJSON_TOK_STRING) return -1; + for (i = j = 0; i < sz - 3 && j < n; i += 2, j++) { + ((unsigned char *) to)[j] = mjson_unhex_nimble(p + i + 1); + } + if (j < n) to[j] = '\0'; + return j; +} + +#if MJSON_ENABLE_BASE64 +static unsigned char mjson_base64rev(int c) { + if (c >= 'A' && c <= 'Z') { + return (unsigned char) (c - 'A'); + } else if (c >= 'a' && c <= 'z') { + return (unsigned char) (c + 26 - 'a'); + } else if (c >= '0' && c <= '9') { + return (unsigned char) (c + 52 - '0'); + } else if (c == '+') { + return 62; + } else if (c == '/') { + return 63; + } else { + return 64; + } +} + +int mjson_base64_dec(const char *src, int n, char *dst, int dlen) { + const char *end = src + n; + int len = 0; + while (src + 3 < end && len < dlen) { + unsigned char a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]), + c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]); + dst[len++] = (char) (unsigned char) ((a << 2) | (b >> 4)); + if (src[2] != '=' && len < dlen) { + dst[len++] = (char) (unsigned char) ((b << 4) | (c >> 2)); + if (src[3] != '=' && len < dlen) { + dst[len++] = (char) (unsigned char) ((c << 6) | d); + } + } + src += 4; + } + if (len < dlen) dst[len] = '\0'; + return len; +} + +int mjson_get_base64(const char *s, int len, const char *path, char *to, + int n) { + const char *p; + int sz; + if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return 0; + return mjson_base64_dec(p + 1, sz - 2, to, n); +} +#endif // MJSON_ENABLE_BASE64 + +#if MJSON_ENABLE_NEXT +struct nextdata { + int off, len, depth, t, vo, arrayindex; + int *koff, *klen, *voff, *vlen, *vtype; +}; + +static int next_cb(int tok, const char *s, int off, int len, void *ud) { + struct nextdata *d = (struct nextdata *) ud; + // int i; + switch (tok) { + case '{': + case '[': + if (d->depth == 0 && tok == '[') d->arrayindex = 0; + if (d->depth == 1 && off > d->off) { + d->vo = off; + d->t = tok == '{' ? MJSON_TOK_OBJECT : MJSON_TOK_ARRAY; + if (d->voff) *d->voff = off; + if (d->vtype) *d->vtype = d->t; + } + d->depth++; + break; + case '}': + case ']': + d->depth--; + if (d->depth == 1 && d->vo) { + d->len = off + len; + if (d->vlen) *d->vlen = d->len - d->vo; + if (d->arrayindex >= 0) { + if (d->koff) *d->koff = d->arrayindex; // koff holds array index + if (d->klen) *d->klen = 0; // klen holds 0 + } + return 1; + } + if (d->depth == 1 && d->arrayindex >= 0) d->arrayindex++; + break; + case ',': + case ':': + break; + case MJSON_TOK_KEY: + if (d->depth == 1 && d->off < off) { + if (d->koff) *d->koff = off; // And report back to the user + if (d->klen) *d->klen = len; // If we have to + } + break; + default: + if (d->depth != 1) break; + // If we're iterating over the array + if (off > d->off) { + d->len = off + len; + if (d->vlen) *d->vlen = len; // value length + if (d->voff) *d->voff = off; // value offset + if (d->vtype) *d->vtype = tok; // value type + if (d->arrayindex >= 0) { + if (d->koff) *d->koff = d->arrayindex; // koff holds array index + if (d->klen) *d->klen = 0; // klen holds 0 + } + return 1; + } + if (d->arrayindex >= 0) d->arrayindex++; + break; + } + (void) s; + return 0; +} + +int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff, + int *vlen, int *vtype) { + struct nextdata d = {off, 0, 0, 0, 0, -1, koff, klen, voff, vlen, vtype}; + mjson(s, n, next_cb, &d); + return d.len; +} +#endif + +#if MJSON_ENABLE_PRINT +int mjson_print_fixed_buf(const char *ptr, int len, void *fn_data) { + struct mjson_fixedbuf *fb = (struct mjson_fixedbuf *) fn_data; + int i, left = fb->size - 1 - fb->len; + if (left < len) len = left; + for (i = 0; i < len; i++) fb->ptr[fb->len + i] = ptr[i]; + fb->len += len; + fb->ptr[fb->len] = '\0'; + return len; +} + +// This function allocates memory in chunks of size MJSON_DYNBUF_CHUNK +// to decrease memory fragmentation, when many calls are executed to +// print e.g. a base64 string or a hex string. +int mjson_print_dynamic_buf(const char *ptr, int len, void *fn_data) { + char *s, *buf = *(char **) fn_data; + size_t curlen = buf == NULL ? 0 : strlen(buf); + size_t new_size = curlen + (size_t) len + 1 + MJSON_DYNBUF_CHUNK; + new_size -= new_size % MJSON_DYNBUF_CHUNK; + + if ((s = (char *) MJSON_REALLOC(buf, new_size)) == NULL) { + return 0; + } else { + memcpy(s + curlen, ptr, (size_t) len); + s[curlen + (size_t) len] = '\0'; + *(char **) fn_data = s; + return len; + } +} + +int mjson_snprintf(char *buf, size_t len, const char *fmt, ...) { + va_list ap; + struct mjson_fixedbuf fb = {buf, (int) len, 0}; + va_start(ap, fmt); + mjson_vprintf(mjson_print_fixed_buf, &fb, fmt, &ap); + va_end(ap); + return fb.len; +} + +char *mjson_aprintf(const char *fmt, ...) { + va_list ap; + char *result = NULL; + va_start(ap, fmt); + mjson_vprintf(mjson_print_dynamic_buf, &result, fmt, &ap); + va_end(ap); + return result; +} + +int mjson_print_null(const char *ptr, int len, void *userdata) { + (void) ptr; + (void) userdata; + return len; +} + +int mjson_print_buf(mjson_print_fn_t fn, void *fnd, const char *buf, int len) { + return fn(buf, len, fnd); +} + +int mjson_print_long(mjson_print_fn_t fn, void *fnd, long val, int is_signed) { + unsigned long v = (unsigned long) val, s = 0, n, i; + char buf[20], t; + if (is_signed && val < 0) buf[s++] = '-', v = (unsigned long) (-val); + // This loop prints a number in reverse order. I guess this is because we + // write numbers from right to left: least significant digit comes last. + // Maybe because we use Arabic numbers, and Arabs write RTL? + for (n = 0; v > 0; v /= 10) buf[s + n++] = "0123456789"[v % 10]; + // Reverse a string + for (i = 0; i < n / 2; i++) + t = buf[s + i], buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t; + if (val == 0) buf[n++] = '0'; // Handle special case + return fn(buf, (int) (s + n), fnd); +} + +int mjson_print_int(mjson_print_fn_t fn, void *fnd, int v, int s) { + return mjson_print_long(fn, fnd, s ? (long) v : (long) (unsigned) v, s); +} + +static int addexp(char *buf, int e, int sign) { + int n = 0; + buf[n++] = 'e'; + buf[n++] = (char) sign; + if (e > 400) return 0; + if (e < 10) buf[n++] = '0'; + if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100); + if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10); + buf[n++] = (char) (e + '0'); + return n; +} + +int mjson_print_dbl(mjson_print_fn_t fn, void *fnd, double d, int width) { + char buf[40]; + int i, s = 0, n = 0, e = 0; + double t, mul, saved; + if (d == 0.0) return fn("0", 1, fnd); + if (isinf(d)) return fn(d > 0 ? "inf" : "-inf", d > 0 ? 3 : 4, fnd); + if (isnan(d)) return fn("nan", 3, fnd); + if (d < 0.0) d = -d, buf[s++] = '-'; + + // Round + saved = d; + mul = 1.0; + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; + while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0; + for (i = 0, t = mul * 5; i < width; i++) t /= 10.0; + d += t; + // Calculate exponent, and 'mul' for scientific representation + mul = 1.0; + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++; + while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--; + // printf(" --> %g %d %g %g\n", saved, e, t, mul); + + if (e >= width) { + struct mjson_fixedbuf fb = {buf + s, (int) sizeof(buf) - s, 0}; + n = mjson_print_dbl(mjson_print_fixed_buf, &fb, saved / mul, width); + // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, fb.len, fb.ptr); + n += addexp(buf + s + n, e, '+'); + return fn(buf, s + n, fnd); + } else if (e <= -width) { + struct mjson_fixedbuf fb = {buf + s, (int) sizeof(buf) - s, 0}; + n = mjson_print_dbl(mjson_print_fixed_buf, &fb, saved / mul, width); + // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, fb.len, fb.ptr); + n += addexp(buf + s + n, -e, '-'); + return fn(buf, s + n, fnd); + } else { + for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) { + int ch = (int) (d / t); + if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0'); + d -= ch * t; + t /= 10.0; + } + // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf); + if (n == 0) buf[s++] = '0'; + while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0; + if (s + n < (int) sizeof(buf)) buf[n + s++] = '.'; + // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf); + for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) { + int ch = (int) (d / t); + buf[s + n++] = (char) (ch + '0'); + d -= ch * t; + t /= 10.0; + } + } + while (n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeros + if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot + buf[s + n] = '\0'; + return fn(buf, s + n, fnd); +} + +int mjson_print_str(mjson_print_fn_t fn, void *fnd, const char *s, int len) { + int i, n = fn("\"", 1, fnd); + for (i = 0; i < len; i++) { + char c = (char) (unsigned char) mjson_escape(s[i]); + if (c) { + n += fn("\\", 1, fnd); + n += fn(&c, 1, fnd); + } else { + n += fn(&s[i], 1, fnd); + } + } + return n + fn("\"", 1, fnd); +} + +#if MJSON_ENABLE_BASE64 +int mjson_print_b64(mjson_print_fn_t fn, void *fnd, const unsigned char *s, + int n) { + const char *t = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int i, len = fn("\"", 1, fnd); + for (i = 0; i < n; i += 3) { + int a = s[i], b = i + 1 < n ? s[i + 1] : 0, c = i + 2 < n ? s[i + 2] : 0; + char buf[4] = {t[a >> 2], t[(a & 3) << 4 | (b >> 4)], '=', '='}; + if (i + 1 < n) buf[2] = t[(b & 15) << 2 | (c >> 6)]; + if (i + 2 < n) buf[3] = t[c & 63]; + len += fn(buf, sizeof(buf), fnd); + } + return len + fn("\"", 1, fnd); +} +#endif /* MJSON_ENABLE_BASE64 */ + +int mjson_vprintf(mjson_print_fn_t fn, void *fnd, const char *fmt, + va_list *ap) { + int i = 0, n = 0; + while (fmt[i] != '\0') { + if (fmt[i] == '%') { + char fc = fmt[++i]; + int is_long = 0; + if (fc == 'l') { + is_long = 1; + fc = fmt[i + 1]; + } + if (fc == 'Q') { + char *buf = va_arg(*ap, char *); + n += mjson_print_str(fn, fnd, buf ? buf : "", + buf ? (int) strlen(buf) : 0); + } else if (strncmp(&fmt[i], ".*Q", 3) == 0) { + int len = va_arg(*ap, int); + char *buf = va_arg(*ap, char *); + n += mjson_print_str(fn, fnd, buf, len); + i += 2; + } else if (fc == 'd' || fc == 'u') { + int is_signed = (fc == 'd'); + if (is_long) { + long val = va_arg(*ap, long); + n += mjson_print_long(fn, fnd, val, is_signed); + i++; + } else { + int val = va_arg(*ap, int); + n += mjson_print_int(fn, fnd, val, is_signed); + } + } else if (fc == 'B') { + const char *s = va_arg(*ap, int) ? "true" : "false"; + n += mjson_print_buf(fn, fnd, s, (int) strlen(s)); + } else if (fc == 's') { + char *buf = va_arg(*ap, char *); + n += mjson_print_buf(fn, fnd, buf, (int) strlen(buf)); + } else if (strncmp(&fmt[i], ".*s", 3) == 0) { + int len = va_arg(*ap, int); + char *buf = va_arg(*ap, char *); + n += mjson_print_buf(fn, fnd, buf, len); + i += 2; + } else if (fc == 'g') { + n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), 6); + } else if (strncmp(&fmt[i], ".*g", 3) == 0) { + int width = va_arg(*ap, int); + n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), width); + i += 2; +#if MJSON_ENABLE_BASE64 + } else if (fc == 'V') { + int len = va_arg(*ap, int); + const char *buf = va_arg(*ap, const char *); + n += mjson_print_b64(fn, fnd, (const unsigned char *) buf, len); +#endif + } else if (fc == 'H') { + const char *hex = "0123456789abcdef"; + int j, len = va_arg(*ap, int); + const unsigned char *p = va_arg(*ap, const unsigned char *); + n += fn("\"", 1, fnd); + for (j = 0; j < len; j++) { + n += fn(&hex[(p[j] >> 4) & 15], 1, fnd); + n += fn(&hex[p[j] & 15], 1, fnd); + } + n += fn("\"", 1, fnd); + } else if (fc == 'M') { + mjson_vprint_fn_t vfn = va_arg(*ap, mjson_vprint_fn_t); + n += vfn(fn, fnd, ap); + } + i++; + } else { + n += mjson_print_buf(fn, fnd, &fmt[i++], 1); + } + } + return n; +} + +int mjson_printf(mjson_print_fn_t fn, void *fnd, const char *fmt, ...) { + va_list ap; + int len; + va_start(ap, fmt); + len = mjson_vprintf(fn, fnd, fmt, &ap); + va_end(ap); + return len; +} +#endif /* MJSON_ENABLE_PRINT */ + +static int is_digit(int c) { + return c >= '0' && c <= '9'; +} + +/* NOTE: strtod() implementation by Yasuhiro Matsumoto. */ +static double mystrtod(const char *str, const char **end) { + double d = 0.0; + int sign = 1, n = 0; + const char *p = str, *a = str; + + /* decimal part */ + if (*p == '-') { + sign = -1; + ++p; + } else if (*p == '+') { + ++p; + } + if (is_digit(*p)) { + d = (double) (*p++ - '0'); + while (*p && is_digit(*p)) { + d = d * 10.0 + (double) (*p - '0'); + ++p; + ++n; + } + a = p; + } else if (*p != '.') { + goto done; + } + d *= sign; + + /* fraction part */ + if (*p == '.') { + double f = 0.0; + double base = 0.1; + ++p; + + if (is_digit(*p)) { + while (*p && is_digit(*p)) { + f += base * (*p - '0'); + base /= 10.0; + ++p; + ++n; + } + } + d += f * sign; + a = p; + } + + /* exponential part */ + if ((*p == 'E') || (*p == 'e')) { + int i, e = 0, neg = 0; + p++; + if (*p == '-') p++, neg++; + if (*p == '+') p++; + while (is_digit(*p)) e = e * 10 + *p++ - '0'; + if (neg) e = -e; +#if 0 + if (d == 2.2250738585072011 && e == -308) { + d = 0.0; + a = p; + goto done; + } + if (d == 2.2250738585072012 && e <= -308) { + d *= 1.0e-308; + a = p; + goto done; + } +#endif + for (i = 0; i < e; i++) d *= 10; + for (i = 0; i < -e; i++) d /= 10; + a = p; + } else if (p > str && !is_digit(*(p - 1))) { + a = str; + goto done; + } + +done: + if (end) *end = a; + return d; +} + +#if MJSON_ENABLE_MERGE +int mjson_merge(const char *s, int n, const char *s2, int n2, + mjson_print_fn_t fn, void *userdata) { + int koff, klen, voff, vlen, t, t2, k, off = 0, len = 0, comma = 0; + if (n < 2) return len; + len += fn("{", 1, userdata); + while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) { + char *path = (char *) alloca((size_t) klen + 1); + const char *val; + memcpy(path, "$.", 2); + memcpy(path + 2, s + koff + 1, (size_t) (klen - 2)); + path[klen] = '\0'; + if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) { + if (t2 == MJSON_TOK_NULL) continue; // null deletes the key + } else { + val = s + voff; // Key is not found in the update. Copy the old value. + } + if (comma) len += fn(",", 1, userdata); + len += fn(s + koff, klen, userdata); + len += fn(":", 1, userdata); + if (t == MJSON_TOK_OBJECT && t2 == MJSON_TOK_OBJECT) { + len += mjson_merge(s + voff, vlen, val, k, fn, userdata); + } else { + if (t2 != MJSON_TOK_INVALID) vlen = k; + len += fn(val, vlen, userdata); + } + comma = 1; + } + // Add missing keys + off = 0; + while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) { + char *path = (char *) alloca((size_t) klen + 1); + const char *val; + if (t == MJSON_TOK_NULL) continue; + memcpy(path, "$.", 2); + memcpy(path + 2, s2 + koff + 1, (size_t) (klen - 2)); + path[klen] = '\0'; + if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue; + if (comma) len += fn(",", 1, userdata); + len += fn(s2 + koff, klen, userdata); + len += fn(":", 1, userdata); + len += fn(s2 + voff, vlen, userdata); + comma = 1; + } + len += fn("}", 1, userdata); + return len; +} +#endif // MJSON_ENABLE_MERGE + +#if MJSON_ENABLE_PRETTY +struct prettydata { + int level; + int len; + int prev; + const char *pad; + int padlen; + mjson_print_fn_t fn; + void *userdata; +}; + +static int pretty_cb(int ev, const char *s, int off, int len, void *ud) { + struct prettydata *d = (struct prettydata *) ud; + int i; + switch (ev) { + case '{': + case '[': + d->level++; + d->len += d->fn(s + off, len, d->userdata); + break; + case '}': + case ']': + d->level--; + if (d->prev != '[' && d->prev != '{' && d->padlen > 0) { + d->len += d->fn("\n", 1, d->userdata); + for (i = 0; i < d->level; i++) + d->len += d->fn(d->pad, d->padlen, d->userdata); + } + d->len += d->fn(s + off, len, d->userdata); + break; + case ',': + d->len += d->fn(s + off, len, d->userdata); + if (d->padlen > 0) { + d->len += d->fn("\n", 1, d->userdata); + for (i = 0; i < d->level; i++) + d->len += d->fn(d->pad, d->padlen, d->userdata); + } + break; + case ':': + d->len += d->fn(s + off, len, d->userdata); + if (d->padlen > 0) d->len += d->fn(" ", 1, d->userdata); + break; + case MJSON_TOK_KEY: + if (d->prev == '{' && d->padlen > 0) { + d->len += d->fn("\n", 1, d->userdata); + for (i = 0; i < d->level; i++) + d->len += d->fn(d->pad, d->padlen, d->userdata); + } + d->len += d->fn(s + off, len, d->userdata); + break; + default: + if (d->prev == '[' && d->padlen > 0) { + d->len += d->fn("\n", 1, d->userdata); + for (i = 0; i < d->level; i++) + d->len += d->fn(d->pad, d->padlen, d->userdata); + } + d->len += d->fn(s + off, len, d->userdata); + break; + } + d->prev = ev; + return 0; +} + +int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn, + void *userdata) { + struct prettydata d = {0, 0, 0, pad, (int) strlen(pad), fn, userdata}; + if (mjson(s, n, pretty_cb, &d) < 0) return -1; + return d.len; +} +#endif // MJSON_ENABLE_PRETTY + +#if MJSON_ENABLE_RPC +struct jsonrpc_ctx jsonrpc_default_context; + +int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) { + int i = 0, j = 0, ni = 0, nj = 0; + while (i < n1 || j < n2) { + if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) { + i++, j++; + } else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) { + ni = i, nj = j + 1, i++; + } else if (nj > 0 && nj <= n2 && (s1[i - 1] == '#' || s2[j] != '/')) { + i = ni, j = nj; + } else { + return 0; + } + } + return 1; +} + +void jsonrpc_return_errorv(struct jsonrpc_request *r, int code, + const char *message, const char *data_fmt, + va_list *ap) { + if (r->id_len == 0) return; + mjson_printf(r->fn, r->fn_data, + "{\"id\":%.*s,\"error\":{\"code\":%d,\"message\":%Q", r->id_len, + r->id, code, message == NULL ? "" : message); + if (data_fmt != NULL) { + mjson_printf(r->fn, r->fn_data, ",\"data\":"); + mjson_vprintf(r->fn, r->fn_data, data_fmt, ap); + } + mjson_printf(r->fn, r->fn_data, "}}\n"); +} + +void jsonrpc_return_error(struct jsonrpc_request *r, int code, + const char *message, const char *data_fmt, ...) { + va_list ap; + va_start(ap, data_fmt); + jsonrpc_return_errorv(r, code, message, data_fmt, &ap); + va_end(ap); +} + +void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt, + va_list *ap) { + if (r->id_len == 0) return; + mjson_printf(r->fn, r->fn_data, "{\"id\":%.*s,\"result\":", r->id_len, r->id); + if (result_fmt != NULL) { + mjson_vprintf(r->fn, r->fn_data, result_fmt, ap); + } else { + mjson_printf(r->fn, r->fn_data, "%s", "null"); + } + mjson_printf(r->fn, r->fn_data, "}\n"); +} + +void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt, + ...) { + va_list ap; + va_start(ap, result_fmt); + jsonrpc_return_successv(r, result_fmt, &ap); + va_end(ap); +} + +void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *buf, int len, + mjson_print_fn_t fn, void *fn_data, void *ud) { + const char *result = NULL, *error = NULL; + int result_sz = 0, error_sz = 0; + struct jsonrpc_method *m = NULL; + struct jsonrpc_request r = {ctx, buf, len, 0, 0, 0, 0, 0, 0, fn, fn_data, ud}; + + // Is is a response frame? + mjson_find(buf, len, "$.result", &result, &result_sz); + if (result == NULL) mjson_find(buf, len, "$.error", &error, &error_sz); + if (result_sz > 0 || error_sz > 0) { + if (ctx->response_cb) ctx->response_cb(buf, len, ctx->response_cb_data); + return; + } + + // Method must exist and must be a string + if (mjson_find(buf, len, "$.method", &r.method, &r.method_len) != + MJSON_TOK_STRING) { + mjson_printf(fn, fn_data, + "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n", len, buf); + return; + } + + // id and params are optional + mjson_find(buf, len, "$.id", &r.id, &r.id_len); + mjson_find(buf, len, "$.params", &r.params, &r.params_len); + + for (m = ctx->methods; m != NULL; m = m->next) { + if (mjson_globmatch(m->method, m->method_sz, r.method + 1, + r.method_len - 2) > 0) { + if (r.params == NULL) r.params = ""; + m->cb(&r); + break; + } + } + if (m == NULL) { + jsonrpc_return_error(&r, JSONRPC_ERROR_NOT_FOUND, "method not found", NULL); + } +} + +static int jsonrpc_print_methods(mjson_print_fn_t fn, void *fn_data, + va_list *ap) { + struct jsonrpc_ctx *ctx = va_arg(*ap, struct jsonrpc_ctx *); + struct jsonrpc_method *m; + int len = 0; + for (m = ctx->methods; m != NULL; m = m->next) { + if (m != ctx->methods) len += mjson_print_buf(fn, fn_data, ",", 1); + len += mjson_print_str(fn, fn_data, m->method, (int) strlen(m->method)); + } + return len; +} + +void jsonrpc_list(struct jsonrpc_request *r) { + jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->ctx); +} + +void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb, + void *response_cb_data) { + ctx->methods = NULL; + ctx->response_cb = response_cb; + ctx->response_cb_data = response_cb_data; +} + +void jsonrpc_init(mjson_print_fn_t response_cb, void *userdata) { + struct jsonrpc_ctx *ctx = &jsonrpc_default_context; + jsonrpc_ctx_init(ctx, response_cb, userdata); + jsonrpc_ctx_export(ctx, MJSON_RPC_LIST_NAME, jsonrpc_list); +} +#endif // MJSON_ENABLE_RPC diff --git a/apps/usr_le_code/thirdpart/mjson/mjson.h b/apps/usr_le_code/thirdpart/mjson/mjson.h new file mode 100644 index 0000000..d8156e0 --- /dev/null +++ b/apps/usr_le_code/thirdpart/mjson/mjson.h @@ -0,0 +1,220 @@ +// Copyright (c) 2018-2020 Cesanta Software Limited +// All rights reserved +// +// 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 MJSON_H +#define MJSON_H + +#include +#include +#include + +#ifndef MJSON_ENABLE_PRINT +#define MJSON_ENABLE_PRINT 1 +#endif + +#ifndef MJSON_ENABLE_RPC +#define MJSON_ENABLE_RPC 1 +#endif + +#ifndef MJSON_ENABLE_BASE64 +#define MJSON_ENABLE_BASE64 1 +#endif + +#ifndef MJSON_ENABLE_MERGE +#define MJSON_ENABLE_MERGE 1 +#endif + +#ifndef MJSON_ENABLE_PRETTY +#define MJSON_ENABLE_PRETTY 1 +#endif + +#ifndef MJSON_ENABLE_NEXT +#define MJSON_ENABLE_NEXT 1 +#endif + +#ifndef MJSON_RPC_LIST_NAME +#define MJSON_RPC_LIST_NAME "rpc.list" +#endif + +#ifndef MJSON_DYNBUF_CHUNK +#define MJSON_DYNBUF_CHUNK 256 // Allocation granularity for print_dynamic_buf +#endif + +#ifndef MJSON_REALLOC +#define MJSON_REALLOC realloc +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define MJSON_ERROR_INVALID_INPUT (-1) +#define MJSON_ERROR_TOO_DEEP (-2) +#define MJSON_TOK_INVALID 0 +#define MJSON_TOK_KEY 1 +#define MJSON_TOK_STRING 11 +#define MJSON_TOK_NUMBER 12 +#define MJSON_TOK_TRUE 13 +#define MJSON_TOK_FALSE 14 +#define MJSON_TOK_NULL 15 +#define MJSON_TOK_ARRAY 91 +#define MJSON_TOK_OBJECT 123 +#define MJSON_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20) + +typedef int (*mjson_cb_t)(int event, const char *buf, int offset, int len, + void *fn_data); + +#ifndef MJSON_MAX_DEPTH +#define MJSON_MAX_DEPTH 20 +#endif + +int mjson(const char *buf, int len, mjson_cb_t cb, void *ud); +int mjson_find(const char *buf, int len, const char *jp, const char **tp, + int *tl); +int mjson_get_number(const char *buf, int len, const char *path, double *v); +int mjson_get_bool(const char *buf, int len, const char *path, int *v); +int mjson_get_string(const char *buf, int len, const char *path, char *to, + int n); +int mjson_get_hex(const char *buf, int len, const char *path, char *to, int n); + +#if MJSON_ENABLE_NEXT +int mjson_next(const char *buf, int len, int offset, int *key_offset, + int *key_len, int *val_offset, int *val_len, int *vale_type); +#endif + +#if MJSON_ENABLE_BASE64 +int mjson_get_base64(const char *buf, int len, const char *path, char *dst, + int dst_len); +int mjson_base64_dec(const char *src, int src_len, char *dst, int dst_len); +#endif + +#if MJSON_ENABLE_PRINT +typedef int (*mjson_print_fn_t)(const char *buf, int len, void *fn_data); +typedef int (*mjson_vprint_fn_t)(mjson_print_fn_t fn, void *, va_list *); + +struct mjson_fixedbuf { + char *ptr; + int size, len; +}; + +int mjson_printf(mjson_print_fn_t fn, void *fn_data, const char *fmt, ...); +int mjson_vprintf(mjson_print_fn_t fn, void *fn_data, const char *fmt, + va_list *ap); +int mjson_print_str(mjson_print_fn_t fn, void *fn_data, const char *buf, + int len); +int mjson_print_int(mjson_print_fn_t fn, void *fn_data, int value, + int is_signed); +int mjson_print_long(mjson_print_fn_t fn, void *fn_data, long value, + int is_signed); +int mjson_print_buf(mjson_print_fn_t fn, void *fn_data, const char *buf, + int len); +int mjson_print_dbl(mjson_print_fn_t fn, void *fn_data, double d, int width); + +int mjson_print_null(const char *ptr, int len, void *fn_data); +int mjson_print_fixed_buf(const char *ptr, int len, void *fn_data); +int mjson_print_dynamic_buf(const char *ptr, int len, void *fn_data); + +int mjson_snprintf(char *buf, size_t len, const char *fmt, ...); +char *mjson_aprintf(const char *fmt, ...); + +#if MJSON_ENABLE_PRETTY +int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn, + void *fn_data); +#endif + +#if MJSON_ENABLE_MERGE +int mjson_merge(const char *s, int n, const char *s2, int n2, + mjson_print_fn_t fn, void *fn_data); +#endif + +#endif // MJSON_ENABLE_PRINT + +#if MJSON_ENABLE_RPC + +void jsonrpc_init(mjson_print_fn_t response_cb, void *fn_data); +int mjson_globmatch(const char *s1, int n1, const char *s2, int n2); + +struct jsonrpc_request { + struct jsonrpc_ctx *ctx; + const char *frame; // Points to the whole frame + int frame_len; // Frame length + const char *params; // Points to the "params" in the request frame + int params_len; // Length of the "params" + const char *id; // Points to the "id" in the request frame + int id_len; // Length of the "id" + const char *method; // Points to the "method" in the request frame + int method_len; // Length of the "method" + mjson_print_fn_t fn; // Printer function + void *fn_data; // Printer function data + void *userdata; // Callback's user data as specified at export time +}; + +struct jsonrpc_method { + const char *method; + int method_sz; + void (*cb)(struct jsonrpc_request *); + struct jsonrpc_method *next; +}; + +// Main RPC context, stores current request information and a list of +// exported RPC methods. +struct jsonrpc_ctx { + struct jsonrpc_method *methods; + mjson_print_fn_t response_cb; + void *response_cb_data; +}; + +// Registers function fn under the given name within the given RPC context +#define jsonrpc_ctx_export(ctx, name, fn) \ + do { \ + static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0}; \ + m.next = (ctx)->methods; \ + (ctx)->methods = &m; \ + } while (0) + +void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb, + void *response_cb_data); +void jsonrpc_return_error(struct jsonrpc_request *r, int code, + const char *message, const char *data_fmt, ...); +void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt, + ...); +void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz, + mjson_print_fn_t fn, void *fn_data, void *userdata); + +extern struct jsonrpc_ctx jsonrpc_default_context; +extern void jsonrpc_list(struct jsonrpc_request *r); + +#define jsonrpc_export(name, fn) \ + jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn)) + +#define jsonrpc_process(buf, len, fn, fnd, ud) \ + jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (fnd), (ud)) + +#define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */ +#define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */ +#define JSONRPC_ERROR_BAD_PARAMS -32602 /* Invalid params passed */ +#define JSONRPC_ERROR_INTERNAL -32603 /* Internal JSON-RPC error */ + +#endif // MJSON_ENABLE_RPC +#ifdef __cplusplus +} +#endif +#endif // MJSON_H diff --git a/apps/usr_le_code/usr_le_adv.c b/apps/usr_le_code/usr_le_adv.c new file mode 100644 index 0000000..9faa88c --- /dev/null +++ b/apps/usr_le_code/usr_le_adv.c @@ -0,0 +1,340 @@ +/****************************************************************************** + * @file usr_le_adv.c + * @brief BLE 广播 / 配对 / OTA 广播切换 + * @author cyWu <1917507415@qq.com> + * @date 2026.08.19 + * @version V1.1.0 + * @history + * - V1.0.0, 2026.08.03, cyWu, 首次发布 + * - V1.1.0, 2026.08.19, cyWu, 对齐模组注释与助手;保留 SOC 上电绑广播策略 + ******************************************************************************/ + +#include "system/includes.h" +#include "app_config.h" +#include "app_main.h" +#include "vm.h" +#include "bleproto.h" +#include "bleproto_api.h" +#include "usr_le_product.h" + +#define LOG_TAG_CONST APP +#define LOG_TAG "[LE_ADV]" +#define LOG_ERROR_ENABLE +#define LOG_DEBUG_ENABLE +#define LOG_INFO_ENABLE +#include "debug.h" + +/****************************************************************************** + * 广播相关三个标志(务必区分): + * + * 1) usr_auth_data.paired_flag —— 配网结果(存 VM,掉电保持) + * - 0:未配网 + * - 1:已配网(三元组已保存) + * + * 2) usr_var.usr_goto_pair —— 是否主动进入配网流程(RAM,掉电丢失) + * - 0:非配网流程 + * - 1:app_main 上电 / usr_goto_pair_mode() 触发,发【绑定广播】 + * + * 3) usr_var.usr_adv_flag —— 广播内容是否需要刷新到协议栈(一次性) + * - 1:usr_ble_adv_updata() 会重新 set adv data 并 enable + * - 0:本周期不刷新 + * + * SOC 决策表(与模组不同:未配网也发绑定广播,便于上电配网): + * usr_goto_pair==1 → 绑定广播 + * paired_flag==0 → 绑定广播 + * paired_flag==1 → 回连广播 + ******************************************************************************/ + +extern u8 muti_adv_data[31]; +extern u8 muti_adv_data_len; + +/* 定义在 usr_le_recieve.c,库内共用,不对外导出 */ +extern bleproto_authsetup_req_t usr_auth_data; + +extern u16 usr_get_conn_service(void); +extern int muti_make_set_adv_data(u8 *data, u8 data_len); +extern int muti_make_set_adv_data_updata(void); +extern void usr_set_adv_interval(u16 val); +extern u16 usr_get_adv_interval(void); +extern void ble_multi_trans_disconnect(void); +extern void ble_trans_module_enable(u8 flag); +extern void usr_timer_loop(void); +extern u8 usr_get_ota_status(void); +extern void usr_ota_exit(void); + +void usr_ble_adv_init(void); +void usr_ble_adv_updata(void); + +static int le_timer_handle = 0; + +/** + * @brief BLE 是否已连接 + * @return 1=已连接,0=未连接 + */ +uint8_t usr_ble_is_connected(void) +{ + return (usr_get_conn_service() != 0) ? 1u : 0u; +} + +/** + * @brief 从 VM 读取配对信息到 usr_auth_data + * @note 失败则清零,并将 paired_flag 置 0 写回 + */ +void usr_get_pair_info(void) +{ + int ret; + + ret = syscfg_read(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data)); + if (ret <= 0) + { + memset(&usr_auth_data, 0x00, sizeof(usr_auth_data)); + usr_auth_data.paired_flag = 0; + syscfg_write(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data)); + } +} + +/** + * @brief 标记已配网并保存三元组到 VM + * @note AUTHSETUP 成功后调用,paired_flag = 1 + */ +void usr_write_pair_info(void) +{ + usr_auth_data.paired_flag = 1; + syscfg_write(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data)); +} + +/** + * @brief 清除配对信息并延时复位 + */ +void usr_clear_pair_info(void) +{ + printf("usr_clear_pair_info_reset\n"); + memset(&usr_auth_data, 0x00, sizeof(usr_auth_data)); + usr_auth_data.paired_flag = 0; + syscfg_write(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data)); + sys_timeout_add(NULL, cpu_reset, 600); +} + +/** + * @brief 清除配对信息但不复位(用于重新配网) + */ +void usr_clear_pair_info_noreset(void) +{ + printf("usr_clear_pair_info_noreset\n"); + memset(&usr_auth_data, 0x00, sizeof(usr_auth_data)); + usr_auth_data.paired_flag = 0; + syscfg_write(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data)); +} + +/** + * @brief 进入配网模式:清配对 + 置 usr_goto_pair + 发绑定广播 + */ +void usr_goto_pair_mode(void) +{ + ble_multi_trans_disconnect(); + usr_clear_pair_info_noreset(); + usr_var.usr_goto_pair = 1; + os_time_dly(5); + usr_ble_adv_init(); +} + +/** + * @brief 断开 BLE 并停止广播(复位/重启前) + */ +void usr_ble_disconnect_and_stop_adv(void) +{ + printf("usr_ble_disconnect_and_stop_adv\n"); + ble_multi_trans_disconnect(); + usr_var.usr_goto_pair = 0; + usr_var.usr_adv_flag = 0; + ble_trans_module_enable(0); +} + +/** + * @brief 按当前标志刷新广播策略并启动周期更新 + * @note 见文件头「三个标志」;SOC 未配网也发绑定广播 + */ +void usr_ble_adv_init(void) +{ + int8_t txpower = 0; + uint8_t manucode[BLEPROTO_ADV_TLV_LENGTH_MANUCODE] = {0}; + uint8_t prodcode[BLEPROTO_ADV_TLV_LENGTH_PRODCODE]; + uint8_t gmac[6] = {0}; + uint8_t pcode[BLE_PROTO_ADV_LENGTH_PCODE]; + + /* 产品身份由应用层 g_usr_le_* 提供(见 usr_jb_main.c) */ + memcpy(prodcode, g_usr_le_prodcode, BLEPROTO_ADV_TLV_LENGTH_PRODCODE); + memcpy(pcode, g_usr_le_pcode, BLE_PROTO_ADV_LENGTH_PCODE); + + usr_set_adv_interval(80); + if (usr_var.usr_ota_succ_flag) + { + return; + } + + if (usr_var.usr_goto_pair) + { + /* app_main 上电已置 usr_goto_pair=1 并清配对信息,此处发绑定广播供网关配网 */ + usr_clear_pair_info_noreset(); + printf("goto pair bind adv, paired_flag=%d\n", usr_auth_data.paired_flag); + muti_adv_data_len = bleproto_advdata_encode4bind(muti_adv_data, txpower, + manucode, pcode, prodcode); + usr_var.usr_adv_flag = 1; + } + else + { + usr_get_pair_info(); + if (usr_auth_data.paired_flag == 0) + { + /* SOC:未配网也发绑定广播(模组此处为不广播) */ + muti_adv_data_len = bleproto_advdata_encode4bind(muti_adv_data, txpower, + manucode, pcode, prodcode); + usr_var.usr_adv_flag = 0; + } + else + { + memcpy(gmac, usr_auth_data.gwmac, 6); + muti_adv_data_len = bleproto_advdata_encode4reconn(muti_adv_data, txpower, gmac, + manucode, pcode, prodcode); + usr_var.usr_adv_flag = 1; + } + } + + usr_ble_adv_updata(); + if (le_timer_handle == 0) + { + le_timer_handle = sys_timer_add(NULL, usr_ble_adv_updata, 500); + } +} + +/** + * @brief 获取是否已配网 + * @return 1=已配网,0=未配网 + */ +u8 usr_get_pair_flag(void) +{ + return usr_auth_data.paired_flag; +} + +/** + * @brief 周期处理广播刷新(由 500ms 定时器调用) + * @note 仅当 usr_adv_flag==1 时:停广播 → 写入 muti_adv_data → 再开广播 + */ +void usr_ble_adv_updata(void) +{ + static u16 usr_goto_slow_adv = 0; + + if (usr_var.usr_adv_flag) + { + ble_trans_module_enable(0); + usr_var.usr_adv_flag = 0; + muti_make_set_adv_data(muti_adv_data, muti_adv_data_len); + printf("###muti_adv_data = "); + printf_buf(muti_adv_data, muti_adv_data_len); + ble_trans_module_enable(1); + } + + if (!usr_ble_is_connected()) + { + /* 未连接:回连快广播(80)持续约 60s(500ms×120)后切慢广播 */ + if (usr_get_adv_interval() == 80) + { + if ((usr_var.usr_goto_pair == 0) && usr_auth_data.paired_flag) + { + if (usr_var.usr_ota_succ_flag == 0) + { + usr_goto_slow_adv++; + printf("usr_goto_slow_adv = %d\n", usr_goto_slow_adv); + } + if (usr_goto_slow_adv >= 120) + { + usr_goto_slow_adv = 0; + usr_set_adv_interval(80 * 10); + usr_var.usr_adv_flag = 1; + } + } + } + else + { + usr_goto_slow_adv = 0; + } + } + else + { + usr_goto_slow_adv = 0; + } + + if (usr_get_ota_status() == 0) + { + if (!usr_ble_is_connected()) + { + if (usr_var.usr_ota_succ_flag == 0) + { + usr_ota_exit(); + } + } + } + + /* 协议层周期处理(上报/应答) */ + usr_timer_loop(); +} + +void usr_soft_off(void) +{ + sys_enter_soft_poweroff(NULL); +} + +void usr_ble_adv_updata_OTA(void) +{ + ble_trans_module_enable(0); + usr_var.usr_adv_flag = 0; + + extern u8 usr_mac_addr[6]; + extern int le_controller_set_mac(void *addr); + usr_mac_addr[0] = usr_mac_addr[0] + 1; + le_controller_set_mac(usr_mac_addr); + muti_make_set_adv_data_updata(); + printf("###muti_adv_data = "); + printf_buf(muti_adv_data, muti_adv_data_len); + ble_trans_module_enable(1); +} + +void usr_ota_process(void) +{ + static u8 cnt = 0; + + if (cnt == 0) + { + ble_multi_trans_disconnect(); + } + + if (!usr_ble_is_connected()) + { + cnt++; + if (cnt == 2) + { + usr_ble_adv_updata_OTA(); + } + if (cnt >= 60) + { + cnt = 0; + cpu_reset(); + } + } + else + { + cnt = 1; + } +} + +/** + * @brief 旧版 RCSP/JSON OTA 入口;产品 JB 0x30/0x32 路径不要调用 + */ +void usr_app_ota_init(void) +{ + usr_var.usr_goto_updata = 1; + if (usr_var.ota_timer == 0) + { + usr_var.ota_timer = sys_timer_add(NULL, usr_ota_process, 1000); + } +} diff --git a/apps/usr_le_code/usr_le_recieve.c b/apps/usr_le_code/usr_le_recieve.c new file mode 100644 index 0000000..4e0ddfe --- /dev/null +++ b/apps/usr_le_code/usr_le_recieve.c @@ -0,0 +1,377 @@ +/****************************************************************************** + * @file usr_le_recieve.c + * @brief BLE 收包入口:bleproto 解析后分发配网 / 对时 / JB 协议 / 组应答 + * @author cyWu <1917507415@qq.com> + * @date 2026.08.19 + * @version V1.1.0 + * @history + * - V1.0.0, 2026.08.03, cyWu, 首次发布 + * - V1.1.0, 2026.08.19, cyWu, 对齐模组结构;废弃 JSON OTA;修 AUTHDELETE serviceid + ******************************************************************************/ + +#include "system/includes.h" +#include "app_config.h" +#include "app_main.h" +#include "vm.h" +#include "bleproto.h" +#include "bleproto_api.h" +#include "ble_proto.h" +#include "usr_rtc.h" +#include "usr_le_api.h" +#include "jb_protocol.h" + +#define LOG_TAG_CONST APP +#define LOG_TAG "[USR_LE]" +#define LOG_ERROR_ENABLE +#define LOG_DEBUG_ENABLE +#define LOG_INFO_ENABLE +#include "debug.h" + +/* 拼 GATT 分包:业务包 ≈ bleproto头 + V2 开销 + JB 帧(<=128),1024 足够 */ +#define USR_LE_RX_BUF_LEN 1024 +#define USR_LE_V2_WRAP_OVERHEAD 32u /* V2 头开销,JB 帧最大 128 → 约 160 */ +#define USR_LE_V2_CMD_REPORT 100u /* REPORTCMD_V2 内层 cmd */ +#define USR_LE_V2_CMD_RUN 110u /* RUNCMD_V2 内层 cmd */ +#define USR_LE_JB_CMD_OFF 4u /* 55 AA | LEN_H LEN_L | CMD */ + +static int encode_len = 0; +static u8 txbuf[384] = {0}; +static u8 rxbuf[USR_LE_RX_BUF_LEN] = {0}; +static u8 s_last_runcmd_msgid = 0; /* RUNCMD_V2 应答须回填最近 msgid */ + +bleproto_authsetup_req_t usr_auth_data; + +extern void usr_ble_send_data(u8 *data, u16 len); +extern u8 usr_ble_is_notify_ready(void); +extern void usr_write_pair_info(void); +extern void usr_clear_pair_info(void); +extern void usr_jb_le_recieve_data(u8 *data, u16 len); +extern int8_t jbReportAckCheck(u16 cmd, u8 gateway_int_cnt, int32_t gateway_int0); + +static void usr_data_ana(bleproto_appdata_desc_t *desc); +static void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id); + +/** + * @brief 填 bleproto 应答头 + * @param out 输出 desc + * @param in 请求 desc(取 msgid) + * @param serviceid 服务号 + */ +static void usr_le_fill_rsp_header(bleproto_appdata_desc_t *out, + const bleproto_appdata_desc_t *in, + uint8_t serviceid) +{ + out->header.version = 0; + out->header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON; + out->header.msgtype = BLEPROTO_APPDATA_MSGTYPE_RSP; + out->header.msgid = in->header.msgid; + out->header.encrypt = 0; + out->header.serviceid = serviceid; +} + +/** + * @brief 编码并经 BLE 发出 + * @param desc 已填好的应答 + */ +static void usr_le_encode_and_send(bleproto_appdata_desc_t *desc) +{ + encode_len = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), desc); + if (encode_len > 0) + { + printf("encode_len = %d\n", encode_len); + usr_ble_send_data(txbuf, encode_len); + } + else + { + printf("encode_len fail %d\n", encode_len); + } +} + +/** + * @brief 是否走网关主动上报(REPORTCMD_V2) + * @param jb_cmd 55AA 命令字 + * @return 1=主动上报,0=RUNCMD 应答 + */ +static uint8_t usr_le_is_gateway_report_cmd(uint8_t jb_cmd) +{ + return (jb_cmd == CMD_REPORT || + jb_cmd == CMD_OTA_COMPLETE || + jb_cmd == CMD_OTA_STOP_MCU) + ? 1u + : 0u; +} + +/** + * @brief BLE 收包入口 + * @param data 收包数据 + * @param len 数据长度 + */ +void usr_le_data_recieve(u8 *data, u16 len) +{ + static u16 usr_len = 0; + /* desc 含 ycmd 等 union,放 static,避免每次收包在栈上开大块 */ + static bleproto_appdata_desc_t s_rx_desc; + int rc_result = 0; + + if (!data || len == 0) + { + return; + } + if ((u32)usr_len + len > sizeof(rxbuf)) + { + printf("### rxbuf overflow, usr_len=%u len=%u, reset\n", usr_len, len); + usr_len = 0; + return; + } + + memcpy(&rxbuf[usr_len], data, len); + usr_len += len; + rc_result = bleproto_appdata_decode(rxbuf, usr_len, &s_rx_desc); + if (rc_result < 0) + { + printf("### invild pack\n"); + usr_len = 0; + } + else if (rc_result == 0) + { + printf("### keep recieving\n"); + } + else + { + printf("### pack recieved ok\n"); + usr_len = 0; + usr_data_ana(&s_rx_desc); + } +} + +/** + * @brief 解析 bleproto 服务并分发 + * @param desc 已 decode 的包 + */ +static void usr_data_ana(bleproto_appdata_desc_t *desc) +{ + static bleproto_appdata_desc_t s_encode_desc; + + memset(&s_encode_desc, 0, sizeof(s_encode_desc)); + printf("desc serviceid=%d msgtype=%d\n", + desc->header.serviceid, desc->header.msgtype); + + if (desc->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_REQ) + { + switch (desc->header.serviceid) + { + case E_BLEPROTO_SERVICE_ID_DEVICEINFO: { + bleproto_deviceinfo_req_t *req = &desc->datast.deviceinfo_req; + bleproto_deviceinfo_rsp_t *rsp = &s_encode_desc.datast.deviceinfo_rsp; + + /* 网关从子设备连上开始计时,每 12 小时经 DEVICEINFO 同步一次 */ + usr_rtc_set_from_unix(req->t, (int32_t)req->gmtoff); + + usr_le_fill_rsp_header(&s_encode_desc, desc, E_BLEPROTO_SERVICE_ID_DEVICEINFO); + rsp->hbcycle = 1000; + strcpy(rsp->swv, "V1.0.0.00"); + strcpy(rsp->hwv, "V1.0.0.00"); + rsp->longcon = 1; + rsp->prototype = 4; + usr_le_encode_and_send(&s_encode_desc); + break; + } + case E_BLEPROTO_SERVICE_ID_AUTHSETUP: { + bleproto_authsetup_rsp_t *au_rsp = &s_encode_desc.datast.authsetup_rsp; + + printf("E_BLEPROTO_SERVICE_ID_AUTHSETUP\n"); + usr_le_fill_rsp_header(&s_encode_desc, desc, E_BLEPROTO_SERVICE_ID_AUTHSETUP); + au_rsp->errcode = 0; + memcpy(&usr_auth_data, &desc->datast.authsetup_req, sizeof(usr_auth_data)); + if (usr_auth_data.paired_flag == 0) + { + usr_write_pair_info(); + usr_var.usr_goto_pair = 0; // 新增:退出强制配网 + printf("usr_auth_data %s %s ", usr_auth_data.authcode, usr_auth_data.devid); + printf_buf(usr_auth_data.gwmac, 6); + } + usr_le_encode_and_send(&s_encode_desc); + break; + } + case E_BLEPROTO_SERVICE_ID_AUTHDELETE: { + bleproto_authdelete_rsp_t *aude_rsp = &s_encode_desc.datast.authdelete_rsp; + + printf("E_BLEPROTO_SERVICE_ID_AUTHDELETE\n"); + usr_le_fill_rsp_header(&s_encode_desc, desc, E_BLEPROTO_SERVICE_ID_AUTHDELETE); + memcpy(aude_rsp->gwmac, usr_auth_data.gwmac, 6); + if (!memcmp(usr_auth_data.authcode, desc->datast.authdelete_req.authcode, 32)) + { + aude_rsp->errcode = 0; + usr_clear_pair_info(); + } + else + { + aude_rsp->errcode = 501; + } + usr_le_encode_and_send(&s_encode_desc); + break; + } + case E_BLEPROTO_SERVICE_ID_RUNCMD: + printf("E_BLEPROTO_SERVICE_ID_RUNCMD\n"); + break; + case E_BLEPROTO_SERVICE_ID_RUNCMD_V2: + printf("E_BLEPROTO_SERVICE_ID_RUNCMD_V2\n"); + usr_run_cmd_code_v2(&desc->datast.runcmd_v2_req, desc->header.msgid); + break; + case E_BLEPROTO_SERVICE_ID_OTANOTIFY: + case E_BLEPROTO_SERVICE_ID_OTADATA: + case E_BLEPROTO_SERVICE_ID_OTADATA_V2: + /* JSON OTA 已废弃,SOC 走 JB 0x30/0x32 */ + printf("JSON OTA deprecated, use JB 0x30/0x32\n"); + break; + default: + printf("err !!! default event desc->header.serviceid =%d\n", + desc->header.serviceid); + break; + } + return; + } + + if (desc->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_RSP) + { + switch (desc->header.serviceid) + { + case E_BLEPROTO_SERVICE_ID_REPORTCMD: + printf("E_BLEPROTO_SERVICE_ID_REPORTCMD errcode=%d\n", + desc->datast.reportcmd_rsp.errcode); + break; + case E_BLEPROTO_SERVICE_ID_REPORTCMD_V2: { + ble_proto_packet_t *rpt_ack = &desc->datast.reportcmd_v2_rsp; + + printf("REPORTCMD_V2 cmd=%u cnt=%u int0=%ld\n", + (unsigned)rpt_ack->cmd, + (unsigned)rpt_ack->gateway_int_cnt, + (long)((rpt_ack->gateway_int_cnt > 0) ? rpt_ack->gateway_int[0] : 0)); + jbReportAckCheck(rpt_ack->cmd, + rpt_ack->gateway_int_cnt, + (rpt_ack->gateway_int_cnt > 0) ? rpt_ack->gateway_int[0] : -1); + break; + } + case E_BLEPROTO_SERVICE_ID_OTADATA: + printf("JSON OTA deprecated, use JB 0x32\n"); + break; + default: + printf("unhandled RSP serviceid=%d\n", desc->header.serviceid); + break; + } + } +} + +/** + * @brief APP RUNCMD_V2:55AA 字节流转交 JB 协议层 + * @param runcmd_par 内层包 + * @param usr_msg_id bleproto msgid + */ +static void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id) +{ + u8 jb_frame[MAX_PACKAGE_LEN]; + u16 copy_len; + + if (!runcmd_par || !runcmd_par->bytes_data || runcmd_par->device_bytes_len == 0) + { + printf("usr_run_cmd_code_v2 invalid bytes, len=%u\n", + runcmd_par ? runcmd_par->device_bytes_len : 0); + return; + } + + printf("runcmd_par id = %d, bytes_len = %u\n", + runcmd_par->cmd, runcmd_par->device_bytes_len); + + copy_len = runcmd_par->device_bytes_len; + if (copy_len > sizeof(jb_frame)) + { + printf("usr_run_cmd_code_v2 bytes too long %u\n", copy_len); + return; + } + memcpy(jb_frame, runcmd_par->bytes_data, copy_len); + + s_last_runcmd_msgid = usr_msg_id; + usr_jb_le_recieve_data(jb_frame, copy_len); +} + +/** + * @brief 将 JB 协议帧封装为 bleproto V2 后通过 BLE 发送 + * @param data JB 协议原始帧(55 AA | LEN | CMD | ...) + * @param len 帧长度 + * @note 主动上报(0x07/0x34/0x38) → REPORTCMD_V2;其余 → RUNCMD_V2 应答 + * 对外符号名保持 ble_send_data,供 jb_protocol 调用 + */ +void ble_send_data(u8 *data, u16 len) +{ + ble_proto_packet_t pkt; + bleproto_appdata_header_t hdr; + u8 v2_buf[MAX_PACKAGE_LEN + USR_LE_V2_WRAP_OVERHEAD]; + u8 jb_cmd; + u8 is_active_send; + int plen; + int elen; + + if (!data || len < 5) + { + printf("ble_send_data invalid param, len=%d\n", len); + return; + } + // if (len > MAX_PACKAGE_LEN) + // { + // printf("ble_send_data frame too long %u\n", len); + // return; + // } + if (!usr_ble_is_notify_ready()) + { + printf("ble_send_data wait CCC cmd=0x%02X\n", data[USR_LE_JB_CMD_OFF]); + return; + } + + jb_cmd = data[USR_LE_JB_CMD_OFF]; + is_active_send = usr_le_is_gateway_report_cmd(jb_cmd); + + memset(&pkt, 0, sizeof(pkt)); + memset(&hdr, 0, sizeof(hdr)); + hdr.version = 0; + hdr.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON; + hdr.encrypt = 0; + + if (is_active_send) + { + hdr.msgtype = BLEPROTO_APPDATA_MSGTYPE_REQ; + hdr.msgid = 0; + hdr.serviceid = E_BLEPROTO_SERVICE_ID_REPORTCMD_V2; + pkt.cmd = USR_LE_V2_CMD_REPORT; + pkt.gateway_int_cnt = 0; + } + else + { + hdr.msgtype = BLEPROTO_APPDATA_MSGTYPE_RSP; + hdr.msgid = s_last_runcmd_msgid; + hdr.serviceid = E_BLEPROTO_SERVICE_ID_RUNCMD_V2; + pkt.cmd = USR_LE_V2_CMD_RUN; + pkt.gateway_int_cnt = 2; + pkt.gateway_int[0] = 0; + pkt.gateway_int[1] = 0; + } + + pkt.device_bytes_len = len; + pkt.bytes_data = data; + + plen = ble_proto_pack(&pkt, v2_buf, sizeof(v2_buf)); + if (plen <= 0) + { + printf("ble_send_data pack fail %d\n", plen); + return; + } + + elen = bleproto_appdata_wrap_raw(txbuf, sizeof(txbuf), &hdr, v2_buf, (uint16_t)plen); + printf("ble_send_data jb_cmd=0x%02X active=%d encode_len=%d msgid=%d\n", + jb_cmd, is_active_send, elen, hdr.msgid); + if (elen <= 0) + { + return; + } + usr_ble_send_data(txbuf, elen); + printf_buf(txbuf, elen); +}