|
|
|
|
@@ -62,7 +62,6 @@
|
|
|
|
|
#include "ble_nus.h" /* Nordic UART Service (BLE bidirectional data) */
|
|
|
|
|
#include "app_uart.h" /* Physical UART driver (debug/factory test) */
|
|
|
|
|
#include "app_util_platform.h" /* Interrupt priority macros */
|
|
|
|
|
#include "bsp_btn_ble.h" /* BSP button-BLE integration module */
|
|
|
|
|
#include "nrf_pwr_mgmt.h" /* Power management (WFE/sleep when idle) */
|
|
|
|
|
#include "nrf_delay.h" /* Blocking delay (us/ms) */
|
|
|
|
|
#include "math.h"
|
|
|
|
|
@@ -115,7 +114,7 @@
|
|
|
|
|
#include "debug_print.h" /* Debug output macro (DBG_PRINTF) */
|
|
|
|
|
#include "fstorage.h" /* Flash Storage wrapper (FDS init/save/load) */
|
|
|
|
|
#include "dr_piezo.h" /* Piezo ultrasound driver */
|
|
|
|
|
#include "led_control.h" /* LED direct control driver (not using BSP) */
|
|
|
|
|
#include "led_control.h" /* LED direct control driver */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -213,9 +212,9 @@ static uint16_t m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3; /* N
|
|
|
|
|
static ble_uuid_t m_adv_uuids[] = {{BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE}}; /* UUIDs included in advertising */
|
|
|
|
|
|
|
|
|
|
static uint8_t m_tx_buffer[BLE_NUS_MAX_DATA_LEN] = {0}; /* ASCII text transmission buffer */
|
|
|
|
|
static uint16_t m_tx_len = 0; /* Data length to transmit */
|
|
|
|
|
static volatile bool m_tx_in_progress = false; /* TX in progress flag */
|
|
|
|
|
static volatile bool m_tx_complete_pending = false; /* TX completion pending flag */
|
|
|
|
|
static uint16_t m_tx_len = 0; /* Data length to transmit */
|
|
|
|
|
static volatile bool m_tx_in_progress = false; /* TX in progress flag */
|
|
|
|
|
static volatile bool m_tx_complete_pending = false; /* TX completion pending flag */
|
|
|
|
|
static uint8_t c_addr[6] = {0}; /* Connected peer BLE address */
|
|
|
|
|
|
|
|
|
|
static char * roles_str[] = {"INVALID_ROLE", "CENTRAL", "PERIPHERAL"};
|
|
|
|
|
@@ -244,11 +243,11 @@ extern bool info4; /* Device info transmission complete flag
|
|
|
|
|
extern uint8_t add_cycle; /* Additional measurement cycle counter */
|
|
|
|
|
extern bool motion_raw_data_enabled; /* Motion raw data streaming enabled */
|
|
|
|
|
|
|
|
|
|
uint16_t cnt_s; /* Power button polling counter (5ms units, 150=0.75s) */
|
|
|
|
|
char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN] = {0}; /* BLE text transmission buffer */
|
|
|
|
|
uint16_t cnt_s; /* Power button polling counter (5ms units, 150=0.75s) */
|
|
|
|
|
char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN] = {0}; /* BLE text transmission buffer */
|
|
|
|
|
uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] = {0}; /* BLE binary response buffer */
|
|
|
|
|
uint16_t ble_bin_buff[BLE_NUS_MAX_DATA_LEN/2] = {0}; /* BLE binary transmission buffer (word units) */
|
|
|
|
|
which_cmd_t cmd_type_t; /* Current command source (CMD_BLE or CMD_UART) */
|
|
|
|
|
which_cmd_t cmd_type_t; /* Current command source (CMD_BLE or CMD_UART) */
|
|
|
|
|
|
|
|
|
|
bool device_status = false; /* Device active state (true=running) */
|
|
|
|
|
bool device_reset = true; /* Booting flag (true=not yet booted) */
|
|
|
|
|
@@ -261,14 +260,14 @@ volatile bool ble_connection_st; /* BLE connection state (1=co
|
|
|
|
|
volatile bool data_tx_in_progress = false; /* Binary TX in progress flag */
|
|
|
|
|
|
|
|
|
|
/* -- BLE TX async retry state -- */
|
|
|
|
|
static volatile bool s_tx_pending = false; /* TX retry pending */
|
|
|
|
|
static uint8_t s_tx_pending_buf[BLE_NUS_MAX_DATA_LEN] = {0}; /* Pending packet (with CRC) */
|
|
|
|
|
static uint16_t s_tx_pending_len = 0; /* Pending packet length */
|
|
|
|
|
static volatile bool s_tx_pending = false; /* TX retry pending */
|
|
|
|
|
static uint8_t s_tx_pending_buf[BLE_NUS_MAX_DATA_LEN] = {0}; /* Pending packet (with CRC) */
|
|
|
|
|
static uint16_t s_tx_pending_len = 0; /* Pending packet length */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char m_static_passkey[PASSKEY_LENGTH] = DEFAULT_PASSKEY; /* Static passkey (6 digits, loaded from FDS) */
|
|
|
|
|
char SERIAL_NO[SERIAL_NO_LENGTH] = {0}; /* Serial number (used as BLE device name) */
|
|
|
|
|
char HW_NO[HW_NO_LENGTH] = {0}; /* Hardware number (FDS stored/read) */
|
|
|
|
|
char SERIAL_NO[SERIAL_NO_LENGTH] = {0}; /* Serial number (used as BLE device name) */
|
|
|
|
|
char HW_NO[HW_NO_LENGTH] = {0}; /* Hardware number (FDS stored/read) */
|
|
|
|
|
bool bond_data_delete; /* Bond data delete request flag */
|
|
|
|
|
uint32_t m_life_cycle; /* Device life cycle counter */
|
|
|
|
|
uint8_t resetCount = 0; /* Communication timeout counter (reset detection) */
|
|
|
|
|
@@ -412,8 +411,7 @@ static void load_flash_config(void)
|
|
|
|
|
m_reset_status = m_config.reset_status;
|
|
|
|
|
|
|
|
|
|
DBG_PRINTF("[CFG] HW=%.12s S/N=%s passkey=%.6s bond=%d rst=%d\r\n",
|
|
|
|
|
m_config.hw_no, SERIAL_NO, m_static_passkey, bond_data_delete,
|
|
|
|
|
m_reset_status);
|
|
|
|
|
m_config.hw_no, SERIAL_NO, m_static_passkey, bond_data_delete, m_reset_status);
|
|
|
|
|
DBG_PRINTF("[CFG] piezo: freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n",
|
|
|
|
|
m_config.piezo_freq_option, m_config.piezo_cycles,
|
|
|
|
|
m_config.piezo_averaging, m_config.piezo_delay_us,
|
|
|
|
|
@@ -865,8 +863,7 @@ static void advertising_start(bool erase_bonds_flag)
|
|
|
|
|
{
|
|
|
|
|
if (erase_bonds_flag == true)
|
|
|
|
|
{
|
|
|
|
|
DBG_PRINTF("[BOND] Delete requested (count_before=%lu)\r\n",
|
|
|
|
|
(unsigned long)pm_peer_count());
|
|
|
|
|
DBG_PRINTF("[BOND] Delete requested (count_before=%lu)\r\n", (unsigned long)pm_peer_count());
|
|
|
|
|
|
|
|
|
|
/* Clear bond delete request flag in flash -> prevent re-deletion on next boot */
|
|
|
|
|
bond_data_delete = false;
|
|
|
|
|
@@ -949,9 +946,9 @@ static void pm_evt_handler(pm_evt_t const * p_evt)
|
|
|
|
|
|
|
|
|
|
/* All bonds deleted -> restart advertising only if no active connection */
|
|
|
|
|
case PM_EVT_PEERS_DELETE_SUCCEEDED:
|
|
|
|
|
DBG_PRINTF("[BOND] Deleted all (count_after=%lu)\r\n",
|
|
|
|
|
(unsigned long)pm_peer_count());
|
|
|
|
|
if (m_conn_handle == BLE_CONN_HANDLE_INVALID) {
|
|
|
|
|
DBG_PRINTF("[BOND] Deleted all (count_after=%lu)\r\n", (unsigned long)pm_peer_count());
|
|
|
|
|
if (m_conn_handle == BLE_CONN_HANDLE_INVALID)
|
|
|
|
|
{
|
|
|
|
|
advertising_start(false);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
@@ -970,8 +967,7 @@ static void pm_evt_handler(pm_evt_t const * p_evt)
|
|
|
|
|
if (p_evt->params.peer_data_update_succeeded.data_id == PM_PEER_DATA_ID_BONDING)
|
|
|
|
|
{
|
|
|
|
|
DBG_PRINTF("[BOND] Created peer_id=%u addr=%02X:%02X:%02X:%02X:%02X:%02X (count=%lu)\r\n",
|
|
|
|
|
p_evt->peer_id,
|
|
|
|
|
c_addr[5], c_addr[4], c_addr[3], c_addr[2], c_addr[1], c_addr[0],
|
|
|
|
|
p_evt->peer_id, c_addr[5], c_addr[4], c_addr[3], c_addr[2], c_addr[1], c_addr[0],
|
|
|
|
|
(unsigned long)pm_peer_count());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1029,7 +1025,8 @@ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if FEATURE_SECURE_CONNECTION
|
|
|
|
|
if (!ble_security_is_dev_mode()) {
|
|
|
|
|
if (!ble_security_is_dev_mode())
|
|
|
|
|
{
|
|
|
|
|
pm_handler_secure_on_connection(p_ble_evt);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
@@ -1192,85 +1189,15 @@ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*==============================================================================
|
|
|
|
|
* BSP (Board Support Package) Event Handler
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief BSP event handler (buttons/LEDs)
|
|
|
|
|
*
|
|
|
|
|
* - SLEEP: enter sleep mode
|
|
|
|
|
* - DISCONNECT: force BLE disconnect
|
|
|
|
|
* - WHITELIST_OFF: restart advertising without whitelist
|
|
|
|
|
* - POWER_CONTROL: start power-off sequence
|
|
|
|
|
*/
|
|
|
|
|
void bsp_event_handler(bsp_event_t event)
|
|
|
|
|
{
|
|
|
|
|
uint32_t err_code;
|
|
|
|
|
|
|
|
|
|
switch (event)
|
|
|
|
|
{
|
|
|
|
|
case BSP_EVENT_SLEEP:
|
|
|
|
|
DBG_PRINTF("[BSP] Sleep\r\n");
|
|
|
|
|
go_sleep_mode_enter = true;
|
|
|
|
|
main_timer_start();
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case BSP_EVENT_DISCONNECT:
|
|
|
|
|
err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
|
|
|
|
|
if (err_code != NRF_SUCCESS) {
|
|
|
|
|
m_reset_status = 2;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case BSP_EVENT_WHITELIST_OFF:
|
|
|
|
|
if (m_conn_handle == BLE_CONN_HANDLE_INVALID)
|
|
|
|
|
{
|
|
|
|
|
err_code = ble_advertising_restart_without_whitelist(&m_advertising);
|
|
|
|
|
if (err_code != NRF_ERROR_INVALID_STATE)
|
|
|
|
|
{
|
|
|
|
|
APP_ERROR_CHECK(err_code);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case BSP_EVENT_POWER_CONTROL:
|
|
|
|
|
if (processing == false)
|
|
|
|
|
{
|
|
|
|
|
DBG_PRINTF("[BSP] Power\r\n");
|
|
|
|
|
led_set_state(LED_STATE_POWER_OFF);
|
|
|
|
|
go_device_power_off = true;
|
|
|
|
|
main_timer_start();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*==============================================================================
|
|
|
|
|
* Utility Functions
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
|
|
|
|
/** @brief BSP button init + LED direct init (boot Phase 5) */
|
|
|
|
|
/** @brief LED init + erase_bonds default (boot Phase 5) */
|
|
|
|
|
static void buttons_leds_init(bool * p_erase_bonds)
|
|
|
|
|
{
|
|
|
|
|
bsp_event_t startup_event;
|
|
|
|
|
|
|
|
|
|
uint32_t err_code = bsp_init(BSP_INIT_BUTTONS, bsp_event_handler);
|
|
|
|
|
APP_ERROR_CHECK(err_code);
|
|
|
|
|
|
|
|
|
|
led_init();
|
|
|
|
|
|
|
|
|
|
err_code = bsp_btn_ble_init(NULL, &startup_event);
|
|
|
|
|
APP_ERROR_CHECK(err_code);
|
|
|
|
|
|
|
|
|
|
/* VesiScan boots by pressing the power button, so the button is always
|
|
|
|
|
* read as pressed at boot time. Nordic BSP interprets this as
|
|
|
|
|
* BSP_EVENT_CLEAR_BONDING_DATA and tries to delete bonds,
|
|
|
|
|
* so we invalidate the startup event-based bond erase trigger. */
|
|
|
|
|
(void)startup_event;
|
|
|
|
|
*p_erase_bonds = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1427,13 +1354,22 @@ void device_power_off(void)
|
|
|
|
|
*/
|
|
|
|
|
void data_tx_handler(char const *p_data_to_send)
|
|
|
|
|
{
|
|
|
|
|
if (m_tx_in_progress) return;
|
|
|
|
|
if (m_tx_in_progress)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char const *p_end_char = strchr(p_data_to_send, '\r');
|
|
|
|
|
if (p_end_char == NULL) return;
|
|
|
|
|
if (p_end_char == NULL)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint16_t data_len = p_end_char - p_data_to_send;
|
|
|
|
|
if (data_len > (BLE_NUS_MAX_DATA_LEN - 2)) return;
|
|
|
|
|
if (data_len > (BLE_NUS_MAX_DATA_LEN - 2))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memcpy(m_tx_buffer, p_data_to_send, data_len);
|
|
|
|
|
uint16_t crc = crc16_compute(m_tx_buffer, data_len, NULL);
|
|
|
|
|
@@ -1446,15 +1382,22 @@ void data_tx_handler(char const *p_data_to_send)
|
|
|
|
|
m_tx_in_progress = true;
|
|
|
|
|
uint32_t err_code = ble_nus_data_send(&m_nus, m_tx_buffer, &m_tx_len, m_conn_handle);
|
|
|
|
|
|
|
|
|
|
if (err_code == NRF_SUCCESS) {
|
|
|
|
|
if (err_code == NRF_SUCCESS)
|
|
|
|
|
{
|
|
|
|
|
// OK
|
|
|
|
|
} else if (err_code == NRF_ERROR_RESOURCES) {
|
|
|
|
|
}
|
|
|
|
|
else if (err_code == NRF_ERROR_RESOURCES)
|
|
|
|
|
{
|
|
|
|
|
// Retry later
|
|
|
|
|
} else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND) {
|
|
|
|
|
}
|
|
|
|
|
else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND)
|
|
|
|
|
{
|
|
|
|
|
ble_connection_st = BLE_DISCONNECTED_ST;
|
|
|
|
|
m_conn_handle = BLE_CONN_HANDLE_INVALID;
|
|
|
|
|
m_tx_in_progress = false;
|
|
|
|
|
} else {
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
DBG_PRINTF("[BLE TX] Err:0x%X\r\n", err_code); // Log instead of APP_ERROR_CHECK - jhChun 26.03.16
|
|
|
|
|
m_tx_in_progress = false;
|
|
|
|
|
//APP_ERROR_CHECK(err_code);
|
|
|
|
|
@@ -1497,7 +1440,8 @@ void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, s
|
|
|
|
|
buffer[2] = (uint8_t)(tag2 & 0xFF);
|
|
|
|
|
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < length; i++) {
|
|
|
|
|
for (size_t i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
buffer[4 + i * 2 + 1] = (uint8_t)(data_array[i] & 0xFF);
|
|
|
|
|
buffer[4 + i * 2] = (uint8_t)((data_array[i] >> 8) & 0xFF);
|
|
|
|
|
}
|
|
|
|
|
@@ -1518,7 +1462,8 @@ void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_arra
|
|
|
|
|
buffer[2] = (uint8_t)(tag2 & 0xFF);
|
|
|
|
|
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < length; i++) {
|
|
|
|
|
for (size_t i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
buffer[4 + i] = (uint8_t)(data_array[i] & 0xFF);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1539,7 +1484,8 @@ void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii,
|
|
|
|
|
buffer[2] = (uint8_t)(tag2 & 0xFF);
|
|
|
|
|
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < length; i++) {
|
|
|
|
|
for (size_t i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
buffer[4 + i] = (uint8_t)(data_ascii[i] & 0xFF);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1612,7 +1558,8 @@ void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length)
|
|
|
|
|
|
|
|
|
|
uint16_t total_len = length * sizeof(uint16_t) + 2;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
uint16_t send_len = total_len;
|
|
|
|
|
err_code = ble_nus_data_send(&m_nus, tx_buffer, &send_len, m_conn_handle);
|
|
|
|
|
|
|
|
|
|
@@ -1663,7 +1610,7 @@ static void main_s(void * p_context)
|
|
|
|
|
|
|
|
|
|
if (button_released)
|
|
|
|
|
{
|
|
|
|
|
if ((cnt_s < 150) && (m_reset_status != 2))
|
|
|
|
|
if ((cnt_s < 200) && (m_reset_status != 2))
|
|
|
|
|
{
|
|
|
|
|
DBG_PRINTF("[BTN] Short->OFF\r\n");
|
|
|
|
|
led_set_state(LED_STATE_OFF);
|
|
|
|
|
@@ -1684,7 +1631,7 @@ static void main_s(void * p_context)
|
|
|
|
|
go_device_power_off = true;
|
|
|
|
|
main_timer_start();
|
|
|
|
|
}
|
|
|
|
|
else if (cnt_s > 150 || (m_reset_status == 2))
|
|
|
|
|
else if (cnt_s > 200 || (m_reset_status == 2))
|
|
|
|
|
{
|
|
|
|
|
DBG_PRINTF("[BTN] Boot (cnt=%d)\r\n", cnt_s);
|
|
|
|
|
device_reset = false;
|
|
|
|
|
@@ -1708,9 +1655,10 @@ static void main_s(void * p_context)
|
|
|
|
|
cnt_s++;
|
|
|
|
|
device_reset = false;
|
|
|
|
|
|
|
|
|
|
if (cnt_s == 150) {
|
|
|
|
|
if (cnt_s == 200)
|
|
|
|
|
{
|
|
|
|
|
led_set_state(LED_STATE_POWER_ON);
|
|
|
|
|
DBG_PRINTF("[BTN] 1.5s\r\n");
|
|
|
|
|
DBG_PRINTF("[BTN] 2.0s\r\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timers_start();
|
|
|
|
|
@@ -1736,11 +1684,14 @@ int main(void)
|
|
|
|
|
* - GPIO (power button input)
|
|
|
|
|
* - App timers (power polling, battery, main loop)
|
|
|
|
|
* - Default config (serial number, passkey)
|
|
|
|
|
* - Buttons/LEDs (BSP)
|
|
|
|
|
* - Buttons/LEDs
|
|
|
|
|
*────────────────────────────────────────────────────────────*/
|
|
|
|
|
power_hold_init();
|
|
|
|
|
|
|
|
|
|
if (power_off_duble_prohibit) return 0;
|
|
|
|
|
if (power_off_duble_prohibit)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
cnt_s = 0;
|
|
|
|
|
|
|
|
|
|
log_init();
|
|
|
|
|
@@ -1823,7 +1774,8 @@ int main(void)
|
|
|
|
|
#endif
|
|
|
|
|
DBG_PRINTF(" gap/gatt/svc/adv/conn/sec OK\r\n");
|
|
|
|
|
|
|
|
|
|
if (m_need_save_defaults) {
|
|
|
|
|
if (m_need_save_defaults)
|
|
|
|
|
{
|
|
|
|
|
config_save();
|
|
|
|
|
m_need_save_defaults = false;
|
|
|
|
|
DBG_PRINTF(" fds defaults saved\r\n");
|
|
|
|
|
@@ -1859,7 +1811,8 @@ int main(void)
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
/* Process command received in BLE callback here (prevents blocking) */
|
|
|
|
|
if (pending_cmd_len > 0) {
|
|
|
|
|
if (pending_cmd_len > 0)
|
|
|
|
|
{
|
|
|
|
|
uint8_t len = pending_cmd_len;
|
|
|
|
|
pending_cmd_len = 0;
|
|
|
|
|
dr_cmd_parser((const uint8_t *)pending_cmd_buf, len);
|
|
|
|
|
|