Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

36
src/fw/util/array.c Normal file
View file

@ -0,0 +1,36 @@
/*
* 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 "util/array.h"
#include "system/passert.h"
#include <stdlib.h>
void array_remove_nulls(void **array, size_t *num) {
PBL_ASSERTN(array && num);
size_t i = 0;
while (i < *num) {
if (array[i] == NULL) {
for (size_t j = i + 1; j < *num; j++) {
array[j-1] = array[j];
}
(*num)--;
} else {
i++;
}
}
}

24
src/fw/util/array.h Normal file
View file

@ -0,0 +1,24 @@
/*
* 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.
*/
#pragma once
#include <stdlib.h>
#define ARRAY_INDEX_IS_FIRST_INDEX(index) ((index) == 0)
#define ARRAY_INDEX_IS_LAST_INDEX(index, array_length) ((index) == ((array_length) - 1))
void array_remove_nulls(void **array, size_t *num);

128
src/fw/util/base64.c Normal file
View file

@ -0,0 +1,128 @@
/*
* 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 "net.h"
#include "system/passert.h"
#include <ctype.h>
#include <string.h>
static int8_t decode_char(uint8_t c) {
if (isupper(c)) return c - 'A';
if (islower(c)) return c - 'a' + 26;
if (isdigit(c)) return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
if (c == '=') return 127;
return -1;
}
unsigned int base64_decode_inplace(char* buffer, unsigned int length) {
unsigned int read_index = 0;
unsigned int write_index = 0;
while (read_index < length) {
int quad_index = 0;
unsigned int v = 0;
for (; quad_index < 4; ++quad_index) {
int8_t c = decode_char(buffer[read_index + quad_index]);
if (c == -1) return 0; // Error, invalid character
if (c == 127) break; // Padding found
v = (v * 64) + c;
}
// Handle the padding if we broke out the loop early (0-2 '=' characters).
const unsigned int padding_amount = 4 - quad_index;
if (padding_amount > 2) return 0; // Mades no sense to pad an entire triplet.
if (memcmp(buffer + read_index + quad_index, "==", padding_amount) != 0) return 0; // There are characters after our padding?
// Chop off extra unused low bits if we're padded.
// If there's only 2 6-bit characters (+ 2 '='s for padding), this results in 12-bits of data.
// We only want the first 8-bits, so shift out 4.
// If there's only 3 6-bit character (+ '=' for padding), this results in 18-bits of data.
// We only want the first 16-bits, so shift out 2.
v = v >> (padding_amount * 2);
const char* v_as_bytes = ((const char*) &v);
for (unsigned int i = 0; i < (3 - padding_amount); ++i) {
buffer[write_index + i] = v_as_bytes[(2 - padding_amount) - i];
}
read_index += 4;
write_index += 3 - padding_amount;
if (padding_amount != 0 && read_index < length) {
// error, padded quad in the middle our string.
return 0;
}
}
return write_index;
}
static char prv_encode_char(uint8_t binary) {
if (binary < 26) {
return binary + 'A';
} else if (binary < 52) {
return binary - 26 + 'a';
} else if (binary < 62) {
return binary - 52 + '0';
} else if (binary == 62) {
return '+';
} else if (binary == 63) {
return '/';
} else {
WTF;
}
}
int32_t base64_encode(char *out, int out_len, const uint8_t *data, int32_t data_len) {
int result = (data_len + 2) / 3 * 4;
if (result > out_len) {
return result;
}
int32_t i;
for (i = 0; i < data_len - 2; i += 3) {
*out++ = prv_encode_char(data[i] >> 2);
*out++ = prv_encode_char(((data[i] & 0x03) << 4) | (data[i + 1] >> 4));
*out++ = prv_encode_char(((data[i + 1] & 0x0F) << 2) | (data[i + 2] >> 6));
*out++ = prv_encode_char(data[i + 2] & 0x3F);
}
if (i < data_len) {
if (i == data_len - 2) {
// if 2 leftover bytes
*out++ = prv_encode_char(data[i] >> 2);
*out++ = prv_encode_char(((data[i] & 0x03) << 4) | (data[i + 1] >> 4));
*out++ = prv_encode_char((data[i + 1] & 0x0F) << 2);
*out++ = '=';
} else if (i == data_len - 1) {
// if 1 leftover byte
*out++ = prv_encode_char(data[i] >> 2);
*out++ = prv_encode_char((data[i] & 0x03) << 4);
*out++ = '=';
*out++ = '=';
}
}
if (result < out_len) {
*out++ = 0;
}
return result;
}

30
src/fw/util/base64.h Normal file
View file

@ -0,0 +1,30 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
unsigned int base64_decode_inplace(char* buffer, unsigned int length);
// Encode a buffer as base 64
// @param out the encoded base64 string is written here
// @param out_len the size of the out buffer
// @param data the binary data to encode
// @param data_len the number of bytes of binary data
// @retval the number of characters required to encode all of the data, not including the
// terminating null at the end. If this is less than the passed in out_len, then
// NO characters will be written to the out buffer.
int32_t base64_encode(char *out, int out_len, const uint8_t *data, int32_t data_len);

33
src/fw/util/bitset.c Normal file
View file

@ -0,0 +1,33 @@
/*
* 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 "bitset.h"
#include <stdint.h>
uint8_t count_bits_set(uint8_t *bitset_bytes, int num_bits) {
uint8_t num_bits_set = 0;
int num_bytes = (num_bits + 7) / 8;
if ((num_bits % 8) != 0) {
bitset_bytes[num_bytes] &= ((0x1 << (num_bits)) - 1);
}
for (int i = 0; i < num_bytes; i++) {
num_bits_set += __builtin_popcount(bitset_bytes[i]);
}
return (num_bits_set);
}

110
src/fw/util/bitset.h Normal file
View file

@ -0,0 +1,110 @@
/*
* 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.
*/
//! @file util/bitset.h
//!
//! Helper functions for dealing with a bitsets of various widths.
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "system/passert.h"
static inline void bitset8_set(uint8_t* bitset, unsigned int index) {
bitset[index / 8] |= (1 << (index % 8));
}
static inline void bitset8_clear(uint8_t* bitset, unsigned int index) {
bitset[index / 8] &= ~(1 << (index % 8));
}
static inline void bitset8_update(uint8_t* bitset, unsigned int index, bool value) {
if (value) {
bitset8_set(bitset, index);
} else {
bitset8_clear(bitset, index);
}
}
static inline bool bitset8_get(const uint8_t* bitset, unsigned int index) {
return bitset[index / 8] & (1 << (index % 8));
}
static inline void bitset16_set(uint16_t* bitset, unsigned int index) {
bitset[index / 16] |= (1 << (index % 16));
}
static inline void bitset16_clear(uint16_t* bitset, unsigned int index) {
bitset[index / 16] &= ~(1 << (index % 16));
}
static inline void bitset16_update(uint16_t* bitset, unsigned int index, bool value) {
if (value) {
bitset16_set(bitset, index);
} else {
bitset16_clear(bitset, index);
}
}
static inline bool bitset16_get(const uint16_t* bitset, unsigned int index) {
return bitset[index / 16] & (1 << (index % 16));
}
static inline void bitset32_set(uint32_t* bitset, unsigned int index) {
bitset[index / 32] |= (1 << (index % 32));
}
static inline void bitset32_clear(uint32_t* bitset, unsigned int index) {
bitset[index / 32] &= ~(1 << (index % 32));
}
static inline void bitset32_clear_all(uint32_t* bitset, unsigned int width) {
if (width > 32) {
// TODO: revisit
WTF;
}
*bitset &= ~((1 << (width + 1)) - 1);
}
static inline void bitset32_update(uint32_t* bitset, unsigned int index, bool value) {
if (value) {
bitset32_set(bitset, index);
} else {
bitset32_clear(bitset, index);
}
}
static inline bool bitset32_get(const uint32_t* bitset, unsigned int index) {
return bitset[index / 32] & (1 << (index % 32));
}
#ifdef __arm__
#define rotl32(x, shift) \
__asm__ volatile ("ror %0,%0,%1" : "+r" (src) : "r" (32 - (shift)) :);
#else
#define rotl32(x, shift) \
uint32_t s = shift % 32; \
{x = ((x << (s)) | x >> (32 - (s)));}
#endif
//! Count the number of bits that are set to 1 in a multi-byte bitset.
//! @param bitset_bytes The bytes of the bitset
//! @param num_bits The width of the bitset
//! @note this function zeroes out any bits in the last byte if there
//! are more bits than num_bits.
uint8_t count_bits_set(uint8_t *bitset_bytes, int num_bits);

76
src/fw/util/buffer.c Normal file
View file

@ -0,0 +1,76 @@
/*
* 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 "buffer.h"
#include "system/logging.h"
#include "system/passert.h"
#include "kernel/pbl_malloc.h"
#include <string.h>
size_t buffer_get_bytes_remaining(Buffer *b) {
return (b->length - b->bytes_written);
}
size_t buffer_add(Buffer* const b, const uint8_t* const data, const size_t length) {
PBL_ASSERTN(b);
PBL_ASSERTN(data);
PBL_ASSERTN(length);
if (buffer_get_bytes_remaining(b) < length) {
return 0;
}
uint8_t* cursor = b->data + b->bytes_written;
memcpy(cursor, data, length);
b->bytes_written += length;
return length;
}
size_t buffer_remove(Buffer* const b, const size_t offset, const size_t length) {
PBL_ASSERTN(offset + length <= b->bytes_written);
memmove(&b->data[offset], &b->data[offset + length], b->bytes_written - length - offset);
b->bytes_written -= length;
return length;
}
Buffer* buffer_create(const size_t size_bytes) {
PBL_ASSERTN(size_bytes);
Buffer* b = kernel_malloc_check(sizeof(Buffer) + size_bytes);
*b = (Buffer) {
.length = size_bytes,
.bytes_written = 0,
};
return b;
}
void buffer_init(Buffer * const buffer, const size_t length) {
buffer->bytes_written = 0;
buffer->length = length;
}
void buffer_clear(Buffer * const buffer) {
buffer->bytes_written = 0;
}
bool buffer_is_empty(Buffer * const buffer) {
return buffer->bytes_written == 0;
}

50
src/fw/util/buffer.h Normal file
View file

@ -0,0 +1,50 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
typedef struct {
size_t length;
size_t bytes_written;
uint8_t data[];
} Buffer;
//! Atomically add and return `length` bytes or add none and return 0
size_t buffer_add(Buffer* const buffer, const uint8_t* const data, const size_t length);
//! Remove `length` starting at `offset`.
//! The combination of length and offset must not exceed the written bytes.
size_t buffer_remove(Buffer* const b, const size_t offset, const size_t length);
//! Create buffer with a data region of size_bytes
Buffer* buffer_create(const size_t size_bytes);
//! Initializes Buffer with a given `length`.
//! Make sure that `buffer`'s data is can store `length` bytes.
void buffer_init(Buffer * const buffer, const size_t length);
//! Returns the number of remaining bytes that can be filled with `buffer_add`
size_t buffer_get_bytes_remaining(Buffer *b);
//! Empty buffer
void buffer_clear(Buffer * const buffer);
//! Return true if buffer is empty
bool buffer_is_empty(Buffer * const buffer);

37
src/fw/util/crc32_lut.py Executable file
View file

@ -0,0 +1,37 @@
#!/usr/bin/env python
# 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.
from __future__ import print_function
CRC_POLY = 0xEDB88320
def crc_table(bits):
lookup_table = []
for i in xrange(2**bits):
rr = i * 16
for x in xrange(8):
rr = (rr >> 1) ^ (-(rr & 1) & CRC_POLY)
lookup_table.append(rr & 0xffffffff)
return lookup_table
table = ['0x{:08x},'.format(entry) for entry in crc_table(4)]
chunks = zip(*[iter(table)]*4)
print('static const uint32_t s_lookup_table[] = {')
for chunk in chunks:
print(' ' + ' '.join(chunk))
print('};')

50
src/fw/util/crc8.c Normal file
View file

@ -0,0 +1,50 @@
/*
* 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 "util/crc8.h"
uint8_t crc8_calculate_bytes(const uint8_t *data, uint32_t data_len, bool big_endian) {
uint8_t checksum = 0;
crc8_calculate_bytes_streaming(data, data_len, &checksum, big_endian);
return checksum;
}
void crc8_calculate_bytes_streaming(const uint8_t *data, uint32_t data_len, uint8_t *crc,
bool big_endian) {
// Optimal polynomial chosen based on
// http://users.ece.cmu.edu/~koopman/roses/dsn04/koopman04_crc_poly_embedded.pdf
// Note that this is different than the standard CRC-8 polynomial, because the
// standard CRC-8 polynomial is not particularly good.
// nibble lookup table for (x^8 + x^5 + x^3 + x^2 + x + 1)
static const uint8_t lookup_table[] =
{ 0, 47, 94, 113, 188, 147, 226, 205, 87, 120, 9, 38, 235, 196,
181, 154 };
for (uint32_t i = 0; i < data_len * 2; i++) {
uint8_t nibble;
if (big_endian) {
nibble = data[data_len - (i / 2) - 1];
} else {
nibble = data[i / 2];
}
if (i % 2 == 0) {
nibble >>= 4;
}
int index = nibble ^ (*crc >> 4);
*crc = lookup_table[index & 0xf] ^ ((*crc << 4) & 0xf0);
}
}

29
src/fw/util/crc8.h Normal file
View file

@ -0,0 +1,29 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
/*
* Calculate an 8-bit CRC of a given byte sequence. Note that this is not using the standard CRC-8
* polynomial, because the standard polynomial isn't very good. If the big_endian flag is set, the
* crc will be calculated by going through the data in reverse order (high->low index).
*/
uint8_t crc8_calculate_bytes(const uint8_t *data, uint32_t data_length, bool big_endian);
void crc8_calculate_bytes_streaming(const uint8_t *data, uint32_t data_length, uint8_t *crc,
bool big_endian);

48
src/fw/util/date.c Normal file
View file

