minimcd/protocol/protocol.go
2025-01-23 13:59:24 +08:00

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))
}