package main import ( "encoding/base64" "fmt" "log" "math/rand" "time" "badat.dev/maeqtt/v2/mqtt/packets" ) func init() { rand.Seed(time.Now().UnixNano()) } func Auth(username string, password []byte) bool { return true } type Session struct { ClientID *string // Nullable Connection *Connection SubscriptionChannel chan packets.PublishPacket ExpiryInterval time.Duration expireTimer time.Timer // TODO } func NewSession(conn *Connection, p packets.ConnectPacket) Session { sess := Session{} sess.SubscriptionChannel = make(chan packets.PublishPacket) sess.Connect(conn, p) return sess } func (s *Session) Connect(conn *Connection, p packets.ConnectPacket) { if s.Connection != nil { //TODO panic("Disconnect if already have a connection, unimplemented") } connAck := packets.ConnackPacket{} s.updateExpireTimer(p.Properties.SessionExpiryInterval.Value) if p.ClientId != nil { if s.ClientID == nil { s.ClientID = genClientID() } connAck.Properties.AssignedClientIdentifier.Value = s.ClientID } true := byte(1) false := byte(0) connAck.Properties.WildcardSubscriptionAvailable.Value = &true connAck.Properties.RetainAvailable.Value = &false connAck.Properties.SharedSubscriptionAvailable.Value = &false s.Connection = conn err := s.Connection.sendPacket(connAck) if err != nil { panic(err) } } // Starts a loop the recieves and responds to packets func (s *Session) HandlerLoop() { for s.Connection != nil { select { case packet := <-s.Connection.PacketChannel: packet.Visit(s) case _ = <-s.Connection.ClientDisconnectedChan: s.OnDisconnect() case subMessage := <-s.SubscriptionChannel: //TODO, log for now log.Printf("Recieved subscription message, handling UNIMPLEMENTED, message: %v", subMessage) } } } func (s *Session) Disconnect() error { panic("Disconnection unimplemented") err := s.Connection.close() if err != nil { return err } s.OnDisconnect() return nil } func (s *Session) OnDisconnect() { s.Connection = nil s.resetExpireTimer() log.Printf("Client disconnected, id: %s", *s.ClientID) } // newTime is nullable func (s *Session) updateExpireTimer(newTime *uint32) { var expiry = uint32(0) if newTime != nil { expiry = *newTime } else { expiry = uint32(0) } s.ExpiryInterval = time.Duration(expiry) * time.Second if s.Connection == nil { s.resetExpireTimer() } } func (s *Session) resetExpireTimer() { //s.expireTimer.Reset(s.ExpiryInterval) } func genClientID() *string { buf := make([]byte, 32) _, err := rand.Read(buf) if err != nil { // I don't think this can actually happen but just in case panic panic(fmt.Errorf("Failed to generate a client id, %e", err)) } id := "Client_rand_" + base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(buf) return &id } func (s *Session) VisitConnect(_ packets.ConnectPacket) { // ERROR CANNOT RECIEVE CONNECT ON AN ALREADY OPEN CONNECTION s.Disconnect() } func (s *Session) VisitPublish(p packets.PublishPacket) { println("UNIMPLEMENTED, Publishing packet, message:", string(p.Payload)) subs, lock := Subscriptions.GetSubscriptions(p.TopicName) defer lock.Unlock() for _, sub := range subs { go func(sub Subscription) {sub <- p}(sub) } } func (s *Session) VisitDisconnect(p packets.DisconnectPacket) { //TODO FINISH // HANDLE CLIENT DISCONNECTING s.OnDisconnect() } func (s *Session) VisitSubscribe(p packets.SubscribePacket) { //TODO FINISH for _, filter := range p.TopicFilters { Subscriptions.Subscribe(filter.Topic, s.SubscriptionChannel) } } func (s *Session) VisitUnsubscribe(_ packets.UnsubscribePacket) { panic("not implemented") // TODO: Implement } func (s *Session) VisitPing(p packets.PingreqPacket) { s.Connection.sendPacket(packets.PingrespPacket{}) } func (s *Session) VisitPubackPacket(_ packets.PubackPacket) { panic("not implemented") // TODO: Implement } func (s *Session) VisitPubrecPacket(_ packets.PubrecPacket) { panic("not implemented") // TODO: Implement } func (s *Session) VisitPubrelPacket(_ packets.PubrelPacket) { panic("not implemented") // TODO: Implement } func (s *Session) VisitPubcompPacket(_ packets.PubcompPacket) { panic("not implemented") // TODO: Implement }