@ -0,0 +1,48 @@
/*
* 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 "date.h"
bool date_util_is_leap_year(int year) {
return (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
}
int date_util_get_max_days_in_month(int month, bool is_leap_year) {
int days;
switch (month) {
case 4: //April
case 6: //June
case 9: //September
case 11: //November
{
days = 30;
break;
}
case 2: // February
{
days = is_leap_year ? 29 : 28;
break;
}
default:
{
// Jan, March, May, July, August, October, December
days = 31;
break;
}
}
return days;
}

28
src/fw/util/date.h Normal file
View file

@ -0,0 +1,28 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#define STDTIME_YEAR_OFFSET 1900
#define DAYS_PER_YEAR 365
#define MAX_DAYS_PER_YEAR 366
bool date_util_is_leap_year(int year);
int date_util_get_max_days_in_month(int month, bool is_leap_year);

440
src/fw/util/dict.c Normal file
View file

@ -0,0 +1,440 @@
/*
* 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 "dict.h"
#include <string.h>
#include "system/passert.h"
#include "kernel/pbl_malloc.h"
#include "util/list.h"
#include "util/net.h"
static DictionaryResult dict_init(DictionaryIterator *iter, const uint8_t * const buffer, const uint16_t length) {
if (iter == NULL ||
buffer == NULL) {
return DICT_INVALID_ARGS;
}
if (length < sizeof(Dictionary)) {
return DICT_NOT_ENOUGH_STORAGE;
}
iter->dictionary = (Dictionary *) buffer;
iter->cursor = iter->dictionary->head;
iter->end = buffer + length;
return DICT_OK;
}
uint32_t dict_size(DictionaryIterator* iter) {
return (uint8_t*)iter->end - (uint8_t*)iter->dictionary;
}
DictionaryResult dict_write_begin(DictionaryIterator *iter, uint8_t * const buffer, const uint16_t length) {
const DictionaryResult result = dict_init(iter, buffer, length);
if (result == DICT_OK) {
iter->dictionary->count = 0;
}
return result;
}
static Tuple * cursor_after_tuple_with_data_length(const DictionaryIterator *iter, const uint16_t length) {
return (Tuple *) (((uint8_t *)iter->cursor) + sizeof(Tuple) + length);
}
static DictionaryResult dict_write_data_internal(DictionaryIterator *iter, const uint32_t key, const uint8_t * const data, const uint16_t data_length, const TupleType type) {
if (iter == NULL ||
iter->dictionary == NULL ||
iter->cursor == NULL) {
return DICT_INVALID_ARGS;
}
if (iter->cursor == iter->dictionary->head) {
// Reset implicitly if the cursor is at the head, so writing again after
// calling dict_write_end() won't screw up the count and will just work:
iter->dictionary->count = 0;
}
Tuple * const next_cursor = cursor_after_tuple_with_data_length(iter, data_length);
if (iter->end < (void *)next_cursor) {
return DICT_NOT_ENOUGH_STORAGE;
}
iter->cursor->key = key;
iter->cursor->length = data_length;
iter->cursor->type = type;
if (data_length > 0) {
if (data == NULL) {
return DICT_INVALID_ARGS;
}
memcpy(iter->cursor->value->data, data, data_length);
}
iter->cursor = next_cursor;
++iter->dictionary->count;
return DICT_OK;
}
DictionaryResult dict_write_data(DictionaryIterator *iter, const uint32_t key, const uint8_t * const data, const uint16_t length) {
return dict_write_data_internal(iter, key, data, length, TUPLE_BYTE_ARRAY);
}
DictionaryResult dict_write_cstring(DictionaryIterator *iter, const uint32_t key, const char * const cstring) {
return dict_write_data_internal(iter, key, (const uint8_t * const) cstring, cstring ? strlen(cstring) + 1 : 0, TUPLE_CSTRING);
}
DictionaryResult dict_write_uint8(DictionaryIterator *iter, const uint32_t key, const uint8_t value) {
return dict_write_data_internal(iter, key, (const uint8_t * const) &value, sizeof(value), TUPLE_UINT);
}
DictionaryResult dict_write_uint16(DictionaryIterator *iter, const uint32_t key, const uint16_t value) {
return dict_write_data_internal(iter, key, (const uint8_t * const) &value, sizeof(value), TUPLE_UINT);
}
DictionaryResult dict_write_uint32(DictionaryIterator *iter, const uint32_t key, const uint32_t value) {
return dict_write_data_internal(iter, key, (const uint8_t * const) &value, sizeof(value), TUPLE_UINT);
}
DictionaryResult dict_write_int8(DictionaryIterator *iter, const uint32_t key, const int8_t value) {
return dict_write_data_internal(iter, key, (const uint8_t * const) &value, sizeof(value), TUPLE_INT);
}
DictionaryResult dict_write_int16(DictionaryIterator *iter, const uint32_t key, const int16_t value) {
return dict_write_data_internal(iter, key, (const uint8_t * const) &value, sizeof(value), TUPLE_INT);
}
DictionaryResult dict_write_int32(DictionaryIterator *iter, const uint32_t key, const int32_t value) {
return dict_write_data_internal(iter, key, (const uint8_t * const) &value, sizeof(value), TUPLE_INT);
}
DictionaryResult dict_write_int(DictionaryIterator *iter, const uint32_t key, const void *integer, const uint8_t width_bytes, const bool is_signed) {
return dict_write_data_internal(iter, key, integer, width_bytes, is_signed ? TUPLE_INT : TUPLE_UINT);
}
uint32_t dict_write_end(DictionaryIterator *iter) {
if (iter == NULL ||
iter->dictionary == NULL ||
iter->cursor == NULL) {
return 0;
}
iter->end = iter->cursor;
return dict_size(iter);
}
//! Returns the cursor, or NULL if the tuple at cursor extends beyond the bounds of the backing storage
static Tuple * get_safe_cursor(DictionaryIterator *iter) {
// If iter->cursor is already at the end, return now so we don't try and read past the end of
// the malloc'ed block (when fetching iter->cursor->length) and possibly cause a memory read
// exception.
if ((void*)iter->cursor >= iter->end) {
return NULL;
}
Tuple * const next_cursor = cursor_after_tuple_with_data_length(iter, iter->cursor->length);
if ((void *)next_cursor > iter->end) {
return NULL;
}
return iter->cursor;
}
Tuple * dict_read_begin_from_buffer(DictionaryIterator *iter, const uint8_t * const buffer, const uint16_t length) {
const DictionaryResult result = dict_init(iter, buffer, length);
if (result != DICT_OK) {
return NULL;
}
return get_safe_cursor(iter);
}
Tuple * dict_read_next(DictionaryIterator *iter) {
if (iter == NULL ||
iter->dictionary == NULL ||
iter->cursor == NULL) {
return NULL;
}
iter->cursor = cursor_after_tuple_with_data_length(iter, iter->cursor->length);
return get_safe_cursor(iter);
}
Tuple * dict_read_first(DictionaryIterator *iter) {
if (iter == NULL ||
iter->dictionary == NULL ||
iter->cursor == NULL) {
return NULL;
}
iter->cursor = iter->dictionary->head;
return get_safe_cursor(iter);
}
uint32_t dict_calc_buffer_size(const uint8_t count, ...) {
uint32_t total_size = sizeof(Dictionary);
if (count == 0) {
return total_size;
}
va_list vl;
va_start(vl, count);
for (unsigned int i = 0; i < count; ++i) {
total_size += va_arg(vl, unsigned int) + sizeof(Tuple);
}
va_end(vl);
return total_size;
}
uint32_t dict_calc_buffer_size_from_tuplets(const Tuplet * const tuplets, const uint8_t tuplets_count) {
uint32_t total_size = sizeof(Dictionary);
if (tuplets_count == 0) {
return total_size;
}
for (unsigned int i = 0; i < tuplets_count; ++i) {
const Tuplet * const tuplet = &tuplets[i];
switch (tuplet->type) {
case TUPLE_BYTE_ARRAY:
total_size += tuplet->bytes.length;
break;
case TUPLE_CSTRING:
total_size += tuplet->cstring.length;
break;
case TUPLE_INT:
case TUPLE_UINT:
total_size += tuplet->integer.width;
break;
}
total_size += sizeof(Tuple);
}
return total_size;
}
// Legacy version to prevent previous app breakage, __deprecated preserves order
uint32_t dict_calc_buffer_size_from_tuplets__deprecated(const uint8_t tuplets_count, const Tuplet * const tuplets) {
return dict_calc_buffer_size_from_tuplets(tuplets, tuplets_count);
}
static DictionaryResult dict_write_tuple(DictionaryIterator* iter, Tuple* tuple) {
return dict_write_data_internal(iter, tuple->key, (uint8_t*)tuple->value,
tuple->length, tuple->type);
}
DictionaryResult dict_write_tuplet(DictionaryIterator *iter, const Tuplet * const tuplet) {
if (iter == NULL ||
iter->dictionary == NULL ||
iter->cursor == NULL) {
return DICT_INVALID_ARGS;
}
switch (tuplet->type) {
case TUPLE_BYTE_ARRAY:
return dict_write_data_internal(iter, tuplet->key, tuplet->bytes.data, tuplet->bytes.length, tuplet->type);
case TUPLE_CSTRING:
return dict_write_data_internal(iter, tuplet->key, (uint8_t *)tuplet->cstring.data, tuplet->cstring.length, tuplet->type);
case TUPLE_UINT:
case TUPLE_INT:
return dict_write_data_internal(iter, tuplet->key, (uint8_t *)&tuplet->integer.storage, tuplet->integer.width, tuplet->type);
}
return DICT_INVALID_ARGS;
}
DictionaryResult dict_serialize_tuplets_to_buffer_with_iter(
DictionaryIterator *iter,
const Tuplet * const tuplets, const uint8_t tuplets_count,
uint8_t *buffer, uint32_t *size_in_out) {
if (size_in_out == NULL ||
buffer == NULL ||
tuplets == NULL) {
return DICT_INVALID_ARGS;
}
DictionaryResult result;
if ((result = dict_write_begin(iter, buffer, *size_in_out)) != DICT_OK) {
return result;
}
for (unsigned int i = 0; i < tuplets_count; ++i) {
if ((result = dict_write_tuplet(iter, &tuplets[i])) != DICT_OK) {
return result;
}
}
*size_in_out = dict_write_end(iter);
return DICT_OK;
}
// Legacy version to prevent previous app breakage, __deprecated preserves order
DictionaryResult dict_serialize_tuplets_to_buffer_with_iter__deprecated(const uint8_t tuplets_count,
const Tuplet * const tuplets, DictionaryIterator *iter, uint8_t *buffer, uint32_t *size_in_out) {
return dict_serialize_tuplets_to_buffer_with_iter(iter,
tuplets, tuplets_count, buffer, size_in_out);
}
DictionaryResult dict_serialize_tuplets_to_buffer(const Tuplet * const tuplets, const uint8_t tuplets_count, uint8_t *buffer, uint32_t *size_in_out) {
DictionaryIterator iter;
return dict_serialize_tuplets_to_buffer_with_iter(&iter, tuplets, tuplets_count, buffer, size_in_out);
}
// Legacy version to prevent previous app breakage, __deprecated preserves order
DictionaryResult dict_serialize_tuplets_to_buffer__deprecated(
const uint8_t tuplets_count, const Tuplet * const tuplets,
uint8_t *buffer, uint32_t *size_in_out) {
DictionaryIterator iter;
return dict_serialize_tuplets_to_buffer_with_iter(&iter,
tuplets, tuplets_count, buffer, size_in_out);
}
DictionaryResult dict_serialize_tuplets(DictionarySerializeCallback callback, void *context, const Tuplet * const tuplets, const uint8_t tuplets_count) {
if (tuplets_count == 0) {
const Dictionary dict = { .count = 0 };
callback((const uint8_t *)&dict, sizeof(Dictionary), context);
return DICT_OK;
}
uint32_t size = dict_calc_buffer_size_from_tuplets(tuplets, tuplets_count);
uint8_t buffer[size];
DictionaryResult result = dict_serialize_tuplets_to_buffer(tuplets, tuplets_count, buffer, &size);
if (result != DICT_OK) {
return result;
}
callback(buffer, size, context);
return DICT_OK;
}
// Legacy version to prevent previous app breakage, __deprecated preserves order
DictionaryResult dict_serialize_tuplets__deprecated(DictionarySerializeCallback callback, void *context, const uint8_t tuplets_count, const Tuplet * const tuplets) {
return dict_serialize_tuplets(callback, context, tuplets, tuplets_count);
}
static const uint8_t NULL_TUPLE_BUFFER[sizeof(Tuple) + sizeof(uint32_t)] = { 0 };
const Tuple * const NULL_TUPLE = (const Tuple * const) NULL_TUPLE_BUFFER;
static uint8_t* dict_copy(DictionaryIterator* iter) {
size_t size = dict_size(iter);
uint8_t* buf = task_malloc(size);
if (buf == NULL) return NULL;
memcpy(buf, iter->dictionary, size);
return buf;
}
// Merge orig_iter and new_iter into dest_iter. Keys which exist in both
// orig_iter and new_iter will get the value they have in new_iter.
static DictionaryResult dict_merge_to(DictionaryIterator* dest_iter,
DictionaryIterator* orig_iter,
DictionaryIterator* new_iter,
const bool update_existing_keys_only,
const DictionaryKeyUpdatedCallback update_key_callback,
void* context) {
DictionaryResult result = DICT_OK;
// First, write the updated keys.
for (Tuple* new = dict_read_first(new_iter); new; new = dict_read_next(new_iter)) {
uint32_t key = new->key;
const Tuple* orig = dict_find(orig_iter, key);
if (orig == NULL && update_existing_keys_only) {
continue;
}
if (orig == NULL) {
orig = NULL_TUPLE;
}
Tuple* dest = dest_iter->cursor;
result = dict_write_tuple(dest_iter, new);
if (result != DICT_OK) return result;
update_key_callback(key, dest, orig, context);
}
// Then, write any old keys which were not updated this round.
// We still call update_key_callback here, even though the values
// themselves have not changed, because we have shuffled them
// around in memory, so their old buffers are no longer valid.
for (Tuple* orig = dict_read_first(orig_iter); orig; orig = dict_read_next(orig_iter)) {
uint32_t key = orig->key;
Tuple* new = dict_find(new_iter, key);
if (new != NULL) {
// We already wrote this key, above.
continue;
}
Tuple* dest = dest_iter->cursor;
result = dict_write_tuple(dest_iter, orig);
if (result != DICT_OK) return result;
update_key_callback(key, dest, orig, context);
}
return DICT_OK;
}
// Calculate the amount of space needed for a dest_iter which can fit the result
// of merging orig_iter and new_iter. This logic should always mirror the logic
// in dict_merge_to, except it should simply count the size, rather than
// actually merging the results.
static size_t dict_merge_to_size(DictionaryIterator* orig_iter,
DictionaryIterator* new_iter,
const bool update_existing_keys_only) {
size_t total_size_required = sizeof(Dictionary);
// First, calculate the size of the new/updated keys.
for (Tuple* new = dict_read_first(new_iter); new; new = dict_read_next(new_iter)) {
if (dict_find(orig_iter, new->key) == NULL && update_existing_keys_only) continue;
total_size_required += sizeof(*new) + new->length;
}
// Then, add in the size of the keys which have not changed.
for (Tuple* orig = dict_read_first(orig_iter); orig; orig = dict_read_next(orig_iter)) {
if (dict_find(new_iter, orig->key) != NULL) continue;
total_size_required += sizeof(*orig) + orig->length;
}
return total_size_required;
}
DictionaryResult dict_merge(DictionaryIterator* dest_iter,
uint32_t* dest_buf_length_in_out,
DictionaryIterator* new_iter,
const bool update_existing_keys_only,
const DictionaryKeyUpdatedCallback update_key_callback,
void* context) {
if (dest_iter == NULL || new_iter == NULL || dest_buf_length_in_out == NULL) {
return DICT_INVALID_ARGS;
}
size_t required_size = dict_merge_to_size(dest_iter, new_iter, update_existing_keys_only);
if (*dest_buf_length_in_out < required_size) {
return DICT_NOT_ENOUGH_STORAGE;
}
uint8_t* orig_buffer = dict_copy(dest_iter);
if (orig_buffer == NULL) return DICT_MALLOC_FAILED;
DictionaryIterator orig_iter;
DictionaryResult result = dict_init(&orig_iter, orig_buffer, dict_size(dest_iter));
if (result != DICT_OK) goto cleanup;
result = dict_write_begin(dest_iter,
(uint8_t*)dest_iter->dictionary,
(uint16_t)*dest_buf_length_in_out);
if (result != DICT_OK) goto cleanup;
result = dict_merge_to(dest_iter, &orig_iter, new_iter,
update_existing_keys_only,
update_key_callback, context);
if (result != DICT_OK) goto cleanup;
*dest_buf_length_in_out = dict_write_end(dest_iter);
cleanup:
task_free(orig_buffer);
return result;
}
Tuple *dict_find(const DictionaryIterator *iter, const uint32_t key) {
DictionaryIterator iter_copy = *iter;
Tuple *tuple = dict_read_first(&iter_copy);
while (tuple) {
if (tuple->key == key) {
return tuple;
}
tuple = dict_read_next(&iter_copy);
}
return NULL;
}

483
src/fw/util/dict.h Normal file
View file

@ -0,0 +1,483 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <stdarg.h>
#include <stdbool.h>
//! @file dict.h Generic key/value serializer and parser.
//! @addtogroup Foundation
//! @{
//! @addtogroup Dictionary
//! \brief Data serialization utilities
//!
//!
//! Data residing in different parts of Pebble memory (RAM) may need to be gathered and assembled into
//! a single continuous block for transport over the network via Bluetooth. The process of gathering
//! and assembling this continuous block of data is called serialization.
//!
//! You use data serialization utilities, like Dictionary, Tuple and Tuplet data structures and accompanying
//! functions, to accomplish this task. No transformations are performed on the actual data, however.
//! These Pebble utilities simply help assemble the data into one continuous buffer according to a
//! specific format.
//!
//! \ref AppMessage uses these utilities--in particular, Dictionary--to send information between mobile
//! and Pebble watchapps.
//!
//! <h3>Writing key/value pairs</h3>
//! To write two key/value pairs, without using Tuplets, you would do this:
//! \code{.c}
//! // Byte array + key:
//! static const uint32_t SOME_DATA_KEY = 0xb00bf00b;
//! static const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
//!
//! // CString + key:
//! static const uint32_t SOME_STRING_KEY = 0xabbababe;
//! static const char *string = "Hello World";
//!
//! // Calculate the buffer size that is needed for the final Dictionary:
//! const uint8_t key_count = 2;
//! const uint32_t size = dict_calc_buffer_size(key_count, sizeof(data),
//! strlen(string) + 1);
//!
//! // Stack-allocated buffer in which to create the Dictionary:
//! uint8_t buffer[size];
//!
//! // Iterator variable, keeps the state of the creation serialization process:
//! DictionaryIterator iter;
//!
//! // Begin:
//! dict_write_begin(&iter, buffer, sizeof(buffer));
//! // Write the Data:
//! dict_write_data(&iter, SOME_DATA_KEY, data, sizeof(data));
//! // Write the CString:
//! dict_write_cstring(&iter, SOME_STRING_KEY, string);
//! // End:
//! const uint32_t final_size = dict_write_end(&iter);
//!
//! // buffer now contains the serialized information
//!
//! \endcode
//!
//! <h3>Reading key/value pairs</h3>
//! To iterate over the key/value pairs in the dictionary that
//! was created in the previous example code, you would do this:
//!
//! \code{.c}
//! Tuple *tuple = dict_read_begin_from_buffer(&iter, buffer, final_size);
//! while (tuple) {
//! switch (tuple->key) {
//! case SOME_DATA_KEY:
//! foo(tuple->value->data, tuple->length);
//! break;
//! case SOME_STRING_KEY:
//! bar(tuple->value->cstring);
//! break;
//! }
//! tuple = dict_read_next(&iter);
//! }
//! \endcode
//!
//! <h3>Tuple and Tuplet data structures</h3>
//! To understand the difference between Tuple and Tuplet data structures:
//! Tuple is the header for a serialized key/value pair, while Tuplet is a helper
//! data structure that references the value you want to serialize. This data
//! structure exists to make the creation of a Dictionary easier to write.
//! Use this mnemonic to remember the difference: TupleT(emplate), the Tuplet being
//! a template to create a Dictionary with Tuple structures.
//!
//! For example:
//! \code{.c}
//! Tuplet pairs[] = {
//! TupletInteger(WEATHER_ICON_KEY, (uint8_t) 1),
//! TupletCString(WEATHER_TEMPERATURE_KEY, "1234 Fahrenheit"),
//! };
//! uint8_t buffer[256];
//! uint32_t size = sizeof(buffer);
//! dict_serialize_tuplets_to_buffer(pairs, ARRAY_LENGTH(pairs), buffer, &size);
//!
//! // buffer now contains the serialized information
//! \endcode
//! @{
//! Return values for dictionary write/conversion functions.
typedef enum {
//! The operation returned successfully
DICT_OK = 0,
//! There was not enough backing storage to complete the operation
DICT_NOT_ENOUGH_STORAGE = 1 << 1,
//! One or more arguments were invalid or uninitialized
DICT_INVALID_ARGS = 1 << 2,
//! The lengths and/or count of the dictionary its tuples are inconsistent
DICT_INTERNAL_INCONSISTENCY = 1 << 3,
//! A requested operation required additional memory to be allocated, but
//! the allocation failed, likely due to insufficient remaining heap memory.
DICT_MALLOC_FAILED = 1 << 4,
} DictionaryResult;
//! Values representing the type of data that the `value` field of a Tuple contains
typedef enum {
//! The value is an array of bytes
TUPLE_BYTE_ARRAY = 0,
//! The value is a zero-terminated, UTF-8 C-string
TUPLE_CSTRING = 1,
//! The value is an unsigned integer. The tuple's `.length` field is used to
//! determine the size of the integer (1, 2, or 4 bytes).
TUPLE_UINT = 2,
//! The value is a signed integer. The tuple's `.length` field is used to
//! determine the size of the integer (1, 2, or 4 bytes).
TUPLE_INT = 3,
} TupleType;
//! Data structure for one serialized key/value tuple
//! @note The structure is variable length! The length depends on the value data that the tuple
//! contains.
typedef struct __attribute__((__packed__)) {
//! The key
uint32_t key;
//! The type of data that the `.value` fields contains.
TupleType type:8;
//! The length of `.value` in bytes
uint16_t length;
//! @brief The value itself.
//!
//! The different union fields are provided for convenience, avoiding the need for manual casts.
//! @note The array length is of incomplete length on purpose, to facilitate
//! variable length data and because a data length of zero is valid.
//! @note __Important: The integers are little endian!__
union {
//! The byte array value. Valid when `.type` is \ref TUPLE_BYTE_ARRAY.
uint8_t data[0];
//! The C-string value. Valid when `.type` is \ref TUPLE_CSTRING.
char cstring[0];
//! The 8-bit unsigned integer value. Valid when `.type` is \ref TUPLE_UINT
//! and `.length` is 1 byte.
uint8_t uint8;
//! The 16-bit unsigned integer value. Valid when `.type` is \ref TUPLE_UINT
//! and `.length` is 2 byte.
uint16_t uint16;
//! The 32-bit unsigned integer value. Valid when `.type` is \ref TUPLE_UINT
//! and `.length` is 4 byte.
uint32_t uint32;
//! The 8-bit signed integer value. Valid when `.type` is \ref TUPLE_INT
//! and `.length` is 1 byte.
int8_t int8;
//! The 16-bit signed integer value. Valid when `.type` is \ref TUPLE_INT
//! and `.length` is 2 byte.
int16_t int16;
//! The 32-bit signed integer value. Valid when `.type` is \ref TUPLE_INT
//! and `.length` is 4 byte.
int32_t int32;
} value[];
} Tuple;
//! @internal
//! Header data structure of a serialized "dictionary" of zero or more Tuple
//! key-value pairs.
typedef struct __attribute__((__packed__)) {
uint8_t count; //!< The number of key-value pairs (Tuples) in the dictionary
Tuple head[]; //!< The first Tuple in the dictionary
} Dictionary;
//! An iterator can be used to iterate over the key/value
//! tuples in an existing dictionary, using \ref dict_read_begin_from_buffer(),
//! \ref dict_read_first() and \ref dict_read_next().
//! An iterator can also be used to append key/value tuples to a dictionary,
//! for example using \ref dict_write_data() or \ref dict_write_cstring().
typedef struct {
Dictionary *dictionary; //!< The dictionary being iterated
const void *end; //!< Points to the first memory address after the last byte of the dictionary
//! Points to the next Tuple in the dictionary. Given the end of the
//! Dictionary has not yet been reached: when writing, the next key/value
//! pair will be written at the cursor. When reading, the next call
//! to \ref dict_read_next() will return the cursor.
Tuple *cursor;
} DictionaryIterator;
//! Calculates the number of bytes that a dictionary will occupy, given
//! one or more value lengths that need to be stored in the dictionary.
//! @note The formula to calculate the size of a Dictionary in bytes is:
//! <pre>1 + (n * 7) + D1 + ... + Dn</pre>
//! Where `n` is the number of Tuples in the Dictionary and `Dx` are the sizes
//! of the values in the Tuples. The size of the Dictionary header is 1 byte.
//! The size of the header for each Tuple is 7 bytes.
//! @param tuple_count The total number of key/value pairs in the dictionary.
//! @param ... The sizes of each of the values that need to be
//! stored in the dictionary.
//! @return The total number of bytes of storage needed.
uint32_t dict_calc_buffer_size(const uint8_t tuple_count, ...);
//! Calculates the size of data that has been written to the dictionary.
//! AKA, the "dictionary size". Note that this is most likely different
//! than the size of the backing storage/backing buffer.
//! @param iter The dictionary iterator
//! @return The total number of bytes which have been written to the dictionary.
uint32_t dict_size(DictionaryIterator* iter);
//! Initializes the dictionary iterator with a given buffer and size,
//! resets and empties it, in preparation of writing key/value tuples.
//! @param iter The dictionary iterator
//! @param buffer The storage of the dictionary
//! @param size The storage size of the dictionary
//! @return \ref DICT_OK, \ref DICT_NOT_ENOUGH_STORAGE or \ref DICT_INVALID_ARGS
//! @see dict_calc_buffer_size
//! @see dict_write_end
DictionaryResult dict_write_begin(DictionaryIterator *iter, uint8_t * const buffer, const uint16_t size);
//! Adds a key with a byte array value pair to the dictionary.
//! @param iter The dictionary iterator
//! @param key The key
//! @param data Pointer to the byte array
//! @param size Length of the byte array
//! @return \ref DICT_OK, \ref DICT_NOT_ENOUGH_STORAGE or \ref DICT_INVALID_ARGS
//! @note The data will be copied into the backing storage of the dictionary.
//! @note There is _no_ checking for duplicate keys.
DictionaryResult dict_write_data(DictionaryIterator *iter, const uint32_t key, const uint8_t * const data, const uint16_t size);
//! Adds a key with a C string value pair to the dictionary.
//! @param iter The dictionary iterator
//! @param key The key
//! @param cstring Pointer to the zero-terminated C string
//! @return \ref DICT_OK, \ref DICT_NOT_ENOUGH_STORAGE or \ref DICT_INVALID_ARGS
//! @note The string will be copied into the backing storage of the dictionary.
//! @note There is _no_ checking for duplicate keys.
DictionaryResult dict_write_cstring(DictionaryIterator *iter, const uint32_t key, const char * const cstring);
//! Adds a key with an integer value pair to the dictionary.
//! @param iter The dictionary iterator
//! @param key The key
//! @param integer Pointer to the integer value
//! @param width_bytes The width of the integer value
//! @param is_signed Whether the integer's type is signed or not
//! @return \ref DICT_OK, \ref DICT_NOT_ENOUGH_STORAGE or \ref DICT_INVALID_ARGS
//! @note There is _no_ checking for duplicate keys. dict_write_int() is only for serializing a single
//! integer. width_bytes can only be 1, 2, or 4.
DictionaryResult dict_write_int(DictionaryIterator *iter, const uint32_t key, const void *integer, const uint8_t width_bytes, const bool is_signed);
//! Adds a key with an unsigned, 8-bit integer value pair to the dictionary.
//! @param iter The dictionary iterator
//! @param key The key
//! @param value The unsigned, 8-bit integer value
//! @return \ref DICT_OK, \ref DICT_NOT_ENOUGH_STORAGE or \ref DICT_INVALID_ARGS
//! @note There is _no_ checking for duplicate keys.
//! @note There are counterpart functions for different signedness and widths,
//! `dict_write_uint16()`, `dict_write_uint32()`, `dict_write_int8()`,
//! `dict_write_int16()` and `dict_write_int32()`. The documentation is not
//! repeated for brevity's sake.
DictionaryResult dict_write_uint8(DictionaryIterator *iter, const uint32_t key, const uint8_t value);
DictionaryResult dict_write_uint16(DictionaryIterator *iter, const uint32_t key, const uint16_t value);
DictionaryResult dict_write_uint32(DictionaryIterator *iter, const uint32_t key, const uint32_t value);
DictionaryResult dict_write_int8(DictionaryIterator *iter, const uint32_t key, const int8_t value);
DictionaryResult dict_write_int16(DictionaryIterator *iter, const uint32_t key, const int16_t value);
DictionaryResult dict_write_int32(DictionaryIterator *iter, const uint32_t key, const int32_t value);
//! End a series of writing operations to a dictionary.
//! This must be called before reading back from the dictionary.
//! @param iter The dictionary iterator
//! @return The size in bytes of the finalized dictionary, or 0 if the parameters were invalid.
uint32_t dict_write_end(DictionaryIterator *iter);
//! Initializes the dictionary iterator with a given buffer and size,
//! in preparation of reading key/value tuples.
//! @param iter The dictionary iterator
//! @param buffer The storage of the dictionary
//! @param size The storage size of the dictionary
//! @return The first tuple in the dictionary, or NULL in case the dictionary was empty or if there was a parsing error.
Tuple * dict_read_begin_from_buffer(DictionaryIterator *iter, const uint8_t * const buffer, const uint16_t size);
//! Progresses the iterator to the next key/value pair.
//! @param iter The dictionary iterator
//! @return The next tuple in the dictionary, or NULL in case the end has been reached or if there was a parsing error.
Tuple * dict_read_next(DictionaryIterator *iter);
//! Resets the iterator back to the same state as a call to \ref dict_read_begin_from_buffer() would do.
//! @param iter The dictionary iterator
//! @return The first tuple in the dictionary, or NULL in case the dictionary was empty or if there was a parsing error.
Tuple * dict_read_first(DictionaryIterator *iter);
/** Dictionary Utilities */
//! Non-serialized, template data structure for a key/value pair.
//! For strings and byte arrays, it only has a pointer to the actual data.
//! For integers, it provides storage for integers up to 32-bits wide.
//! The Tuplet data structure is useful when creating dictionaries from values
//! that are already stored in arbitrary buffers.
//! See also \ref Tuple, with is the header of a serialized key/value pair.
typedef struct Tuplet {
//! The type of the Tuplet. This determines which of the struct fields in the
//! anonymomous union are valid.
TupleType type;
//! The key.
uint32_t key;
//! Anonymous union containing the reference to the Tuplet's value, being
//! either a byte array, c-string or integer. See documentation of `.bytes`,
//! `.cstring` and `.integer` fields.
union {
//! Valid when `.type.` is \ref TUPLE_BYTE_ARRAY
struct {
//! Pointer to the data
const uint8_t *data;
//! Length of the data
const uint16_t length;
} bytes;
//! Valid when `.type.` is \ref TUPLE_CSTRING
struct {
//! Pointer to the c-string data
const char *data;
//! Length of the c-string, including terminating zero.
const uint16_t length;
} cstring;
//! Valid when `.type.` is \ref TUPLE_INT or \ref TUPLE_UINT
struct {
//! Actual storage of the integer.
//! The signedness can be derived from the `.type` value.
uint32_t storage;
//! Width of the integer.
const uint16_t width;
} integer;
}; //!< See documentation of `.bytes`, `.cstring` and `.integer` fields.
} Tuplet;
//! Macro to create a Tuplet with a byte array value
//! @param _key The key
//! @param _data Pointer to the bytes
//! @param _length Length of the buffer
#define TupletBytes(_key, _data, _length) \
((const Tuplet) { .type = TUPLE_BYTE_ARRAY, .key = _key, .bytes = { .data = _data, .length = _length }})
//! Macro to create a Tuplet with a c-string value
//! @param _key The key
//! @param _cstring The c-string value
#define TupletCString(_key, _cstring) \
((const Tuplet) { .type = TUPLE_CSTRING, .key = _key, .cstring = { .data = _cstring, .length = _cstring ? strlen(_cstring) + 1 : 0 }})
//! Macro to create a Tuplet with an integer value
//! @param _key The key
//! @param _integer The integer value
#define TupletInteger(_key, _integer) \
((const Tuplet) { .type = IS_SIGNED(_integer) ? TUPLE_INT : TUPLE_UINT, .key = _key, .integer = { .storage = _integer, .width = sizeof(_integer) }})
//! Callback for \ref dict_serialize_tuplets() utility.
//! @param data The data of the serialized dictionary
//! @param size The size of data
//! @param context The context pointer as passed in to \ref dict_serialize_tuplets()
//! @see dict_serialize_tuplets
typedef void (*DictionarySerializeCallback)(const uint8_t * const data, const uint16_t size, void *context);
//! Utility function that takes a list of Tuplets from which a dictionary
//! will be serialized, ready to transmit or store.
//! @note The callback will be called before the function returns, so the data that
//! that `context` points to, can be stack allocated.
//! @param callback The callback that will be called with the serialized data of the generated dictionary.
//! @param context Pointer to any application specific data that gets passed into the callback.
//! @param tuplets An array of Tuplets that need to be serialized into the dictionary.
//! @param tuplets_count The number of tuplets that follow.
//! @return \ref DICT_OK, \ref DICT_NOT_ENOUGH_STORAGE or \ref DICT_INVALID_ARGS
DictionaryResult dict_serialize_tuplets(DictionarySerializeCallback callback, void *context, const Tuplet * const tuplets, const uint8_t tuplets_count);
// Legacy version to prevent previous app breakage, __deprecated preserves order
DictionaryResult dict_serialize_tuplets__deprecated(DictionarySerializeCallback callback, void *context, const uint8_t tuplets_count, const Tuplet * const tuplets);
//! Utility function that takes an array of Tuplets and serializes them into
//! a dictionary with a given buffer and size.
//! @param tuplets The array of tuplets
//! @param tuplets_count The number of tuplets in the array
//! @param buffer The buffer in which to write the serialized dictionary
//! @param [in] size_in_out The available buffer size in bytes
//! @param [out] size_in_out The number of bytes written
//! @return \ref DICT_OK, \ref DICT_NOT_ENOUGH_STORAGE or \ref DICT_INVALID_ARGS
DictionaryResult dict_serialize_tuplets_to_buffer(const Tuplet * const tuplets, const uint8_t tuplets_count, uint8_t *buffer, uint32_t *size_in_out);
// Legacy version to prevent previous app breakage, __deprecated preserves order
DictionaryResult dict_serialize_tuplets_to_buffer__deprecated(const uint8_t tuplets_count, const Tuplet * const tuplets, uint8_t *buffer, uint32_t *size_in_out);
//! Serializes an array of Tuplets into a dictionary with a given buffer and size.
//! @param iter The dictionary iterator
//! @param tuplets The array of tuplets
//! @param tuplets_count The number of tuplets in the array
//! @param buffer The buffer in which to write the serialized dictionary
//! @param [in] size_in_out The available buffer size in bytes
//! @param [out] size_in_out The number of bytes written
//! @return \ref DICT_OK, \ref DICT_NOT_ENOUGH_STORAGE or \ref DICT_INVALID_ARGS
DictionaryResult dict_serialize_tuplets_to_buffer_with_iter(DictionaryIterator *iter, const Tuplet * const tuplets, const uint8_t tuplets_count, uint8_t *buffer, uint32_t *size_in_out);
// Legacy version to prevent previous app breakage, __deprecated preserves order
DictionaryResult dict_serialize_tuplets_to_buffer_with_iter__deprecated(const uint8_t tuplets_count, const Tuplet * const tuplets, DictionaryIterator *iter, uint8_t *buffer, uint32_t *size_in_out);
//! Serializes a Tuplet and writes the resulting Tuple into a dictionary.
//! @param iter The dictionary iterator
//! @param tuplet The Tuplet describing the key/value pair to write
//! @return \ref DICT_OK, \ref DICT_NOT_ENOUGH_STORAGE or \ref DICT_INVALID_ARGS
DictionaryResult dict_write_tuplet(DictionaryIterator *iter, const Tuplet * const tuplet);
//! Calculates the number of bytes that a dictionary will occupy, given
//! one or more Tuplets that need to be stored in the dictionary.
//! @note See \ref dict_calc_buffer_size() for the formula for the calculation.
//! @param tuplets An array of Tuplets that need to be stored in the dictionary.
//! @param tuplets_count The total number of Tuplets that follow.
//! @return The total number of bytes of storage needed.
//! @see Tuplet
uint32_t dict_calc_buffer_size_from_tuplets(const Tuplet * const tuplets, const uint8_t tuplets_count);
// Legacy version to prevent previous app breakage, __deprecated preserves order
uint32_t dict_calc_buffer_size_from_tuplets__deprecated(const uint8_t tuplets_count, const Tuplet * const tuplets);
//! Tuple that represents an empty tuple.
//! @see DictionaryKeyUpdatedCallback
extern const Tuple * const NULL_TUPLE;
//! Type of the callback used in \ref dict_merge()
//! @param key The key that is being updated.
//! @param new_tuple The new tuple. The tuple points to the actual, updated destination dictionary or NULL_TUPLE
//! in case there was an error (e.g. backing buffer was too small).
//! Therefore the Tuple can be used after the callback returns, until the destination dictionary
//! storage is free'd (by the application itself).
//! @param old_tuple The values that will be replaced with `new_tuple`. The key, value and type will be
//! equal to the previous tuple in the old destination dictionary, however the `old_tuple points
//! to a stack-allocated copy of the old data.
//! @param context Pointer to application specific data
//! The storage backing `old_tuple` can only be used during the callback and
//! will no longer be valid after the callback returns.
//! @see dict_merge
typedef void (*DictionaryKeyUpdatedCallback)(const uint32_t key, const Tuple *new_tuple, const Tuple *old_tuple, void *context);
//! Merges entries from another "source" dictionary into a "destination" dictionary.
//! All Tuples from the source are written into the destination dictionary, while
//! updating the exsting Tuples with matching keys.
//! @param dest The destination dictionary to update
//! @param [in,out] dest_max_size_in_out In: the maximum size of buffer backing `dest`. Out: the final size of the updated dictionary.
//! @param source The source dictionary of which its Tuples will be used to update dest.
//! @param update_existing_keys_only Specify True if only the existing keys in `dest` should be updated.
//! @param key_callback The callback that will be called for each Tuple in the merged destination dictionary.
//! @param context Pointer to app specific data that will get passed in when `update_key_callback` is called.
//! @return \ref DICT_OK, \ref DICT_INVALID_ARGS, \ref DICT_NOT_ENOUGH_STORAGE
DictionaryResult dict_merge(DictionaryIterator *dest, uint32_t *dest_max_size_in_out,
DictionaryIterator *source,
const bool update_existing_keys_only,
const DictionaryKeyUpdatedCallback key_callback, void *context);
//! Tries to find a Tuple with specified key in a dictionary
//! @param iter Iterator to the dictionary to search in.
//! @param key The key for which to find a Tuple
//! @return Pointer to a found Tuple, or NULL if there was no Tuple with the specified key.
Tuple *dict_find(const DictionaryIterator *iter, const uint32_t key);
//! @} // end addtogroup Dictionary
//! @} // end addtogroup Foundation

