mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
CRITICAL (5): - Hub panic recovery now calls h.Stop() after 3 panics (ws/hub.go) - Ring buffer EventsSince returns non-nil empty slice for current seq (ws/ringbuffer.go) - PTT event listener stores unsubscribe handle to prevent leak (ptt.ts) - verifyTotp respects config.allowSelfSigned instead of hardcoding (api.ts) - ptt_listen_for_key uses spawn_blocking to avoid thread pool starvation (ptt.rs) HIGH - Server (13): - TOTP rate-limit checked after body decode; counters reset on success - TOTP enable returns 409 if already enabled (must disable first) - Global search pre-computes accessible channel IDs for FTS WHERE clause - DeleteAccount queries roles by name instead of hard-coded IDs - BackupToSafe uses absClean in VACUUM INTO - Voice camera slot uses atomic EnableCameraIfUnderLimit DB method - readPump snapshots voiceChID before unregister for TOCTOU safety - Voice join sets state after token send; rollback takes broadcast flag - Updater download uses probe pattern instead of overflow write - Webhook checks Authorization header before reading body - Storage.Save adds fsync and fixes double-close - Default WS origin denies cross-origin (was: accept all) HIGH - Client (6): - WS reconnect uses generation counter to discard stale events - AudioPipeline uses generation counter against stale worklet callbacks - Screenshare mute state preserved across reconnect (not full leave) - handleVoiceToken uses iterative loop instead of unbounded recursion - store.ts re-entrancy guard with pending update queue - Notification AudioContext cleaned up on logout Reviewed by 4 parallel agents across Server Core, Server Realtime, Client & Tauri, and Security. 55 total findings; 24 CRITICAL+HIGH fixed here, 31 MEDIUM+LOW tracked in vault backlog (T-265–T-295).
79 lines
1.8 KiB
Go
79 lines
1.8 KiB
Go
package ws
|
|
|
|
import "sync"
|
|
|
|
// eventEntry stores a broadcast event for potential replay.
|
|
type eventEntry struct {
|
|
seq uint64
|
|
data []byte
|
|
}
|
|
|
|
// EventRingBuffer is a bounded, thread-safe ring buffer for recent broadcast events.
|
|
type EventRingBuffer struct {
|
|
mu sync.RWMutex
|
|
entries []eventEntry
|
|
size int
|
|
pos int // next write position
|
|
count int // total entries stored (up to size)
|
|
}
|
|
|
|
// NewEventRingBuffer creates a ring buffer with the given capacity.
|
|
func NewEventRingBuffer(size int) *EventRingBuffer {
|
|
return &EventRingBuffer{
|
|
entries: make([]eventEntry, size),
|
|
size: size,
|
|
}
|
|
}
|
|
|
|
// Push adds an event to the ring buffer.
|
|
func (rb *EventRingBuffer) Push(seq uint64, data []byte) {
|
|
rb.mu.Lock()
|
|
defer rb.mu.Unlock()
|
|
rb.entries[rb.pos] = eventEntry{seq: seq, data: data}
|
|
rb.pos = (rb.pos + 1) % rb.size
|
|
if rb.count < rb.size {
|
|
rb.count++
|
|
}
|
|
}
|
|
|
|
// EventsSince returns all events with seq > afterSeq, in order.
|
|
// Returns nil if afterSeq is too old (no longer in the buffer).
|
|
func (rb *EventRingBuffer) EventsSince(afterSeq uint64) [][]byte {
|
|
rb.mu.RLock()
|
|
defer rb.mu.RUnlock()
|
|
|
|
if rb.count == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Find the oldest entry in the buffer.
|
|
oldestIdx := (rb.pos - rb.count + rb.size) % rb.size
|
|
oldestSeq := rb.entries[oldestIdx].seq
|
|
|
|
// If the requested seq is older than our oldest, we can't replay.
|
|
if afterSeq < oldestSeq {
|
|
return nil
|
|
}
|
|
|
|
result := make([][]byte, 0)
|
|
for i := 0; i < rb.count; i++ {
|
|
idx := (oldestIdx + i) % rb.size
|
|
e := rb.entries[idx]
|
|
if e.seq > afterSeq {
|
|
result = append(result, e.data)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// OldestSeq returns the oldest sequence number in the buffer, or 0 if empty.
|
|
func (rb *EventRingBuffer) OldestSeq() uint64 {
|
|
rb.mu.RLock()
|
|
defer rb.mu.RUnlock()
|
|
if rb.count == 0 {
|
|
return 0
|
|
}
|
|
oldestIdx := (rb.pos - rb.count + rb.size) % rb.size
|
|
return rb.entries[oldestIdx].seq
|
|
}
|