mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
fix: resolve the six blocked batch-4 ledger findings (#1393)
* fix(ws): resolve an empty READ audience for a channel whose row is gone channelReadAudience already failed closed on a GetChannel error; a deleted channel returns (nil, nil) and fell through to the role scan. Return nobody for a missing row too — voice teardown callers union the room's participants and the leaver back in, so their signals still land. Test locks both halves: the non-participant hears nothing, the leaver still gets voice_leave. (OC-0090) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ws): re-elect the key holder in CleanupVoiceForChannel Every other voice-removal path re-elects (finishVoiceLeave, the LiveKit webhook, registerNow, rollbackVoiceJoin, sweepStaleVoiceStates); the channel delete/archive path did not, so a torn-down channel's voiceKeyHolders entry lived for the process lifetime. One updateKeyHolder call at the end of the teardown deletes it. (OC-0012) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ws): implement BroadcastMemberUnban so unban reaches connected clients The admin unban path reaches the hub through an optional-capability type assertion that *ws.Hub never satisfied, so it always missed silently and clients connected during a ban kept the user missing from their member store. Implement the mirror of BroadcastMemberBan: fan out the same member_join a fresh connect sends (clients already map it to addMember), reporting offline since the unbanned user cannot be connected. A compile-time assertion in admin pins the wiring so the assertion can never silently miss again. (OC-0058) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(db): exclude the requester's own flag from the video-cap stream count EnableCameraIfUnderLimit and EnableScreenshareIfUnderLimit counted every stream in the channel including the very flag the UPDATE sets, so a user whose server-side flag was already 1 (client lost track and retried) was refused at the cap against their own stream, with no path out. Subtract the outer row's own bit from the correlated count: re-enable becomes idempotent while the requester's other stream and everyone else's still count. sqlc layer regenerated. (OC-0081) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(ledger): resolve the six blocked batch-4 findings Four fixed in this branch (OC-0012, OC-0058, OC-0081, OC-0090), each with an independent revert-proof pass. Two were already fixed on main by later sibling fixes and are recorded as such: OC-0086 by the OC-0017 pre-delete re-check (#1374), OC-0101 by the OC-0206 early watermark bump (#1375). The ledger holds zero open and zero blocked findings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -76,10 +76,19 @@ type Querier interface {
|
||||
// OC-0023), and a single user with both flags set must consume two of the N
|
||||
// slots, not one (OC-0006) -- so both gates sum `vs2.camera + vs2.screenshare`
|
||||
// across the channel's rows rather than counting rows where either is set.
|
||||
// The enabling user's own bit is still 0 at gate time, so no self-exclusion
|
||||
// term is needed.
|
||||
// The enabling user's own bit for the flag being set CAN already be 1 at
|
||||
// gate time (a client that lost track of the server-side flag retries the
|
||||
// enable), so each gate excludes exactly that one bit from the count --
|
||||
// see the per-query comments below (OC-0081).
|
||||
// The channel-wide stream count excludes the requester's own camera flag
|
||||
// (subtracted via the correlated outer-row reference), so re-enabling an
|
||||
// already-set camera is idempotent at the cap instead of being refused
|
||||
// against the requester's own stream (OC-0081). Their screenshare, and
|
||||
// every other user's streams, still count.
|
||||
EnableCameraIfUnderLimit(ctx context.Context, arg EnableCameraIfUnderLimitParams) (sql.Result, error)
|
||||
EnablePlugin(ctx context.Context, id int64) error
|
||||
// Mirror of EnableCameraIfUnderLimit: the count excludes the requester's
|
||||
// own screenshare flag so re-enable is idempotent at the cap (OC-0081).
|
||||
EnableScreenshareIfUnderLimit(ctx context.Context, arg EnableScreenshareIfUnderLimitParams) (sql.Result, error)
|
||||
EvictOldestSessions(ctx context.Context, arg EvictOldestSessionsParams) error
|
||||
ForceLogoutUser(ctx context.Context, userID int64) error
|
||||
|
||||
@@ -102,7 +102,7 @@ const enableCameraIfUnderLimit = `-- name: EnableCameraIfUnderLimit :execresult
|
||||
|
||||
UPDATE voice_states SET camera = 1
|
||||
WHERE voice_states.user_id = ? AND voice_states.channel_id = ?
|
||||
AND (SELECT COALESCE(SUM(vs2.camera), 0) + COALESCE(SUM(vs2.screenshare), 0) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) < ?4
|
||||
AND (SELECT COALESCE(SUM(vs2.camera), 0) + COALESCE(SUM(vs2.screenshare), 0) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) - voice_states.camera < ?4
|
||||
`
|
||||
|
||||
type EnableCameraIfUnderLimitParams struct {
|
||||
@@ -118,8 +118,15 @@ type EnableCameraIfUnderLimitParams struct {
|
||||
// OC-0023), and a single user with both flags set must consume two of the N
|
||||
// slots, not one (OC-0006) -- so both gates sum `vs2.camera + vs2.screenshare`
|
||||
// across the channel's rows rather than counting rows where either is set.
|
||||
// The enabling user's own bit is still 0 at gate time, so no self-exclusion
|
||||
// term is needed.
|
||||
// The enabling user's own bit for the flag being set CAN already be 1 at
|
||||
// gate time (a client that lost track of the server-side flag retries the
|
||||
// enable), so each gate excludes exactly that one bit from the count --
|
||||
// see the per-query comments below (OC-0081).
|
||||
// The channel-wide stream count excludes the requester's own camera flag
|
||||
// (subtracted via the correlated outer-row reference), so re-enabling an
|
||||
// already-set camera is idempotent at the cap instead of being refused
|
||||
// against the requester's own stream (OC-0081). Their screenshare, and
|
||||
// every other user's streams, still count.
|
||||
func (q *Queries) EnableCameraIfUnderLimit(ctx context.Context, arg EnableCameraIfUnderLimitParams) (sql.Result, error) {
|
||||
return q.db.ExecContext(ctx, enableCameraIfUnderLimit,
|
||||
arg.UserID,
|
||||
@@ -132,7 +139,7 @@ func (q *Queries) EnableCameraIfUnderLimit(ctx context.Context, arg EnableCamera
|
||||
const enableScreenshareIfUnderLimit = `-- name: EnableScreenshareIfUnderLimit :execresult
|
||||
UPDATE voice_states SET screenshare = 1
|
||||
WHERE voice_states.user_id = ? AND voice_states.channel_id = ?
|
||||
AND (SELECT COALESCE(SUM(vs2.camera), 0) + COALESCE(SUM(vs2.screenshare), 0) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) < ?4
|
||||
AND (SELECT COALESCE(SUM(vs2.camera), 0) + COALESCE(SUM(vs2.screenshare), 0) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) - voice_states.screenshare < ?4
|
||||
`
|
||||
|
||||
type EnableScreenshareIfUnderLimitParams struct {
|
||||
@@ -142,6 +149,8 @@ type EnableScreenshareIfUnderLimitParams struct {
|
||||
MaxVideo int64 `json:"maxVideo"`
|
||||
}
|
||||
|
||||
// Mirror of EnableCameraIfUnderLimit: the count excludes the requester's
|
||||
// own screenshare flag so re-enable is idempotent at the cap (OC-0081).
|
||||
func (q *Queries) EnableScreenshareIfUnderLimit(ctx context.Context, arg EnableScreenshareIfUnderLimitParams) (sql.Result, error) {
|
||||
return q.db.ExecContext(ctx, enableScreenshareIfUnderLimit,
|
||||
arg.UserID,
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package db_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// OC-0081: EnableCameraIfUnderLimit's guard subquery counted every video
|
||||
// stream in the channel INCLUDING the requester's own camera row, so a user
|
||||
// whose server-side camera flag was already 1 could never re-enable at the
|
||||
// cap: the sole publisher in a max_video=1 room got VIDEO_LIMIT against
|
||||
// their own stream, with no path out (the client believes the camera is off
|
||||
// and never sends a disable). Re-enable must be idempotent; a genuinely new
|
||||
// publisher at the cap must still be refused; the requester's OTHER stream
|
||||
// (screenshare) must still count against enabling their camera.
|
||||
|
||||
func TestVoice_EnableCameraIfUnderLimit_ReEnableIdempotentAtCap(t *testing.T) {
|
||||
database := newVoiceTestDB(t)
|
||||
u1 := seedVoiceUser(t, database, "cam-reen-u1")
|
||||
chanID := seedVoiceChannel(t, database, "cam-reen-ch")
|
||||
_ = database.JoinVoiceChannel(context.Background(), u1, chanID)
|
||||
|
||||
ok, err := database.EnableCameraIfUnderLimit(context.Background(), u1, chanID, 1)
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("first enable: ok=%v err=%v, want true", ok, err)
|
||||
}
|
||||
|
||||
// Same user, camera row already 1, still the only stream in the room.
|
||||
ok, err = database.EnableCameraIfUnderLimit(context.Background(), u1, chanID, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("re-enable: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("re-enable at the cap was refused against the requester's own camera row")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoice_EnableCameraIfUnderLimit_OtherUserStillRefusedAtCap(t *testing.T) {
|
||||
database := newVoiceTestDB(t)
|
||||
u1 := seedVoiceUser(t, database, "cam-cap-u1")
|
||||
u2 := seedVoiceUser(t, database, "cam-cap-u2")
|
||||
chanID := seedVoiceChannel(t, database, "cam-cap-ch")
|
||||
_ = database.JoinVoiceChannel(context.Background(), u1, chanID)
|
||||
_ = database.JoinVoiceChannel(context.Background(), u2, chanID)
|
||||
|
||||
if ok, err := database.EnableCameraIfUnderLimit(context.Background(), u1, chanID, 1); err != nil || !ok {
|
||||
t.Fatalf("u1 enable: ok=%v err=%v, want true", ok, err)
|
||||
}
|
||||
ok, err := database.EnableCameraIfUnderLimit(context.Background(), u2, chanID, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("u2 enable: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Error("a new publisher was admitted past the video cap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoice_EnableCameraIfUnderLimit_OwnScreenshareStillCounts(t *testing.T) {
|
||||
database := newVoiceTestDB(t)
|
||||
u1 := seedVoiceUser(t, database, "cam-ss-u1")
|
||||
chanID := seedVoiceChannel(t, database, "cam-ss-ch")
|
||||
_ = database.JoinVoiceChannel(context.Background(), u1, chanID)
|
||||
|
||||
if ok, err := database.EnableScreenshareIfUnderLimit(context.Background(), u1, chanID, 1); err != nil || !ok {
|
||||
t.Fatalf("screenshare enable: ok=%v err=%v, want true", ok, err)
|
||||
}
|
||||
// The camera would be a SECOND stream from this user; the cap is 1.
|
||||
ok, err := database.EnableCameraIfUnderLimit(context.Background(), u1, chanID, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("camera enable: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Error("own screenshare must still count toward the cap when enabling the camera")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoice_EnableScreenshareIfUnderLimit_ReEnableIdempotentAtCap(t *testing.T) {
|
||||
database := newVoiceTestDB(t)
|
||||
u1 := seedVoiceUser(t, database, "ss-reen-u1")
|
||||
chanID := seedVoiceChannel(t, database, "ss-reen-ch")
|
||||
_ = database.JoinVoiceChannel(context.Background(), u1, chanID)
|
||||
|
||||
if ok, err := database.EnableScreenshareIfUnderLimit(context.Background(), u1, chanID, 1); err != nil || !ok {
|
||||
t.Fatalf("first enable: ok=%v err=%v, want true", ok, err)
|
||||
}
|
||||
ok, err := database.EnableScreenshareIfUnderLimit(context.Background(), u1, chanID, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("re-enable: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("screenshare re-enable at the cap was refused against the requester's own row")
|
||||
}
|
||||
}
|
||||
@@ -99,18 +99,27 @@ UPDATE voice_states SET server_deafened = 0 WHERE user_id = ? AND channel_id = ?
|
||||
-- OC-0023), and a single user with both flags set must consume two of the N
|
||||
-- slots, not one (OC-0006) -- so both gates sum `vs2.camera + vs2.screenshare`
|
||||
-- across the channel's rows rather than counting rows where either is set.
|
||||
-- The enabling user's own bit is still 0 at gate time, so no self-exclusion
|
||||
-- term is needed.
|
||||
-- The enabling user's own bit for the flag being set CAN already be 1 at
|
||||
-- gate time (a client that lost track of the server-side flag retries the
|
||||
-- enable), so each gate excludes exactly that one bit from the count --
|
||||
-- see the per-query comments below (OC-0081).
|
||||
|
||||
-- name: EnableCameraIfUnderLimit :execresult
|
||||
-- The channel-wide stream count excludes the requester's own camera flag
|
||||
-- (subtracted via the correlated outer-row reference), so re-enabling an
|
||||
-- already-set camera is idempotent at the cap instead of being refused
|
||||
-- against the requester's own stream (OC-0081). Their screenshare, and
|
||||
-- every other user's streams, still count.
|
||||
UPDATE voice_states SET camera = 1
|
||||
WHERE voice_states.user_id = ? AND voice_states.channel_id = ?
|
||||
AND (SELECT COALESCE(SUM(vs2.camera), 0) + COALESCE(SUM(vs2.screenshare), 0) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) < sqlc.arg(max_video);
|
||||
AND (SELECT COALESCE(SUM(vs2.camera), 0) + COALESCE(SUM(vs2.screenshare), 0) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) - voice_states.camera < sqlc.arg(max_video);
|
||||
|
||||
-- name: EnableScreenshareIfUnderLimit :execresult
|
||||
-- Mirror of EnableCameraIfUnderLimit: the count excludes the requester's
|
||||
-- own screenshare flag so re-enable is idempotent at the cap (OC-0081).
|
||||
UPDATE voice_states SET screenshare = 1
|
||||
WHERE voice_states.user_id = ? AND voice_states.channel_id = ?
|
||||
AND (SELECT COALESCE(SUM(vs2.camera), 0) + COALESCE(SUM(vs2.screenshare), 0) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) < sqlc.arg(max_video);
|
||||
AND (SELECT COALESCE(SUM(vs2.camera), 0) + COALESCE(SUM(vs2.screenshare), 0) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) - voice_states.screenshare < sqlc.arg(max_video);
|
||||
|
||||
-- name: ClearVoiceState :exec
|
||||
DELETE FROM voice_states WHERE user_id = ?;
|
||||
|
||||
Reference in New Issue
Block a user