View file

@ -0,0 +1,53 @@
/*
* 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 "generic_attribute.h"
#include "system/logging.h"
GenericAttribute *generic_attribute_find_attribute(GenericAttributeList *attr_list, uint8_t id,
size_t size) {
uint8_t *cursor = (uint8_t *)(attr_list->attributes);
uint8_t *end = (uint8_t *)attr_list + size;
for (unsigned int i = 0; i < attr_list->num_attributes; i++) {
GenericAttribute *attribute = (GenericAttribute *)cursor;
// Check that we do not read past the end of the buffer
if ((cursor + sizeof(GenericAttribute) >= end) || (attribute->data + attribute->length > end)) {
PBL_LOG(LOG_LEVEL_WARNING, "Attribute list is invalid");
return NULL;
}
if (attribute->id == id) {
return attribute;
}
cursor += sizeof(GenericAttribute) + attribute->length;
}
return NULL;
}
GenericAttribute *generic_attribute_add_attribute(GenericAttribute *attr, uint8_t id, void *data,
size_t size) {
*attr = (GenericAttribute) {
.id = id,
.length = size,
};
memcpy(attr->data, data, size);
uint8_t *cursor = (uint8_t *)attr;
cursor += sizeof(GenericAttribute) + size;
return (GenericAttribute *)cursor;
}

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
#pragma once
#include "util/attributes.h"
#include <inttypes.h>
#include <stddef.h>
typedef struct PACKED GenericAttribute {
uint8_t id;
uint16_t length;
uint8_t data[];
} GenericAttribute;
typedef struct PACKED GenericAttributeList {
uint8_t num_attributes;
GenericAttribute attributes[];
} GenericAttributeList;
GenericAttribute *generic_attribute_find_attribute(GenericAttributeList *attr_list, uint8_t id,
size_t size);
GenericAttribute *generic_attribute_add_attribute(GenericAttribute *attr, uint8_t id, void *data,
size_t size);

76
src/fw/util/graphics.h Normal file
View file

@ -0,0 +1,76 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
//! This function extracts a value for a specific bit per pixel depth from an image buffer
//! at a specific x y position.
//! @note inlined to support performance requirements of iterating over every pixel in an image
//! @param buffer pointer to a buffer containing pixel image data
//! @param x the x coordinates for the pixel to retrieve
//! @param y the y coordinates for the pixel to retrieve
//! @param width provides the image width
//! @param row_stride_bytes the byte-aligned width in bytes
//! @param bitdepth bits per pixel for the image (1,2,4 or 8 supported)
//! @return The value from the image buffer at the specified coordinates
static ALWAYS_INLINE uint8_t raw_image_get_value_for_bitdepth(const uint8_t *raw_image_buffer,
uint32_t x, uint32_t y, uint16_t row_stride_bytes, uint8_t bitdepth) {
// Retrieve the byte from the image buffer containing the requested pixel
uint32_t pixel_in_byte = raw_image_buffer[y * row_stride_bytes + (x * bitdepth / 8)];
// Find the index of the pixel in terms of coordinates and aligned_width
uint32_t pixel_index = y * (row_stride_bytes * 8 / bitdepth) + x;
// Shift and mask the requested pixel data from the byte containing it and return
return (uint8_t)((pixel_in_byte >> ((((8 / bitdepth) - 1) - (pixel_index % (8 / bitdepth)))
* bitdepth)) & ~(~0 << bitdepth));
}
//! This function sets a pixel value for a specific bits-per-pixel depth in an image buffer
//! at a specific (x, y) coordinate.
//! @note inlined to support performance requirements of iterating over every pixel in an image
//! @param raw_image_buffer Pointer to a buffer containing image pixel data
//! @param x The x coordinate for the pixel to retrieve
//! @param y The y coordinate for the pixel to retrieve
//! @param row_stride_bytes The byte-aligned width of each row in bytes
//! @param bitdepth The bits-per-pixel for the image (Only 1, 2, 4 or 8 bitdepths are supported)
//! @param value The pixel value to set in the image buffer at the specified (x, y) coordinates
static ALWAYS_INLINE void raw_image_set_value_for_bitdepth(uint8_t *raw_image_buffer,
uint32_t x, uint32_t y,
uint16_t row_stride_bytes,
uint8_t bitdepth, uint8_t value) {
const uint8_t pixels_per_byte = (uint8_t)(8 / bitdepth);
// Retrieve the byte from the image buffer containing the requested pixel
const uint32_t byte_offset = y * row_stride_bytes + (x * bitdepth / 8);
const uint8_t pixel_in_byte = raw_image_buffer[byte_offset];
// Find the index of the pixel in terms of coordinates and aligned_width
const uint32_t pixel_index = (y * (row_stride_bytes * pixels_per_byte) + x) % pixels_per_byte;
// For example, bitdepth=1 -> bitdepth_mask=0b1, bitdepth=2 -> bitdepth_mask=0b11, etc.
const uint8_t bitdepth_mask = (uint8_t)~(~0 << bitdepth);
const uint32_t bits_to_shift = (pixels_per_byte - 1 - pixel_index) * bitdepth;
const uint8_t value_position_mask = ~(bitdepth_mask << bits_to_shift);
const uint8_t value_shifted_to_position = (value & bitdepth_mask) << bits_to_shift;
// Finally, update the byte where the pixel is located using the value_mask
const uint8_t new_byte_value = (pixel_in_byte & value_position_mask) | value_shifted_to_position;
raw_image_buffer[byte_offset] = new_byte_value;
}

66
src/fw/util/hdlc.c Normal file
View file

@ -0,0 +1,66 @@
/*
* 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 "hdlc.h"
#include "system/passert.h"
void hdlc_streaming_decode_reset(HdlcStreamingContext *ctx) {
ctx->escape = false;
}
bool hdlc_streaming_decode(HdlcStreamingContext *ctx, uint8_t *data, bool *should_store,
bool *hdlc_error) {
PBL_ASSERTN(data != NULL && should_store != NULL && hdlc_error != NULL);
bool is_complete = false;
*hdlc_error = false;
*should_store = false;
if (*data == HDLC_FLAG) {
if (ctx->escape) {
// extra escape character before flag
ctx->escape = false;
*hdlc_error = true;
}
// we've reached the end of the frame
is_complete = true;
} else if (*data == HDLC_ESCAPE) {
if (ctx->escape) {
// invalid sequence
ctx->escape = false;
*hdlc_error = true;
} else {
// ignore this character and escape the next one
ctx->escape = true;
}
} else {
if (ctx->escape) {
*data ^= HDLC_ESCAPE_MASK;
ctx->escape = false;
}
*should_store = true;
}
return is_complete;
}
bool hdlc_encode(uint8_t *data) {
if (*data == HDLC_FLAG || *data == HDLC_ESCAPE) {
*data ^= HDLC_ESCAPE_MASK;
return true;
}
return false;
}

33
src/fw/util/hdlc.h Normal file
View file

@ -0,0 +1,33 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
static const uint8_t HDLC_FLAG = 0x7E;
static const uint8_t HDLC_ESCAPE = 0x7D;
static const uint8_t HDLC_ESCAPE_MASK = 0x20;
typedef struct {
bool escape;
} HdlcStreamingContext;
void hdlc_streaming_decode_reset(HdlcStreamingContext *ctx);
bool hdlc_streaming_decode(HdlcStreamingContext *ctx, uint8_t *data, bool *complete,
bool *is_invalid);
bool hdlc_encode(uint8_t *data);

52
src/fw/util/ihex.c Normal file
View file

@ -0,0 +1,52 @@
/*
* 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 "util/ihex.h"
typedef struct IHEXRecord {
char start;
char data_count[2];
char address[4];
char type[2];
char data[]; // Variable data and checksum
} IHEXRecord;
static void prv_hexlify(char *str, uint32_t value, uint8_t num_bytes) {
for (int i = num_bytes * 2 - 1; i >= 0; --i) {
uint8_t nibble = (value >> (4 * i)) & 0xF;
if (nibble >= 0xA) {
*str++ = 'A' + (nibble - 0xA);
} else {
*str++ = '0' + nibble;
}
}
}
void ihex_encode(uint8_t *out, uint8_t type, uint16_t address,
const void *data, uint8_t data_len) {
IHEXRecord *record = (IHEXRecord *)out;
uint8_t checksum = data_len + (address >> 8) + (address & 0xff) + type;
record->start = ':';
prv_hexlify(record->data_count, data_len, sizeof(data_len));
prv_hexlify(record->address, address, sizeof(address));
prv_hexlify(record->type, type, sizeof(type));
for (uint16_t i = 0; i < data_len; ++i) {
uint8_t data_byte = ((uint8_t *)data)[i];
checksum += data_byte;
prv_hexlify(&record->data[i*2], data_byte, sizeof(data_byte));
}
prv_hexlify(&record->data[data_len*2], ~checksum + 1, sizeof(checksum));
}

41
src/fw/util/ihex.h Normal file
View file

@ -0,0 +1,41 @@
/*
* 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.
*/
//! Intel HEX utilities
#pragma once
#include <stdint.h>
#define IHEX_TYPE_DATA (0u)
#define IHEX_TYPE_EOF (1u)
#define IHEX_RECORD_LENGTH(len) ((len)*2 + 11)
//! Encode an Intel HEX record with the specified record type, address
//! and data, and write it to out.
//!
//! \param [out] out destination buffer. Must be at least
//! IHEX_RECORD_LENGTH(data_len) bytes long.
//! \param type record type
//! \param address record address
//! \param [in] data data for the record. May be NULL if data_len is 0.
//! \param data_len length of data to include in the record.
void ihex_encode(uint8_t *out, uint8_t type, uint16_t address,
const void *data, uint8_t data_len);

View file

@ -0,0 +1,128 @@
/*
* 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 "util/legacy_checksum.h"
#include <stdint.h>
#include <string.h>
// Software implementation of the legacy checksum. This emulates the behaviour
// of the CRC peripheral in the STM32F2/F4 series MCUs and the bugs in the
// legacy CRC driver implementation. While the raw throughput of the hardware
// CRC peripheral is greater, it is not reentrant and so a mutex is required
// to prevent concurrent access to the peripheral by multiple tasks. The
// overhead of locking and unlocking the mutex, while not yet benchmarked, is
// potentially significant enough to cancel out the increased throughput of
// the hardware acceleration. There is also no evidence (for or against) that
// checksums are a performance bottleneck in the first place. Given the added
// complexity of the hardware-accelerated implementation, and the dubious
// performance improvement of using it, hardware acceleration of checksums is a
// case of premature optimization.
//
// That being said, the API for the legacy checksum is fully capable of
// supporting a non-reentrant implementation. Simply acquire the mutex in
// legacy_defective_checksum_init and release it in
// legacy_defective_checksum_finish.
// The implementation is based on a nybble-wide table driven CRC.
// The CRC implementation (but not the checksum based on it) has the
// model parameters:
// Width: 32
// Poly: 04C11DB7
// Init: FFFFFFFF
// RefIn: False
// RefOut: False
// XorOut: 00000000
//
// The CRC lookup table was generated by legacy_checksum_crc_table.py
static const uint32_t s_lookup_table[] = {
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
};
static uint32_t prv_crc_byte(uint32_t crc, uint8_t input) {
crc = (crc << 4) ^ s_lookup_table[((crc >> 28) ^ (input >> 4)) & 0x0f];
crc = (crc << 4) ^ s_lookup_table[((crc >> 28) ^ (input >> 0)) & 0x0f];
return crc;
}
void legacy_defective_checksum_init(LegacyChecksum *checksum) {
*checksum = (LegacyChecksum) {
.reg = 0xffffffff,
};
}
void legacy_defective_checksum_update(
LegacyChecksum * restrict checksum,
const void * restrict data, size_t length) {
const char * restrict data_bytes = data;
uint32_t * restrict reg = &checksum->reg;
uint8_t * restrict accumulator = checksum->accumulator;
uint8_t * restrict accumulated_length = &checksum->accumulated_length;
if (*accumulated_length) {
for (; *accumulated_length < 3 && length; length--) {
accumulator[(*accumulated_length)++] = *data_bytes++;
}
if (*accumulated_length == 3 && length) {
*reg = prv_crc_byte(*reg, *data_bytes++);
length--;
*reg = prv_crc_byte(*reg, accumulator[2]);
*reg = prv_crc_byte(*reg, accumulator[1]);
*reg = prv_crc_byte(*reg, accumulator[0]);
*accumulated_length = 0;
}
}
for (; length >= 4; length -= 4) {
*reg = prv_crc_byte(*reg, data_bytes[3]);
*reg = prv_crc_byte(*reg, data_bytes[2]);
*reg = prv_crc_byte(*reg, data_bytes[1]);
*reg = prv_crc_byte(*reg, data_bytes[0]);
data_bytes += 4;
}
for (; length; length--) {
accumulator[(*accumulated_length)++] = *data_bytes++;
}
}
uint32_t legacy_defective_checksum_finish(LegacyChecksum *checksum) {
if (checksum->accumulated_length) {
// CRC the final bytes forwards (reversed relative to the normal checksum)
// padded on the left(!) with null bytes.
for (int padding = 4 - checksum->accumulated_length; padding; padding--) {
checksum->reg = prv_crc_byte(checksum->reg, 0);
}
for (int i = 0; i < checksum->accumulated_length; ++i) {
checksum->reg = prv_crc_byte(checksum->reg, checksum->accumulator[i]);
}
}
return checksum->reg;
}
uint32_t legacy_defective_checksum_memory(const void * restrict data,
size_t length) {
LegacyChecksum checksum;
legacy_defective_checksum_init(&checksum);
legacy_defective_checksum_update(&checksum, data, length);
return legacy_defective_checksum_finish(&checksum);
}

View file

@ -0,0 +1,54 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <string.h>
//! \file
//! Calculate the legacy checksum of data.
//!
//! The calculation is somewhat like a CRC with the CRC-32 polynomial, but with
//! the data bytes reordered oddly. The checksum is calculated 32 bits at a
//! time, little-endian, MSB-first. The legacy checksum of bytes A B C D E F G H
//! is equal to the CRC-32 of bytes D C B A H G F E (xor 0xFFFFFFFF). When the
//! data being checksummmed is not a multiple of four bytes in length, the
//! remainder bytes are zero-padded and byte-swapped(!) before being checksummed
//! like the previous full words. For example, the legacy checksum of bytes
//! 1 2 3 4 5 6 is equal to the checksum of bytes 1 2 3 4 6 5 0 0, which is
//! equivalent to the CRC-32 of bytes 4 3 2 1 0 0 5 6 (xor 0xFFFFFFFF).
//!
//! The legacy checksum should not be used except when required for
//! backwards-compatibility purposes.
typedef struct LegacyChecksum {
uint32_t reg;
uint8_t accumulator[3];
uint8_t accumulated_length;
} LegacyChecksum;
void legacy_defective_checksum_init(LegacyChecksum *checksum);
void legacy_defective_checksum_update(
LegacyChecksum * restrict checksum,
const void * restrict data, size_t length);
uint32_t legacy_defective_checksum_finish(LegacyChecksum *checksum);
//! Convenience wrapper to checksum memory in one shot.
uint32_t legacy_defective_checksum_memory(const void * restrict data,
size_t length);

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python
# 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.
from __future__ import print_function
CRC_POLY = 0x04C11DB7
CRC_WIDTH = 32
def precompute_table(bits):
lookup_table = []
for i in xrange(2**bits):
rr = i << (CRC_WIDTH - bits)
for x in xrange(bits):
if rr & 0x80000000:
rr = (rr << 1) ^ CRC_POLY
else:
rr <<= 1
lookup_table.append(rr & 0xffffffff)
return lookup_table
print('static const uint32_t s_lookup_table[] = {')
for entry in precompute_table(4):
print(' 0x{:08x},'.format(entry))
print('};')

110
src/fw/util/lru_cache.c Normal file
View file

@ -0,0 +1,110 @@
/*
* 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 "lru_cache.h"
#include "system/passert.h"
#include <string.h>
void lru_cache_init(LRUCache* c, size_t item_size, uint8_t *buffer, size_t buffer_size) {
*c = (LRUCache) {
.buffer = buffer,
.item_size = item_size,
.max_items = buffer_size / (item_size + sizeof(CacheEntry)),
.least_recent = NULL
};
}
static CacheEntry *entry_for_index(LRUCache *c, int index) {
return ((CacheEntry *)(c->buffer + index * (sizeof(CacheEntry) + c->item_size)));
}
void lru_cache_flush(LRUCache *c) {
c->least_recent = NULL;
}
void *lru_cache_get(LRUCache* c, uint32_t key) {
// cur_ptr is a pointer-to-pointer to the more_recent
// field in the parent of the current entry
CacheEntry **cur_ptr = &c->least_recent;
CacheEntry *found = NULL;
for (int i = 0; i < c->max_items; ++i) {
CacheEntry *cur_entry = *cur_ptr;
if (cur_entry == NULL) {
break;
}
if (cur_entry->key == key) {
*cur_ptr = cur_entry->more_recent;
found = cur_entry;
}
if (*cur_ptr == NULL) {
break;
}
cur_ptr = &cur_entry->more_recent;
}
// cur_ptr should point to the last pointer in the list
PBL_ASSERTN(*cur_ptr == NULL);
if (found) {
found->more_recent = NULL;
*cur_ptr = found;
return (found->data);
} else {
return (NULL);
}
}
void lru_cache_put(LRUCache* c, uint32_t key, void* item) {
// cur_ptr is a pointer-to-pointer to the more_recent
// field in the parent of the current entry
CacheEntry **cur_ptr = &c->least_recent;
CacheEntry *new = NULL;
for (int i = 0; i < c->max_items; ++i) {
CacheEntry *cur_entry = *cur_ptr;
if (cur_entry == NULL) {
// cache is not full
new = entry_for_index(c, i);
break;
} else if (cur_entry->key == key) {
// key already in cache, update
*cur_ptr = cur_entry->more_recent;
new = cur_entry;
}
if (*cur_ptr == NULL) {
break;
}
cur_ptr = &cur_entry->more_recent;
}
// cur_ptr should point to the last pointer in the list
PBL_ASSERTN(*cur_ptr == NULL);
if (new == NULL) {
// cache full, evict LRU
new = c->least_recent;
c->least_recent = new->more_recent;
}
new->more_recent = NULL;
new->key = key;
memcpy(new->data, item, c->item_size);
*cur_ptr = new;
}

68
src/fw/util/lru_cache.h Normal file
View file

@ -0,0 +1,68 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
//! This is a pretty simple & lean LRU cache
//! It works with a pre-allocated buffer in which it stores a singly linked list
//! of the items in LRU order (head of the list is the LRU item)
//! put and get are both O[N]
//! Note: we could save 2 bytes per entry by using array indices rather than pointer
//! but this would complicate the code quite a bit
typedef struct CacheEntry {
struct CacheEntry *more_recent; ///< The next entry in the linked list
uint32_t key; ///< The key that identifies this entry
uint8_t data[]; ///< the data associated with this entry
} CacheEntry;
typedef struct {
uint8_t *buffer; ///< A pointer to the buffer allocated for storing cache data
size_t item_size; ///< The size in bytes of items in the cache
int max_items; ///< The max number of items that can fit in the cache
CacheEntry *least_recent; ///< The head of the singly linked list of cache entries
} LRUCache;
//! Initialize an LRU cache
//! @param c a reference to a cache struct
//! @param item_size the size in bytes of items to be stored in the cache
//! @param buffer a buffer to store cache items into
//! @param buffer_size the size of the buffer
//! @note each entry is 8 bytes larger than item_size, allocate accordingly!
void lru_cache_init(LRUCache* c, size_t item_size, uint8_t *buffer, size_t buffer_size);
//! Retrieve an item from the cache and mark the item as most recently used
//! @param c the cache to retrieve from
//! @param key the key for the item we want retrieved
//! @return a pointer to the item, or NULL if not found
void *lru_cache_get(LRUCache* c, uint32_t key);
//! Add an item to the cache.
//! @note This will evict the least recently used item if the cache is full
//! @note If the key already exist, the old item is overridden
//! @param c the cache to retrieve data from
//! @param key the key to associate to the item
//! @param item a pointer to the item data
void lru_cache_put(LRUCache* c, uint32_t key, void* item);
//! Flush the cache. This removes all data from the cache.
//! @param c the cache to flush
void lru_cache_flush(LRUCache *c);

20
src/fw/util/macro.h Normal file
View file

@ -0,0 +1,20 @@
/*
* 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.
*/
#pragma once
#define CONCAT_IMPL(x, y) x##y
#define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y)

197
src/fw/util/mbuf.c Normal file
View file

