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