refactor(core): bundle the utimeq module
What changed, and why it matters
This commit is a routine maintenance refactor. MicroPython removed its built-in 'utimeq' module, so Trezor copied the same code into its own firmware tree and renamed it to 'timeq'. There is no security bug being fixed and no new vulnerability being introduced that is visible in the diff.
No security action required. Treat as normal build/dependency maintenance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change vendors the former MicroPython extmod/modutimeq.c into core/embed/upymod/modtimeq.c, updates build files to compile it, removes the MICROPY_PY_UTIMEQ config flag, renames Python type stubs from utimeq to timeq, and updates core/src/trezor/loop.py to import timeq instead of utimeq. The C implementation is substantially the same heap-based time queue as before, with the addition of a discard() method already present in the previous Python stub. No memory-safety, logic, or cryptographic changes are evident.
Changed components
core/embed/upymod/modtimeq.ccore/src/trezor/loop.pycore/SConscript.firmwarecore/SConscript.unixcore/embed/projects/firmware/mpconfigport.hcore/embed/projects/unix/mpconfigport.hcore/embed/upymod/build.rscore/mocks/generated/timeq.pyicore/mocks/generated/utimeq.pyicore/mocks/timeq.pyicore/mocks/utimeq.pyipyproject.tomlInspect captured patch +284 / −22
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index 13b7758c..f4927e5c 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -317,15 +317,14 @@ SOURCE_MOD += [
'embed/upymod/rustmods.c',
]
-# modutime
+# modutime, modtimeq
SOURCE_MOD += [
+ 'embed/upymod/modtimeq.c',
'embed/upymod/modutime.c',
]
SOURCE_MICROPYTHON = [
'vendor/micropython/extmod/moductypes.c',
- 'vendor/micropython/extmod/moduheapq.c',
- 'vendor/micropython/extmod/modutimeq.c',
'vendor/micropython/extmod/utime_mphal.c',
'vendor/micropython/shared/libc/abort_.c',
'vendor/micropython/shared/libc/printf.c',
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 64ebdc56..90e4ee8d 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -299,16 +299,15 @@ SOURCE_MOD += [
'embed/upymod/rustmods.c',
]
-# modutime
+# modutime, modtimeq
SOURCE_MOD += [
+ 'embed/upymod/modtimeq.c',
'embed/upymod/modutime.c',
]
SOURCE_MICROPYTHON = [
'vendor/micropython/extmod/moductypes.c',
- 'vendor/micropython/extmod/moduheapq.c',
'vendor/micropython/extmod/moduos.c',
- 'vendor/micropython/extmod/modutimeq.c',
'vendor/micropython/extmod/utime_mphal.c',
'vendor/micropython/shared/readline/readline.c',
'vendor/micropython/shared/timeutils/timeutils.c',
diff --git a/core/embed/projects/firmware/mpconfigport.h b/core/embed/projects/firmware/mpconfigport.h
index f5e6d4c3..97cbeff2 100644
--- a/core/embed/projects/firmware/mpconfigport.h
+++ b/core/embed/projects/firmware/mpconfigport.h
@@ -153,7 +153,6 @@
#define MICROPY_PY_URANDOM_EXTRA_FUNCS (0)
#define MICROPY_PY_USELECT (0)
#define MICROPY_PY_UTIME (1)
-#define MICROPY_PY_UTIMEQ (1)
#define MICROPY_PY_UTIME_MP_HAL (1)
#define MICROPY_PY_OS_DUPTERM (0)
#define MICROPY_PY_LWIP_SOCK_RAW (0)
diff --git a/core/embed/projects/unix/mpconfigport.h b/core/embed/projects/unix/mpconfigport.h
index 7ac33e30..d7916961 100644
--- a/core/embed/projects/unix/mpconfigport.h
+++ b/core/embed/projects/unix/mpconfigport.h
@@ -167,7 +167,6 @@
#define MICROPY_PY_URANDOM (0)
#define MICROPY_PY_URANDOM_EXTRA_FUNCS (0)
#define MICROPY_PY_USELECT (0)
-#define MICROPY_PY_UTIMEQ (1)
#define MICROPY_PY_UTIME (1)
#define MICROPY_PY_UTIME_MP_HAL (1)
#define MICROPY_PY_OS_DUPTERM (0)
diff --git a/core/embed/upymod/build.rs b/core/embed/upymod/build.rs
index bbbafee7..24af9788 100644
--- a/core/embed/upymod/build.rs
+++ b/core/embed/upymod/build.rs
@@ -95,6 +95,7 @@ fn main() -> Result<()> {
lib.add_private_include("../rust");
lib.add_sources([
+ "modtimeq.c",
"modutime.c",
"rustmods.c",
"trezorobj.c",
@@ -129,8 +130,6 @@ fn main() -> Result<()> {
lib.add_sources_in_dir(
mpy_dir,
[
- "extmod/moduheapq.c",
- "extmod/modutimeq.c",
"extmod/utime_mphal.c",
"shared/timeutils/timeutils.c",
"py/argcheck.c",
diff --git a/core/embed/upymod/modtimeq.c b/core/embed/upymod/modtimeq.c
new file mode 100644
index 00000000..c5a680b4
--- /dev/null
+++ b/core/embed/upymod/modtimeq.c
@@ -0,0 +1,267 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014 Damien P. George
+ * Copyright (c) 2016-2017 Paul Sokolovsky
+ * Copyright (c) 2018 Jan Pochyla
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// copy of extmod/modtimeq.c from last commit before removal -
+// 8211d56712301d58970904892da5312b11b2ab7c, plus discard method by
+// jpochyla@gmail.com
+
+#include <string.h>
+
+#include "py/objlist.h"
+#include "py/runtime.h"
+#include "py/smallint.h"
+
+#define MODULO MICROPY_PY_TIME_TICKS_PERIOD
+
+#define DEBUG 0
+
+// the algorithm here is modelled on CPython's heapq.py
+
+struct qentry {
+ mp_uint_t time;
+ mp_uint_t id;
+ mp_obj_t callback;
+ mp_obj_t args;
+};
+
+typedef struct _mp_obj_timeq_t {
+ mp_obj_base_t base;
+ mp_uint_t alloc;
+ mp_uint_t len;
+ struct qentry items[];
+} mp_obj_timeq_t;
+
+static mp_uint_t timeq_id;
+
+static mp_obj_timeq_t *timeq_get_heap(mp_obj_t heap_in) {
+ return MP_OBJ_TO_PTR(heap_in);
+}
+
+static bool time_less_than(struct qentry *item, struct qentry *parent) {
+ mp_uint_t item_tm = item->time;
+ mp_uint_t parent_tm = parent->time;
+ mp_uint_t res = parent_tm - item_tm;
+ if (res == 0) {
+ // TODO: This actually should use the same "ring" logic
+ // as for time, to avoid artifacts when id's overflow.
+ return item->id < parent->id;
+ }
+ if ((mp_int_t)res < 0) {
+ res += MODULO;
+ }
+ return res && res < (MODULO / 2);
+}
+
+static mp_obj_t timeq_make_new(const mp_obj_type_t *type, size_t n_args,
+ size_t n_kw, const mp_obj_t *args) {
+ mp_arg_check_num(n_args, n_kw, 1, 1, false);
+ mp_uint_t alloc = mp_obj_get_int(args[0]);
+ mp_obj_timeq_t *o =
+ mp_obj_malloc_var(mp_obj_timeq_t, items, struct qentry, alloc, type);
+ memset(o->items, 0, sizeof(*o->items) * alloc);
+ o->alloc = alloc;
+ o->len = 0;
+ return MP_OBJ_FROM_PTR(o);
+}
+
+static void timeq_heap_siftdown(mp_obj_timeq_t *heap, mp_uint_t start_pos,
+ mp_uint_t pos) {
+ struct qentry item = heap->items[pos];
+ while (pos > start_pos) {
+ mp_uint_t parent_pos = (pos - 1) >> 1;
+ struct qentry *parent = &heap->items[parent_pos];
+ bool lessthan = time_less_than(&item, parent);
+ if (lessthan) {
+ heap->items[pos] = *parent;
+ pos = parent_pos;
+ } else {
+ break;
+ }
+ }
+ heap->items[pos] = item;
+}
+
+static void timeq_heap_siftup(mp_obj_timeq_t *heap, mp_uint_t pos) {
+ mp_uint_t start_pos = pos;
+ mp_uint_t end_pos = heap->len;
+ struct qentry item = heap->items[pos];
+ for (mp_uint_t child_pos = 2 * pos + 1; child_pos < end_pos;
+ child_pos = 2 * pos + 1) {
+ // choose right child if it's <= left child
+ if (child_pos + 1 < end_pos) {
+ bool lessthan =
+ time_less_than(&heap->items[child_pos], &heap->items[child_pos + 1]);
+ if (!lessthan) {
+ child_pos += 1;
+ }
+ }
+ // bubble up the smaller child
+ heap->items[pos] = heap->items[child_pos];
+ pos = child_pos;
+ }
+ heap->items[pos] = item;
+ timeq_heap_siftdown(heap, start_pos, pos);
+}
+
+static mp_obj_t mod_timeq_heappush(size_t n_args, const mp_obj_t *args) {
+ (void)n_args;
+ mp_obj_t heap_in = args[0];
+ mp_obj_timeq_t *heap = timeq_get_heap(heap_in);
+ if (heap->len == heap->alloc) {
+ mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("queue overflow"));
+ }
+ mp_uint_t l = heap->len;
+ heap->items[l].time = MP_OBJ_SMALL_INT_VALUE(args[1]);
+ heap->items[l].id = timeq_id++;
+ heap->items[l].callback = args[2];
+ heap->items[l].args = args[3];
+ timeq_heap_siftdown(heap, 0, heap->len);
+ heap->len++;
+ return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_timeq_heappush_obj, 4, 4,
+ mod_timeq_heappush);
+
+static mp_obj_t mod_timeq_heappop(mp_obj_t heap_in, mp_obj_t list_ref) {
+ mp_obj_timeq_t *heap = timeq_get_heap(heap_in);
+ if (heap->len == 0) {
+ mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty heap"));
+ }
+ mp_obj_list_t *ret = MP_OBJ_TO_PTR(list_ref);
+ if (!mp_obj_is_type(list_ref, &mp_type_list) || ret->len < 3) {
+ mp_raise_TypeError(NULL);
+ }
+
+ struct qentry *item = &heap->items[0];
+ ret->items[0] = MP_OBJ_NEW_SMALL_INT(item->time);
+ ret->items[1] = item->callback;
+ ret->items[2] = item->args;
+ heap->len -= 1;
+ heap->items[0] = heap->items[heap->len];
+ heap->items[heap->len].callback =
+ MP_OBJ_NULL; // so we don't retain a pointer
+ heap->items[heap->len].args = MP_OBJ_NULL;
+ if (heap->len) {
+ timeq_heap_siftup(heap, 0);
+ }
+ return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_2(mod_timeq_heappop_obj, mod_timeq_heappop);
+
+static mp_obj_t mod_timeq_peektime(mp_obj_t heap_in) {
+ mp_obj_timeq_t *heap = timeq_get_heap(heap_in);
+ if (heap->len == 0) {
+ mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty heap"));
+ }
+
+ struct qentry *item = &heap->items[0];
+ return MP_OBJ_NEW_SMALL_INT(item->time);
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(mod_timeq_peektime_obj, mod_timeq_peektime);
+
+static mp_obj_t mod_timeq_discard(mp_obj_t heap_in, mp_obj_t callback) {
+ mp_obj_timeq_t *heap = timeq_get_heap(heap_in);
+ if (heap->len == 0) {
+ return mp_const_none;
+ }
+ for (mp_uint_t i = 0; i < heap->len; i++) {
+ if (heap->items[i].callback != callback) {
+ continue;
+ }
+ heap->len -= 1;
+ heap->items[i] = heap->items[heap->len];
+ heap->items[heap->len].callback = MP_OBJ_NULL;
+ heap->items[heap->len].args = MP_OBJ_NULL;
+ if (i < heap->len) {
+ timeq_heap_siftup(heap, i);
+ timeq_heap_siftdown(heap, 0, i);
+ }
+ }
+ return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_2(mod_timeq_discard_obj, mod_timeq_discard);
+
+#if DEBUG
+static mp_obj_t mod_timeq_dump(mp_obj_t heap_in) {
+ mp_obj_timeq_t *heap = timeq_get_heap(heap_in);
+ for (int i = 0; i < heap->len; i++) {
+ printf(UINT_FMT "\t%p\t%p\n", heap->items[i].time,
+ MP_OBJ_TO_PTR(heap->items[i].callback),
+ MP_OBJ_TO_PTR(heap->items[i].args));
+ }
+ return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(mod_timeq_dump_obj, mod_timeq_dump);
+#endif
+
+static mp_obj_t timeq_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
+ mp_obj_timeq_t *self = MP_OBJ_TO_PTR(self_in);
+ switch (op) {
+ case MP_UNARY_OP_BOOL:
+ return mp_obj_new_bool(self->len != 0);
+ case MP_UNARY_OP_LEN:
+ return MP_OBJ_NEW_SMALL_INT(self->len);
+ default:
+ return MP_OBJ_NULL; // op not supported
+ }
+}
+
+static const mp_rom_map_elem_t timeq_locals_dict_table[] = {
+ {MP_ROM_QSTR(MP_QSTR_push), MP_ROM_PTR(&mod_timeq_heappush_obj)},
+ {MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&mod_timeq_heappop_obj)},
+ {MP_ROM_QSTR(MP_QSTR_peektime), MP_ROM_PTR(&mod_timeq_peektime_obj)},
+ {MP_ROM_QSTR(MP_QSTR_discard), MP_ROM_PTR(&mod_timeq_discard_obj)},
+#if DEBUG
+ {MP_ROM_QSTR(MP_QSTR_dump), MP_ROM_PTR(&mod_timeq_dump_obj)},
+#endif
+};
+
+static MP_DEFINE_CONST_DICT(timeq_locals_dict, timeq_locals_dict_table);
+
+// clang-format off
+static MP_DEFINE_CONST_OBJ_TYPE(timeq_type,
+ MP_QSTR_timeq, MP_TYPE_FLAG_NONE,
+ make_new, timeq_make_new,
+ unary_op, timeq_unary_op,
+ locals_dict, &timeq_locals_dict);
+// clang-format on
+
+static const mp_rom_map_elem_t mp_module_timeq_globals_table[] = {
+ {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_timeq)},
+ {MP_ROM_QSTR(MP_QSTR_timeq), MP_ROM_PTR(&timeq_type)},
+};
+
+static MP_DEFINE_CONST_DICT(mp_module_timeq_globals,
+ mp_module_timeq_globals_table);
+
+const mp_obj_module_t mp_module_timeq = {
+ .base = {&mp_type_module},
+ .globals = (mp_obj_dict_t *)&mp_module_timeq_globals,
+};
+
+MP_REGISTER_MODULE(MP_QSTR_timeq, mp_module_timeq);
diff --git a/core/mocks/generated/timeq.pyi b/core/mocks/generated/timeq.pyi
new file mode 120000
index 00000000..aa61762e
--- /dev/null
+++ b/core/mocks/generated/timeq.pyi
@@ -0,0 +1 @@
+../timeq.pyi
\ No newline at end of file
diff --git a/core/mocks/generated/utimeq.pyi b/core/mocks/generated/utimeq.pyi
deleted file mode 120000
index 248c1812..00000000
--- a/core/mocks/generated/utimeq.pyi
+++ /dev/null
@@ -1 +0,0 @@
-../utimeq.pyi
\ No newline at end of file
diff --git a/core/mocks/timeq.pyi b/core/mocks/timeq.pyi
new file mode 100644
index 00000000..5d3287ef
--- /dev/null
+++ b/core/mocks/timeq.pyi
@@ -0,0 +1,8 @@
+from typing import *
+
+class timeq:
+ def __init__(self, max_queue_size: int) -> None: ...
+ def push(self, time: int, callback: Any, value: Any) -> None: ...
+ def pop(self, entry: List[Any]) -> None: ...
+ def peektime(self) -> int: ...
+ def discard(self, callback: Any) -> None: ...
diff --git a/core/mocks/utimeq.pyi b/core/mocks/utimeq.pyi
deleted file mode 100644
index c18f5828..00000000
--- a/core/mocks/utimeq.pyi
+++ /dev/null
@@ -1,8 +0,0 @@
-from typing import *
-
-class utimeq:
- def __init__(self, max_queue_size: int) -> None: ...
- def push(self, time: int, callback: Any, value: Any) -> None: ...
- def pop(self, entry: List[Any]) -> None: ...
- def peektime(self) -> int: ...
- def discard(self, callback: Any) -> None: ...
diff --git a/core/src/trezor/loop.py b/core/src/trezor/loop.py
index 95ba235a..753a4e73 100644
--- a/core/src/trezor/loop.py
+++ b/core/src/trezor/loop.py
@@ -7,8 +7,8 @@ stepped through until completion, and can get asynchronously blocked by
See `schedule`, `run`, and syscalls `sleep`, `wait`, `signal` and `race`.
"""
+import timeq
import utime
-import utimeq
from typing import TYPE_CHECKING
from trezor import io, log
@@ -28,7 +28,7 @@ else:
T = 0
# tasks scheduled for execution in the future
-_queue = utimeq.utimeq(64)
+_queue = timeq.timeq(64)
# tasks paused on I/O
_paused: dict[int, set[Task]] = {}
diff --git a/pyproject.toml b/pyproject.toml
index e878b8c3..702b9f0a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -94,7 +94,7 @@ extra_standard_library = [
"ustruct",
"uctypes",
"utime",
- "utimeq",
+ "timeq",
"trezorio",
"trezorui",
"trezorutils",
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.