/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "radio_metadata"
/*#define LOG_NDEBUG 0*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <system/radio.h>
#include <system/radio_metadata.h>
#include <radio_metadata_hidden.h>
#include <cutils/log.h>
const radio_metadata_type_t metadata_key_type_table[] =
{
RADIO_METADATA_TYPE_TEXT,
RADIO_METADATA_TYPE_TEXT,
RADIO_METADATA_TYPE_INT,
RADIO_METADATA_TYPE_INT,
RADIO_METADATA_TYPE_TEXT,
RADIO_METADATA_TYPE_TEXT,
RADIO_METADATA_TYPE_TEXT,
RADIO_METADATA_TYPE_TEXT,
RADIO_METADATA_TYPE_TEXT,
RADIO_METADATA_TYPE_RAW,
RADIO_METADATA_TYPE_RAW,
RADIO_METADATA_TYPE_CLOCK,
};
/**
* private functions
*/
bool is_valid_metadata_key(const radio_metadata_key_t key)
{
if (key < RADIO_METADATA_KEY_MIN || key > RADIO_METADATA_KEY_MAX) {
return false;
}
return true;
}
int check_size(radio_metadata_buffer_t **metadata_ptr, const unsigned int size_int)
{
radio_metadata_buffer_t *metadata = *metadata_ptr;
unsigned int index_offset = metadata->size_int - metadata->count - 1;
unsigned int data_offset = *((unsigned int *)metadata + index_offset);
unsigned int req_size_int;
unsigned int new_size_int;
if (size_int == 0) {
return 0;
}
req_size_int = data_offset + metadata->count + 1 + 1 + size_int;
/* do not grow buffer if it can accommodate the new entry plus an additional index entry */
if (req_size_int <= metadata->size_int) {
return 0;
}
if (req_size_int > RADIO_METADATA_MAX_SIZE || metadata->size_int >= RADIO_METADATA_MAX_SIZE) {
return -ENOMEM;
}
/* grow meta data buffer by a factor of 2 until new data fits */
new_size_int = metadata->size_int;
while (new_size_int < req_size_int)
new_size_int *= 2;
ALOGV("%s growing from %u to %u", __func__, metadata->size_int, new_size_int);
metadata = realloc(metadata, new_size_int * sizeof(unsigned int));
/* move index table */
memmove((unsigned int *)metadata + new_size_int - (metadata->count + 1),
(unsigned int *)metadata + metadata->size_int - (metadata->count + 1),
(metadata->count + 1) * sizeof(unsigned int));
metadata->size_int = new_size_int;
*metadata_ptr = metadata;
return 0;
}
/* checks on size and key validity are done before calling this function */
int add_metadata(radio_metadata_buffer_t **metadata_ptr,
const radio_metadata_key_t key,
const radio_metadata_type_t type,
const void *value,
const unsigned int size)
{
unsigned int entry_size_int;
int ret;
radio_metadata_entry_t *entry;
unsigned int index_offset;
unsigned int data_offset;
radio_metadata_buffer_t *metadata = *metadata_ptr;
entry_size_int = size + sizeof(radio_metadata_entry_t);
entry_size_int = (entry_size_int + sizeof(unsigned int) - 1) / sizeof(unsigned int);
ret = check_size(metadata_ptr, entry_size_int);
if (ret < 0) {
return ret;
}
metadata = *metadata_ptr;
index_offset = metadata->size_int - metadata->count - 1;
data_offset = *((unsigned int *)metadata + index_offset);
entry = (radio_metadata_entry_t *)((unsigned int *)metadata + data_offset);
entry->key = key;
entry->type = type;
entry->size = size;
memcpy(entry->data, value, size);
data_offset += entry_size_int;
*((unsigned int *)metadata + index_offset -1) = data_offset;
metadata->count++;
return 0;
}
radio_metadata_entry_t *get_entry_at_index(
const radio_metadata_buffer_t *metadata,
const unsigned index,
bool check)
{
unsigned int index_offset = metadata->size_int - index - 1;
unsigned int data_offset = *((unsigned int *)metadata + index_offset);
if (check) {
if (index >= metadata->count) {
return NULL;
}
unsigned int min_offset;
unsigned int max_offset;
unsigned int min_entry_size_int;
min_offset = (sizeof(radio_metadata_buffer_t) + sizeof(unsigned int) - 1) /
sizeof(unsigned int);
if (data_offset < min_offset) {
return NULL;
}
min_entry_size_int = 1 + sizeof(radio_metadata_entry_t);
min_entry_size_int = (min_entry_size_int + sizeof(unsigned int) - 1) / sizeof(unsigned int);
max_offset = metadata->size_int - metadata->count - 1 - min_entry_size_int;
if (data_offset > max_offset) {
return NULL;
}
}
return (radio_metadata_entry_t *)((unsigned int *)metadata + data_offset);
}
/**
* metadata API functions
*/
radio_metadata_type_t radio_metadata_type_of_key(const radio_metadata_key_t key)
{
if (!is_valid_metadata_key(key)) {
return RADIO_METADATA_TYPE_INVALID;
}
return metadata_key_type_table[key - RADIO_METADATA_KEY_MIN];
}
int radio_metadata_allocate(radio_metadata_t **metadata,
const unsigned int channel,
const unsigned int sub_channel)
{
radio_metadata_buffer_t *metadata_buf =
(radio_metadata_buffer_t *)calloc(RADIO_METADATA_DEFAULT_SIZE, sizeof(unsigned int));
if (metadata_buf == NULL) {
return -ENOMEM;
}
metadata_buf->channel = channel;
metadata_buf->sub_channel = sub_channel;
metadata_buf->size_int = RADIO_METADATA_DEFAULT_SIZE;
*((unsigned int *)metadata_buf + RADIO_METADATA_DEFAULT_SIZE - 1) =
(sizeof(radio_metadata_buffer_t) + sizeof(unsigned int) - 1) /
sizeof(unsigned int);
*metadata = (radio_metadata_t *)metadata_buf;
return 0;
}
void radio_metadata_deallocate(radio_metadata_t *metadata)
{
free(metadata);
}
int radio_metadata_add_int(radio_metadata_t **metadata,
const radio_metadata_key_t key,
const int value)
{
radio_metadata_type_t type = radio_metadata_type_of_key(key);
if (metadata == NULL || *metadata == NULL || type != RADIO_METADATA_TYPE_INT) {
return -EINVAL;
}
return add_metadata((radio_metadata_buffer_t **)metadata,
key, type, &value, sizeof(int));
}
int radio_metadata_add_text(radio_metadata_t **metadata,
const radio_metadata_key_t key,
const char *value)
{
radio_metadata_type_t type = radio_metadata_type_of_key(key);
if (metadata == NULL || *metadata == NULL || type != RADIO_METADATA_TYPE_TEXT ||
value == NULL || strlen(value) >= RADIO_METADATA_TEXT_LEN_MAX) {
return -EINVAL;
}
return add_metadata((radio_metadata_buffer_t **)metadata, key, type, value, strlen(value) + 1);
}
int radio_metadata_add_raw(radio_metadata_t **metadata,
const radio_metadata_key_t key,
const unsigned char *value,
const unsigned int size)
{
radio_metadata_type_t type = radio_metadata_type_of_key(key);
if (metadata == NULL || *metadata == NULL || type != RADIO_METADATA_TYPE_RAW || value == NULL) {
return -EINVAL;
}
return add_metadata((radio_metadata_buffer_t **)metadata, key, type, value, size);
}
int radio_metadata_add_clock(radio_metadata_t **metadata,
const radio_metadata_key_t key,
const radio_metadata_clock_t *clock) {
radio_metadata_type_t type = radio_metadata_type_of_key(key);
if (metadata == NULL || *metadata == NULL || type != RADIO_METADATA_TYPE_CLOCK ||
clock == NULL || clock->timezone_offset_in_minutes < (-12 * 60) ||
clock->timezone_offset_in_minutes > (14 * 60)) {
return -EINVAL;
}
return add_metadata(
(radio_metadata_buffer_t **)metadata, key, type, clock, sizeof(radio_metadata_clock_t));
}
int radio_metadata_add_metadata(radio_metadata_t **dst_metadata,
radio_metadata_t *src_metadata)
{
radio_metadata_buffer_t *src_metadata_buf = (radio_metadata_buffer_t *)src_metadata;
radio_metadata_buffer_t *dst_metadata_buf;
int status;
unsigned int index;
if (dst_metadata == NULL || src_metadata == NULL) {
return -EINVAL;
}
if (*dst_metadata == NULL) {
status = radio_metadata_allocate(dst_metadata, src_metadata_buf->channel,
src_metadata_buf->sub_channel);
if (status != 0) {
return status;
}
}
dst_metadata_buf = (radio_metadata_buffer_t *)*dst_metadata;
dst_metadata_buf->channel = src_metadata_buf->channel;
dst_metadata_buf->sub_channel = src_metadata_buf->sub_channel;
for (index = 0; index < src_metadata_buf->count; index++) {
radio_metadata_key_t key;
radio_metadata_type_t type;
void *value;
unsigned int size;
status = radio_metadata_get_at_index(src_metadata, index, &key, &type, &value, &size);
if (status != 0)
continue;
status = add_metadata((radio_metadata_buffer_t **)dst_metadata, key, type, value, size);
if (status != 0)
break;
}
return status;
}
int radio_metadata_check(const radio_metadata_t *metadata)
{
radio_metadata_buffer_t *metadata_buf =
(radio_metadata_buffer_t *)metadata;
unsigned int count;
unsigned int min_entry_size_int;
if (metadata_buf == NULL) {
return -EINVAL;
}
if (metadata_buf->size_int > RADIO_METADATA_MAX_SIZE) {
return -EINVAL;
}
/* sanity check on entry count versus buffer size */
min_entry_size_int = 1 + sizeof(radio_metadata_entry_t);
min_entry_size_int = (min_entry_size_int + sizeof(unsigned int) - 1) /
sizeof(unsigned int);
if ((metadata_buf->count * min_entry_size_int + metadata_buf->count + 1 +
(sizeof(radio_metadata_buffer_t) + sizeof(unsigned int) - 1) / sizeof(unsigned int)) >
metadata_buf->size_int) {
return -EINVAL;
}
/* sanity check on each entry */
for (count = 0; count < metadata_buf->count; count++) {
radio_metadata_entry_t *entry = get_entry_at_index(metadata_buf, count, true);
radio_metadata_entry_t *next_entry;
if (entry == NULL) {
return -EINVAL;
}
if (!is_valid_metadata_key(entry->key)) {
return -EINVAL;
}
if (entry->type != radio_metadata_type_of_key(entry->key)) {
return -EINVAL;
}
/* do not request check because next entry can be the free slot */
next_entry = get_entry_at_index(metadata_buf, count + 1, false);
if ((char *)entry->data + entry->size > (char *)next_entry) {
return -EINVAL;
}
}
return 0;
}
size_t radio_metadata_get_size(const radio_metadata_t *metadata)
{
radio_metadata_buffer_t *metadata_buf =
(radio_metadata_buffer_t *)metadata;
if (metadata_buf == NULL) {
return 0;
}
return (size_t)(metadata_buf->size_int * sizeof(unsigned int));
}
int radio_metadata_get_count(const radio_metadata_t *metadata)
{
radio_metadata_buffer_t *metadata_buf =
(radio_metadata_buffer_t *)metadata;
if (metadata_buf == NULL) {
return -EINVAL;
}
return (int)metadata_buf->count;
}
int radio_metadata_get_at_index(const radio_metadata_t *metadata,
const unsigned int index,
radio_metadata_key_t *key,
radio_metadata_type_t *type,
void **value,
unsigned int *size)
{
radio_metadata_entry_t *entry;
radio_metadata_buffer_t *metadata_buf =
(radio_metadata_buffer_t *)metadata;
if (metadata_buf == NULL || key == NULL || type == NULL ||
value == NULL || size == NULL) {
return -EINVAL;
}
if (index >= metadata_buf->count) {
return -EINVAL;
}
entry = get_entry_at_index(metadata_buf, index, false);
*key = entry->key;
*type = entry->type;
*value = (void *)entry->data;
*size = entry->size;
return 0;
}
int radio_metadata_get_from_key(const radio_metadata_t *metadata,
const radio_metadata_key_t key,
radio_metadata_type_t *type,
void **value,
unsigned int *size)
{
unsigned int count;
radio_metadata_entry_t *entry = NULL;
radio_metadata_buffer_t *metadata_buf =
(radio_metadata_buffer_t *)metadata;
if (metadata_buf == NULL || type == NULL || value == NULL || size == NULL) {
return -EINVAL;
}
if (!is_valid_metadata_key(key)) {
return -EINVAL;
}
for (count = 0; count < metadata_buf->count; entry = NULL, count++) {
entry = get_entry_at_index(metadata_buf, count, false);
if (entry->key == key) {
break;
}
}
if (entry == NULL) {
return -ENOENT;
}
*type = entry->type;
*value = (void *)entry->data;
*size = entry->size;
return 0;
}