package core import ( "bytes" "encoding/binary" "fmt" "io" "strings" "time" ) type Wrapper interface { Wrap() (PayloadType, []any) } type PayloadType uint8 const ( PayloadUnknown PayloadType = 0x00 PayloadSuccess = 0x01 PayloadError = 0x02 PayloadHandshake = 0x03 PayloadUserAuth = 0x04 PayloadMessage = 0x05 PayloadServerAuth = 0x06 PayloadUsermode = 0x07 PayloadHistory = 0x08 PayloadTest = 0xFF ) type ErrorType uint8 const ( ErrorUnknown ErrorType = 0x00 ErrorAuthFailed = 0x01 ErrorNotFound = 0x02 ErrorUnauthorized = 0x03 ) type ConnType uint8 const ( ConnTypeUnknown ConnType = 0x00 ConnTypeUser = 0x01 ConnTypeServer = 0x02 ) type UsermodeType uint8 const ( UsermodeNone UsermodeType = 0x00 UsermodeInChannel = 0x01 ) type Frame struct { Length uint16 Type PayloadType Payload []any } type Binary struct { Length uint16 Payload []byte } func Encode(w Wrapper) ([]byte, error) { payloadType, payload := w.Wrap() buf := bytes.NewBuffer(make([]byte, 3)) for _, p := range payload { switch v := p.(type) { case string: strBytes := []byte(v) err := binary.Write(buf, binary.BigEndian, uint16(len(strBytes))) if err != nil { return []byte{}, fmt.Errorf("cannot encode string length: %v", err) } _, err = buf.Write(strBytes) if err != nil { return []byte{}, fmt.Errorf("cannot encode string: %v", err) } case []byte: err := binary.Write(buf, binary.BigEndian, uint16(len(v))) if err != nil { return []byte{}, fmt.Errorf("cannot encode byte slice length: %v", err) } _, err = buf.Write(v) if err != nil { return []byte{}, fmt.Errorf("cannot encode byte slice: %v", err) } case time.Time: err := binary.Write(buf, binary.BigEndian, uint64(v.UnixMilli())) if err != nil { return []byte{}, fmt.Errorf("cannot encode time: %v", err) } default: err := binary.Write(buf, binary.BigEndian, v) if err != nil { return []byte{}, fmt.Errorf("cannot encode: %v", err) } } } frame := buf.Bytes() frame[0] = uint8(payloadType) binary.BigEndian.PutUint16(frame[1:], uint16(len(frame)-3)) return frame, nil } func Decode(buf io.Reader) (any, error) { var pTypeByte uint8 err := binary.Read(buf, binary.BigEndian, &pTypeByte) if err != nil { return nil, fmt.Errorf("cannot read payload type: %w", err) } pType := PayloadType(pTypeByte) var pLen uint16 err = binary.Read(buf, binary.BigEndian, &pLen) if err != nil { return nil, fmt.Errorf("cannot read payload length: %w", err) } switch pType { case PayloadSuccess: return Success{}, nil case PayloadError: return DecodeError(buf) case PayloadHandshake: return DecodeHandshake(buf) case PayloadUserAuth: return DecodeUserAuth(buf) case PayloadServerAuth: return DecodeServerAuth(buf) case PayloadMessage: return DecodeMessage(buf) case PayloadUsermode: return DecodeUsermode(buf) case PayloadHistory: return DecodeHistory(buf) case PayloadTest: return DecodeTest(buf) default: return nil, fmt.Errorf("invalid payload type: %v", pType) } } func decodeNumeric(buf io.Reader, ptr any) error { err := binary.Read(buf, binary.BigEndian, ptr) if err != nil { return fmt.Errorf("cannot decode: %v", err) } return nil } func decodeTime(buf io.Reader, ptr *time.Time) error { var timeBuf uint64 err := binary.Read(buf, binary.BigEndian, &timeBuf) if err != nil { return fmt.Errorf("cannot decode time: %v", err) } *ptr = time.UnixMilli(int64(timeBuf)) return nil } func decodeBin(buf io.Reader, ptr *[]byte) error { var payloadLen uint16 err := binary.Read(buf, binary.BigEndian, &payloadLen) if err != nil { return fmt.Errorf("cannot decode payload length: %v", err) } payload := make([]byte, payloadLen) _, err = buf.Read(payload) if err != nil { return fmt.Errorf("cannot decode payload: %v", err) } *ptr = payload return nil } func decodeString(buf io.Reader, ptr *string) error { var strBytes []byte err := decodeBin(buf, &strBytes) if err != nil { return err } *ptr = string(strBytes) return nil } type Addr struct { Channel string Host string Type AddrType } type AddrType string const ( AddrUser AddrType = "user" AddrGroup = "group" ) func ReadAddr(addrStr string) (Addr, error) { var ( addr Addr ok bool ) addr.Channel, addr.Host, ok = strings.Cut(addrStr, "@") if !ok { return addr, ErrInvalidAddress } rest, ok := strings.CutPrefix(addr.Channel, "#") if ok { addr.Channel = rest addr.Type = AddrGroup } else { addr.Type = AddrUser } return addr, nil } func (a Addr) String() string { var prefix string if a.Type == AddrGroup { prefix = "#" } return fmt.Sprintf("%s%s@%s", prefix, a.Channel, a.Host) }