@ -0,0 +1,197 @@
/*
* 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 "mbuf.h"
#include "kernel/pbl_malloc.h"
#include "system/logging.h"
#include "os/mutex.h"
#include "system/passert.h"
#include "util/size.h"
//! Flags used for internal purposes (bits 24-31 are allocated for this purpose)
#define MBUF_FLAG_IS_MANAGED ((uint32_t)(1 << 24))
#define MBUF_FLAG_IS_FREE ((uint32_t)(1 << 25))
T_STATIC MBuf *s_free_list;
static PebbleMutex *s_free_list_lock;
//! This array should be initialized with the maximum number of MBufs which may be allocated for
//! each pool.
static int s_mbuf_pool_space[] = {
[MBufPoolSmartstrap] = 2,
#if UNITTEST
[MBufPoolUnitTest] = 100,
#endif
};
_Static_assert(ARRAY_LENGTH(s_mbuf_pool_space) == NumMBufPools,
"s_mbuf_pool_space array does not match MBufPool enum");
// Init
////////////////////////////////////////////////////////////////////////////////
void mbuf_init(void) {
s_free_list_lock = mutex_create();
}
// Allocation / free list management
////////////////////////////////////////////////////////////////////////////////
//! Bug-catcher checks that nobody has corrupted the free list or modified MBufs within it
//! NOTE: the caller must hold s_free_list_lock
static void prv_check_free_list(void) {
mutex_assert_held_by_curr_task(s_free_list_lock, true);
MBuf *m = s_free_list;
while (m) {
PBL_ASSERTN(mbuf_is_flag_set(m, MBUF_FLAG_IS_MANAGED));
PBL_ASSERTN(mbuf_is_flag_set(m, MBUF_FLAG_IS_FREE));
PBL_ASSERTN(!mbuf_get_data(s_free_list));
PBL_ASSERTN(mbuf_get_length(m) == 0);
m = mbuf_get_next(m);
}
}
MBuf *mbuf_get(void *data, uint32_t length, MBufPool pool) {
PBL_ASSERTN(pool < NumMBufPools);
MBuf *m;
mutex_lock(s_free_list_lock);
// get an MBuf out of the free list if possible, or else allocate a new one
if (s_free_list) {
prv_check_free_list();
// remove the head of the free list to be returned
m = s_free_list;
s_free_list = mbuf_get_next(s_free_list);
mbuf_clear_next(m);
} else {
// check that there is space left in this pool
PBL_ASSERTN(s_mbuf_pool_space[pool] > 0);
s_mbuf_pool_space[pool]--;
// allocate and initialize a new MBuf for the pool
m = kernel_zalloc_check(sizeof(MBuf));
mbuf_set_flag(m, MBUF_FLAG_IS_MANAGED, true);
}
mutex_unlock(s_free_list_lock);
mbuf_set_flag(m, MBUF_FLAG_IS_FREE, false);
mbuf_set_data(m, data, length);
return m;
}
void mbuf_free(MBuf *m) {
if (!m) {
return;
}
PBL_ASSERTN(mbuf_is_flag_set(m, MBUF_FLAG_IS_MANAGED));
PBL_ASSERTN(!mbuf_is_flag_set(m, MBUF_FLAG_IS_FREE)); // double free
// clear the MBuf
*m = MBUF_EMPTY;
mbuf_set_flag(m, MBUF_FLAG_IS_MANAGED, true);
mbuf_set_flag(m, MBUF_FLAG_IS_FREE, true);
// add it to the free list
mutex_lock(s_free_list_lock);
if (s_free_list) {
mbuf_append(s_free_list, m);
} else {
s_free_list = m;
}
prv_check_free_list();
mutex_unlock(s_free_list_lock);
}
// Basic setters and getters
////////////////////////////////////////////////////////////////////////////////
void mbuf_set_data(MBuf *m, void *data, uint32_t length) {
PBL_ASSERTN(m);
//! We should never be trying to set the data on an mbuf in the free list
PBL_ASSERTN(!mbuf_is_flag_set(m, MBUF_FLAG_IS_FREE));
m->data = data;
m->length = length;
}
void *mbuf_get_data(MBuf *m) {
PBL_ASSERTN(m);
return m->data;
}
bool mbuf_is_flag_set(MBuf *m, uint32_t flag) {
PBL_ASSERTN(m);
return m->flags & flag;
}
void mbuf_set_flag(MBuf *m, uint32_t flag, bool is_set) {
PBL_ASSERTN(m);
if (is_set) {
m->flags |= flag;
} else {
m->flags &= ~flag;
}
}
MBuf *mbuf_get_next(MBuf *m) {
PBL_ASSERTN(m);
return m->next;
}
uint32_t mbuf_get_length(MBuf *m) {
PBL_ASSERTN(m);
return m->length;
}
uint32_t mbuf_get_chain_length(MBuf *m) {
uint32_t total = 0;
while (m) {
total += m->length;
m = m->next;
}
return total;
}
// MBuf chain management
////////////////////////////////////////////////////////////////////////////////
void mbuf_append(MBuf *m, MBuf *new_mbuf) {
PBL_ASSERTN(m);
// advance to the tail
while (m->next) {
m = m->next;
}
m->next = new_mbuf;
}
void mbuf_clear_next(MBuf *m) {
PBL_ASSERTN(m);
m->next = NULL;
}
// Debug
////////////////////////////////////////////////////////////////////////////////
void mbuf_debug_dump(MBuf *m) {
char buffer[80];
while (m) {
dbgserial_putstr_fmt(buffer, sizeof(buffer), "MBuf <%p>: length=%"PRIu32", data=%p, "
"flags=0x%"PRIx32, m, m->length, m->data, m->flags);
m = m->next;
}
}

108
src/fw/util/mbuf.h Normal file
View file

@ -0,0 +1,108 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/*
* Generally, an mbuf is a header for a buffer which adds some useful functionality with regards to
* grouping multiple distinct buffers together into a single packet. They are primarily used for
* networking. As you go down a traditional network stack, headers need to be added to the data.
* Rather than having to allocate and copy every time a new header needs to be added, or forcing the
* upper layer to leave room for the header, mbufs allows for buffers to be chained together into an
* mbuf chain. With mbufs, as you go down the stack, you simply add the headers as new mbufs at the
* start of the chain. Then, the lowest layer can simply walk through the chain to get the content
* of the entire packet, and no copying is necessary at any point in the process. Going up the stack
* works the same way, except that MBufs are removed as you go up the stack instead of added.
*
* This is a very basic implementation of the mbuf type found in FreeBSD. If you're interested in
* learning more about real mbufs, the FreeBSD man page is a good read:
* https://www.freebsd.org/cgi/man.cgi?query=mbuf&sektion=9
*
* For the purposes of this implementation, MBuf headers are of a fixed size with a single pointer
* to the data which the header is responsible for. Linking multiple MBuf chains together is not
* supported.
*/
//! Helper macro for clearing an MBuf
#define MBUF_EMPTY ((MBuf){ 0 })
//! Flags used by consumers of MBufs (bits 0-23 are allocated for this purpose)
#define MBUF_FLAG_IS_FRAMING ((uint32_t)(1 << 0))
//! Consumers of MBufs which use mbuf_get() should add an enum value and add the maximum number of
//! MBufs which may be allocated for that pool to the MBUF_POOL_MAX_ALLOCATED array within mbuf.c.
typedef enum {
MBufPoolSmartstrap,
#if UNITTEST
MBufPoolUnitTest,
#endif
NumMBufPools
} MBufPool;
typedef struct MBuf {
//! The next MBuf in the chain
struct MBuf *next;
//! A pointer to the data itself
void *data;
//! The length of the data
uint32_t length;
//! Flags which are used by the consumers of MBufs
uint32_t flags;
} MBuf;
//! Initializes the MBuf code (called from main.c)
void mbuf_init(void);
//! Returns a new heap-allocated MBuf (either from an internal pool or by allocating a new one)
MBuf *mbuf_get(void *data, uint32_t length, MBufPool pool);
//! Frees an MBuf which was created via mbuf_get()
void mbuf_free(MBuf *m);
//! Sets the data and length fields of an MBuf
void mbuf_set_data(MBuf *m, void *data, uint32_t length);
//! Returns the data for the MBuf
void *mbuf_get_data(MBuf *m);
//! Returns whether or not the specified flag is set
bool mbuf_is_flag_set(MBuf *m, uint32_t flag);
//! Sets the specified flag to the specified value
void mbuf_set_flag(MBuf *m, uint32_t flag, bool is_set);
//! Gets the next MBuf in the chain
MBuf *mbuf_get_next(MBuf *m);
//! Gets the length of the specified MBuf (NOT the entire chain)
uint32_t mbuf_get_length(MBuf *m);
//! Gets the total number of bytes in the MBuf chain
uint32_t mbuf_get_chain_length(MBuf *m);
//! Appends a new MBuf chain to the end of the chain
void mbuf_append(MBuf *m, MBuf *new_mbuf);
//! Removes any MBufs in the chain after the specified one
void mbuf_clear_next(MBuf *m);
//! Dump an MBuf chain to dbgserial
void mbuf_debug_dump(MBuf *m);

View file

@ -0,0 +1,70 @@
/*
* 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 "mbuf_iterator.h"
bool prv_iter_to_valid_mbuf(MBufIterator *iter) {
// advance the iterator to the next MBuf with data
while ((iter->m != NULL) && (mbuf_get_length(iter->m) == 0)) {
iter->m = mbuf_get_next(iter->m);
iter->data_index = 0;
}
return iter->m != NULL;
}
void mbuf_iterator_init(MBufIterator *iter, MBuf *m) {
iter->m = m;
iter->data_index = 0;
prv_iter_to_valid_mbuf(iter);
}
bool mbuf_iterator_is_finished(MBufIterator *iter) {
if (!prv_iter_to_valid_mbuf(iter)) {
return true;
}
if (iter->data_index >= mbuf_get_length(iter->m)) {
// we're at the end of this MBuf so move to the next one
iter->m = mbuf_get_next(iter->m);
iter->data_index = 0;
// make sure this MBuf has data
if (!prv_iter_to_valid_mbuf(iter)) {
return true;
}
}
return false;
}
bool mbuf_iterator_read_byte(MBufIterator *iter, uint8_t *data) {
if (mbuf_iterator_is_finished(iter)) {
return false;
}
uint8_t *buffer = mbuf_get_data(iter->m);
*data = buffer[iter->data_index++];
return true;
}
bool mbuf_iterator_write_byte(MBufIterator *iter, uint8_t data) {
if (mbuf_iterator_is_finished(iter)) {
return false;
}
uint8_t *buffer = mbuf_get_data(iter->m);
buffer[iter->data_index++] = data;
return true;
}
MBuf *mbuf_iterator_get_current_mbuf(MBufIterator *iter) {
return iter->m;
}

View file

@ -0,0 +1,45 @@
/*
* 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.
*/
#pragma once
#include "util/mbuf.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
//! NOTE: MBufIterator APIs are not thread safe
typedef struct {
MBuf *m;
uint32_t data_index;
} MBufIterator;
//! Initializes an MBufIterator
void mbuf_iterator_init(MBufIterator *iter, MBuf *m);
//! Check if there is no data left in the MBuf chain
bool mbuf_iterator_is_finished(MBufIterator *iter);
//! Reads the next byte of data in the MBuf chain
bool mbuf_iterator_read_byte(MBufIterator *iter, uint8_t *data);
//! Writes the next byte of data in the MBuf chain
bool mbuf_iterator_write_byte(MBufIterator *iter, uint8_t data);
//! Gets the MBuf which the next byte of data is in
MBuf *mbuf_iterator_get_current_mbuf(MBufIterator *iter);

80
src/fw/util/net.h Normal file
View file

@ -0,0 +1,80 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
// When compiling test, the host OS might have conflicting defines for this:
#undef ntohs
#undef htons
#undef ntohl
#undef htonl
#undef ltohs
#undef ltohl
static inline uint16_t ntohs(uint16_t v) {
// return ((v & 0x00ff) << 8) | ((v & 0xff00) >> 8);
return __builtin_bswap16(v);
}
static inline uint16_t htons(uint16_t v) {
return ntohs(v);
}
static inline uint32_t ntohl(uint32_t v) {
// return ((v & 0x000000ff) << 24) |
// ((v & 0x0000ff00) << 8) |
// ((v & 0x00ff0000) >> 8) |
// ((v & 0xff000000) >> 24);
return __builtin_bswap32(v);
}
static inline uint32_t htonl(uint32_t v) {
return ntohl(v);
}
#define ltohs(v) (v)
#define ltohl(v) (v)
// Types for values in network byte-order. They are wrapped in structs so that
// the compiler will disallow implicit casting of these types to or from
// integral types. This way it is a compile error to try using variables of
// these types without first performing a byte-order conversion.
// There is no overhead for wrapping the values in structs.
typedef struct net16 {
uint16_t v;
} net16;
typedef struct net32 {
uint32_t v;
} net32;
static inline uint16_t ntoh16(net16 net) {
return ntohs(net.v);
}
static inline net16 hton16(uint16_t v) {
return (net16){ htons(v) };
}
static inline uint32_t ntoh32(net32 net) {
return ntohl(net.v);
}
static inline net32 hton32(uint32_t v) {
return (net32){ htonl(v) };
}

24
src/fw/util/pack.h Normal file
View file

@ -0,0 +1,24 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
// Convert 4 character strings to values for value based comparison
#define MAKE_BYTE(b) (uint8_t)((b) & 0xFF)
#define MAKE_WORD(a, b, c, d) \
(uint32_t)((MAKE_BYTE(a) << 24) | (MAKE_BYTE(b) << 16) | (MAKE_BYTE(c) << 8) | MAKE_BYTE(d))

43
src/fw/util/packbits.c Normal file
View file

@ -0,0 +1,43 @@
/*
* 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 "packbits.h"
#include <string.h>
void packbits_unpack(const char* src, int src_length, uint8_t* dest) {
int length = 0;
while (length < src_length) {
int8_t header = *((int8_t*) src++);
length++;
if (header >= 0) {
int count = header + 1;
memcpy(dest, src, count);
dest += count;
src += count;
length += count;
} else {
int count = 1 - header;
memset(dest, *src, count);
dest += count;
src += 1;
length += 1;
}
}
}

21
src/fw/util/packbits.h Normal file
View file

@ -0,0 +1,21 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
void packbits_unpack(const char* src, int src_length, uint8_t* dest);

195
src/fw/util/pstring.c Normal file
View file

@ -0,0 +1,195 @@
/*
* 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 <string.h>
#include "system/logging.h"
#include "kernel/pbl_malloc.h"
#include "util/pstring.h"
PascalString16 *pstring_create_pstring16(uint16_t size) {
PascalString16* pstring = task_malloc_check(sizeof(uint16_t) + sizeof(char) * size);
pstring->str_length = 0;
return pstring;
}
PascalString16 *pstring_create_pstring16_from_string(char string[]) {
uint16_t length = strlen(string);
PascalString16* pstring;
if (length == 0) {
// Empty string
pstring = task_malloc_check(sizeof(uint16_t) + sizeof(char) * 1);
pstring->str_length = length;
pstring->str_value[0] = '\0';
} else {
pstring = task_malloc_check(sizeof(uint16_t) + sizeof(char) * length);
pstring->str_length = length;
strncpy(pstring->str_value, string, length);
}
return pstring;
}
void pstring_destroy_pstring16(PascalString16 *pstring) {
task_free(pstring);
}
void pstring_pstring16_to_string(const PascalString16 *pstring, char *string_out) {
strncpy(string_out, pstring->str_value, pstring->str_length);
string_out[(pstring->str_length)] = '\0';
}
void pstring_string_to_pstring16(char string[], PascalString16 *pstring_out) {
uint16_t length = strlen(string);
if (length == 0) {
// Empty string
pstring_out->str_length = length;
pstring_out->str_value[0] = '\0';
} else {
pstring_out->str_length = length;
strncpy(pstring_out->str_value, string, length);
}
}
bool pstring_equal(const PascalString16 *ps1, const PascalString16 *ps2) {
return ps1 && ps2 && (ps1->str_length == ps2->str_length) &&
(memcmp(ps1->str_value, ps2->str_value, ps1->str_length) == 0);
}
bool pstring_equal_cstring(const PascalString16 *pstr, const char *cstr) {
return pstr && cstr && (pstr->str_length == strlen(cstr)) &&
(memcmp(pstr->str_value, cstr, pstr->str_length) == 0);
}
//-------
SerializedArray *pstring_create_serialized_array(uint16_t data_size) {
size_t num = data_size + sizeof(uint16_t);
size_t size = sizeof(uint8_t);
SerializedArray *serialized_array = task_calloc_check(num, size);
serialized_array->data_size = data_size;
return serialized_array;
}
void pstring_destroy_serialized_array(SerializedArray* serialized_array) {
task_free(serialized_array);
}
// Assumes a list of 0s is an empty list, not a list full of empty pstrings
uint16_t pstring_get_number_of_pstring16s_in_list(PascalString16List *pstring16_list) {
size_t size = sizeof(uint16_t);
uint16_t count = 0;
uint16_t empty_count = 0;
// Traverse list
uint8_t *data_ptr = (pstring16_list->pstrings)->data;
uint16_t pstring_length;
do {
pstring_length = (uint16_t) *data_ptr;
if (pstring_length == 0) {
empty_count++;
} else {
count += empty_count + 1;
empty_count = 0;
}
data_ptr += pstring_length + size;
} while ((&((pstring16_list->pstrings)->data[(pstring16_list->pstrings)->data_size]) - data_ptr) >
1); // Need at least 2 bytes for another pstring
if (count != 0) {
count += empty_count;
}
return count;
}
void pstring_project_list_on_serialized_array(PascalString16List *pstring16_list,
SerializedArray *serialized_array) {
pstring16_list->pstrings = serialized_array;
pstring16_list->count = pstring_get_number_of_pstring16s_in_list(pstring16_list);
}
bool pstring_add_pstring16_to_list(PascalString16List *pstring16_list, PascalString16* pstring) {
size_t size = sizeof(uint16_t);
// Traverse list
uint8_t *data_ptr = (pstring16_list->pstrings)->data;
uint8_t idx = 0;
uint16_t pstring_length;
do {
if (idx == pstring16_list->count) {
// End of list, copy contents of pstring
memcpy(data_ptr, &(pstring->str_length), size);
data_ptr += size;
memcpy(data_ptr, pstring->str_value, pstring->str_length);
(pstring16_list->count)++;
return true;
}
// Advance pointer and index
pstring_length = (uint16_t) *data_ptr;
data_ptr += pstring_length + size;
idx++;
} while ((&((pstring16_list->pstrings)->data[(pstring16_list->pstrings)->data_size]) - data_ptr) >
1); // Need at least 2 bytes for another pstring
return false;
}
PascalString16 *pstring_get_pstring16_from_list(PascalString16List *pstring16_list,
uint16_t index) {
size_t size = sizeof(uint16_t);
PascalString16 *pstring = NULL;
uint16_t idx = 0;
if (index >= pstring16_list->count) {
return NULL;
}
// Traverse list
uint8_t *data_ptr = (pstring16_list->pstrings)->data;
uint16_t pstring_length;
do {
if (idx == index) {
// Found the requested pstring
pstring = (PascalString16*) data_ptr;
break;
}
pstring_length = (uint16_t) *data_ptr;
data_ptr += pstring_length + size;
idx++;
} while ((&((pstring16_list->pstrings)->data[(pstring16_list->pstrings)->data_size]) - data_ptr) >
1); // Need at least 2 bytes for another pstring
return pstring;
}
void pstring_print_pstring(PascalString16 *pstring) {
PBL_LOG(LOG_LEVEL_DEBUG, "Length: %i ", pstring->str_length);
char *buffer = task_malloc_check(sizeof(char) * (pstring->str_length + 1));
pstring_pstring16_to_string(pstring, buffer);
PBL_LOG(LOG_LEVEL_DEBUG, "%s", buffer);
task_free(buffer);
buffer = NULL;
}
void pstring_print_pstring16list(PascalString16List *list) {
PBL_LOG(LOG_LEVEL_DEBUG, "Data size: %i ", (list->pstrings)->data_size);
PascalString16 *pstring;
for (int i = 0; i < list->count; i++) {
pstring = pstring_get_pstring16_from_list(list, i);
pstring_print_pstring(pstring);
}
}

88
src/fw/util/pstring.h Normal file
View file

@ -0,0 +1,88 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
// PascalStrings with string length of 0 are considered empty.
typedef struct {
uint16_t str_length;
char str_value[];
} PascalString16;
typedef struct {
uint16_t data_size;
uint8_t data[];
} SerializedArray;
// Used to encapsulate multiple PascalStrings.
// Empty PascalStrings only have their length serialized (no byte for value).
typedef struct {
uint16_t count;
SerializedArray *pstrings;
} PascalString16List;
// Create a PascalString16 with the passed max pstring size.
PascalString16 *pstring_create_pstring16(uint16_t size);
// Create a PascalString16 from the passed string.
PascalString16 *pstring_create_pstring16_from_string(char string[]);
void pstring_destroy_pstring16(PascalString16 *pstring);
//! @param string_out Array of chars to hold the converted pstring.
//! Must be at least size (pstring->length + 1).
void pstring_pstring16_to_string(const PascalString16 *pstring, char *string_out);
//! @param pstring_out Array of chars to hold the converted string.
//! Must be at least size (string + 1).
void pstring_string_to_pstring16(char string[], PascalString16 *pstring_out);
//! Checks if 2 pstrings are euqual and returns true if so.
//! @note returns false if either / both pstrings are NULL
bool pstring_equal(const PascalString16 *ps1, const PascalString16 *ps2);
//! Compares a pstring to a cstring and returns true if they match
//! @note returns false if either / both params are NULL
bool pstring_equal_cstring(const PascalString16 *pstr, const char *cstr);
//------
SerializedArray *pstring_create_serialized_array(uint16_t data_size);
void pstring_destroy_serialized_array(SerializedArray* serialized_array);
// Projects a list on a serialized array so that pstring operations may be performed on it.
void pstring_project_list_on_serialized_array(PascalString16List *pstring16_list,
SerializedArray *serialized_array);
// Adds a PascalString16 to the end of the list.
// Returns true if the PascalString16 was successfully added, false if there was no room.
bool pstring_add_pstring16_to_list(PascalString16List *pstring16_list, PascalString16* pstring);
// Retrieves the number of PascalString16s in the list.
uint16_t pstring_get_number_of_pstring16s_in_list(PascalString16List *pstring16_list);
// Returns a pointer to a PascalString16 of the passed index within the list.
// If the given index is not valid or the list is empty, returns NULL.
PascalString16* pstring_get_pstring16_from_list(PascalString16List *pstring16_list, uint16_t index);
void pstring_print_pstring(PascalString16 *pstring);
void pstring_print_pstring16list(PascalString16List *list);

28
src/fw/util/rand.h Normal file
View file

@ -0,0 +1,28 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <stdlib.h>
// Implemented in fw/util/rand/rand.c
//! rand() without the 31-bit truncation
uint32_t rand32(void);
static inline int bounded_rand_int(int M, int N) {
return M + rand() / (RAND_MAX / (N - M + 1) + 1);
}

100
src/fw/util/rand/rand.c Normal file
View file

@ -0,0 +1,100 @@
/*
* 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.
*/
///////////////////////////////////////
// Implements:
// int rand(void);
// int rand_r(unsigned int *seedp);
// void srand(unsigned int seed);
///////////////////////////////////////
// Exports to apps:
// rand, srand
///////////////////////////////////////
// Notes:
// RNG implementation is using TinyMT
// Apps and Workers have unique RNG seed variables, all kernel tasks share an RNG seed.
// All other libc's seem to just segfault if seedp is NULL to rand_r; we assert instead.
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "kernel/pebble_tasks.h"
#include "system/passert.h"
#include "vendor/tinymt32/tinymt32.h"
extern uint32_t *app_state_get_rand_ptr(void);
extern uint32_t *worker_state_get_rand_ptr(void);
// Kernel random seed
static tinymt32_t s_kernel_rand = {{0}};
static tinymt32_t *prv_get_seed_ptr(void) {
switch (pebble_task_get_current()) {
case PebbleTask_App:
return (tinymt32_t*)app_state_get_rand_ptr();
case PebbleTask_Worker:
return (tinymt32_t*)worker_state_get_rand_ptr();
default:
return &s_kernel_rand;
}
}
static void prv_seed(tinymt32_t *state, uint32_t seed) {
// Generated from ID 2841590142
// characteristic=9a1431e60e5e03b118c9173c2f60761f
// type=32
// id=2841590142
// mat1=d728239b
// mat2=57e7ffaf
// tmat=ebb03f7f
// weight=59
// delta=0
state->mat1 = 0xd728239b;
state->mat2 = 0x57e7ffaf;
state->tmat = 0xebb03f7f;
tinymt32_init(state, seed);
}
static int prv_next(tinymt32_t *state) {
if (state->mat1 == 0) { // Not initialized yet
prv_seed(state, 0x9a1431e6); // Just any ol' number
}
return tinymt32_generate_uint32(state);
}
uint32_t rand32(void) {
return prv_next(prv_get_seed_ptr());
}
int rand(void) {
return rand32() & 0x7FFFFFFF;
}
int rand_r(unsigned int *seedp) { // Please don't use this
PBL_ASSERTN(seedp != NULL);
tinymt32_t state = {{0}};
prv_seed(&state, *seedp);
*seedp = prv_next(&state) & 0x7FFFFFFF;
return *seedp;
}
void srand(unsigned int seed) {
prv_seed(prv_get_seed_ptr(), seed);
}

