Quick answer: buffer_copy is memcpy-style: undefined on overlap. Either copy via a temp buffer, or implement memmove-safe direction (forward when dst < src, backward when dst > src).

A networking module rotates a ring buffer by copying tail bytes to front of same buffer. Output is garbage after the first byte. Source and destination overlap; buffer_copy doesn’t handle it.

Temp Buffer Pattern

function safe_shift(buf, src_offset, size, dst_offset) {
    var tmp = buffer_create(size, buffer_fixed, 1);
    buffer_copy(buf, src_offset, size, tmp, 0);
    buffer_copy(tmp, 0, size, buf, dst_offset);
    buffer_delete(tmp);
}

Two copies, but never overlapping; safe.

Memmove-Style Direction

If dst < src (shift earlier), iterate forward; if dst > src (shift later), iterate backward:

function buffer_move(buf, src, size, dst) {
    if (dst < src) {
        // forward
        for (var i = 0; i < size; i++) {
            buffer_poke(buf, dst + i, buffer_u8, buffer_peek(buf, src + i, buffer_u8));
        }
    } else if (dst > src) {
        // backward
        for (var i = size - 1; i >= 0; i--) {
            buffer_poke(buf, dst + i, buffer_u8, buffer_peek(buf, src + i, buffer_u8));
        }
    }
}

Mirrors C’s memmove. Slower than buffer_copy (byte-by-byte) but safe.

For Large Shifts

Per-byte poke is slow. For large overlap shifts (KB+), the temp buffer approach is faster despite the double-copy.

Ring Buffer Without Shift

Ring buffer typically doesn’t shift — it uses a head index. Read from (head + i) % capacity. No copy needed; just advance the head.

If you find yourself copying within a ring buffer, you may have a design issue. Reconsider the index-based approach.

Verifying

Shift bytes within a buffer; print before/after. Bytes match expected positions. No garbage in overlap region.

“Memory overlap is silent corruption. Either use a temp buffer or write a memmove helper.”

For network buffers, index-based ring buffers eliminate the need for shifts entirely — consider refactoring rather than band-aiding.