mirror of
https://github.com/google/pebble
synced 2026-10-16 00:04:12 +02:00
300 lines
9.6 KiB
C
300 lines
9.6 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "sync.h"
|
|
#include "endpoint_private.h"
|
|
|
|
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
|
|
#include "services/common/comm_session/session.h"
|
|
#include "services/common/analytics/analytics.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "system/status_codes.h"
|
|
#include "system/hexdump.h"
|
|
#include "util/attributes.h"
|
|
#include "util/net.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
//! @file endpoint.c
|
|
//! BlobDB Endpoint
|
|
//!
|
|
//! There are 3 commands implemented in this endpoint: INSERT, DELETE, and CLEAR
|
|
//!
|
|
//! <b>INSERT:</b> This command will insert a key and value into the database specified.
|
|
//!
|
|
//! \code{.c}
|
|
//! 0x01 <uint16_t token> <uint8_t DatabaseId>
|
|
//! <uint8_t key_size M> <uint8_t[M]> key_bytes>
|
|
//! <uint16_t value_size N> <uint8_t[N]> value_bytes>
|
|
//! \endcode
|
|
//!
|
|
//! <b>DELETE:</b> This command will delete an entry with the key in the database specified.
|
|
//!
|
|
//! \code{.c}
|
|
//! 0x04 <uint16_t token> <uint8_t DatabaseId>
|
|
//! <uint8_t key_size M> <uint8_t[M]> key_bytes>
|
|
//! \endcode
|
|
//!
|
|
//! <b>CLEAR:</b> This command will clear all entries in the database specified.
|
|
//!
|
|
//! \code{.c}
|
|
//! 0x05 <uint16_t token> <uint8_t DatabaseId>
|
|
//! \endcode
|
|
|
|
//! BlobDB Endpoint ID
|
|
static const uint16_t BLOB_DB_ENDPOINT_ID = 0xb1db;
|
|
|
|
static const uint8_t KEY_DATA_LENGTH = (sizeof(uint8_t) + sizeof(uint8_t));
|
|
static const uint8_t VALUE_DATA_LENGTH = (sizeof(uint16_t) + sizeof(uint8_t));
|
|
|
|
//! Message Length Constants
|
|
static const uint8_t MIN_INSERT_LENGTH = 8;
|
|
static const uint8_t MIN_DELETE_LENGTH = 6;
|
|
static const uint8_t MIN_CLEAR_LENGTH = 3;
|
|
|
|
static bool s_bdb_accepting_messages;
|
|
|
|
static void prv_send_response(CommSession *session, BlobDBToken token, BlobDBResponse result) {
|
|
struct PACKED BlobDBResponseMsg {
|
|
BlobDBToken token;
|
|
BlobDBResponse result;
|
|
} response = {
|
|
.token = token,
|
|
.result = result,
|
|
};
|
|
|
|
comm_session_send_data(session, BLOB_DB_ENDPOINT_ID, (uint8_t*)&response, sizeof(response),
|
|
COMM_SESSION_DEFAULT_TIMEOUT);
|
|
}
|
|
|
|
static BlobDBResponse prv_interpret_db_ret_val(status_t ret_val) {
|
|
switch (ret_val) {
|
|
case S_SUCCESS:
|
|
return BLOB_DB_SUCCESS;
|
|
case E_DOES_NOT_EXIST:
|
|
return BLOB_DB_KEY_DOES_NOT_EXIST;
|
|
case E_RANGE:
|
|
return BLOB_DB_INVALID_DATABASE_ID;
|
|
case E_INVALID_ARGUMENT:
|
|
return BLOB_DB_INVALID_DATA;
|
|
case E_OUT_OF_STORAGE:
|
|
return BLOB_DB_DATABASE_FULL;
|
|
case E_INVALID_OPERATION:
|
|
return BLOB_DB_DATA_STALE;
|
|
default:
|
|
PBL_LOG(LOG_LEVEL_WARNING, "BlobDB return value caught by default case");
|
|
return BLOB_DB_GENERAL_FAILURE;
|
|
}
|
|
}
|
|
|
|
static const uint8_t *prv_read_ptr(const uint8_t *iter, const uint8_t *iter_end,
|
|
const uint8_t **out_buf, uint16_t buf_len) {
|
|
|
|
// >= because we will be reading more bytes after this point
|
|
if ((buf_len == 0) || ((iter + buf_len) > iter_end)) {
|
|
PBL_LOG(LOG_LEVEL_WARNING, "BlobDB: read invalid length");
|
|
return NULL;
|
|
}
|
|
|
|
// grab pointer to start of buf
|
|
*out_buf = (uint8_t*)iter;
|
|
iter += buf_len;
|
|
|
|
return iter;
|
|
}
|
|
|
|
static const uint8_t *prv_read_key_size(const uint8_t *iter, const uint8_t *iter_end,
|
|
uint8_t *out_int) {
|
|
|
|
// copy length from iter to out_len, then save a local copy of the length
|
|
*out_int = *iter++;
|
|
return iter;
|
|
}
|
|
|
|
static const uint8_t *prv_read_value_size(const uint8_t *iter, const uint8_t *iter_end,
|
|
uint16_t *out_int) {
|
|
|
|
// copy length from iter to out_len, then save a local copy of the length
|
|
*out_int = *(uint16_t*)iter;
|
|
iter += sizeof(uint16_t);
|
|
return iter;
|
|
}
|
|
|
|
static BlobDBToken prv_try_read_token(const uint8_t *data, uint32_t length) {
|
|
if (length < sizeof(BlobDBToken)) {
|
|
return 0;
|
|
}
|
|
|
|
return *(BlobDBToken*)data;
|
|
}
|
|
|
|
static void prv_handle_database_insert(CommSession *session, const uint8_t *data, uint32_t length) {
|
|
if (length < MIN_INSERT_LENGTH) {
|
|
prv_send_response(session, prv_try_read_token(data, length), BLOB_DB_INVALID_DATA);
|
|
return;
|
|
}
|
|
|
|
const uint8_t *iter = data;
|
|
BlobDBToken token;
|
|
BlobDBId db_id;
|
|
|
|
// Read token and db_id
|
|
iter = endpoint_private_read_token_db_id(iter, &token, &db_id);
|
|
|
|
// read key length and key bytes ptr
|
|
uint8_t key_size;
|
|
const uint8_t *key_bytes = NULL;
|
|
iter = prv_read_key_size(iter, data + length, &key_size);
|
|
iter = prv_read_ptr(iter, data + length, &key_bytes, key_size);
|
|
|
|
// If read past end or there is not enough data left in buffer for a value size and data to exist
|
|
if (!iter || (iter > (data + length - VALUE_DATA_LENGTH))) {
|
|
prv_send_response(session, token, BLOB_DB_INVALID_DATA);
|
|
return;
|
|
}
|
|
|
|
// read value length and value bytes ptr
|
|
uint16_t value_size;
|
|
const uint8_t *value_bytes = NULL;
|
|
iter = prv_read_value_size(iter, data + length, &value_size);
|
|
iter = prv_read_ptr(iter, data + length, &value_bytes, value_size);
|
|
|
|
// If we read too many bytes or didn't read all the bytes (2nd test)
|
|
if (!iter || (iter != (data + length))) {
|
|
prv_send_response(session, token, BLOB_DB_INVALID_DATA);
|
|
return;
|
|
}
|
|
|
|
// perform action on database and return result
|
|
status_t ret = blob_db_insert(db_id, key_bytes, key_size, value_bytes, value_size);
|
|
prv_send_response(session, token, prv_interpret_db_ret_val(ret));
|
|
}
|
|
|
|
|
|
static void prv_handle_database_delete(CommSession *session, const uint8_t *data, uint32_t length) {
|
|
if (length < MIN_DELETE_LENGTH) {
|
|
prv_send_response(session, prv_try_read_token(data, length), BLOB_DB_INVALID_DATA);
|
|
return;
|
|
}
|
|
|
|
const uint8_t *iter = data;
|
|
BlobDBToken token;
|
|
BlobDBId db_id;
|
|
|
|
// Read token and db_id
|
|
iter = endpoint_private_read_token_db_id(iter, &token, &db_id);
|
|
|
|
// Read key length and key bytes
|
|
uint8_t key_size;
|
|
const uint8_t *key_bytes = NULL;
|
|
iter = prv_read_key_size(iter, data + length, &key_size);
|
|
iter = prv_read_ptr(iter, data + length, &key_bytes, key_size);
|
|
|
|
// If we read too many bytes or key_size is 0 or didn't read all the bytes
|
|
if (!iter || (iter != (data + length))) {
|
|
prv_send_response(session, token, BLOB_DB_INVALID_DATA);
|
|
return;
|
|
}
|
|
|
|
// perform action on database and return result
|
|
status_t ret = blob_db_delete(db_id, key_bytes, key_size);
|
|
prv_send_response(session, token, prv_interpret_db_ret_val(ret));
|
|
}
|
|
|
|
|
|
static void prv_handle_database_clear(CommSession *session, const uint8_t *data, uint32_t length) {
|
|
if (length < MIN_CLEAR_LENGTH) {
|
|
prv_send_response(session, prv_try_read_token(data, length), BLOB_DB_INVALID_DATA);
|
|
return;
|
|
}
|
|
|
|
BlobDBToken token;
|
|
BlobDBId db_id;
|
|
|
|
// Read token and db_id
|
|
endpoint_private_read_token_db_id(data, &token, &db_id);
|
|
|
|
// perform action on database and return result
|
|
status_t ret = blob_db_flush(db_id);
|
|
prv_send_response(session, token, prv_interpret_db_ret_val(ret));
|
|
|
|
// Mark the device as faithful after successfully flushing
|
|
if (ret == S_SUCCESS) {
|
|
bt_persistent_storage_set_unfaithful(false /* We are now faithful */);
|
|
}
|
|
}
|
|
|
|
static void prv_blob_db_msg_decode_and_handle(
|
|
CommSession *session, BlobDBCommand cmd, const uint8_t *data, size_t data_length) {
|
|
switch (cmd) {
|
|
case BLOB_DB_COMMAND_INSERT:
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Got INSERT");
|
|
prv_handle_database_insert(session, data, data_length);
|
|
break;
|
|
case BLOB_DB_COMMAND_DELETE:
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Got DELETE");
|
|
prv_handle_database_delete(session, data, data_length);
|
|
break;
|
|
case BLOB_DB_COMMAND_CLEAR:
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Got CLEAR");
|
|
prv_handle_database_clear(session, data, data_length);
|
|
break;
|
|
// Commands not implemented.
|
|
case BLOB_DB_COMMAND_READ:
|
|
case BLOB_DB_COMMAND_UPDATE:
|
|
PBL_LOG(LOG_LEVEL_ERROR, "BlobDB Command not implemented");
|
|
// Fallthrough
|
|
default:
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Invalid BlobDB message received, cmd is %u", cmd);
|
|
prv_send_response(session, prv_try_read_token(data, data_length), BLOB_DB_INVALID_OPERATION);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void blob_db_protocol_msg_callback(CommSession *session, const uint8_t* data, size_t length) {
|
|
PBL_ASSERT_TASK(PebbleTask_KernelBackground);
|
|
|
|
analytics_inc(ANALYTICS_DEVICE_METRIC_BLOB_DB_EVENT_COUNT, AnalyticsClient_System);
|
|
|
|
PBL_HEXDUMP_D(LOG_DOMAIN_BLOBDB, LOG_LEVEL_DEBUG, data, length);
|
|
|
|
// Each BlobDB message is required to have at least a Command and a Token
|
|
static const uint8_t MIN_RAW_DATA_LEN = sizeof(BlobDBCommand) + sizeof(BlobDBToken);
|
|
if (length < MIN_RAW_DATA_LEN) {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Got a blob_db message that was too short, len: %zu", length);
|
|
prv_send_response(session, 0, BLOB_DB_INVALID_DATA);
|
|
return;
|
|
}
|
|
|
|
const BlobDBCommand cmd = *data;
|
|
data += sizeof(BlobDBCommand); // fwd to message contents
|
|
const size_t data_length = length - sizeof(BlobDBCommand);
|
|
|
|
if (!s_bdb_accepting_messages) {
|
|
prv_send_response(session, prv_try_read_token(data, length), BLOB_DB_TRY_LATER);
|
|
return;
|
|
}
|
|
|
|
prv_blob_db_msg_decode_and_handle(session, cmd, data, data_length);
|
|
}
|
|
|
|
void blob_db_set_accepting_messages(bool enabled) {
|
|
s_bdb_accepting_messages = enabled;
|
|
}
|