42
src/fw/util/ratio.h Normal file
View file

@ -0,0 +1,42 @@
/*
* 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.
*/
#pragma once
#include <inttypes.h>
// ratio32 - uint32 as a real number between 0 and 1
#define RATIO32_MIN 0
#define RATIO32_MAX 65535
static inline uint32_t ratio32_mul(uint32_t a, uint32_t b) {
return (a * b) / RATIO32_MAX;
}
static inline uint32_t ratio32_div(uint32_t a, uint32_t b) {
return (a * RATIO32_MAX) / b;
}
//! Converts a ratio32 to a percent in the range [0, 100]
static inline uint32_t ratio32_to_percent(uint32_t ratio) {
return (ratio * 100) / RATIO32_MAX;
}
//! Converts a percent in the range [0, 100] to a ratio32
static inline uint32_t ratio32_from_percent(uint32_t percent) {
return (percent * RATIO32_MAX) / 100 + 1;
}

61
src/fw/util/reverse.h Normal file
View file

@ -0,0 +1,61 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
inline static uint32_t bswap32(uint32_t v) {
return __builtin_bswap32(v);
}
inline static uint16_t bswap16(uint16_t v) {
return __builtin_bswap16(v);
}
#ifdef __arm__
inline static char reverse_byte(uint8_t input) {
uint8_t result;
__asm__ ("rev %[result], %[input]\n\t"
"rbit %[result], %[result]"
: [result] "=r" (result)
: [input] "r" (input));
return result;
}
#else
static unsigned char BitReverseTable256[] = {
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
};
inline static char reverse_byte(uint8_t byte) {
return BitReverseTable256[byte];
}
#endif

View file

@ -0,0 +1,293 @@
/*
* 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 "shared_circular_buffer.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/math.h"
#include <string.h>
// -------------------------------------------------------------------------------------------------
// Returns the amount of data available for the given clien
static uint32_t prv_get_data_length(const SharedCircularBuffer* buffer, SharedCircularBufferClient *client) {
uint32_t len;
// The end_index is the index of the next byte to go into the buffer
// The read_index is the index of the first byte
// An empty buffer is when end_index == read_index
if (buffer->write_index >= client->read_index) {
len = buffer->write_index - client->read_index;
} else {
len = buffer->buffer_size - client->read_index;
len += buffer->write_index;
}
return len;
}
// -------------------------------------------------------------------------------------------------
// Returns max amount of data available among all clients
// On exit, *max_client will contain the client with the most amount of data available
static uint32_t prv_get_max_data_length(const SharedCircularBuffer* buffer, SharedCircularBufferClient **max_client) {
ListNode *iter = buffer->clients;
if (!iter) {
*max_client = NULL;
return 0;
}
uint32_t max_data = 0;
uint32_t len;
while (iter) {
len = prv_get_data_length(buffer, (SharedCircularBufferClient *)iter);
if (len >= max_data) {
*max_client = (SharedCircularBufferClient *)iter;
max_data = len;
}
iter = iter->next;
}
return max_data;
}
// -------------------------------------------------------------------------------------------------
void shared_circular_buffer_init(SharedCircularBuffer* buffer, uint8_t* storage, uint16_t storage_size) {
buffer->buffer = storage;
buffer->buffer_size = storage_size;
buffer->clients = NULL;
buffer->write_index = 0;
}
// -------------------------------------------------------------------------------------------------
bool shared_circular_buffer_add_client(SharedCircularBuffer* buffer, SharedCircularBufferClient *client) {
PBL_ASSERTN(!list_contains(buffer->clients, &client->list_node));
buffer->clients = list_prepend(buffer->clients, &client->list_node);
client->read_index = buffer->write_index;
return true;
}
// -------------------------------------------------------------------------------------------------
void shared_circular_buffer_remove_client(SharedCircularBuffer* buffer, SharedCircularBufferClient *client) {
PBL_ASSERTN(list_contains(buffer->clients, &client->list_node));
list_remove(&client->list_node, &buffer->clients, NULL);
}
// -------------------------------------------------------------------------------------------------
bool shared_circular_buffer_write(SharedCircularBuffer* buffer, const uint8_t* data, uint16_t length,
bool advance_slackers) {
// If no clients, no need to write
if (!buffer->clients) {
PBL_LOG(LOG_LEVEL_WARNING, "no readers");
return false;
}
// If this is bigger than the queue would allow, error
if (length >= buffer->buffer_size) {
return false;
}
// Make sure there's room, deleting bytes from slackers if requested
SharedCircularBufferClient *slacker;
uint32_t max_data = prv_get_max_data_length(buffer, &slacker);
uint32_t avail_space = buffer->buffer_size - 1 - max_data;
while (length > avail_space) {
if (!advance_slackers) {
return false;
}
// Delete data from the biggest slacker
slacker->read_index = buffer->write_index;
max_data = prv_get_max_data_length(buffer, &slacker);
avail_space = buffer->buffer_size - max_data;
}
const uint16_t remaining_length = buffer->buffer_size - buffer->write_index;
if (remaining_length < length) {
// Need to write the message in two chunks around the end of the buffer. Write the first chunk
memcpy(&buffer->buffer[buffer->write_index], data, remaining_length);
buffer->write_index = 0;
data += remaining_length;
length -= remaining_length;
}
// Write the last chunk
memcpy(&buffer->buffer[buffer->write_index], data, length);
buffer->write_index = (buffer->write_index + length) % buffer->buffer_size;
return true;
}
// ---------------------------------------------------------------------------------------------
bool shared_circular_buffer_read(const SharedCircularBuffer* buffer,
SharedCircularBufferClient *client, uint16_t length,
const uint8_t** data_out, uint16_t* length_out) {
PBL_ASSERTN(list_contains(buffer->clients, &client->list_node));
uint32_t data_length = prv_get_data_length(buffer, client);
if (data_length < length) {
return false;
}
*data_out = &buffer->buffer[client->read_index];
const uint16_t bytes_read = buffer->buffer_size - client->read_index;
*length_out = MIN(bytes_read, length);
return true;
}
// -------------------------------------------------------------------------------------------------
bool shared_circular_buffer_consume(SharedCircularBuffer* buffer, SharedCircularBufferClient *client, uint16_t length) {
PBL_ASSERTN(list_contains(buffer->clients, &client->list_node));
uint32_t data_length = prv_get_data_length(buffer, client);
if (data_length < length) {
return false;
}
client->read_index = (client->read_index + length) % buffer->buffer_size;
return true;
}
// -------------------------------------------------------------------------------------------------
uint16_t shared_circular_buffer_get_write_space_remaining(const SharedCircularBuffer* buffer) {
SharedCircularBufferClient *slacker;
uint32_t max_data = prv_get_max_data_length(buffer, &slacker);
return buffer->buffer_size - 1 - max_data;
}
// -------------------------------------------------------------------------------------------------
uint16_t shared_circular_buffer_get_read_space_remaining(const SharedCircularBuffer* buffer,
SharedCircularBufferClient *client) {
PBL_ASSERTN(list_contains(buffer->clients, &client->list_node));
return prv_get_data_length(buffer, client);
}
// -------------------------------------------------------------------------------------------------
bool shared_circular_buffer_read_consume(SharedCircularBuffer *buffer, SharedCircularBufferClient *client,
uint16_t length, uint8_t *data, uint16_t *length_out) {
uint16_t data_length = prv_get_data_length(buffer, client);
uint16_t bytes_left = MIN(length, data_length);
*length_out = bytes_left;
while (bytes_left) {
uint16_t chunk;
const uint8_t *read_ptr;
shared_circular_buffer_read(buffer, client, bytes_left, &read_ptr, &chunk);
memcpy(data, read_ptr, chunk);
shared_circular_buffer_consume(buffer, client, chunk);
data += chunk;
bytes_left -= chunk;
}
return (*length_out == length);
}
// -------------------------------------------------------------------------------------------------
void shared_circular_buffer_add_subsampled_client(
SharedCircularBuffer *buffer, SubsampledSharedCircularBufferClient *client,
uint16_t subsample_numerator, uint16_t subsample_denominator) {
PBL_ASSERTN(shared_circular_buffer_add_client(buffer,
&client->buffer_client));
subsampled_shared_circular_buffer_client_set_ratio(
client, subsample_numerator, subsample_denominator);
}
// -------------------------------------------------------------------------------------------------
void shared_circular_buffer_remove_subsampled_client(
SharedCircularBuffer *buffer,
SubsampledSharedCircularBufferClient *client) {
shared_circular_buffer_remove_client(buffer, &client->buffer_client);
}
// -------------------------------------------------------------------------------------------------
void subsampled_shared_circular_buffer_client_set_ratio(
SubsampledSharedCircularBufferClient *client,
uint16_t numerator, uint16_t denominator) {
PBL_ASSERTN(numerator > 0 && denominator >= numerator);
if (client->numerator != numerator || client->denominator != denominator) {
// The subsampling algorithm does not need the subsampling ratio to
// be normalied to reduced form.
client->numerator = numerator;
client->denominator = denominator;
// Initialize the state so that the next item in the buffer is copied,
// not discarded.
client->subsample_state = denominator - numerator;
}
}
// -------------------------------------------------------------------------------------------------
size_t shared_circular_buffer_read_subsampled(
SharedCircularBuffer* buffer,
SubsampledSharedCircularBufferClient *client,
size_t item_size, void *data, uint16_t num_items) {
uint16_t bytes_available = prv_get_data_length(
buffer, &client->buffer_client);
// Optimized case when no subsampling
if (client->numerator == client->denominator) {
num_items = MIN(num_items, bytes_available / item_size);
uint16_t bytes_out;
shared_circular_buffer_read_consume(
buffer, &client->buffer_client, num_items * item_size,
(uint8_t *)data, &bytes_out);
PBL_ASSERTN(bytes_out == num_items * item_size);
return num_items;
}
// An interesting property of the subsampling algorithm used is that
// the subsampling ratio does not need to be in reduced form. It will
// give the exact same results if the numerator and denominator have a
// common divisor.
char *out_buf = data;
size_t items_read = 0;
while (items_read < num_items && bytes_available >= item_size) {
bytes_available -= item_size;
client->subsample_state += client->numerator;
if (client->subsample_state >= client->denominator) {
client->subsample_state %= client->denominator;
uint16_t bytes_out;
shared_circular_buffer_read_consume(buffer, &client->buffer_client,
item_size, (uint8_t *)out_buf,
&bytes_out);
PBL_ASSERTN(bytes_out == item_size);
out_buf += item_size;
items_read++;
} else {
PBL_ASSERTN(shared_circular_buffer_consume(buffer, &client->buffer_client,
item_size));
}
}
return items_read;
}

View file

@ -0,0 +1,183 @@
/*
* 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.
*/
#pragma once
#include "util/list.h"
#include "util/attributes.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
//! This is a CircularBuffer that supports one writer but multiple read clients. Data added to the buffer is kept
//! available for reading until every client has read it. Each client has their own read index that gets updated as
//! they consume data. If desired, the read index for clients that "fall behind" can be force advanced to make room
//! for new write data.
typedef struct PACKED SharedCircularBufferClient {
ListNode list_node;
uint16_t read_index; // Index of next available byte in the buffer for this client
} SharedCircularBufferClient;
typedef struct SharedCircularBuffer {
uint8_t *buffer;
uint16_t buffer_size;
uint16_t write_index; //! where next byte will be written, (read_index == write_index) is an empty queue
ListNode *clients; //! linked list of clients
} SharedCircularBuffer;
//! Init the buffer
//! @param buffer The buffer to initialize
//! @param storage storage for the data
//! @param storage_size Size of the storage buffer
void shared_circular_buffer_init(SharedCircularBuffer* buffer, uint8_t* storage, uint16_t storage_size);
//! Add data to the buffer
//! @param buffer The buffer to write to
//! @param data The data to write
//! @param length Number of bytes to write
//! @param advance_slackers If true, automatically advance the read index, starting from the client farthest behind,
//! until there is room for the new data. If the length is bigger than the entire buffer, then false is returned.
//! @return false if there's insufficient space.
bool shared_circular_buffer_write(SharedCircularBuffer* buffer, const uint8_t* data, uint16_t length,
bool advance_slackers);
//! Add a read client
//! @param buffer The buffer to add the client to
//! @param client Pointer to a client structure. This structure must be allocated by the caller and can not
//! be freed until the client is removed
//! @return true if successfully added
bool shared_circular_buffer_add_client(SharedCircularBuffer* buffer, SharedCircularBufferClient *client);
//! Remove a read client
//! @param buffer The buffer to remove the client from
//! @param client Pointer to a client structure. This must be the same pointer passed to circular_buffer_add_client
void shared_circular_buffer_remove_client(SharedCircularBuffer* buffer, SharedCircularBufferClient *client);
//! Read a contiguous chunk of memory from the circular buffer. The data remains on the buffer until
//! circular_buffer_consume is called.
//!
//! If the circular buffer wraps in the middle of the requested data, this function call will return true but will
//! provide fewer bytes that requested. When this happens, the length_out parameter will be set to a value smaller
//! than length. A second read call can be made with the remaining smaller length to retreive the rest.
//!
//! The reason this read doesn't consume is to avoid having to copy out the data. The data_out pointer should be
//! stable until you explicitely ask for it to be consumed with circular_buffer_consume.
//!
//! @param buffer The buffer to read from
//! @param client pointer to the client struct originally passed to circular_buffer_add_client
//! @param length How many bytes to read
//! @param[out] data_out The bytes that were read.
//! @param[out] length_out How many bytes were read.
//! @return false if there's less than length bytes in the buffer.
bool shared_circular_buffer_read(const SharedCircularBuffer* buffer, SharedCircularBufferClient *client,
uint16_t length, const uint8_t** data_out, uint16_t* length_out);
//! Removes length bytes of the oldest data from the buffer.
//! @param buffer The buffer to operate on
//! @param client Pointer to a client structure originally passed to circular_buffer_add_client
//! @param length The number of bytes to consume
//! @return True if success
bool shared_circular_buffer_consume(SharedCircularBuffer* buffer, SharedCircularBufferClient *client, uint16_t length);
//! @param buffer The buffer to operate on
//! @return The number of bytes we can write before circular_buffer_write will return false.
uint16_t shared_circular_buffer_get_write_space_remaining(const SharedCircularBuffer* buffer);
//! @param buffer The buffer to operate on
//! @param client Pointer to a client structure originally passed to circular_buffer_add_client
//! @return The number of bytes we can read before circular_buffer_read will return false.
uint16_t shared_circular_buffer_get_read_space_remaining(const SharedCircularBuffer* buffer,
SharedCircularBufferClient *client);
//! Read and consume bytes.
//!
//! @param buffer The buffer to read from
//! @param client pointer to the client struct originally passed to circular_buffer_add_client
//! @param length How many bytes to read
//! @param data Buffer to copy the data into
//! @param[out] length_out How many bytes were read into the caller's buffer
//! @return true if we returned length bytes, false if we returned less.
bool shared_circular_buffer_read_consume(SharedCircularBuffer *buffer, SharedCircularBufferClient *client,
uint16_t length, uint8_t *data, uint16_t *length_out);
typedef struct SubsampledSharedCircularBufferClient {
SharedCircularBufferClient buffer_client;
uint16_t numerator;
uint16_t denominator;
//! Used to track whether to copy or discard each successive data item
uint16_t subsample_state;
} SubsampledSharedCircularBufferClient;
//! Add a read client which subsamples the data.
//!
//! @param buffer The buffer to add the client to
//! @param client Pointer to a client structure. This structure must be
//! allocated by the caller and can not be freed until the client is
//! removed.
//! @param subsample_numerator The numerator of the client's initial subsampling
//! ratio.
//! @param subsample_denominator The denominator of the client's initial
//! subsampling ratio. This value must be equal to or greater than the
//! subsampling numerator.
//! @sa subsampled_shared_circular_buffer_client_set_ratio
void shared_circular_buffer_add_subsampled_client(
SharedCircularBuffer *buffer, SubsampledSharedCircularBufferClient *client,
uint16_t subsample_numerator, uint16_t subsample_denominator);
//! Remove a subsampling read client
//! @param buffer The buffer to remove the client from
//! @param client Pointer to a client structure. This must be the same pointer
//! passed to shared_circular_buffer_add_subsampled_client
void shared_circular_buffer_remove_subsampled_client(
SharedCircularBuffer *buffer, SubsampledSharedCircularBufferClient *client);
//! Change the subsampling ratio of a subsampling shared circular buffer client.
//!
//! This resets the subsampling state, which may introduce jitter on the next
//! read operation.
//!
//! @param client The client to apply the new ratio to
//! @param numerator The numerator of the subsampling ratio.
//! @param denominator The denominator of the subsampling ratio. This value must
//! be equal to or greater than the numerator.
//!
//! @note Specifying a subsampling ratio with a numerator greater than 1 will
//! introduce jitter to the subsampled data stream.
void subsampled_shared_circular_buffer_client_set_ratio(
SubsampledSharedCircularBufferClient *client,
uint16_t numerator, uint16_t denominator);
//! Read and consume items with subsampling.
//!
//! @param buffer The buffer to read from
//! @param client pointer to the client struct originally passed to
//! shared_circular_buffer_add_subsampled_client
//! @param item_size Size of each item, in bytes
//! @param data Buffer to copy the data into. Must be at least
//! item_size * num_items bytes in size.
//! @param num_items How many items to read. This is the number of items AFTER
//! subsampling.
//! @return The number of items actually read into the data buffer after
//! subsampling. This may be less than num_items.
size_t shared_circular_buffer_read_subsampled(
SharedCircularBuffer* buffer,
SubsampledSharedCircularBufferClient *client,
size_t item_size, void *data, uint16_t num_items);

