- Extract WebSocket protocol handling to dedicated module - Add helper functions for DB operations, validation, and responses - Improve WebSocket frame handling and opcodes - Refactor dataset, job, and Jupyter handlers - Add duplicate detection processing
121 lines
3.5 KiB
Go
121 lines
3.5 KiB
Go
// Package helpers provides shared utilities for WebSocket handlers.
|
|
package helpers
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
)
|
|
|
|
// PayloadParser provides helpers for parsing binary WebSocket payloads.
|
|
type PayloadParser struct {
|
|
payload []byte
|
|
offset int
|
|
}
|
|
|
|
// NewPayloadParser creates a new payload parser starting after the API key hash.
|
|
func NewPayloadParser(payload []byte, apiKeyHashLen int) *PayloadParser {
|
|
return &PayloadParser{
|
|
payload: payload,
|
|
offset: apiKeyHashLen,
|
|
}
|
|
}
|
|
|
|
// ParseByte parses a single byte and advances the offset.
|
|
func (p *PayloadParser) ParseByte() (byte, error) {
|
|
if p.offset >= len(p.payload) {
|
|
return 0, fmt.Errorf("payload too short at offset %d", p.offset)
|
|
}
|
|
b := p.payload[p.offset]
|
|
p.offset++
|
|
return b, nil
|
|
}
|
|
|
|
// ParseUint16 parses a 2-byte big-endian uint16 and advances the offset.
|
|
func (p *PayloadParser) ParseUint16() (uint16, error) {
|
|
if p.offset+2 > len(p.payload) {
|
|
return 0, fmt.Errorf("payload too short for uint16 at offset %d", p.offset)
|
|
}
|
|
v := binary.BigEndian.Uint16(p.payload[p.offset : p.offset+2])
|
|
p.offset += 2
|
|
return v, nil
|
|
}
|
|
|
|
// ParseLengthPrefixedString parses a length-prefixed string.
|
|
// Format: [length:1][string:var]
|
|
func (p *PayloadParser) ParseLengthPrefixedString() (string, error) {
|
|
if p.offset >= len(p.payload) {
|
|
return "", fmt.Errorf("payload too short for length at offset %d", p.offset)
|
|
}
|
|
length := int(p.payload[p.offset])
|
|
p.offset++
|
|
if length < 0 {
|
|
return "", fmt.Errorf("invalid negative length at offset %d", p.offset-1)
|
|
}
|
|
if p.offset+length > len(p.payload) {
|
|
return "", fmt.Errorf("payload too short for string of length %d at offset %d", length, p.offset)
|
|
}
|
|
str := string(p.payload[p.offset : p.offset+length])
|
|
p.offset += length
|
|
return str, nil
|
|
}
|
|
|
|
// ParseUint16PrefixedString parses a string prefixed by a 2-byte length.
|
|
// Format: [length:2][string:var]
|
|
func (p *PayloadParser) ParseUint16PrefixedString() (string, error) {
|
|
if p.offset+2 > len(p.payload) {
|
|
return "", fmt.Errorf("payload too short for uint16 length at offset %d", p.offset)
|
|
}
|
|
length := int(binary.BigEndian.Uint16(p.payload[p.offset : p.offset+2]))
|
|
p.offset += 2
|
|
if length < 0 {
|
|
return "", fmt.Errorf("invalid negative length at offset %d", p.offset-2)
|
|
}
|
|
if p.offset+length > len(p.payload) {
|
|
return "", fmt.Errorf("payload too short for string of length %d at offset %d", length, p.offset)
|
|
}
|
|
str := string(p.payload[p.offset : p.offset+length])
|
|
p.offset += length
|
|
return str, nil
|
|
}
|
|
|
|
// Payload returns the underlying payload bytes.
|
|
func (p *PayloadParser) Payload() []byte {
|
|
return p.payload
|
|
}
|
|
|
|
// Offset returns the current offset into the payload.
|
|
func (p *PayloadParser) Offset() int {
|
|
return p.offset
|
|
}
|
|
|
|
// HasRemaining returns true if there are remaining bytes.
|
|
func (p *PayloadParser) HasRemaining() bool {
|
|
return p.offset < len(p.payload)
|
|
}
|
|
|
|
// Remaining returns the remaining bytes in the payload from current offset.
|
|
func (p *PayloadParser) Remaining() []byte {
|
|
if p.offset >= len(p.payload) {
|
|
return nil
|
|
}
|
|
return p.payload[p.offset:]
|
|
}
|
|
|
|
// ParseBool parses a byte as a boolean (0 = false, non-zero = true).
|
|
func (p *PayloadParser) ParseBool() (bool, error) {
|
|
b, err := p.ParseByte()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return b != 0, nil
|
|
}
|
|
|
|
// ParseFixedBytes parses a fixed-length byte slice.
|
|
func (p *PayloadParser) ParseFixedBytes(length int) ([]byte, error) {
|
|
if p.offset+length > len(p.payload) {
|
|
return nil, fmt.Errorf("payload too short for %d bytes at offset %d", length, p.offset)
|
|
}
|
|
bytes := p.payload[p.offset : p.offset+length]
|
|
p.offset += length
|
|
return bytes, nil
|
|
}
|