/* * 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 #include #include //! @file endpoint.c //! BlobDB Endpoint //! //! There are 3 commands implemented in this endpoint: INSERT, DELETE, and CLEAR //! //! INSERT: This command will insert a key and value into the database specified. //! //! \code{.c} //! 0x01 //! key_bytes> //! value_bytes> //! \endcode //! //! DELETE: This command will delete an entry with the key in the database specified. //! //! \code{.c} //! 0x04 //! key_bytes> //! \endcode //! //! CLEAR: This command will clear all entries in the database specified. //! //! \code{.c} //! 0x05 //! \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; }