64
src/fw/util/sle.c Normal file
View file

@ -0,0 +1,64 @@
/*
* 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 "sle.h"
#include "system/passert.h"
// See waftools/sparse_length_encoding.py for more info on SLE encoding/decoding
void sle_decode_init(SLEDecodeContext *ctx, const void *sle_buffer) {
PBL_ASSERTN(ctx && sle_buffer);
const uint8_t *buffer = (const uint8_t *)sle_buffer;
ctx->escape = *(buffer++);
ctx->sle_buffer = buffer;
ctx->zeros_remaining = 0;
}
bool sle_decode(SLEDecodeContext *ctx, uint8_t *out) {
if (!ctx->sle_buffer) {
return false;
}
if (ctx->zeros_remaining) {
*out = 0;
--ctx->zeros_remaining;
return true;
}
uint8_t byte = *(ctx->sle_buffer++);
if (byte != ctx->escape) {
*out = byte;
} else {
byte = *(ctx->sle_buffer++);
if (byte == 0x00) {
// end of stream
ctx->sle_buffer = NULL;
return false;
} else if (byte == 0x01) {
// literal escape byte
*out = ctx->escape;
} else {
// a sequence of zeros
if ((byte & 0x80) == 0) {
// the count is 1 byte (1-127)
ctx->zeros_remaining = byte - 1;
} else {
ctx->zeros_remaining = (((byte & 0x7f) << 8) | *(ctx->sle_buffer++)) + 0x80 - 1;
}
*out = 0;
}
}
return true;
}

32
src/fw/util/sle.h Normal file
View file

@ -0,0 +1,32 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef struct {
const uint8_t *sle_buffer;
uint16_t zeros_remaining;
uint8_t escape;
} SLEDecodeContext;
//! Initialize the decode context to decode the given buffer.
void sle_decode_init(SLEDecodeContext *ctx, const void *sle_buffer);
//! Decode the next byte in the incoming buffer.
bool sle_decode(SLEDecodeContext *ctx, uint8_t *out);

248
src/fw/util/stats.c Normal file
View file

@ -0,0 +1,248 @@
/*
* 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 "stats.h"
#include "kernel/pbl_malloc.h"
#include <util/math.h>
#include <util/sort.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdbool.h>
// ------------------------------------------------------------------------------------------------
// Returns the median of a given array
// If given an even number of elements, it will return the lower of the two values
// Torben median algorithm from http://ndevilla.free.fr/median/median/index.html
static int32_t prv_calculate_median(const int32_t *data, uint32_t num_data, int32_t min,
int32_t max, uint32_t num_values, StatsBasicFilter filter,
void *context) {
if ((num_data == 0) || (num_values == 0)) {
return 0;
}
uint32_t less;
uint32_t greater;
uint32_t equal;
int32_t guess;
int32_t max_lt_guess;
int32_t min_gt_guess;
while (1) {
guess = (min + max) / 2;
less = 0;
greater = 0;
equal = 0;
max_lt_guess = min;
min_gt_guess = max;
for (uint32_t i = 0; i < num_data; ++i) {
const int32_t value = data[i];
if (filter && !filter(i, value, context)) {
continue;
}
if (value < guess) {
less++;
if (value > max_lt_guess) {
max_lt_guess = value;
}
} else if (value > guess) {
greater++;
if (value < min_gt_guess) {
min_gt_guess = value;
}
} else {
equal++;
}
}
if (less <= ((num_values + 1) / 2) && greater <= ((num_values + 1) / 2)) {
break;
} else if (less > greater) {
max = max_lt_guess;
} else {
min = min_gt_guess;
}
}
if (less >= ((num_values + 1) / 2)) {
return max_lt_guess;
} else if ((less + equal) >= ((num_values + 1) / 2)) {
return guess;
} else {
return min_gt_guess;
}
}
// ------------------------------------------------------------------------------------------------
void stats_calculate_basic(StatsBasicOp op, const int32_t *data, size_t num_data,
StatsBasicFilter filter, void *context, int32_t *basic_out) {
if (!data) {
return;
}
int32_t num_values = 0;
int32_t sum = 0;
int32_t min = INT32_MAX;
int32_t max = INT32_MIN;
int32_t consecutive_max = 0;
int32_t consecutive_current = 0;
int32_t consecutive_first = 0;
bool calc_consecutive_first = (op & StatsBasicOp_ConsecutiveFirst);
for (size_t i = 0; i < num_data; i++) {
const int32_t value = data[i];
if (filter && !filter(i, value, context)) {
if (op & StatsBasicOp_Consecutive) {
if (consecutive_current > consecutive_max) {
consecutive_max = consecutive_current;
}
consecutive_current = 0;
}
calc_consecutive_first = false;
continue;
}
if (op & (StatsBasicOp_Sum | StatsBasicOp_Average)) {
sum += value;
}
if ((op & (StatsBasicOp_Min | StatsBasicOp_Median)) && (value < min)) {
min = value;
}
if ((op & (StatsBasicOp_Max | StatsBasicOp_Median)) && (value > max)) {
max = value;
}
if (op & StatsBasicOp_Consecutive) {
consecutive_current++;
}
if (calc_consecutive_first) {
consecutive_first++;
}
num_values++;
}
int out_index = 0;
if (op & StatsBasicOp_Sum) {
basic_out[out_index++] = sum;
}
if (op & StatsBasicOp_Average) {
basic_out[out_index++] = num_values ? sum / num_values : 0;
}
if (op & StatsBasicOp_Min) {
basic_out[out_index++] = min;
}
if (op & StatsBasicOp_Max) {
basic_out[out_index++] = max;
}
if (op & StatsBasicOp_Count) {
basic_out[out_index++] = num_values;
}
if (op & StatsBasicOp_Consecutive) {
basic_out[out_index++] = MAX(consecutive_max, consecutive_current);
}
if (op & StatsBasicOp_ConsecutiveFirst) {
basic_out[out_index++] = consecutive_first;
}
if (op & StatsBasicOp_Median) {
basic_out[out_index++] = prv_calculate_median(data, num_data, min, max, num_values, filter,
context);
}
}
typedef struct WeightedValue {
int32_t value;
int32_t weight_x100;
} WeightedValue;
static int prv_cmp_weighted_value(const void *a, const void *b) {
int32_t t_a = ((WeightedValue *)a)->value;
int32_t t_b = ((WeightedValue *)b)->value;
if (t_a < t_b) {
return -1;
} else if (t_a > t_b) {
return 1;
} else {
return 0;
}
}
//! Source:
//! http://artax.karlin.mff.cuni.cz/r-help/library/matrixStats/html/weightedMedian.html
//! Description: (copied here in case website goes down)
//! For the n elements x = c(x[1], x[2], ..., x[n]) with positive weights
//! w = c(w[1], w[2], ..., w[n]) such that sum(w) = S, the weighted median is defined as the
//! element x[k] for which the total weight of all elements x[i] < x[k] is less or equal to S/2
//! and for which the total weight of all elements x[i] > x[k] is less or equal to S/2 (c.f. [1]).
//! Ties:
//! How to solve ties between two x's that are satisfying the weighted median criteria.
//! Note that at most two values can satisfy the criteria. If a tie occurs, the mean
//! (not weighted mean) of the two values is returned.
//! NOTES:
//! Integer division is used throughout. Take note. That is why this is here.
int32_t stats_calculate_weighted_median(const int32_t *vals, const int32_t *weights_x100,
size_t num_data) {
if (!vals || !weights_x100 || num_data < 1) {
// Invalid args
return 0;
}
WeightedValue *values = task_zalloc(sizeof(int32_t) * num_data * 2);
if (!values) {
return 0;
}
// Copy the values and sort them in ascending order
for (size_t i = 0; i < num_data; i++) {
values[i] = (WeightedValue) {
.value = vals[i],
.weight_x100 = weights_x100[i],
};
}
sort_bubble(values, num_data, sizeof(*values), prv_cmp_weighted_value);
// Find the sum of all of the weights
int32_t S_x100;
stats_calculate_basic(StatsBasicOp_Sum, weights_x100, num_data, NULL, NULL, &S_x100);
if (S_x100 == 0) {
// All weights are zero
return 0;
}
int32_t tmp_sum = S_x100;
int k = 0;
int32_t rv = 0;
for (;;) {
tmp_sum -= values[k].weight_x100;
// Have to modulo since we need to know if this is *exact*. Integer division will not let us
// know if this is exact if it is an odd number.
if ((S_x100 % 2 == 0) && tmp_sum == (S_x100 / 2)) {
// This is a `tie`
rv = (values[k].value + values[k + 1].value) / 2;
break;
} else if (tmp_sum <= (S_x100 / 2)) {
// If we are not *exactly* equal (implied by the if above), but less than or equal,
// stop and record this value.
rv = values[k].value;
break;
}
k++;
}
task_free(values);
return rv;
}

71
src/fw/util/stats.h Normal file
View file

@ -0,0 +1,71 @@
/*
* 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.
*/
#pragma once
#include <stdlib.h>
#include <inttypes.h>
#include <stdbool.h>
//! Filter the basic statistical calculation.
//! @param index Index of the value in the data array being calculated
//! @param value Value of the current candidate data point being considered
//! @param context User data which can be used for additional context
//! @return true if the value should be included in the statistics, false otherwise
typedef bool (*StatsBasicFilter)(int index, int32_t value, void *context);
//! StatsBasicOp is a bitfield that specifies which operations \ref stats_calculate_basic should
//! perform. The ops will operate only on the filtered values when a filter is present.
typedef enum {
StatsBasicOp_Sum = (1 << 0), //!< Calculate the sum
StatsBasicOp_Average = (1 << 1), //!< Calculate the average
//! Find the minimum value. If there is no data, or if no values match the filter, the minimum
//! will default to INT32_MAX.
StatsBasicOp_Min = (1 << 2),
//! Find the maximum value. If there is no data, or if no values match the filter, the maximum
//! will default to INT32_MIN.
StatsBasicOp_Max = (1 << 3),
//! Count the number of filtered values included in calculation.
//! Equivalent to the number of data points when no filter is applied.
StatsBasicOp_Count = (1 << 4),
//! Find the maximum streak of consecutive filtered values included in calculation.
//! Equivalent to the number of data points when no filter is applied.
StatsBasicOp_Consecutive = (1 << 5),
//! Find the first streak of consecutive filtered values included in calculation.
//! Equivalent to the number of data points when no filter is applied.
StatsBasicOp_ConsecutiveFirst = (1 << 6),
//! Find the median of filtered values included in calculation.
StatsBasicOp_Median = (1 << 7),
} StatsBasicOp;
//! Calculate basic statistical information on a given array of int32_t values.
//! When returning the results, the values will be written sequentially as defined in the enum to
//! basic_out without gaps. For example, if given the op `(StatsBasicOp_Max | StatsBasicOp_Sum)`,
//! basic_out[0] will contain the sum and basic_out[1] will contain the max since Sum is specified
//! before Max in the StatsBasicOp enum. No gaps are present for Average or Min since those ops were
//! not specified for calculation.
//! @param op StatsBasicOp describing the operations to calculate
//! @param data int32_t pointer to an array of data. If data is NULL, there will be no output
//! @param num_data size_t number of data points in the data array
//! @param filter Optional StatsBasicFilter to filter data against, NULL if none specified
//! @param context Optional StatsBasicFilter context, NULL if non specified
//! @param[out] basic_out address to an int32_t or int32_t array to write results to
void stats_calculate_basic(StatsBasicOp op, const int32_t *data, size_t num_data,
StatsBasicFilter filter, void *context, int32_t *basic_out);
int32_t stats_calculate_weighted_median(const int32_t *vals, const int32_t *weights_x100,
size_t num_data);

77
src/fw/util/stringlist.c Normal file
View file

