dae/component/sniffing/internal/quicutils/relocation.go
2024-01-04 17:28:16 +08:00

265 lines
6.0 KiB
Go

/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
*/
package quicutils
import (
"fmt"
"io/fs"
"sort"
)
var (
ErrUnknownFrameType = fmt.Errorf("unknown frame type")
ErrOutOfRange = fmt.Errorf("index out of range")
)
const (
Quic_FrameType_Padding = 0
Quic_FrameType_Ping = 1
Quic_FrameType_Crypto = 6
Quic_FrameType_ConnectionClose = 0x1c
Quic_FrameType_ConnectionClose2 = 0x1d
)
type CryptoFrameOffset struct {
UpperAppOffset int
// Offset of data in quic payload.
Data []byte
}
func ReassembleCryptos(offsets []*CryptoFrameOffset, newPayload []byte) (newOffsets []*CryptoFrameOffset, err error) {
oldLen := len(offsets)
var frameSize int
var offset *CryptoFrameOffset
var boundary int
// Extract crypto frames.
for iNextFrame := 0; iNextFrame < len(newPayload); iNextFrame += frameSize {
offset, frameSize, err = ExtractCryptoFrameOffset(newPayload[iNextFrame:], iNextFrame)
if err != nil {
return nil, err
}
if offset == nil {
continue
}
offsets = append(offsets, offset)
if offset.UpperAppOffset+len(offset.Data) > boundary {
boundary = offset.UpperAppOffset + len(offset.Data)
}
}
// Sort the new part.
newPart := offsets[oldLen:]
sort.Slice(newPart, func(i, j int) bool {
return newPart[i].UpperAppOffset < newPart[j].UpperAppOffset
})
// Insertion sort.
for i := oldLen; i < len(offsets); i++ {
item := offsets[i]
j := i - 1
for ; j >= 0; j-- {
if item.UpperAppOffset < offsets[j].UpperAppOffset {
offsets[j+1] = offsets[j]
} else {
if offsets[j+1] != item {
offsets[j+1] = item
}
break
}
}
if j < 0 {
offsets[0] = item
}
}
return offsets, nil
}
func ExtractCryptoFrameOffset(remainder []byte, transportOffset int) (offset *CryptoFrameOffset, frameSize int, err error) {
if len(remainder) == 0 {
return nil, 0, fmt.Errorf("frame has no length: %w", ErrOutOfRange)
}
frameType, nextField, err := BigEndianUvarint(remainder)
if err != nil {
return nil, 0, err
}
switch frameType {
case Quic_FrameType_Ping:
return nil, nextField, nil
case Quic_FrameType_Padding:
for ; nextField < len(remainder) && remainder[nextField] == 0; nextField++ {
}
return nil, nextField, nil
case Quic_FrameType_Crypto:
offset, n, err := BigEndianUvarint(remainder[nextField:])
if err != nil {
return nil, 0, err
}
nextField += n
length, n, err := BigEndianUvarint(remainder[nextField:])
if err != nil {
return nil, 0, err
}
nextField += n
return &CryptoFrameOffset{
UpperAppOffset: int(offset),
Data: remainder[nextField : nextField+int(length)],
}, nextField + int(length), nil
case Quic_FrameType_ConnectionClose, Quic_FrameType_ConnectionClose2:
return nil, 0, fmt.Errorf("connection closed: %w", fs.ErrClosed)
default:
return nil, 0, fmt.Errorf("%w: %v", ErrUnknownFrameType, frameType)
}
}
var (
ErrMissingCrypto = fmt.Errorf("missing crypto frame")
)
type Locator interface {
Range(i, j int) ([]byte, error)
Slice(i, j int) (Locator, error)
At(i int) (byte, error)
Len() int
Bytes() ([]byte, error)
}
// LinearLocator only searches forward and have no boundary check.
type LinearLocator struct {
left int
length int
iOuter int
baseEnd int
baseStart int
baseData []byte
o []*CryptoFrameOffset
}
func NewLinearLocator(o []*CryptoFrameOffset) *LinearLocator {
if len(o) == 0 {
return &LinearLocator{}
}
return &LinearLocator{
left: 0,
length: o[len(o)-1].UpperAppOffset + len(o[len(o)-1].Data),
iOuter: 0,
baseData: o[0].Data,
baseStart: o[0].UpperAppOffset,
baseEnd: o[0].UpperAppOffset + len(o[0].Data),
o: o,
}
}
func (l *LinearLocator) relocate(i int) error {
// Relocate ll.iOuter.
for i >= l.baseEnd {
if l.iOuter+1 >= len(l.o) {
return ErrMissingCrypto
}
l.iOuter++
l.baseData = l.o[l.iOuter].Data
l.baseStart = l.o[l.iOuter].UpperAppOffset
l.baseEnd = l.baseStart + len(l.baseData)
}
if i < l.baseStart {
return ErrMissingCrypto
}
return nil
}
func (l *LinearLocator) Range(i, j int) ([]byte, error) {
if i == j {
return []byte{}, nil
}
if len(l.o) == 0 {
return nil, ErrMissingCrypto
}
size := j - i
// We find bytes including i and j, so we should sub j with 1.
i += l.left
j += l.left - 1
if err := l.relocate(i); err != nil {
return nil, err
}
// Linearly copy.
if j < l.baseEnd {
// In the same block, no copy needed.
return l.baseData[i-l.baseStart : j-l.baseStart+1], nil
}
b := make([]byte, size)
k := 0
for j >= l.baseEnd {
n := copy(b[k:], l.baseData[i-l.baseStart:])
k += n
i += n
if l.iOuter+1 >= len(l.o) || l.o[l.iOuter].UpperAppOffset+len(l.o[l.iOuter+1].Data) != l.o[l.iOuter].UpperAppOffset {
// Some crypto is missing.
return nil, ErrMissingCrypto
}
l.iOuter++
l.baseData = l.o[l.iOuter].Data
l.baseStart = l.o[l.iOuter].UpperAppOffset
l.baseEnd = l.baseStart + len(l.baseData)
}
copy(b[k:], l.baseData[i-l.baseStart:j-l.baseStart+1])
return b, nil
}
func (l *LinearLocator) At(i int) (byte, error) {
if len(l.o) == 0 {
return 0, ErrMissingCrypto
}
i += l.left
if err := l.relocate(i); err != nil {
return 0, err
}
b := l.baseData[i-l.baseStart]
return b, nil
}
func (l *LinearLocator) Slice(i, j int) (Locator, error) {
// We do not care about right.
newLL := *l
newLL.left += i
newLL.length = j - i + 1
return &newLL, nil
}
func (l *LinearLocator) Bytes() ([]byte, error) {
return l.Range(0, l.length)
}
var _ Locator = &LinearLocator{}
func (l *LinearLocator) Len() int {
return l.length
}
type BuiltinBytesLocator []byte
func (l BuiltinBytesLocator) Range(i, j int) ([]byte, error) {
return l[i:j], nil
}
func (l BuiltinBytesLocator) At(i int) (byte, error) {
return l[i], nil
}
func (l BuiltinBytesLocator) Slice(i, j int) (Locator, error) {
return l[i:j], nil
}
func (l BuiltinBytesLocator) Len() int {
return len(l)
}
func (l BuiltinBytesLocator) Bytes() ([]byte, error) {
return l, nil
}
var _ Locator = BuiltinBytesLocator{}