chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -0,0 +1,282 @@
package postgres
import (
"context"
"errors"
"fmt"
"github.com/jackc/pgx/v5"
"telesrv/internal/domain"
"telesrv/internal/store/postgres/sqlcgen"
)
// EncryptedQueueStore 是 store.EncryptedQueueStore 的 PostgreSQL 实现(迁移 0138
// 盲中继 qts 投递队列qts 分配secret_qts_watermarks.reserved_qts 自增)+ 写队列行
// 在单事务内完成,保证设备 qts 无空洞。bytes 原样 BYTEA 存储,永不解密。
type EncryptedQueueStore struct {
db sqlcgen.DBTX
}
// NewEncryptedQueueStore 基于 pgx 连接池创建 EncryptedQueueStore。
func NewEncryptedQueueStore(db sqlcgen.DBTX) *EncryptedQueueStore {
return &EncryptedQueueStore{db: db}
}
const encryptedMessageColumns = `receiver_auth_key_id, qts, receiver_user_id, chat_id, random_id,
date, is_service, bytes, file_id, file_access_hash, file_size, file_dc_id, file_key_fingerprint`
func scanEncryptedMessage(row rowScanner) (domain.SecretChatMessage, error) {
var m domain.SecretChatMessage
var (
fileID, fileAccessHash, fileSize *int64
fileDC, fileKeyFP *int32
)
if err := row.Scan(
&m.ReceiverAuthKeyID, &m.Qts, &m.ReceiverUserID, &m.ChatID, &m.RandomID,
&m.Date, &m.IsService, &m.Bytes, &fileID, &fileAccessHash, &fileSize, &fileDC, &fileKeyFP,
); err != nil {
return domain.SecretChatMessage{}, err
}
if fileID != nil {
ref := domain.EncryptedFileRef{ID: *fileID}
if fileAccessHash != nil {
ref.AccessHash = *fileAccessHash
}
if fileSize != nil {
ref.Size = *fileSize
}
if fileDC != nil {
ref.DCID = int(*fileDC)
}
if fileKeyFP != nil {
ref.KeyFingerprint = int(*fileKeyFP)
}
m.File = &ref
}
return m, nil
}
func (s *EncryptedQueueStore) begin(ctx context.Context, op string) (pgx.Tx, error) {
beginner, ok := s.db.(txBeginner)
if !ok {
return nil, fmt.Errorf("%s: db does not support transactions", op)
}
tx, err := beginner.Begin(ctx)
if err != nil {
return nil, fmt.Errorf("begin %s: %w", op, err)
}
return tx, nil
}
func (s *EncryptedQueueStore) AppendEncryptedMessage(ctx context.Context, msg domain.SecretChatMessage) (domain.SecretChatMessage, bool, error) {
tx, err := s.begin(ctx, "append encrypted message")
if err != nil {
return domain.SecretChatMessage{}, false, err
}
committed := false
defer func() {
if !committed {
_ = tx.Rollback(ctx)
}
}()
// 幂等:同接收设备同 chat 同 random_id 已存在 → 返回既有行(不重分配 qts
existing, err := scanEncryptedMessage(tx.QueryRow(ctx,
`SELECT `+encryptedMessageColumns+` FROM encrypted_message_queue
WHERE receiver_auth_key_id = $1 AND chat_id = $2 AND random_id = $3`,
msg.ReceiverAuthKeyID, msg.ChatID, msg.RandomID))
if err == nil {
if cerr := tx.Commit(ctx); cerr != nil {
return domain.SecretChatMessage{}, false, fmt.Errorf("commit append (dedup): %w", cerr)
}
committed = true
return existing, true, nil
}
if !errors.Is(err, pgx.ErrNoRows) {
return domain.SecretChatMessage{}, false, fmt.Errorf("dedup lookup: %w", err)
}
// 分配下一个 qts首值 1与写队列同事务。
var qts int
if err := tx.QueryRow(ctx, `
INSERT INTO secret_qts_watermarks (auth_key_id, reserved_qts)
VALUES ($1, 1)
ON CONFLICT (auth_key_id) DO UPDATE SET reserved_qts = secret_qts_watermarks.reserved_qts + 1, updated_at = now()
RETURNING reserved_qts`, msg.ReceiverAuthKeyID).Scan(&qts); err != nil {
return domain.SecretChatMessage{}, false, fmt.Errorf("reserve device qts: %w", err)
}
msg.Qts = qts
var (
fileID, fileAccessHash, fileSize any
fileDC, fileKeyFP any
)
if msg.File != nil {
fileID = msg.File.ID
fileAccessHash = msg.File.AccessHash
fileSize = msg.File.Size
fileDC = int32(msg.File.DCID)
fileKeyFP = int32(msg.File.KeyFingerprint)
}
if _, err := tx.Exec(ctx, `
INSERT INTO encrypted_message_queue (receiver_auth_key_id, qts, receiver_user_id, chat_id, random_id,
date, is_service, bytes, file_id, file_access_hash, file_size, file_dc_id, file_key_fingerprint)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
msg.ReceiverAuthKeyID, msg.Qts, msg.ReceiverUserID, msg.ChatID, msg.RandomID,
msg.Date, msg.IsService, msg.Bytes, fileID, fileAccessHash, fileSize, fileDC, fileKeyFP); err != nil {
return domain.SecretChatMessage{}, false, fmt.Errorf("insert encrypted message: %w", err)
}
if err := tx.Commit(ctx); err != nil {
return domain.SecretChatMessage{}, false, fmt.Errorf("commit append encrypted message: %w", err)
}
committed = true
return msg, false, nil
}
func (s *EncryptedQueueStore) ListEncryptedMessagesSince(ctx context.Context, receiverAuthKeyID int64, sinceQts, limit int) ([]domain.SecretChatMessage, error) {
if limit <= 0 || limit > 1000 {
limit = 1000
}
rows, err := s.db.Query(ctx,
`SELECT `+encryptedMessageColumns+` FROM encrypted_message_queue
WHERE receiver_auth_key_id = $1 AND qts > $2 ORDER BY qts ASC LIMIT $3`,
receiverAuthKeyID, sinceQts, limit)
if err != nil {
return nil, fmt.Errorf("list encrypted messages: %w", err)
}
defer rows.Close()
var out []domain.SecretChatMessage
for rows.Next() {
m, err := scanEncryptedMessage(rows)
if err != nil {
return nil, err
}
out = append(out, m)
}
if err := rows.Err(); err != nil {
return nil, err
}
return out, nil
}
func (s *EncryptedQueueStore) ReservedQts(ctx context.Context, receiverAuthKeyID int64) (int, error) {
var qts int
err := s.db.QueryRow(ctx,
`SELECT reserved_qts FROM secret_qts_watermarks WHERE auth_key_id = $1`, receiverAuthKeyID).Scan(&qts)
if errors.Is(err, pgx.ErrNoRows) {
return 0, nil
}
if err != nil {
return 0, fmt.Errorf("reserved qts: %w", err)
}
return qts, nil
}
func (s *EncryptedQueueStore) AckEncryptedMessages(ctx context.Context, receiverAuthKeyID int64, maxQts int) error {
if maxQts <= 0 {
return nil
}
// 推进 confirmed_qtsGREATEST 幂等,回退忽略)。
if _, err := s.db.Exec(ctx, `
INSERT INTO secret_qts_watermarks (auth_key_id, confirmed_qts)
VALUES ($1, $2)
ON CONFLICT (auth_key_id) DO UPDATE SET confirmed_qts = GREATEST(secret_qts_watermarks.confirmed_qts, EXCLUDED.confirmed_qts), updated_at = now()`,
receiverAuthKeyID, maxQts); err != nil {
return fmt.Errorf("advance confirmed qts: %w", err)
}
if _, err := s.db.Exec(ctx, `
UPDATE encrypted_message_queue SET acked = true
WHERE receiver_auth_key_id = $1 AND qts <= $2 AND NOT acked`, receiverAuthKeyID, maxQts); err != nil {
return fmt.Errorf("ack encrypted messages: %w", err)
}
return nil
}
func (s *EncryptedQueueStore) AppendStateEvent(ctx context.Context, ev domain.EncryptedStateEvent) (int64, error) {
var id int64
if err := s.db.QueryRow(ctx, `
INSERT INTO encrypted_state_events (target_user_id, target_auth_key_id, chat_id, event_type, max_date, date)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id`,
ev.TargetUserID, ev.TargetAuthKeyID, ev.ChatID, int16(ev.Type), ev.MaxDate, ev.Date).Scan(&id); err != nil {
return 0, fmt.Errorf("append state event: %w", err)
}
return id, nil
}
func (s *EncryptedQueueStore) ListUndeliveredStateEvents(ctx context.Context, targetUserID, deviceAuthKeyID int64, limit int) ([]domain.EncryptedStateEvent, error) {
if limit <= 0 || limit > 1000 {
limit = 1000
}
rows, err := s.db.Query(ctx, `
SELECT e.id, e.target_user_id, e.target_auth_key_id, e.chat_id, e.event_type, e.max_date, e.date
FROM encrypted_state_events e
WHERE e.target_user_id = $1
AND (e.target_auth_key_id = 0 OR e.target_auth_key_id = $2)
AND NOT EXISTS (SELECT 1 FROM encrypted_state_event_delivery d WHERE d.event_id = e.id AND d.auth_key_id = $2)
ORDER BY e.id ASC LIMIT $3`, targetUserID, deviceAuthKeyID, limit)
if err != nil {
return nil, fmt.Errorf("list undelivered state events: %w", err)
}
defer rows.Close()
var out []domain.EncryptedStateEvent
for rows.Next() {
var ev domain.EncryptedStateEvent
var typ int16
if err := rows.Scan(&ev.ID, &ev.TargetUserID, &ev.TargetAuthKeyID, &ev.ChatID, &typ, &ev.MaxDate, &ev.Date); err != nil {
return nil, err
}
ev.Type = domain.EncryptedStateEventType(typ)
out = append(out, ev)
}
if err := rows.Err(); err != nil {
return nil, err
}
return out, nil
}
func (s *EncryptedQueueStore) MarkStateEventsDelivered(ctx context.Context, deviceAuthKeyID int64, eventIDs []int64) error {
if len(eventIDs) == 0 {
return nil
}
if _, err := s.db.Exec(ctx, `
INSERT INTO encrypted_state_event_delivery (event_id, auth_key_id)
SELECT unnest($1::bigint[]), $2
ON CONFLICT (event_id, auth_key_id) DO NOTHING`, eventIDs, deviceAuthKeyID); err != nil {
return fmt.Errorf("mark state events delivered: %w", err)
}
return nil
}
func (s *EncryptedQueueStore) PutEncryptedFile(ctx context.Context, ownerUserID int64, ref domain.EncryptedFileRef) error {
if _, err := s.db.Exec(ctx, `
INSERT INTO encrypted_files (id, access_hash, owner_user_id, size, dc_id, key_fingerprint)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (id) DO UPDATE SET access_hash = EXCLUDED.access_hash, size = EXCLUDED.size,
dc_id = EXCLUDED.dc_id, key_fingerprint = EXCLUDED.key_fingerprint`,
ref.ID, ref.AccessHash, ownerUserID, ref.Size, int32(ref.DCID), int32(ref.KeyFingerprint)); err != nil {
return fmt.Errorf("put encrypted file: %w", err)
}
return nil
}
func (s *EncryptedQueueStore) GetEncryptedFile(ctx context.Context, id, accessHash int64) (domain.EncryptedFileRef, bool, error) {
var (
ref domain.EncryptedFileRef
dc int32
keyFP int32
)
err := s.db.QueryRow(ctx,
`SELECT id, access_hash, size, dc_id, key_fingerprint FROM encrypted_files WHERE id = $1 AND access_hash = $2`,
id, accessHash).Scan(&ref.ID, &ref.AccessHash, &ref.Size, &dc, &keyFP)
if errors.Is(err, pgx.ErrNoRows) {
return domain.EncryptedFileRef{}, false, nil
}
if err != nil {
return domain.EncryptedFileRef{}, false, fmt.Errorf("get encrypted file: %w", err)
}
ref.DCID = int(dc)
ref.KeyFingerprint = int(keyFP)
return ref, true, nil
}