package websocket import ( "context" "encoding/json" "fmt" "net/http" "time" "fiskerinc.com/modules/grpc/kafka_grpc" "fiskerinc.com/modules/common" "fiskerinc.com/modules/kafka" "fiskerinc.com/modules/logger" "fiskerinc.com/modules/security" "github.com/gobwas/ws" "github.com/pkg/errors" "google.golang.org/protobuf/proto" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) // NewHMISession serves as the constructor for HMI sessions func NewHMISession(w http.ResponseWriter, r *http.Request, vin string, version string) (SessionInterface, error) { var s SessionInterface conn, _, _, err := ws.UpgradeHTTP(r, w) if err != nil { return s, errors.WithStack(err) } return &SessionHMI{ Session: &Session{ Websocket: conn, Type: common.HMI, Version: version, ID: vin, epoch: time.Now().UnixNano(), }, InAuthentication: false, }, nil } // SessionHMI contains websocket info type SessionHMI struct { *Session SessionID string InAuthentication bool } // Authenticate returns id if proper authentication, else returns error // // validates VIN inputted through "key" field of message func (s *SessionHMI) Authenticate() error { s.InAuthentication = true defer func() { s.InAuthentication = false }() err := s.authenticate() data, _ := json.Marshal(AuthResponse{ Handler: "verify", Data: AuthResponseData{ Authenticated: err == nil, }, }) s.SendMsgToClient(data) if err != nil { errors.Errorf("Unable to send to session %s", s.SessionID) return errors.WithStack(err) } return nil } func (s *SessionHMI) SendMsgToClient(message []byte) error { if !s.InAuthentication && len(s.SessionID) == 0 { return fmt.Errorf("session is not authorized, %s", s.SessionID) } return s.Session.SendMsgToClient(message) } func (s *SessionHMI) authenticate() error { s.InAuthentication = true defer func() { s.InAuthentication = false }() msg, _, err := s.Receive() if err != nil { errors.Errorf("unable to read socket %s", s.ID) return err } var m common.HMISessionMessage err = json.Unmarshal(msg, &m) if err != nil { return errors.WithStack(err) } else if m.Handler != "verify" { return errors.Errorf("incorrect auth handler specified %v", m.Handler) } switch { case m.Data.SessionID != "": salter, err := security.NewSalter(s.ID) if err != nil { errors.Errorf("unable to generate salt %s", s.ID) return err } err = salter.ValidateSessionID(m.Data.SessionID) if err != nil { errors.Errorf("unable to validate session %s", m.Data.SessionID) return err } s.SessionID = m.Data.SessionID case m.Data.Salt != "": s.SessionID = m.Data.Salt default: return ErrFailedAuthentication } return nil } // Listen to websocket session and use handler upon message received func (s *SessionHMI) Listen(ctx context.Context, producer kafka.ProducerInterface) error { span, _ := tracer.StartSpanFromContext(ctx, "listen") defer span.Finish() key := s.Key() for { msg, op, err := s.Receive() if op == ws.OpClose { // logger.At(logger.Info(), key, "conn").Msg("OpClose") //commented out because reported by the call in websocket.go return nil } else if err != nil { // logger.At(logger.Info(), key, "conn").Err(err).Send() //commented out because reported by the call in websocket.go return err } err = s.Route(producer, msg) if err != nil { logger.At(logger.Warn(), key, "route").Err(err).Send() } } } // Route HMI messages func (s *SessionHMI) Route(producer kafka.ProducerInterface, data []byte) error { var m common.MessageRawJSON err := m.Unmarshal(data) if err != nil { return err } if m.Verify == "ack"{ // Throw out ACK messages return nil } key := s.Key() logger.Debug(). Str("id", key). Str("handler", m.Handler). Int("size", len(data)). Msgf("received from %v %s", key, string(data)) topic, ok := kafka.HMIHandlerTopics[m.Handler] if !ok { err = ErrInvalidHandler(m.Handler) logger.Err(err).Str("Byte Data", string(data)).Str("Parsed Data", string(m.Data)).Msg("No Handler Insights") return err } switch topic { case kafka.AttendantServiceGRPCKafka: valetData := common.AttendantRouteHMIGRPC(m) grpcData, err := proto.Marshal(valetData) if err != nil { logger.At(logger.Error(), key, "route"). Err(errors.Wrap(err, "unable to marshal trexlogs GRPC "+topic)).Send() return errors.WithStack(err) } err = producer.ProduceBinary(kafka.AttendantServiceGRPCKafka, key, grpcData, nil) grpcData = nil if err != nil { logger.At(logger.Error(), key, "route"). Err(errors.Wrap(err, "Producer for trex logs failed")).Send() return err } case kafka.DepotServiceGRPCKafka: valetData := common.DepotRouteHMIToGRPC(m) grpcData, err := proto.Marshal(valetData) if err != nil { logger.At(logger.Error(), key, "route"). Err(errors.Wrap(err, "unable to marshal trexlogs GRPC "+topic)).Send() return errors.WithStack(err) } err = producer.ProduceBinary(kafka.DepotServiceGRPCKafka, key, grpcData, nil) grpcData = nil if err != nil { logger.At(logger.Error(), key, "route"). Err(errors.Wrap(err, "Producer for trex logs failed")).Send() return err } case kafka.ValetServiceGRPCKafka: valetData := common.ValetRouteHMIPayloadGRPC(m) grpcData, err := proto.Marshal(valetData) if err != nil { logger.At(logger.Error(), key, "route"). Err(errors.Wrap(err, "unable to marshal trexlogs GRPC "+topic)).Send() return errors.WithStack(err) } err = producer.ProduceBinary(kafka.ValetServiceGRPCKafka, key, grpcData, nil) grpcData = nil if err != nil { logger.At(logger.Error(), key, "route"). Err(errors.Wrap(err, "Producer for trex logs failed")).Send() return err } default: err = producer.Produce(topic, key, m, nil) } if err != nil { return err } return nil } func (s *SessionHMI) Load(producer kafka.ProducerInterface) error { key := s.Key() hmiSession := &kafka_grpc.GRPC_DepotPayload_HmiSession{ HmiSession: &kafka_grpc.HMISessionData{ SessionId: s.SessionID, }, } payload := kafka_grpc.GRPC_DepotPayload{ Handler: "init", Data: hmiSession, } binaryPayload, _ := proto.Marshal(&payload) err := producer.ProduceBinary(kafka.DepotServiceGRPCKafka, key, binaryPayload, nil) return err }