@ -0,0 +1,77 @@
/*
* 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 "stringlist.h"
#include "util/math.h"
#include "util/string.h"
#include <stdbool.h>
#include <string.h>
size_t string_list_count(StringList *list) {
if (!list || list->serialized_byte_length == 0) {
return 0;
}
size_t result = 1;
for (int i = 0; i < list->serialized_byte_length; i++) {
if (list->data[i] == '\0') {
result++;
}
}
return result;
}
char *string_list_get_at(StringList *list, size_t index) {
if (!list) {
return NULL;
}
char *ptr = list->data;
const char *max_ptr = ptr + list->serialized_byte_length;
while (index > 0 && ptr < max_ptr) {
ptr += strlen(ptr) + 1;
index--;
}
if (index > 0) {
return NULL;
} else {
return ptr;
}
}
int string_list_add_string(StringList *list, size_t max_list_size, const char *buffer,
size_t max_str_size) {
if (!list) {
return 0;
}
const size_t str_length = strnlen(buffer, max_str_size);
const int size_remaining =
max_list_size - (sizeof(StringList) + list->serialized_byte_length + 1);
if (size_remaining <= 0) {
return 0;
}
const bool has_last_terminator = (list->serialized_byte_length > 0);
char *cursor = &list->data[list->serialized_byte_length + (has_last_terminator ? 1 : 0)];
const size_t bytes_written = MIN(str_length, (size_t)size_remaining - 1);
strncpy(cursor, buffer, bytes_written);
cursor[bytes_written] = '\0';
list->serialized_byte_length += (has_last_terminator ? 1 : 0) + bytes_written;
return bytes_written;
}

56
src/fw/util/stringlist.h Normal file
View file

@ -0,0 +1,56 @@
/*
* 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.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <string.h>
//! A string list is a serialized array of NULL terminated strings
//! It is used to pass groups of strings as a single attribute on the wire.
//! E.g. canned responses for notifications
//! @note the serialized_byte_length does not include the last terminated byte
//! Calculate the maximum string list size given the number of values and their max length
#define StringListSize(num_values, max_value_size) \
(sizeof(StringList) + ((num_values) * (max_value_size)))
typedef struct {
uint16_t serialized_byte_length;
char data[];
} StringList;
//! Retrieve a string from a string list
//! @param list a pointer to the string list
//! @param index of the desired string
//! @note string lists are zero indexed
//! @return a pointer to the start of the string, NULL if index out of bounds
char *string_list_get_at(StringList *list, size_t index);
//! Count the number of strings in a string list
//! @param list a pointer to the string list
//! @return the number of strings in a list
size_t string_list_count(StringList *list);
//! Adds a string to a string list
//! @param list a pointer to the string list
//! @param max_list_size the max size of the list (includes the header and last terminated byte)
//! @param str the string to add
//! @param max_str_size the string to add
//! @return the number of bytes written not including the null terminator
int string_list_add_string(StringList *list, size_t max_list_size, const char *str,
size_t max_str_size);

25
src/fw/util/swap.h Normal file
View file

@ -0,0 +1,25 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
static inline void swap16(int16_t *a, int16_t *b) {
int16_t t = *a;
*a = *b;
*b = t;
}

17
src/fw/util/test_base64.sh Executable file
View file

@ -0,0 +1,17 @@
#!/bin/sh
# 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.
gcc test_base64.c base64.c -I../ -g -std=c99 -DUNIT_TEST && ./a.out

328
src/fw/util/time/time.c Normal file
View file

@ -0,0 +1,328 @@
/*
* 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 "drivers/rtc.h"
#include "syscall/syscall_internal.h"
#include "time.h"
#include "util/math.h"
#include <string.h>
#include "FreeRTOS.h"
#include "portmacro.h"
// timezone abbreviation
static char s_timezone_abbr[TZ_LEN] = { 0 }; // longest timezone abbreviation is 5 char + null
static int32_t s_timezone_gmtoffset = 0;
static int32_t s_dst_adjust = SECONDS_PER_HOUR;
static time_t s_dst_start = 0;
static time_t s_dst_end = 0;
static const uint8_t s_mon_lengths[2][MONTHS_PER_YEAR] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
static const uint16_t s_year_lengths[2] = {
365,
366
};
int32_t time_get_gmtoffset(void) {
return s_timezone_gmtoffset;
}
bool time_get_isdst(time_t utc_time) {
// do we have any DST set for the timezone we are in
if ((s_dst_start == 0) || (s_dst_end == 0)) {
return false;
}
return ((s_dst_start <= utc_time) && (utc_time < s_dst_end));
}
int time_will_transition_dst(time_t prev, time_t next) {
if (time_get_isdst(prev) == time_get_isdst(next)) {
return 0;
} else if (time_get_isdst(prev)) {
return time_get_dstoffset();
} else {
return -time_get_dstoffset();
}
}
int32_t time_get_dstoffset(void) {
return s_dst_adjust;
}
time_t time_get_dst_start(void) {
return s_dst_start;
}
time_t time_get_dst_end(void) {
return s_dst_end;
}
DEFINE_SYSCALL(time_t, sys_time_utc_to_local, time_t t) {
return time_utc_to_local(t);
}
time_t time_utc_to_local(time_t utc_time) {
utc_time += time_get_isdst(utc_time) ? s_dst_adjust : 0;
utc_time += s_timezone_gmtoffset;
return utc_time;
}
time_t time_local_to_utc(time_t local_time) {
// Note that there is 1 hour a year where it is impossible to undo the DST offset based solely
// on local time. For example, if the clock goes backward by 1 hour at 2am, then all times
// between 1am and 2am will appear twice, and there is no way to tell which of the two
// intervals we are being passed.
local_time -= s_timezone_gmtoffset;
local_time -= time_get_isdst(local_time - s_dst_adjust) ? s_dst_adjust : 0;
return local_time;
}
void time_get_timezone_abbr(char *out_buf, time_t utc_time) {
if (!out_buf) {
return;
}
strncpy(out_buf, s_timezone_abbr, TZ_LEN);
out_buf[TZ_LEN - 1] = 0;
// Timezones with daylight savings, update modifier with current dst char
// ie. P*T is PDT for daylight savings, PST for non-daylight savings
char *tz_zone_dst_char = memchr(out_buf, '*', TZ_LEN);
if (tz_zone_dst_char) {
*tz_zone_dst_char = (time_get_isdst(utc_time)) ? 'D' : 'S';
// Workaround for UK Winter, Greenwich Mean Time; UK Summer, British Summer Time
if (!strncmp(out_buf, "BDT", TZ_LEN)) {
strncpy(out_buf, "BST", TZ_LEN);
} else if (!strncmp(out_buf, "BST", TZ_LEN)) {
strncpy(out_buf, "GMT", TZ_LEN);
}
}
}
// converts time_t to struct tm for localtime and gmtime
struct tm *time_to_tm(const time_t * tim_p, struct tm *res, bool utc_mode) {
time_t local_time;
time_t utc_time = *tim_p;
if (utc_mode) {
res->tm_gmtoff = 0;
res->tm_isdst = 0;
strncpy(res->tm_zone, "UTC", TZ_LEN);
res->tm_zone[TZ_LEN - 1] = '\0';
local_time = utc_time;
} else {
res->tm_gmtoff = time_get_gmtoffset();
res->tm_isdst = time_get_isdst(utc_time);
time_get_timezone_abbr(res->tm_zone, utc_time);
local_time = utc_time + res->tm_gmtoff + (res->tm_isdst ? s_dst_adjust : 0);
}
int32_t days = local_time / SECONDS_PER_DAY;
int32_t rem = local_time % SECONDS_PER_DAY;
while (rem < 0) {
rem += SECONDS_PER_DAY;
--days;
}
while (rem >= SECONDS_PER_DAY) {
rem -= SECONDS_PER_DAY;
++days;
}
/* compute hour, min, and sec */
res->tm_hour = (int) (rem / SECONDS_PER_HOUR);
rem %= SECONDS_PER_HOUR;
res->tm_min = (int) (rem / SECONDS_PER_MINUTE);
res->tm_sec = (int) (rem % SECONDS_PER_MINUTE);
/* compute day of week */
if ((res->tm_wday = ((EPOCH_WDAY + days) % DAYS_PER_WEEK)) < 0) {
res->tm_wday += DAYS_PER_WEEK;
}
/* compute year & day of year */
int y = EPOCH_YEAR;
int yleap;
if (days >= 0) {
for (;;) {
yleap = YEAR_IS_LEAP(y);
if (days < s_year_lengths[yleap]) {
break;
}
y++;
days -= s_year_lengths[yleap];
}
} else {
do {
--y;
yleap = YEAR_IS_LEAP(y);
days += s_year_lengths[yleap];
} while (days < 0);
}
res->tm_year = y - TM_YEAR_ORIGIN;
res->tm_yday = days;
const uint8_t *ip = s_mon_lengths[yleap];
for (res->tm_mon = 0; days >= ip[res->tm_mon]; ++res->tm_mon) {
days -= ip[res->tm_mon];
}
res->tm_mday = days + 1;
return res;
}
struct tm *gmtime_r(const time_t *timep, struct tm *result) {
return time_to_tm(timep, result, true);
}
struct tm *localtime_r(const time_t *timep, struct tm *result) {
return time_to_tm(timep, result, false);
}
void time_util_split_seconds_into_parts(uint32_t seconds,
uint32_t *day_part, uint32_t *hour_part, uint32_t *minute_part, uint32_t *second_part) {
*day_part = seconds / SECONDS_PER_DAY;
seconds -= *day_part * SECONDS_PER_DAY;
*hour_part = seconds / SECONDS_PER_HOUR;
seconds -= *hour_part * SECONDS_PER_HOUR;
*minute_part = seconds / SECONDS_PER_MINUTE;
seconds -= *minute_part * SECONDS_PER_MINUTE;
*second_part = seconds;
}
int time_util_get_num_hours(int hours, bool is24h) {
return is24h ? hours : (hours + 12 - 1) % 12 + 1;
}
int time_util_get_seconds_until_daily_time(struct tm *time, int hour, int minute) {
int hour_diff = hour - time->tm_hour;
if (hour < time->tm_hour || (hour == time->tm_hour && minute <= time->tm_min)) {
// It's past the mark, skip to tomorrow.
hour_diff += HOURS_PER_DAY;
}
int minutes_diff = (hour_diff * MINUTES_PER_HOUR) + (minute - time->tm_min);
return (minutes_diff * SECONDS_PER_MINUTE) - (time->tm_sec);
}
void time_util_update_timezone(const TimezoneInfo *tz_info) {
strncpy(s_timezone_abbr, tz_info->tm_zone, sizeof(tz_info->tm_zone));
s_timezone_abbr[TZ_LEN - 1] = '\0';
s_timezone_gmtoffset = tz_info->tm_gmtoff;
s_dst_start = tz_info->dst_start;
s_dst_end = tz_info->dst_end;
// Lord Howe Island has a half-hour DST
if (tz_info->dst_id == DSTID_LORDHOWE) {
s_dst_adjust = SECONDS_PER_HOUR / 2;
} else {
s_dst_adjust = SECONDS_PER_HOUR;
}
}
time_t time_util_get_midnight_of(time_t ts) {
struct tm tm;
localtime_r(&ts, &tm);
tm.tm_hour = 0;
tm.tm_min = 0;
tm.tm_sec = 0;
return mktime(&tm);
}
bool time_util_range_spans_day(time_t start, time_t end, time_t start_of_day) {
return (start <= start_of_day && end >= (start_of_day + SECONDS_PER_DAY));
}
time_t time_utc_to_local_using_offset(time_t utc_time, int16_t utc_offset_min) {
if (utc_offset_min < 0) {
return utc_time - (time_t) ABS(utc_offset_min) * SECONDS_PER_MINUTE;
} else {
return utc_time + (time_t) utc_offset_min * SECONDS_PER_MINUTE;
}
}
time_t time_local_to_utc_using_offset(time_t local_time, int16_t utc_offset_min) {
if (utc_offset_min < 0) {
return local_time + (time_t) ABS(utc_offset_min) * SECONDS_PER_MINUTE;
} else {
return local_time - (time_t) utc_offset_min * SECONDS_PER_MINUTE;
}
}
time_t time_util_utc_to_local_offset(void) {
time_t now = rtc_get_time();
return (time_utc_to_local(now) - now);
}
// ---------------------------------------------------------------------------------------
DayInWeek time_util_get_day_in_week(time_t utc_sec) {
struct tm local_tm;
localtime_r(&utc_sec, &local_tm);
return local_tm.tm_wday;
}
// ---------------------------------------------------------------------------------------
uint16_t time_util_get_day(time_t utc_sec) {
// Convert to local seconds
time_t local_sec = time_utc_to_local(utc_sec);
// Figure out the day index
return (local_sec / SECONDS_PER_DAY);
}
// ---------------------------------------------------------------------------------------
int time_util_get_minute_of_day(time_t utc_sec) {
struct tm local_tm;
localtime_r(&utc_sec, &local_tm);
return (local_tm.tm_hour * MINUTES_PER_HOUR) + local_tm.tm_min;
}
// ---------------------------------------------------------------------------------------
int time_util_minute_of_day_adjust(int minute, int delta) {
minute += delta;
if (minute < 0) {
minute += MINUTES_PER_DAY;
} else if (minute >= MINUTES_PER_DAY) {
minute -= MINUTES_PER_DAY;
}
return minute;
}
// ---------------------------------------------------------------------------------------
time_t time_start_of_today(void) {
time_t now = rtc_get_time();
return time_util_get_midnight_of(now);
}
DEFINE_SYSCALL(time_t, sys_time_start_of_today, void) {
return time_start_of_today();
}
// ---------------------------------------------------------------------------------------
uint32_t time_get_uptime_seconds(void) {
RtcTicks ticks = rtc_get_ticks();
return ticks / configTICK_RATE_HZ;
}

226
src/fw/util/time/time.h Normal file
View file

@ -0,0 +1,226 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h> // time_t and size_t
#define TM_YEAR_ORIGIN 1900
#define EPOCH_YEAR 1970
#define EPOCH_WDAY 4
#define DAYS_PER_WEEK 7
#define MONTHS_PER_YEAR 12
#define MS_PER_SECOND (1000)
#define SECONDS_PER_MINUTE (60)
#define MS_PER_MINUTE (MS_PER_SECOND * SECONDS_PER_MINUTE)
#define MINUTES_PER_HOUR (60)
#define SECONDS_PER_HOUR (SECONDS_PER_MINUTE * MINUTES_PER_HOUR)
#define HOURS_PER_DAY (24)
#define MINUTES_PER_DAY (HOURS_PER_DAY * MINUTES_PER_HOUR)
#define SECONDS_PER_DAY (MINUTES_PER_DAY * SECONDS_PER_MINUTE)
#define SECONDS_PER_WEEK (SECONDS_PER_DAY * DAYS_PER_WEEK)
#define YEAR_IS_LEAP(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
#define IS_WEEKDAY(d) (d >= Monday && d <= Friday)
#define IS_WEEKEND(d) (d == Saturday || d == Sunday)
#define TZ_LEN 6
// DST special cases. These map to indexes in the tools/timezones.py script that handles parsing
// the olsen database into a compressed form. Don't change these without changing the script.
//
// Note that we don't correctly handle Morroco's DST rules, they're incredibly complex due to them
// suspending DST each year for Ramadan, resulting in 4 DST transitions each year.
//
// Any DST ids that aren't listed below have sane DST rules, where they change to DST in the
// spring on the same day by 1 hour each year and change from DST on a later day each year.
#define DSTID_BRAZIL 6
#define DSTID_LORDHOWE 20
//! @file time.h
//! @addtogroup StandardC Standard C
//! @{
//! @addtogroup StandardTime Time
//! \brief Standard system time functions
//!
//! This module contains standard time functions and formatters for printing.
//! Note that Pebble now supports both local time and UTC time
//! (including timezones and daylight savings time).
//! Most of these functions are part of the C standard library which is documented at
//! https://sourceware.org/newlib/libc.html#Timefns
//! @{
//! structure containing broken-down time for expressing calendar time
//! (ie. Year, Month, Day of Month, Hour of Day) and timezone information
struct tm {
int tm_sec; /*!< Seconds. [0-60] (1 leap second) */
int tm_min; /*!< Minutes. [0-59] */
int tm_hour; /*!< Hours. [0-23] */
int tm_mday; /*!< Day. [1-31] */
int tm_mon; /*!< Month. [0-11] */
int tm_year; /*!< Years since 1900 */
int tm_wday; /*!< Day of week. [0-6] */
int tm_yday; /*!< Days in year.[0-365] */
int tm_isdst; /*!< DST. [-1/0/1] */
int tm_gmtoff; /*!< Seconds east of UTC */
char tm_zone[TZ_LEN]; /*!< Timezone abbreviation */
};
//! Enumeration of each day of the week.
typedef enum DayInWeek {
Sunday = 0,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
} DayInWeek;
//! Obtain the number of seconds and milliseconds part since the epoch.
//! This is a non-standard C function provided for convenience.
//! @param tloc Optionally points to an address of a time_t variable to store the time in.
//! You may pass NULL into tloc if you don't need a time_t variable to be set
//! with the seconds since the epoch
//! @param out_ms Optionally points to an address of a uint16_t variable to store the
//! number of milliseconds since the last second in.
//! If you only want to use the return value, you may pass NULL into out_ms instead
//! @return The number of milliseconds since the last second
uint16_t time_ms(time_t *tloc, uint16_t *out_ms);
//! @} // end addtogroup StandardTime
//! @} // end addtogroup StandardC
// The below standard c time functions are documented for the SDK
// by their applib pbl_override wrappers in pbl_std.h
struct tm *localtime(const time_t *timep);
struct tm *gmtime(const time_t *timep);
size_t strftime(char *s, size_t max, const char *fmt, const struct tm *tm);
time_t mktime(struct tm *tb);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
struct tm *localtime_r(const time_t *timep, struct tm *result);
//! Minimal struct to store timezone info in RTC registers
typedef struct TimezoneInfo {
char tm_zone[TZ_LEN - 1]; //!< Up to 5 character (no null terminator) timezone abbreviation
uint8_t dst_id; //!< Daylight savings time zone index
int16_t timezone_id; //!< Olson index of timezone
int32_t tm_gmtoff; //!< GMT time offset
time_t dst_start; //!< timestamp of start of daylight savings period (0 if none)
time_t dst_end; //!< timestamp of end of daylight savings period (0 if none)
} TimezoneInfo;
//! Provides the timezone abbreviation string for the given time. Uses the utc_time provided
//! to correct the abbreviation for daylight savings time if applicable
//! @param out_buf should have length TZ_LEN
void time_get_timezone_abbr(char *out_buf, time_t utc_time);
//! Provides the gmt offset
int32_t time_get_gmtoffset(void);
//! Returns true if the UNIX time provided falls within DST
bool time_get_isdst(time_t utc_time);
// 0 = no transition
// <0 = dst begins between prev and next
// >0 = dst ends between prev and next
// returns difference in seconds of DST
int time_will_transition_dst(time_t prev, time_t next);
//! Returns the DST offset
int32_t time_get_dstoffset(void);
//! Returns the DST start timestamp
time_t time_get_dst_start(void);
//! Returns the DST end timestamp
time_t time_get_dst_end(void);
//! Convert UTC time, as returned by rtc_get_time() into local time
time_t time_utc_to_local(time_t utc_time);
//! Convert local time to UTC time
time_t time_local_to_utc(time_t local_time);
void time_util_split_seconds_into_parts(uint32_t seconds, uint32_t *day_part,
uint32_t *hour_part, uint32_t *minute_part, uint32_t *second_part);
int time_util_get_num_hours(int hours, bool is24h);
int time_util_get_seconds_until_daily_time(struct tm *time, int hour, int minute);
//! Set the timezone
void time_util_update_timezone(const TimezoneInfo *tz_info);
time_t time_util_get_midnight_of(time_t ts);
bool time_util_range_spans_day(time_t start, time_t end, time_t start_of_day);
//! User-mode access calls
time_t sys_time_utc_to_local(time_t t);
time_t time_util_utc_to_local_offset(void);
time_t time_utc_to_local_using_offset(time_t utc_time, int16_t utc_offset_min);
time_t time_local_to_utc_using_offset(time_t local_time, int16_t utc_offset_min);
//! Computes the day index from UTC seconds. This index should change every day at midnight local
//! time
//! @param utc_sec Time to retrieve index for
//! @return Day index
uint16_t time_util_get_day(time_t utc_sec);
DayInWeek time_util_get_day_in_week(time_t utc_sec);
//! Computes the minute of the day
//! @param utc_sec Time to retrieve minute for
//! @return Minute of day
int time_util_get_minute_of_day(time_t utc_sec);
//! Adds a delta to the minute of the day and will wrap to the next or previous day if the
//! resulting minutes would have been out of bounds
//! @param minute Minute to adjust
//! @param delta Minutes to add to \ref minute
//! @return Adjusted minutes
int time_util_minute_of_day_adjust(int minute, int delta);
//! Return the UTC time that corresponds to the start of today (midnight).
//! @return the UTC time corresponding to the start of today (midnight)
time_t time_start_of_today(void);
//! Return the number of seconds since the system was restarted. This time is based on the
//! tickcount and so, unlike rtc_get_time(), it won't be affected if the phone changes the UTC
//! time on the watch.
uint32_t time_get_uptime_seconds(void);

34
src/fw/util/units.h Normal file
View file

@ -0,0 +1,34 @@
/*
* 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.
*/
#pragma once
#define MHZ_TO_HZ(hz) (((uint32_t)(hz)) * 1000000)
#define KiBYTES(k) ((k) * 1024) // Kibibytes to Bytes
#define MiBYTES(m) ((m) * 1024 * 1024) // Mebibytes to Bytes
#define EiBYTES(e) ((e) * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) // Exbibytes to Bytes
#define MM_PER_METER 1000
#define METERS_PER_KM 1000
#define METERS_PER_MILE 1609
#define US_PER_MS (1000)
#define US_PER_S (1000000)
#define NS_PER_S (1000000000)
#define PS_PER_NS (1000)
#define PS_PER_US (1000000)
#define PS_PER_S (1000000000000ULL)

View file

@ -0,0 +1,34 @@
/*
* 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 "console/dbgserial.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/assert.h"
#include "util/logging.h"
void util_log(const char *filename, int line, const char *string) {
pbl_log(LOG_LEVEL_INFO, filename, line, string);
}
void util_dbgserial_str(const char *string) {
dbgserial_putstr(string);
}
NORETURN util_assertion_failed(const char *filename, int line) {
passert_failed_no_message(filename, line);
}