mirror of
https://github.com/daeuniverse/dae.git
synced 2025-01-09 23:10:56 +07:00
339 lines
8.6 KiB
Go
339 lines
8.6 KiB
Go
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
|
*/
|
|
|
|
package quicutils
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/mzz2017/softwind/pool"
|
|
"io/fs"
|
|
"sort"
|
|
)
|
|
|
|
var (
|
|
UnknownFrameTypeError = fmt.Errorf("unknown frame type")
|
|
OutOfRangeError = 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
|
|
}
|
|
|
|
type CryptoFrameRelocation struct {
|
|
payload []byte
|
|
o []*CryptoFrameOffset
|
|
length int
|
|
}
|
|
|
|
func NewCryptoFrameRelocation(plaintextPayload []byte) (cryptoRelocation *CryptoFrameRelocation, err error) {
|
|
var frameSize int
|
|
var offset *CryptoFrameOffset
|
|
cryptoRelocation = &CryptoFrameRelocation{
|
|
payload: plaintextPayload,
|
|
o: nil,
|
|
}
|
|
|
|
// Extract crypto frames.
|
|
for iNextFrame := 0; iNextFrame < len(plaintextPayload); iNextFrame += frameSize {
|
|
offset, frameSize, err = ExtractCryptoFrameOffset(plaintextPayload[iNextFrame:], iNextFrame)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if offset == nil {
|
|
continue
|
|
}
|
|
cryptoRelocation.o = append(cryptoRelocation.o, offset)
|
|
}
|
|
|
|
// Sort offsets by UpperAppOffset.
|
|
sort.Slice(cryptoRelocation.o, func(i, j int) bool {
|
|
return cryptoRelocation.o[i].UpperAppOffset < cryptoRelocation.o[j].UpperAppOffset
|
|
})
|
|
|
|
// Store length.
|
|
left := cryptoRelocation.o[0]
|
|
right := cryptoRelocation.o[len(cryptoRelocation.o)-1]
|
|
cryptoRelocation.length = right.UpperAppOffset + len(right.Data) - left.UpperAppOffset
|
|
|
|
return cryptoRelocation, nil
|
|
}
|
|
|
|
func ReassembleCryptoToBytesFromPool(plaintextPayload []byte) (b []byte, err error) {
|
|
var frameSize int
|
|
var offset *CryptoFrameOffset
|
|
var boundary int
|
|
b = pool.Get(len(plaintextPayload))
|
|
// Extract crypto frames.
|
|
for iNextFrame := 0; iNextFrame < len(plaintextPayload); iNextFrame += frameSize {
|
|
offset, frameSize, err = ExtractCryptoFrameOffset(plaintextPayload[iNextFrame:], iNextFrame)
|
|
if err != nil {
|
|
pool.Put(b)
|
|
return nil, err
|
|
}
|
|
if offset == nil {
|
|
continue
|
|
}
|
|
copy(b[offset.UpperAppOffset:], offset.Data)
|
|
if offset.UpperAppOffset+len(offset.Data) > boundary {
|
|
boundary = offset.UpperAppOffset + len(offset.Data)
|
|
}
|
|
}
|
|
return b[:boundary], 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", OutOfRangeError)
|
|
}
|
|
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", UnknownFrameTypeError, frameType)
|
|
}
|
|
}
|
|
|
|
func (r *CryptoFrameRelocation) BinarySearch(iUpper int, leftOuter, rightOuter int) (iOuter int, iInner int, err error) {
|
|
rightOuterInstance := r.o[rightOuter]
|
|
if iUpper < r.o[leftOuter].UpperAppOffset || iUpper >= rightOuterInstance.UpperAppOffset+len(rightOuterInstance.Data) {
|
|
return 0, 0, fmt.Errorf("%w: %v is not in [%v, %v)", OutOfRangeError, iUpper, r.o[leftOuter].UpperAppOffset, rightOuterInstance.UpperAppOffset+len(rightOuterInstance.Data))
|
|
}
|
|
for leftOuter < rightOuter {
|
|
mid := leftOuter + ((rightOuter - leftOuter) >> 1)
|
|
if iUpper < r.o[mid].UpperAppOffset {
|
|
rightOuter = mid - 1
|
|
} else if iUpper >= r.o[mid].UpperAppOffset {
|
|
if iUpper < r.o[mid].UpperAppOffset+len(r.o[mid].Data) {
|
|
return mid, iUpper - r.o[mid].UpperAppOffset, nil
|
|
} else {
|
|
leftOuter = mid + 1
|
|
}
|
|
}
|
|
}
|
|
return leftOuter, iUpper - r.o[leftOuter].UpperAppOffset, nil
|
|
}
|
|
|
|
func (r *CryptoFrameRelocation) BytesFromPool() []byte {
|
|
if len(r.o) == 0 {
|
|
return pool.Get(0)
|
|
}
|
|
right := r.o[len(r.o)-1]
|
|
return r.copyBytesToPool(0, 0, len(r.o)-1, len(right.Data)-1, r.length)
|
|
}
|
|
|
|
// RangeFromPool copy bytes from iUpperAppOffset to jUpperAppOffset.
|
|
// It is not suggested to use it for large range and frequent copy.
|
|
func (r *CryptoFrameRelocation) RangeFromPool(i, j int) []byte {
|
|
if i > j {
|
|
panic(fmt.Sprintf("i > j: %v > %v", i, j))
|
|
}
|
|
// We find bytes including i and j, so we should sub j with 1.
|
|
j--
|
|
|
|
// Find i.
|
|
iOuter, iInner, err := r.BinarySearch(i, 0, len(r.o)-1)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// Check if j and i is in the same outer or adjacent outers.
|
|
// It is very common because we usually have small access range.
|
|
var jOuter, jInner int
|
|
if iInner+j-i < len(r.o[iOuter].Data) {
|
|
jOuter = iOuter
|
|
jInner = iInner + j - i
|
|
} else if iOuter+1 < len(r.o) && j < r.o[iOuter+1].UpperAppOffset+len(r.o[iOuter+1].Data) {
|
|
jOuter = iOuter + 1
|
|
jInner = (j - i) + (len(r.o[iOuter].Data) - iInner)
|
|
} else {
|
|
// We have searched iOuter and iOuter+1
|
|
jOuter, jInner, err = r.BinarySearch(j, iOuter+2, len(r.o)-1)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
return r.copyBytesToPool(iOuter, iInner, jOuter, jInner, j-i+1)
|
|
}
|
|
|
|
// copyBytesToPool copy bytes including i and j.
|
|
func (r *CryptoFrameRelocation) copyBytesToPool(iOuter, iInner, jOuter, jInner, size int) []byte {
|
|
b := pool.Get(size)
|
|
//io := r.o[iOuter]
|
|
k := 0
|
|
for {
|
|
// Most accesses are small range accesses.
|
|
base := r.o[iOuter].Data
|
|
if iOuter == jOuter {
|
|
k += copy(b[k:], base[iInner:jInner+1])
|
|
if k != size {
|
|
panic("unmatched size")
|
|
}
|
|
return b
|
|
} else {
|
|
k += copy(b[k:], base[iInner:])
|
|
if iInner != 0 {
|
|
iInner = 0
|
|
}
|
|
iOuter++
|
|
}
|
|
}
|
|
}
|
|
func (r *CryptoFrameRelocation) At(i int) byte {
|
|
iOuter, iInner, err := r.BinarySearch(i, 0, len(r.o)-1)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return r.o[iOuter].Data[iInner]
|
|
}
|
|
|
|
func (r *CryptoFrameRelocation) Len() int {
|
|
return r.length
|
|
}
|
|
|
|
type Locator interface {
|
|
Range(i, j int) []byte
|
|
Slice(i, j int) Locator
|
|
At(i int) byte
|
|
Len() int
|
|
}
|
|
|
|
// LinearLocator only searches forward and have no boundary check.
|
|
type LinearLocator struct {
|
|
left int
|
|
length int
|
|
iOuter int
|
|
baseEnd int
|
|
baseStart int
|
|
baseData []byte
|
|
cfr *CryptoFrameRelocation
|
|
}
|
|
|
|
func NewLinearLocator(cfr *CryptoFrameRelocation) (linearLocator *LinearLocator) {
|
|
return &LinearLocator{
|
|
left: 0,
|
|
length: cfr.length,
|
|
iOuter: 0,
|
|
baseData: cfr.o[0].Data,
|
|
baseStart: cfr.o[0].UpperAppOffset,
|
|
baseEnd: cfr.o[0].UpperAppOffset + len(cfr.o[0].Data),
|
|
cfr: cfr,
|
|
}
|
|
}
|
|
|
|
func (ll *LinearLocator) relocate(i int) {
|
|
// Relocate ll.iOuter.
|
|
for i >= ll.baseEnd {
|
|
ll.iOuter++
|
|
ll.baseData = ll.cfr.o[ll.iOuter].Data
|
|
ll.baseStart = ll.cfr.o[ll.iOuter].UpperAppOffset
|
|
ll.baseEnd = ll.baseStart + len(ll.baseData)
|
|
}
|
|
}
|
|
|
|
func (ll *LinearLocator) Range(i, j int) []byte {
|
|
if i == j {
|
|
return []byte{}
|
|
}
|
|
size := j - i
|
|
|
|
// We find bytes including i and j, so we should sub j with 1.
|
|
i += ll.left
|
|
j += ll.left - 1
|
|
ll.relocate(i)
|
|
|
|
// Linearly copy.
|
|
|
|
if j < ll.baseEnd {
|
|
// In the same block, no copy needed.
|
|
return ll.baseData[i-ll.baseStart : j-ll.baseStart+1]
|
|
}
|
|
|
|
b := make([]byte, size)
|
|
k := 0
|
|
for j >= ll.baseEnd {
|
|
n := copy(b[k:], ll.baseData[i-ll.baseStart:])
|
|
k += n
|
|
i += n
|
|
ll.iOuter++
|
|
ll.baseData = ll.cfr.o[ll.iOuter].Data
|
|
ll.baseStart = ll.cfr.o[ll.iOuter].UpperAppOffset
|
|
ll.baseEnd = ll.baseStart + len(ll.baseData)
|
|
}
|
|
copy(b[k:], ll.baseData[i-ll.baseStart:j-ll.baseStart+1])
|
|
return b
|
|
}
|
|
|
|
func (ll *LinearLocator) At(i int) byte {
|
|
i += ll.left
|
|
|
|
ll.relocate(i)
|
|
b := ll.baseData[i-ll.baseStart]
|
|
return b
|
|
}
|
|
|
|
func (ll *LinearLocator) Slice(i, j int) Locator {
|
|
// We do not care about right.
|
|
newLL := *ll
|
|
newLL.left += i
|
|
newLL.length = j - i + 1
|
|
return &newLL
|
|
}
|
|
|
|
func (ll *LinearLocator) Len() int {
|
|
return ll.length
|
|
}
|
|
|
|
type BuiltinBytesLocator []byte
|
|
|
|
func (l BuiltinBytesLocator) Range(i, j int) []byte {
|
|
return l[i:j]
|
|
}
|
|
func (l BuiltinBytesLocator) At(i int) byte {
|
|
return l[i]
|
|
}
|
|
func (l BuiltinBytesLocator) Slice(i, j int) Locator {
|
|
return l[i:j]
|
|
}
|
|
func (l BuiltinBytesLocator) Len() int {
|
|
return len(l)
|
|
}
|