243 lines
6.2 KiB
Go
243 lines
6.2 KiB
Go
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
|
|
}
|