Skip to content

Mic: never write past the record() buffer; reject empty requests - #370

Merged
lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:mic_record_bounds
Sep 18, 2026
Merged

lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:mic_record_bounds

Conversation

@ainyan03

Copy link
Copy Markdown
Contributor

Summary

Mic.record() could write past the end of the caller's buffer, and record() with array_len == 0 reported success without recording anything.

The capture task emitted a whole conversion step per iteration (2 samples, or 4 when a mono microphone is recorded into a stereo buffer) without checking how many elements the destination had left. Any array_len that was not a multiple of the step overran the buffer by up to 3 elements. Measured on a mono-microphone device with a guard pattern after the buffer: odd-length 16-bit mono overran by 2 bytes, mono into stereo by up to 6 bytes.

Changes

  • Mic_Class.inl: the task writes per time step. When a mono capture step straddles two buffers, the second time step is kept for the next queued request, so back-to-back recordings stay continuous whatever their length. The carry is dropped only when no request is pending (the buffered input is dropped there as well). A stereo buffer whose last element is a left sample ends with left only.
  • _rec_raw(): array_len == 0 or a null pointer returns false. Previously it started the microphone and returned true without queueing anything, so the release callback never fired for a "successful" request.
  • Mic_Class.hpp: the record() contract is documented (exact element count, odd stereo length, continuity of queued requests, invalid arguments).

Public signatures are unchanged. All record() calls in the examples use a non-zero length.

Verification

Mono-microphone device (ESP32), guard bytes on both sides of the destination, 17 cases covering 8/16-bit, mono/stereo, lengths 0, 1, 3, 253, 255, 256, 257, 258, 4000, 4001:

Build Result
develop back guard corrupted in 9 cases (2-6 bytes), record(…, 0) returned true
this PR all guards intact, every element written, record(…, 0) returns false

Two rounds of independent adversarial review (the first found that clamping alone dropped samples at buffer boundaries, which led to the carry-over). CI run on the ainyan03 fork before this PR.

The capture task emitted a whole conversion step per iteration (2 samples,
or 4 for mono in -> stereo out) regardless of how many elements the
destination had left, so any array_len that was not a multiple of the
step overran the buffer by up to 3 elements (measured on a mono-mic
device: 2 bytes for odd 16-bit mono, 6 bytes for mono -> stereo).

Emit per time step instead, and when a mono capture step straddles two
buffers keep the second time step for the next queued request, so
back-to-back recordings stay continuous whatever their length. The
carry is dropped only when no request is pending (the buffered input is
dropped there as well). A stereo buffer whose last element is a left
sample ends with left only, which is now documented.

record() with array_len == 0 (or a null pointer) used to start the mic
and return true without queueing anything, so the release callback never
fired for a "successful" request. It now returns false.
@lovyan03
lovyan03 merged commit 3662def into m5stack:develop Sep 18, 2026
28 checks passed
@ainyan03
ainyan03 deleted the mic_record_bounds branch September 18, 2026 03:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants