package controllers import ( "encoding/json" "fmt" "github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc" "github.com/fiskerinc/cloud-services/services/attendant/services" "github.com/fiskerinc/cloud-services/pkg/carcommand" "github.com/fiskerinc/cloud-services/pkg/common" "github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus" "github.com/fiskerinc/cloud-services/pkg/common/manifestfingerprintparams" "github.com/fiskerinc/cloud-services/pkg/grpc/sms" "github.com/fiskerinc/cloud-services/pkg/hwversion" "github.com/fiskerinc/cloud-services/pkg/kafka" "github.com/fiskerinc/cloud-services/pkg/logger" "github.com/fiskerinc/cloud-services/pkg/manifestsender" "github.com/fiskerinc/cloud-services/pkg/redis" "github.com/fiskerinc/cloud-services/pkg/tmobile" uhelpers "github.com/fiskerinc/cloud-services/pkg/usecase_helpers" "github.com/fiskerinc/cloud-services/pkg/utils/envtool" "github.com/fiskerinc/cloud-services/pkg/utils/randomvalues" "github.com/fiskerinc/cloud-services/pkg/utils/whereami" "github.com/fiskerinc/cloud-services/pkg/validator" vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig" "google.golang.org/protobuf/proto" "sync" "time" "github.com/pkg/errors" ) // 0100084000000101012200010101010001010101000000000000000000ff7eff7f000101010101000101010100010001010101000101010100000000000000000000000000010101000100000100010101000201010101000101020101000101010200010101010101010101000101000100010001010101010101010101010000000000000100010101020101010101010000000000000000ffffff00010102010102020001010000000000000000000000000000000000000000000000000000000000000000000100202310010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6 var defaultVOD string = envtool.GetEnv("DEFAULT_VOD", "00FF111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111114A") var fileKeyBypass map[int64]string var FileKeyBypassManifest_US map[int64]string = map[int64]string{ 1189: "fe4f19fbc940ed58", 1380: "6a44fd71c940716d", } var FileKeyBypassManifest_EU map[int64]string = map[int64]string{ 893: "fe4f19fbc940ed58", 1002: "6a44fd71c940716d", 1012: "d70e1d32c940a221", } func init() { if whereami.Environment == whereami.PRODUCTION_EU { fileKeyBypass = FileKeyBypassManifest_EU } else { fileKeyBypass = FileKeyBypassManifest_US } } func NewManifestSender( r redis.ClientPoolInterface, db *services.DB, conf vconfig.ConfigServiceInterface, device common.Device, ka *services.KeepAwake, seed int64, //This seed is used only for testing ) *ManifestSender { randomGenerator := randomvalues.NewNonCryptoGenerator("ABCDEFGHIJKLMNOPQRSTUVWXYZ", seed) return &ManifestSender{ Redis: r, db: db, conf: conf, Device: device, sms: services.GetSMSClient(), ka: ka, gen: randomGenerator, } } type ManifestSender struct { Redis redis.ClientPoolInterface db *services.DB conf vconfig.ConfigServiceInterface sms sms.SMSServiceClient Device common.Device ka *services.KeepAwake gen randomvalues.NonCryptoGenerator } func (m *ManifestSender) Process(id string, data []byte) error { // id string parameter is unused except in the following log // the data might not be used either, just for its single carUpdateID u, err := m.parseRequest(data) if err != nil { logger.Err(err).Msgf("Manifest sender, unable to process update %s", id) return err } carUpdate := common.CarUpdate{ID: u.CarUpdateID} err = m.loadCarUpdate(&carUpdate) if err != nil { return err } logger.Info().Msgf("ManifestSender car_update_id %d", carUpdate.ID) carUpdate.UpdateManifest.CarUpdateID = carUpdate.ID helper := uhelpers.NewECUKeys(m.db.GetECCKeys()) err = helper.AddECUECCKeys(carUpdate.UpdateManifest) if err != nil { logger.Err(err).Msgf("Unable to decrypt keys for %d", carUpdate.ID) return err //comment out for local debugging } // If we fail to deliver the sms, then we don't believe the car has awoken // then set the manifest as failed err = m.send(carUpdate.VIN, carUpdate.UpdateManifest) if err != nil { update := NewCarUpdateProgress(m.Redis, m.ka, m.db, common.TRex) err = update.ProcessStatus(carUpdate.VIN, common.CarUpdateProgress{ CarUpdateID: u.CarUpdateID, Status: carupdatestatus.ManifestCanceled, Info: " Underlying error: " + err.Error(), }) } return err } func (m *ManifestSender) destination(manifest *common.UpdateManifest) string { if manifest.HasSelfDownload() { return "ICC" } else { return "ICC/TBOX" } } func (m *ManifestSender) parseRequest(data []byte) (*common.UpdateManifest, error) { var req common.UpdateManifest err := json.Unmarshal(data, &req) if err != nil { return nil, errors.WithStack(err) } err = validator.ValidateIDField(req.CarUpdateID) if err != nil { return &req, errors.WithStack(err) } return &req, nil } func (m *ManifestSender) loadCarUpdate(cu *common.CarUpdate) error { err := m.db.GetCarUpdates().Load(cu) if err != nil { return errors.WithStack(err) } if cu.UpdateManifest == nil { return nil } return nil } func (m *ManifestSender) queueManifest(vin string, manifest *common.UpdateManifest) error { loggedManifest := manifest.Copy() loggedManifest.RemoveECCKeysFromECUs() logger.At(logger.Info(), common.HMI.Key(vin), "update").Interface("manifest.json", loggedManifest).Msgf("HMI ManifestSender sent %v %s %d", common.HMI, vin, manifest.CarUpdateID) client := m.Redis.GetFromPool() defer client.Close() err := client.SafeQueueMessage(common.HMI.Key(vin), common.Message{ Handler: "update_manifest", Data: manifest, }) if err != nil { logger.Err(err).Msgf("failed to queue manifest in redis vin %s ", vin) } return err } func (m *ManifestSender) send(vin string, manifest *common.UpdateManifest) error { updateManifestID := manifest.ID manifest.TransformECUNames() err := hwversion.SetHWVersion(manifest, vin, m.db.GetCars()) if err != nil { logger.Err(err).Msgf("manifest sender failed at SetHwVersion for vin %s", vin) return err //comment out for local debugging } manifest.SortECUs() manifest.RemoveOriginalS19HexFiles() manifest.RemoveOriginalS19HexFilesRollbacks() manifest.FilterCompatibleECUs(vin) err = uhelpers.PopulateECUsCurrentVersion(m.db.GetCars(), vin, manifest.ECUs) if err != nil { logger.Err(err).Msgf("manifest sender failed at PupulateECUsCurrentVersion for vin %s", vin) return err } fpparams := manifestfingerprintparams.GetFPParams() manifest.GenerateFingerprint(fpparams.CurTime(), fpparams.ManifestSerial()) hmiManifest := manifest.Copy() cds, err := m.addVOD(hmiManifest, vin) if err != nil { logger.Err(err).Msgf("manifest sender failed at adding the vod for vin %s", vin) return err // comment out for local debugging } err = validator.ValidateField(hmiManifest.SUMS, "sums_version") if err == nil { err = hmiManifest.AddSUMSToVOD() if err != nil { logger.Err(err).Msgf("manifest sender failed at AddSUMSToVOD for vin %s", vin) return err } } else { logger.Err(err).Msgf("manifest sender failed at Validation for vin %s", vin) return err } hmiManifest.Scrub(common.HMI) client := m.Redis.GetFromPool() defer client.Close() // If HasSelfDownload, we are not yet ready to send to t box, so we wake the car if the update is forced if manifest.HasSelfDownload() && m.sms != nil { // The function that calls this one handles canceling the manifest var msgID string res, err := carcommand.QueueSMSWakeUp(vin, true, client, m.db.GetCars(), m.sms) if err != nil || res == nil || !res.SentSuccessful { logger.Err(err).Msgf("Attendant:manifest sender failed at sendSMSWakeUp for vin %s", vin) msgID = m.gen.GetString(10) defer m.selfKafkaCallback(msgID) } else { msgID = res.SmsMsgID } err = m.ka.SendFirstKeepAwakeMessage(vin) if err != nil { logger.Err(err).Msgf("failed to SendFirstKeepAwakeMessage on msgID %s", msgID) } m.queueManifest(vin, hmiManifest) fileID, ok := fileKeyBypass[updateManifestID] if ok { logger.Info().Str("VIN", vin).Msg("Queueing SendFileKeys") time.AfterFunc(time.Second*3, func() { sendFileKeys(vin, fileID) }) } // Do not send manifest to TBOX, wait until ICC has finished downloading return err } trex := manifestsender.NewTBOXManifestSender(client, m.conf, m.db, services.GetSMSClient(), cds) defer trex.Close() //smsID, err := trex.ProcessSoftwareUpdate(vin, manifest) _, err = trex.ProcessSoftwareUpdate(vin, manifest, services.GetDB().GetCarConfigData()) logger.Debug().Msgf("Send HMI manifest to %s ", vin) m.queueManifest(vin, hmiManifest) // We managed to send an sms, so we want to continue with an sms delivered check // if smsID != "" { // err = m.setManifestCache(smsID, ManifestCachedPoint{ // VIN: vin, // ManifestID: manifest.ID, // Destination: m.destination(manifest), // }) // if err != nil { // return err // } // } return err } // So this flow of continue will be activated at two different times, possible multiple times. // 1) When a new car_update comes through and the .hasdownload is true, and then again when the manifest is being sent to the tbox func (m *ManifestSender) ContinueTBOXSend(id string, data []byte) (err error) { // After a text has been succesfully delivered, we can now continue the update // Check the status and make sure that is is success, otherwise fail and set the update status as failed var msgStatus tmobile.MessageStatus err = json.Unmarshal(data, &msgStatus) if err != nil { return } return nil } func (m *ManifestSender) setManifestCache(msgID string, cache ManifestCachedPoint) (err error) { client := m.Redis.GetFromPool() defer client.Close() return client.Set(msgIDToRedisKey(msgID), cache) } type ManifestCachedPoint struct { VIN string ManifestID int64 Destination string } func msgIDToRedisKey(msgID string) (redisKey string) { return fmt.Sprintf("manifest_tbox_send_cache:%s", msgID) } func (m *ManifestSender) addVOD(manifest *common.UpdateManifest, vin string) (map[string]string, error) { cds, err := uhelpers.GetCDS(m.conf, services.GetDB().GetCarConfigData(), vin) if err != nil { return nil, err } if vod, ok := cds["VOD"]; ok && vod != "" { manifest.VOD = vod } else { manifest.VOD = defaultVOD } return cds, nil } // When car is a virtual trex, we are going to produce to the kafka message queue as if the sms service did it func (m *ManifestSender) selfKafkaCallback(messageID string) { producer, err := services.GetKafkaProducer() if err != nil { logger.Err(err).Send() return } messageStatus := &kafka_grpc.GRPC_AttendantPayload_MessageStatus{ MessageStatus: &kafka_grpc.MessageStatus{ MessageId: messageID, Status: kafka_grpc.EmumStatus_DELIVERED, }, } kafkaMSG := kafka_grpc.GRPC_AttendantPayload{ Handler: "sms_delivery_status_manifest", Data: messageStatus, } binaryPayload, _ := proto.Marshal(&kafkaMSG) err = producer.ProduceBinary(kafka.AttendantServiceGRPCKafka, "4:Service", binaryPayload, nil) if err != nil { err = errors.WithStack(err) logger.Err(err).Msgf("failed to produce kafka message for sms id %s", messageID) } } func (m *ManifestSender) Release() { m.Redis = nil m.db = nil m.conf = nil } var fpParams FingerprintParamer var fpParamsOnce sync.Once func SetFPParams(fpp FingerprintParamer) { fpParams = fpp } func GetFPParams() FingerprintParamer { fpParamsOnce.Do(func() { if fpParams == nil { fpParams = &fingerprintParams{ serialNum: envtool.GetEnv("OTA_MANIFEST_SERIAL", "00000000000000000"), } } }) return fpParams } type fingerprintParams struct { serialNum string } func (p *fingerprintParams) ManifestSerial() string { return p.serialNum } func (p *fingerprintParams) CurTime() time.Time { return time.Now().UTC() } type FingerprintParamer interface { ManifestSerial() string CurTime() time.Time } // Mega hack mode func sendFileKeys(vin string, fileID string) { logger.Info().Str("VIN", vin).Msg("Sending SendFileKeys") db := services.GetDB() err := GetFileKeys(db, common.HMI, vin, []byte(`{"file_ids": ["`+fileID+`"]}`)) if err != nil { logger.Err(err).Str("VIN", vin).Str("file ID", fileID).Msg("Failed to SendFileKeys") } }