mirror of
https://github.com/google/pebble.git
synced 2025-07-04 13:57:04 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
36
src/fw/util/array.c
Normal file
36
src/fw/util/array.c
Normal 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
24
src/fw/util/array.h
Normal 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
128
src/fw/util/base64.c
Normal 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
30
src/fw/util/base64.h
Normal 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
33
src/fw/util/bitset.c
Normal 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
110
src/fw/util/bitset.h
Normal 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
76
src/fw/util/buffer.c
Normal 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
50
src/fw/util/buffer.h
Normal 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
37
src/fw/util/crc32_lut.py
Executable 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
50
src/fw/util/crc8.c
Normal 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
29
src/fw/util/crc8.h
Normal 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
48
src/fw/util/date.c
Normal 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
28
src/fw/util/date.h
Normal 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
440
src/fw/util/dict.c
Normal 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
483
src/fw/util/dict.h
Normal 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
|
53
src/fw/util/generic_attribute.c
Normal file
53
src/fw/util/generic_attribute.c
Normal 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;
|
||||
}
|
39
src/fw/util/generic_attribute.h
Normal file
39
src/fw/util/generic_attribute.h
Normal 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
76
src/fw/util/graphics.h
Normal 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
66
src/fw/util/hdlc.c
Normal 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
33
src/fw/util/hdlc.h
Normal 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
52
src/fw/util/ihex.c
Normal 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
41
src/fw/util/ihex.h
Normal 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);
|
128
src/fw/util/legacy_checksum.c
Normal file
128
src/fw/util/legacy_checksum.c
Normal 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);
|
||||
}
|
54
src/fw/util/legacy_checksum.h
Normal file
54
src/fw/util/legacy_checksum.h
Normal 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);
|
38
src/fw/util/legacy_checksum_crc_table.py
Executable file
38
src/fw/util/legacy_checksum_crc_table.py
Executable 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
110
src/fw/util/lru_cache.c
Normal 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
68
src/fw/util/lru_cache.h
Normal 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
20
src/fw/util/macro.h
Normal 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
197
src/fw/util/mbuf.c
Normal 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
108
src/fw/util/mbuf.h
Normal 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);
|
70
src/fw/util/mbuf_iterator.c
Normal file
70
src/fw/util/mbuf_iterator.c
Normal 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;
|
||||
}
|
45
src/fw/util/mbuf_iterator.h
Normal file
45
src/fw/util/mbuf_iterator.h
Normal 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
80
src/fw/util/net.h
Normal 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
24
src/fw/util/pack.h
Normal 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
43
src/fw/util/packbits.c
Normal 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
21
src/fw/util/packbits.h
Normal 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
195
src/fw/util/pstring.c
Normal 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
88
src/fw/util/pstring.h
Normal 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
28
src/fw/util/rand.h
Normal 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
100
src/fw/util/rand/rand.c
Normal 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
42
src/fw/util/ratio.h
Normal 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
61
src/fw/util/reverse.h
Normal 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
|
293
src/fw/util/shared_circular_buffer.c
Normal file
293
src/fw/util/shared_circular_buffer.c
Normal 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;
|
||||
}
|
183
src/fw/util/shared_circular_buffer.h
Normal file
183
src/fw/util/shared_circular_buffer.h
Normal 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
64
src/fw/util/sle.c
Normal 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
32
src/fw/util/sle.h
Normal 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
248
src/fw/util/stats.c
Normal 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
71
src/fw/util/stats.h
Normal 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
77
src/fw/util/stringlist.c
Normal 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
56
src/fw/util/stringlist.h
Normal 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
25
src/fw/util/swap.h
Normal 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
17
src/fw/util/test_base64.sh
Executable 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
328
src/fw/util/time/time.c
Normal 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
226
src/fw/util/time/time.h
Normal 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
34
src/fw/util/units.h
Normal 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)
|
34
src/fw/util/util_platform.c
Normal file
34
src/fw/util/util_platform.c
Normal 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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue