251 lines
6.2 KiB
Go
251 lines
6.2 KiB
Go
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"io"
|
|
|
|
"golang.org/x/exp/constraints"
|
|
)
|
|
|
|
type intType interface {
|
|
int | int32 | int64
|
|
}
|
|
type Var[T intType] []byte
|
|
type String []byte
|
|
|
|
const SEGMENT_BITS = 0x7F
|
|
const CONTINUE_BIT = 0x80
|
|
|
|
func ToVar[T intType](value T) Var[T] {
|
|
ret := make(Var[T], 0)
|
|
for {
|
|
if (value & ^SEGMENT_BITS) == 0 {
|
|
ret = append(ret, byte(value))
|
|
return ret
|
|
}
|
|
ret = append(ret, byte((value&SEGMENT_BITS)|CONTINUE_BIT))
|
|
value = T(uint(value) >> 7)
|
|
}
|
|
}
|
|
func FromVar[T intType](x Var[T]) T {
|
|
value, position := T(0), 0
|
|
for i := 0; ; i++ {
|
|
cur := x[i]
|
|
value |= T((cur & SEGMENT_BITS) << byte(position))
|
|
if (cur & CONTINUE_BIT) == 0 {
|
|
break
|
|
}
|
|
position += 7
|
|
}
|
|
return value
|
|
}
|
|
func StreamedFromVarNoException[T intType](reader io.Reader) T {
|
|
value, position := T(0), 0
|
|
buf := make(Var[T], 1)
|
|
for i := 0; ; i++ {
|
|
reader.Read(buf)
|
|
cur := buf[0]
|
|
value |= (T(cur) & SEGMENT_BITS) << position
|
|
if (cur & CONTINUE_BIT) == 0 {
|
|
break
|
|
}
|
|
position += 7
|
|
}
|
|
return value
|
|
}
|
|
func ToString(str string) String {
|
|
ret := make(String, 0)
|
|
ret = append(ret, ToVar[int](len(str))...)
|
|
ret = append(ret, []byte(str)...)
|
|
return ret
|
|
}
|
|
|
|
func FromString(x String) string {
|
|
l := FromVar(Var[int](x))
|
|
ll := Length(Var[int](x))
|
|
return string(x[ll : ll+l])
|
|
}
|
|
func StreamedFromStringNoException(reader io.Reader) string {
|
|
l := StreamedFromVarNoException[int](reader)
|
|
buf := make([]byte, l)
|
|
reader.Read(buf)
|
|
return string(buf)
|
|
}
|
|
|
|
type PrefixedArray[T intType | byte] []byte
|
|
|
|
func ToPrefixedArray[T intType | byte](arr []T) PrefixedArray[T] {
|
|
ret := make(PrefixedArray[T], 0)
|
|
ret = append(ret, ToVar[int](len(arr))...)
|
|
switch any(*new(T)).(type) {
|
|
case byte:
|
|
for _, v := range arr {
|
|
ret = append(ret, byte(v))
|
|
}
|
|
default:
|
|
panic("unimplemented!")
|
|
}
|
|
return ret
|
|
}
|
|
func StreamedFromPrefixedArrayNoException(reader io.Reader) []byte { //I don't know how to write parser for other types, left for further change
|
|
l := StreamedFromVarNoException[int](reader)
|
|
ret := make([]byte, l)
|
|
reader.Read(ret)
|
|
return ret
|
|
}
|
|
|
|
type VarLengthTypes interface {
|
|
Var[int] | Var[int32] | Var[int64] | String
|
|
}
|
|
|
|
// TODO: rewrite as method
|
|
func Length[T VarLengthTypes](x T) int {
|
|
switch any(x).(type) {
|
|
case Var[int], Var[int32], Var[int64]:
|
|
i := 0
|
|
for ; ; i++ {
|
|
cur := x[i]
|
|
if (cur & CONTINUE_BIT) == 0 {
|
|
break
|
|
}
|
|
}
|
|
return i + 1
|
|
case String:
|
|
return Length[Var[int]](Var[int](x)) + len(FromString(String(x)))
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
type Boolean [4]byte
|
|
|
|
func ToBoolean(x bool) (ret Boolean) {
|
|
if x {
|
|
ret[3] = 1
|
|
} else {
|
|
ret[3] = 0
|
|
}
|
|
return
|
|
}
|
|
func StreamedFromBooleanNoException(reader io.Reader) bool {
|
|
buf := Boolean{}
|
|
reader.Read(buf[:])
|
|
if buf[3] == 1 {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
type BigEndian[T constraints.Integer] []byte
|
|
|
|
func ToBigEndian[T constraints.Integer](x T) BigEndian[T] {
|
|
buf := new(bytes.Buffer)
|
|
binary.Write(buf, binary.BigEndian, x)
|
|
return buf.Bytes()
|
|
}
|
|
func FromBigEndian[T constraints.Integer](x BigEndian[T]) (ret T) {
|
|
binary.Read(bytes.NewReader(x), binary.BigEndian, &ret)
|
|
return
|
|
}
|
|
func StreamedFromBigEndianNoException[T constraints.Integer](reader io.Reader) (ret T) {
|
|
buf := make(BigEndian[T], binary.Size(ret))
|
|
reader.Read(buf)
|
|
binary.Read(bytes.NewReader(buf), binary.BigEndian, &ret)
|
|
return
|
|
}
|
|
|
|
type UUID [16]byte // I don't think that I should parse the UUID sent from server, which means the endianess will be kept the same
|
|
|
|
type Data []byte
|
|
|
|
func ToHandshakePacketData(ver int, addr string, port uint16, nxtst int) Data {
|
|
ret := make(Data, 0)
|
|
ret = append(ret, ToVar(ver)...)
|
|
ret = append(ret, ToString(addr)...)
|
|
ret = append(ret, ToBigEndian(port)...)
|
|
ret = append(ret, ToVar(nxtst)...)
|
|
return ret
|
|
}
|
|
func FromHandshakePacketData(x Data) (ver int, addr string, port uint16, nxtst int) {
|
|
ver = FromVar(Var[int](x))
|
|
x = x[Length(Var[int](x)):]
|
|
addr = FromString(String(x))
|
|
x = x[Length(String(x)):]
|
|
port = FromBigEndian(BigEndian[uint16](x[:2]))
|
|
x = x[2:]
|
|
nxtst = FromVar(Var[int](x))
|
|
return ver, addr, port, nxtst
|
|
}
|
|
func StreamedFromHandshakePacketDataNoException(reader io.Reader) (ver int, addr string, port uint16, nxtst int) {
|
|
ver = StreamedFromVarNoException[int](reader)
|
|
//x = x[Length(Var[int](x)):]
|
|
addr = StreamedFromStringNoException(reader)
|
|
// x = x[Length(String(x)):]
|
|
port = StreamedFromBigEndianNoException[uint16](reader)
|
|
// x = x[2:]
|
|
nxtst = StreamedFromVarNoException[int](reader)
|
|
return ver, addr, port, nxtst
|
|
}
|
|
func ToLoginStartPacketData(name string, uuid UUID) Data {
|
|
ret := Data(ToString(name))
|
|
ret = append(ret, uuid[:]...)
|
|
return ret
|
|
}
|
|
func StreamedFromLoginStartPacketDataNoException(reader io.Reader) (name string, uuid UUID) {
|
|
name = StreamedFromStringNoException(reader)
|
|
reader.Read(uuid[:])
|
|
return
|
|
}
|
|
func ToEncryptionRequestPacketData(sid string, pubkey []byte, token []byte, auth bool) Data {
|
|
ret := make(Data, 0)
|
|
ret = append(ret, ToString(sid)...)
|
|
ret = append(ret, ToPrefixedArray(pubkey)...)
|
|
ret = append(ret, ToPrefixedArray(token)...)
|
|
authByte := ToBoolean(auth)
|
|
ret = append(ret, authByte[:]...)
|
|
return ret
|
|
}
|
|
func StreamedFromEncryptionRequestPacketData(reader io.Reader) (sid string, pubkey []byte, token []byte, auth bool) {
|
|
sid = StreamedFromStringNoException(reader)
|
|
pubkey = StreamedFromPrefixedArrayNoException(reader)
|
|
token = StreamedFromPrefixedArrayNoException(reader)
|
|
auth = StreamedFromBooleanNoException(reader)
|
|
return
|
|
}
|
|
|
|
type disconnectJsonFields struct {
|
|
Type string `json:"type"`
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
func ToDisconnectPacketData(text string) Data {
|
|
ret, _ := json.Marshal(disconnectJsonFields{"text", text})
|
|
return ret
|
|
}
|
|
|
|
type Packet []byte
|
|
|
|
func ToPacket(p int, data Data) Packet {
|
|
ret := make(Packet, 0)
|
|
pid := ToVar[int](p)
|
|
ret = append(ret, ToVar[int](Length(pid)+len(data))...)
|
|
ret = append(ret, pid...)
|
|
ret = append(ret, data...)
|
|
return ret
|
|
}
|
|
func StreamedFromPacketNoException(reader io.Reader) (int, Data) {
|
|
l := StreamedFromVarNoException[int](reader)
|
|
// ll := Length(Var[int](x))
|
|
//pid := FromVar(Var[int](x[ll:]))
|
|
pid := StreamedFromVarNoException[int](reader)
|
|
ret := make(Data, l-Length(ToVar(pid)))
|
|
reader.Read(ret)
|
|
return pid, ret
|
|
}
|
|
func ToHandshakePacket(ver int, addr string, port uint16, nxtst int) Packet {
|
|
return ToPacket(0x00, ToHandshakePacketData(ver, addr, port, nxtst))
|
|
}
|