308 lines
8.6 KiB
Go
308 lines
8.6 KiB
Go
package websocket
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"fiskerinc.com/modules/dbc/models"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"fiskerinc.com/modules/common"
|
|
"fiskerinc.com/modules/grpc/kafka_grpc"
|
|
"fiskerinc.com/modules/kafka"
|
|
"fiskerinc.com/modules/logger"
|
|
|
|
"github.com/gobwas/ws"
|
|
"github.com/gobwas/ws/wsflate"
|
|
"github.com/pkg/errors"
|
|
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
|
)
|
|
|
|
// NewTRexSession serves as the constructor for TRex sessions
|
|
func NewTRexSession(w http.ResponseWriter, r *http.Request, vin, version, iccid string) (SessionInterface, error) {
|
|
var s SessionInterface
|
|
var compressionNegotiator = wsflate.Extension{
|
|
Parameters: wsflate.DefaultParameters,
|
|
}
|
|
|
|
var websocketUpgrader = ws.HTTPUpgrader{
|
|
Negotiate: compressionNegotiator.Negotiate,
|
|
}
|
|
|
|
conn, _, _, err := websocketUpgrader.Upgrade(r, w)
|
|
if err != nil {
|
|
return s, errors.WithStack(err)
|
|
}
|
|
|
|
if _, ok := compressionNegotiator.Accepted(); !ok {
|
|
conn.Close()
|
|
return s, errors.Errorf("didn't negotiate compression for %s", conn.RemoteAddr())
|
|
}
|
|
|
|
dbc := ParseDBCFromRequest(r)
|
|
|
|
return &SessionTRex{
|
|
Session: &Session{
|
|
Websocket: conn,
|
|
Type: common.TRex,
|
|
Version: version,
|
|
ID: vin,
|
|
epoch: time.Now().UnixNano(),
|
|
},
|
|
DBC: dbc,
|
|
ICCID: iccid,
|
|
}, nil
|
|
}
|
|
|
|
// SessionTRex utilizes a specialized listener
|
|
type SessionTRex struct {
|
|
*Session
|
|
DBC string
|
|
ICCID string
|
|
}
|
|
|
|
// Authenticate returns id if proper authentication, else returns error
|
|
func (s *SessionTRex) Authenticate() error {
|
|
return nil
|
|
}
|
|
|
|
func (s *SessionTRex) Receive() ([]byte, ws.OpCode, error) {
|
|
return s.receive(s.extendDeadline)
|
|
}
|
|
|
|
// Listen to websocket session and use handler upon message received
|
|
func (s *SessionTRex) Listen(ctx context.Context, producer kafka.ProducerInterface) error {
|
|
span, _ := tracer.StartSpanFromContext(ctx, "listen")
|
|
defer span.Finish()
|
|
|
|
defer s.Close()
|
|
for {
|
|
key := s.Key()
|
|
msg, op, err := s.Receive()
|
|
if op == ws.OpClose {
|
|
logger.At(logger.Info(), key, "conn").Msg("OpClose")
|
|
return nil
|
|
} else if err != nil {
|
|
logger.At(logger.Info(), key, "conn").Err(err).Send()
|
|
return err
|
|
}
|
|
|
|
err = s.Route(producer, msg)
|
|
if err != nil {
|
|
logger.At(logger.Warn(), key, "route").Err(err).Send()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Route TRex messages
|
|
func (s *SessionTRex) Route(producer kafka.ProducerInterface, data []byte) error {
|
|
// TODO Unmarshal message and extract CAN frames into Kafka
|
|
var m common.MessageRawJSON
|
|
var err error
|
|
err = m.Unmarshal(data)
|
|
if err != nil {
|
|
return errors.Wrap(err, fmt.Sprintf("msg %s", string(data)))
|
|
}
|
|
|
|
key := s.Key()
|
|
topic, ok := kafka.TRexHandlerTopics[m.Handler]
|
|
if !ok {
|
|
return ErrInvalidHandler(m.Handler)
|
|
}
|
|
|
|
switch topic {
|
|
case kafka.VehicleData:
|
|
m.Version = models.GetShortKey(s.DBC)
|
|
|
|
car := common.CarDataBatchPayload{}
|
|
grpcCan, err := car.ToGrpc(m, s.GetID())
|
|
if err != nil {
|
|
logger.At(logger.Error(), key, "route").
|
|
Err(errors.Wrap(err, "unable to convert to GRPC")).Send()
|
|
return err
|
|
}
|
|
if s.ID == "VCF1ZBU29PG004227" {
|
|
ids := ""
|
|
event := logger.Warn()
|
|
for _, f := range grpcCan.Data.Frames{
|
|
// f.Value
|
|
ids = fmt.Sprintf("%s, %d", ids, f.ID)
|
|
event.Str(fmt.Sprintf("%d",f.ID), fmt.Sprintf("%X", f.Value))
|
|
}
|
|
event.Msg(s.ID)
|
|
}
|
|
|
|
grpcData, err := proto.Marshal(grpcCan)
|
|
if err != nil {
|
|
logger.At(logger.Error(), key, "route").
|
|
Err(errors.Wrap(err, "unable to marshal GRPC")).Send()
|
|
return err
|
|
}
|
|
err = producer.ProduceBinary(kafka.VehicleData, s.GetID(), grpcData, nil)
|
|
grpcData = nil
|
|
grpcCan = nil
|
|
if err != nil {
|
|
logger.At(logger.Error(), key, "route").
|
|
Err(errors.Wrap(err, "Producer failed")).Send()
|
|
return err
|
|
}
|
|
case kafka.LogService:
|
|
var grpcLogs *kafka_grpc.TRexLogs_BatchPayload
|
|
switch m.Handler {
|
|
case "trex_log":
|
|
logs := common.TRexLogs{}
|
|
grpcLogs, err = logs.ToGrpc(m)
|
|
case "error":
|
|
errorr := common.TRexError{}
|
|
grpcLogs, err = errorr.ToGrpc(m)
|
|
}
|
|
|
|
if err != nil {
|
|
logger.At(logger.Error(), key, "route").
|
|
Err(errors.Wrap(err, "unable to convert trex logs to GRPC"+string(m.Data[:]))).Send()
|
|
return err
|
|
}
|
|
|
|
grpcData, err := proto.Marshal(grpcLogs)
|
|
if err != nil {
|
|
logger.At(logger.Error(), key, "route").
|
|
Err(errors.Wrap(err, "unable to marshal trexlogs GRPC"+string(m.Data[:]))).Send()
|
|
return err
|
|
}
|
|
err = producer.ProduceBinary(kafka.LogServiceGRPCKafka, key, grpcData, nil)
|
|
grpcData = nil
|
|
grpcLogs = nil
|
|
if err != nil {
|
|
logger.At(logger.Error(), key, "route").
|
|
Err(errors.Wrap(err, "Producer for trex logs failed")).Send()
|
|
return err
|
|
}
|
|
case kafka.LogServiceGRPCKafka: // This case should not be necessary, but just in case someone gets confused, it is in here
|
|
var grpcLogs *kafka_grpc.TRexLogs_BatchPayload
|
|
switch m.Handler {
|
|
case "trex_log":
|
|
logs := common.TRexLogs{}
|
|
grpcLogs, err = logs.ToGrpc(m)
|
|
case "error":
|
|
errorr := common.TRexError{}
|
|
grpcLogs, err = errorr.ToGrpc(m)
|
|
}
|
|
|
|
// TODO: unable to convert trex logs msg {"code":-32601,"message":"The handler does not exist or is not available"}: json: cannot unmarshal string into Go struct field TRexError.message of type []common.TRexErrorMessage
|
|
if err != nil {
|
|
logger.At(logger.Error(), key, "route").
|
|
Err(errors.Wrap(err, "unable to convert trex logs msg "+string(m.Data[:]))).Send()
|
|
return err
|
|
}
|
|
|
|
grpcData, err := proto.Marshal(grpcLogs)
|
|
if err != nil {
|
|
logger.At(logger.Error(), key, "route").
|
|
Err(errors.Wrap(err, "unable to marshal trexlogs GRPC"+string(m.Data[:]))).Send()
|
|
return errors.WithStack(err)
|
|
}
|
|
err = producer.ProduceBinary(kafka.LogServiceGRPCKafka, key, grpcData, nil)
|
|
grpcData = nil
|
|
grpcLogs = 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.ValetRouteTRexPayloadGRPC(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
|
|
}
|
|
case kafka.DepotServiceGRPCKafka:
|
|
valetData := common.DepotRouteTRexToGRPC(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.AttendantServiceGRPCKafka:
|
|
valetData := common.AttendantRouteTRexGRPC(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
|
|
}
|
|
default:
|
|
err = producer.Produce(topic, key, m, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SessionTRex) KafkaEndSessionMarker(producer kafka.ProducerInterface) error {
|
|
can := kafka_grpc.GRPC_BatchPayload{
|
|
Handler: "",
|
|
Data: nil,
|
|
Version: models.GetShortKey(s.DBC),
|
|
}
|
|
grpcData, _ := proto.Marshal(&can)
|
|
key := s.Key()
|
|
logger.At(logger.Info(), key, "conn").
|
|
Msgf("closing connection %s", key)
|
|
|
|
return producer.ProduceBinary(kafka.VehicleData, s.GetID(), grpcData, nil)
|
|
}
|
|
|
|
func (s *SessionTRex) Teardown(producer kafka.ProducerInterface) error {
|
|
s.KafkaEndSessionMarker(producer)
|
|
return s.Session.Teardown(producer)
|
|
}
|
|
|
|
// Load the session - distributes messages to system notifing of new connection
|
|
func (s *SessionTRex) Load(producer kafka.ProducerInterface) error {
|
|
|
|
m := &kafka_grpc.GRPC_DepotPayload_InitPayload{
|
|
InitPayload: &kafka_grpc.InitPayload{
|
|
Data: map[string]string{
|
|
"version": s.Version,
|
|
"iccid": s.ICCID,
|
|
"ip": s.GetIP(),
|
|
"dbc_version": s.DBC,
|
|
},
|
|
},
|
|
}
|
|
payload := kafka_grpc.GRPC_DepotPayload{
|
|
Handler: "init",
|
|
Data: m,
|
|
}
|
|
binaryPayload, _ := proto.Marshal(&payload)
|
|
return producer.ProduceBinary(kafka.DepotServiceGRPCKafka, s.Key(), binaryPayload, nil)
|
|
}
|