package handlers import ( "encoding/json" "io" "net/http" "strings" "github.com/fiskerinc/cloud-services/services/depot/services" "github.com/fiskerinc/cloud-services/pkg/cache" "github.com/fiskerinc/cloud-services/pkg/carcommand" "github.com/fiskerinc/cloud-services/pkg/common" "github.com/fiskerinc/cloud-services/pkg/db/queries" "github.com/fiskerinc/cloud-services/pkg/dbc/state" "github.com/fiskerinc/cloud-services/pkg/logger" "github.com/fiskerinc/cloud-services/pkg/mongo" "github.com/fiskerinc/cloud-services/pkg/redis" "github.com/fiskerinc/cloud-services/pkg/userconsent" "github.com/fiskerinc/cloud-services/pkg/utils/envtool" "github.com/fiskerinc/cloud-services/pkg/loggerdataresp" "github.com/fiskerinc/cloud-services/pkg/utils" "github.com/fiskerinc/cloud-services/pkg/utils/elptr" "github.com/go-pg/pg/v10" redigo "github.com/gomodule/redigo/redis" "github.com/pkg/errors" mon "go.mongodb.org/mongo-driver/mongo" ) var errInvalidICCID = errors.New("invalid iccid submitted") var errBlocked = errors.New("unable to connect: car is blocked") var logLevel = envtool.GetEnv("TREX_LOG_LEVEL", common.CriticalLabel) var wakeUpVINS = envtool.GetEnv("WAKE_UP_VINS", "VCF1EBU2XPG001140") var defaultFleet = envtool.GetEnv("DEFAULT_FLEET", "Default-Ocean") // id is the vehicles VIN func TRexInit(id string, vMap map[string]string) error { carsDB := services.GetDB().GetCars() blocked, err := insertIfNotExist(id, carsDB) if err != nil { return err } if blocked { return errBlocked } if strings.Contains(wakeUpVINS, id) { go wakeup(id) } client := services.RedisClientPool().GetFromPool() defer client.Close() err = addTRexSession(client, id) if err != nil { logger.Warn().Str("id", id).Err(err).Send() } m, err := services.GetMongoClient() if err != nil { logger.Warn().Str("id", id).Err(err).Send() } else { msg, err := retrieveTRexSettings(client, m, id) if err != nil { return err } // Log the config that will be sent to TREX config, err := json.Marshal(msg) if err != nil { return err } logger.Info().Msgf("TREX Config sent for vin %s: \n%s", id, config) err = sendTRexSettings(client, id, msg) if err != nil { return err } } err = setTRexVersion(client, id, vMap["version"]) if err != nil { return err } err = setDBCVersion(client, id, vMap["dbc_version"]) if err != nil { return err } err = setTRexIP(client, id, vMap["ip"]) if err != nil { return err } err = addICCID(carsDB, id, vMap["iccid"]) if err != nil { logger.Warn().Err(err).Str("VIN", id).Str("ICCID", vMap["iccid"]).Msg("failed to add iccid to car") } uMsg, err := getUserConsentData(id) if err != nil { return err } err = sendTRexUserConsentData(client, id, uMsg) if err != nil { return err } err = checkAndRenewCertificate(id) if err != nil { return err } return nil } func checkAndRenewCertificate(id string) error { logger.Debug().Msg("on trex init: checking TBOX cert for vin " + id) cs := services.GetCertService() certNeedsRenewal, err := cs.CheckCertificateNeedsRenewal(id, common.CertTBOX) if err != nil { return err } else if certNeedsRenewal { logger.Debug().Msg("TBOX cert for vin " + id + " is out of date") cert, err := cs.RenewCertificate(id, common.CertTBOX) if err != nil { return err } client := services.RedisClientPool().GetFromPool() defer client.Close() err = client.SafeQueueMessage( common.TRex.Key(id), common.Message{ Handler: "update_cert", Data: common.UpdateCert{ SSLCertBase64: cert.PublicKey, }, }, ) if err != nil { return err } } logger.Debug().Msg("TBOX cert for vin " + id + " is not out of date") return nil } func getUserConsentData(id string) ([]common.UserConsentDataTrexMsg, error) { var ucdToSend = []common.UserConsentDataTrexMsg{} resp, err := userconsent.GetUserConsentService().UserConsentByVehicleNumber(id) if err != nil { return nil, errors.WithStack(err) } if resp.StatusCode != http.StatusOK { return nil, errors.New("call to user-consent/byVehicleNumber endpoint returned " + resp.Status) } respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.WithStack(err) } ucdResult := []common.UserConsentDataResponse{} err = json.Unmarshal(respBody, &ucdResult) if err != nil { return nil, errors.WithStack(err) } if len(ucdResult) < 1 { return ucdToSend, nil } // only send to TREX the consents that have the userID of the first consent returned var userId = ucdResult[0].UserID for _, ucd := range ucdResult { if ucd.UserID == userId { ucdToSend = append(ucdToSend, common.BuildUserConsentTrexMessage(ucd)) } } return ucdToSend, nil } func sendTRexUserConsentData(r redis.Client, id string, msg []common.UserConsentDataTrexMsg) error { if len(msg) > 0 { return r.SafePublishMessage( common.TRex.Key(id), common.Message{ Handler: "consent", Data: msg, }, ) } return nil } func insertIfNotExist(id string, carsDB queries.CarsInterface) (bool, error) { car, err := carsDB.SelectByVIN(id) if err == nil && car != nil { return car.Blocked, nil } if errors.Is(err, pg.ErrNoRows) { err = nil c, err := utils.ParseVIN(id) if err != nil { return false, errors.WithStack(err) } _, err = carsDB.Insert(c) if err != nil { return false, errors.WithStack(err) } client, err := services.GetMongoClient() if err != nil { return false, errors.WithStack(err) } v := &mongo.Vehicle{ VIN: id, CANBus: common.CANBus{Enabled: true, DataLogger: true, DTCEnabled: elptr.ElPtr(false)}, LogLevel: common.UnmarshalLogLevelString(logLevel), } err = client.GetVehicles().AddVehicle(v) if mon.IsDuplicateKeyError(err) { err = client.GetVehicles().UpdateVehicle(v) } if err != nil { return false, errors.WithStack(err) } err = client.GetFleets().AddVehiclesToFleet(defaultFleet, []string{id}) if err != nil { return false, errors.WithStack(err) } return false, nil } return false, errors.WithStack(err) } func addICCID(db queries.CarsInterface, id, iccid string) error { // Going to only update where an iccid is valid if len(iccid) != 20 { return errInvalidICCID } car := &common.Car{ICCID: iccid, VIN: id} if ct, err := db.UpdateICCID(car); err != nil { return errors.Wrapf(err, "failed to update car's iccid %s", id) } else if ct.RowsAffected() == 1 { logger.Info().Msgf("car %s has been updated with iccid %s", id, iccid) } return nil } func addTRexSession(client redis.Client, id string) error { _, err := client.Execute("SADD", redis.CarSessionsKey(), id) return err } func retrieveTRexSettings(r redis.Client, m mongo.Client, id string) (*common.TRexConfigResponse, error) { return cache.RetrieveVehicleConfig(r, m, id) } func sendTRexSettings(r redis.Client, id string, msg *common.TRexConfigResponse) error { err := r.SafePublishMessage( common.TRex.Key(id), common.Message{ Handler: "config", Data: msg, }, ) return err } func setTRexIP(r redis.Client, vin, ip string) error { elms := strings.Split(ip, ":") return r.SetObjectField(redis.CarStateHashKey(vin), state.TREX_IP, elms[0]) } func setDBCVersion(r redis.Client, vin, version string) error { return setVersion(r, vin, version, state.DBC_VERSION, common.DBCVersionSource) } func setTRexVersion(r redis.Client, vin, version string) error { return setVersion(r, vin, version, state.TREX_VERSION, common.TREXVersionSource) } func setVersion(r redis.Client, vin, version, redisField string, versionSource common.VersionSource) error { v, err := r.GetObjectField(redis.CarStateHashKey(vin), redisField) if err != nil && !errors.Is(err, redigo.ErrNil) { return errors.WithStack(err) } if v == version { return nil } db := services.GetDB().GetCarVersionsLog() err = r.SetObjectField(redis.CarStateHashKey(vin), redisField, version) if err != nil { return errors.WithStack(err) } _, err = db.LogVersionChange(&common.CarVersionLogs{ VIN: vin, VersionSource: versionSource, Version: version, }) if err != nil { return errors.WithStack(err) } return nil } func wakeup(vin string) { wake := carcommand.NewCarWakeUp(services.GetDB().GetCars(), services.GetSMSClient()) err := wake.WakeUp(vin, false) loggerdataresp.BadDataError(err) }