package common import ( "encoding/hex" "encoding/json" "fmt" "sort" "strconv" "strings" "time" v "github.com/fiskerinc/cloud-services/pkg/utils/vod" "github.com/fiskerinc/cloud-services/pkg/vindecoder" "github.com/fiskerinc/cloud-services/pkg/vod_decoder" "github.com/fiskerinc/cloud-services/pkg/common/dbbasemodel" "github.com/fiskerinc/cloud-services/pkg/utils/envtool" "github.com/albenik/bcd" "github.com/jinzhu/copier" "github.com/pkg/errors" ) // Had to copy getenv to stop import cycle var defaultVOD string = envtool.GetEnv("DEFAULT_VOD", "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111") var defaultUpdateDuration int = getDefaultUpdateDuration() const ( ecuICCName string = "ICC" ManifestTypeStandard string = "standard" ManifestTypeForced string = "forced" ManufacturerName string = "FISKER" EnvCurrent string = "current" ) // If a change here will mess with a manifest environment migration, increment MIGRATION_VERSION in // ota_update_go/handlers/update_manifest_migrate.go and create an adapter or return an error if it // is incompatable type UpdateManifest struct { ID int64 `json:"id,omitempty"` Name string `json:"name,omitempty" pg:",unique:name_version" validate:"required,max=255"` Version string `json:"version,omitempty" pg:",unique:name_version" validate:"max=255"` Description string `json:"description,omitempty" validate:"max=5120"` ReleaseNotes string `json:"release_notes,omitempty" validate:"omitempty,max=32768,url"` ECUList string `json:"ecu_list,omitempty"` ECUs []*UpdateManifestECU `json:"ecu_updates,omitempty" pg:"rel:has-many" validate:"omitempty,min=1,dive"` Fingerprint string `json:"fingerprint,omitempty" validate:"max=5000"` CarUpdateID int64 `json:"car_update_id,omitempty" pg:"-"` RollbackEnabled bool `json:"rollback" pg:",use_zero"` Type string `json:"type,omitempty" validate:"max=100"` VOD string `json:"vod,omitempty" pg:"vod"` ManifestType UpdateManifestType `json:"manifest_type,omitempty" validate:"oneof=0 1 2 3 4" swaggerignore:"true"` Active *bool `json:"active,omitempty"` // Use a pointer so we know when we want to change the value Country string `json:"country,omitempty" pg:"country"` PowerTrain string `json:"powertrain,omitempty" pg:"powertrain"` Restraint string `json:"restraint,omitempty" pg:"restraint"` Model string `json:"model,omitempty" pg:"model"` Trim string `json:"trim,omitempty" pg:"trim"` Year int `json:"year,omitempty" pg:"year"` BodyType string `json:"body_type,omitempty" pg:"body_type"` SUMS string `json:"sums,omitempty"` // Software Update Management System Env string `json:"env,omitempty"` // Environment of ECC keys to include UpdateDuration int `json:"update_duration,omitempty" pg:"update_duration"` // Duration of update in minutes MaxAttempts int `json:"max_attempts,omitempty" pg:"max_attempts"` dbbasemodel.DBModelBase } func (um *UpdateManifest) String() string { return fmt.Sprintf("UpdateManifest<%d %s %s>", um.ID, um.Name, um.Version) } // Scrub cleans data for sending to car func (um *UpdateManifest) Scrub(ecuType Device) { um.ID = 0 um.Env = "" um.ECUList = "" um.CreatedAt = nil um.UpdatedAt = nil um.ManifestType = 0 um.Active = nil um.Country = "" um.PowerTrain = "" um.Restraint = "" um.Model = "" um.Trim = "" um.Year = 0 um.BodyType = "" um.SUMS = "" // If the update has no duration, we can default it to 30 if um.UpdateDuration == 0 { um.UpdateDuration = 30 } vodIndex := -1 for x, ecu := range um.ECUs { ecu.Scrub(ecuType, true) if ecu.ECU == "VOD" { vodIndex = x } } if vodIndex != -1 { um.ECUs = append(um.ECUs[:vodIndex], um.ECUs[vodIndex+1:]..., ) } if ecuType != HMI { um.Name = "" um.Version = "" um.Description = "" um.ReleaseNotes = "" } } func (um *UpdateManifest) RemoveParsedS19HexFiles() { for _, ecu := range um.ECUs { ecu.RemoveParsedS19HexFiles() for _, rollback := range ecu.Rollback { rollback.RemoveParsedS19HexFiles() } } } func (um *UpdateManifest) RemoveOriginalS19HexFiles() { for _, ecu := range um.ECUs { ecu.RemoveOriginalS19HexFiles() for _, rollback := range ecu.Rollback { rollback.RemoveOriginalS19HexFiles() } } } func (um *UpdateManifest) RemoveECCKeysFromECUs() { for _, ecu := range um.ECUs { ecu.ECCKeys = nil } } func (um *UpdateManifest) RemoveParsedS19HexFilesRollbacks() { for i := range um.ECUs { for j := range um.ECUs[i].Rollback { um.ECUs[i].Rollback[j].RemoveParsedS19HexFiles() } } } func (um *UpdateManifest) RemoveOriginalS19HexFilesRollbacks() { for i := range um.ECUs { for j := range um.ECUs[i].Rollback { um.ECUs[i].Rollback[j].RemoveOriginalS19HexFiles() } } } func (um *UpdateManifest) SortECUs() { // If this is an old update manifest without sorting info, we will fall back to the old sort nonZero := false for _, ecu := range um.ECUs { if ecu.InstallPriority > 0 { nonZero = true break } } // Have a non-zero value, sort by install priority if nonZero { // Sort in Ascending order sort.Slice(um.ECUs, func(i, j int) bool { return um.ECUs[i].InstallPriority < um.ECUs[j].InstallPriority }) } else { // We do not have any install priorities, so fall back to the old sort method um.sortECUByMode() } } func (um *UpdateManifest) sortECUByMode() { sort.Slice(um.ECUs, func(i, j int) bool { return um.modePosition(um.ECUs[i]) < um.modePosition(um.ECUs[j]) }) } func (um *UpdateManifest) modePosition(ecu *UpdateManifestECU) int { if ecu.SelfDownload { return 4 } if ecu.ECU == "TREX" { return 0 } // Easy catch all case as PDU and OBC are interswappable if ecu.ECU == "PDU" || ecu.ECU == "OBC" { return 3 } if ecu.Mode == "D" { return 2 } return 1 } func (um *UpdateManifest) HasComponent(name string) bool { match := strings.ToUpper(name) for _, ecu := range um.ECUs { if ecu.ECU == match { return true } } return false } func (um *UpdateManifest) HasSelfDownload() bool { for _, ecu := range um.ECUs { if ecu.SelfDownload { return true } } return false } func (um *UpdateManifest) Copy() *UpdateManifest { clone := *um ecus := make([]*UpdateManifestECU, len(um.ECUs)) copier.CopyWithOption(&ecus, &um.ECUs, copier.Option{DeepCopy: true}) clone.ECUs = ecus return &clone } func (um *UpdateManifest) GenerateFingerprint(curDate time.Time, serialSfx string) { if um.Fingerprint == "" { y, m, d := curDate.Date() y = y % 100 um.Fingerprint = fmt.Sprintf("%02d%02d%02d%s%s", y, m, d, ManufacturerName, serialSfx) // 29 is the max length of the fingerprint um.Fingerprint = string([]byte(um.Fingerprint)[:29]) } } // Pulls the vod field from cds, and then applies it to the updateManifest // If the vod is not found, we insert the default vod // We also remove the VOD field from the cds map func (um *UpdateManifest) AddVOD(cds map[string]string) { if um.VOD == "" { if vod, ok := cds["VOD"]; ok && vod != "" { um.VOD = cds["VOD"] } else { um.VOD = defaultVOD } } delete(cds, "VOD") } // Applies the cds coding strings to the VOD field along with each // ECU that is inside the update manifest // Any extra cds does not create the ecu field func (um *UpdateManifest) AddCDSToECUs(cds map[string]string) { um.AddVOD(cds) for key := range um.ECUs { um.ECUs[key].Configuration = cds[um.ECUs[key].ECU] } } // Will take the UpdateManifest's ECU's, and transform their names to send to the car func (um *UpdateManifest) TransformECUNames() { for _, ecu := range um.ECUs { ecu.TransformECUName() } } // Will add the ecu's the set of ECU's if there are no ecu's already in the manifest func (um *UpdateManifest) AddECUsFromCDSList(cds map[string]string) { if len(um.ECUs) == 0 { um.ECUs = make([]*UpdateManifestECU, 0) for key, value := range cds { ecu := UpdateManifestECU{ UpdateManifestID: um.ID, ECU: key, Configuration: value, } um.ECUs = append(um.ECUs, &ecu) } } } // Fill out um.ECUList from um.ECUS func (um *UpdateManifest) FillECUList() { str := "" for _, ecu := range um.ECUs { str = str + ecu.ECU + "," } str = strings.TrimRight(str, ",") um.ECUList = str } func (um *UpdateManifest) GetFileIDs() []string { ids := []string{} if len(um.ECUs) == 0 { return ids } for _, ecu := range um.ECUs { result := ecu.GetFileIDs() if len(result) > 0 { ids = append(ids, result...) } } return ids } // Becaue of the differences in the new hardware versioning, we still need to be able to migrate, so going to send filled old hw_version // This should be deleted as soon as hardware versions list reaches all the way to production func (um *UpdateManifest) MigratePrepareHardwareVersion() { for _, ecu := range um.ECUs { if len(ecu.HWVersions) >= 1 { ecu.HWVersion = ecu.HWVersions[0] } } } var dataLength int = 255 var vodHasCRCToRemovce bool = true // If the given VOD already has a CRC code at the end, remove it var lengthInCRC bool = false // True if the CRC includes the length bytes in its calculation var crcInLength bool = false // True if the length includes the extra 1 bit for crc var lengthInLength bool = true // True if the length includes its own two bytes var choppedLengthOffset int = 2 // Because we are cutting off the front two bytes that would be the length func (um *UpdateManifest) AddSUMSToVOD() (err error) { if um.VOD == "" { um.VOD = defaultVOD } vod, err := hex.DecodeString(um.VOD) if err != nil { return errors.WithStack(err) } //Removing the previous length vod = vod[2:] // Remove the previous CRC if vodHasCRCToRemovce { vod = vod[:len(vod)-1] } // Ensure that the vod byte's is long enough. Just incase it was truncated at the end // 2 byte length, 255 byte data, 1 byte CRC new_vod := make([]byte, dataLength) // copy vod excluding CRC last byte copy(new_vod, vod) // Now we will modify the bytes as so // I did double check this uint16 -> uint8 takes the last 8 bits correctly versionInfo, err := um.parseVersionNumber() if err != nil { return errors.WithStack(err) } //Field Strt Byte StrtBit EndBit byteLength Encoding //Cld. Mnfst.ID 200 bit 0 bit 15 2 bytes BCD bcdEncoded := bcd.FromUint16(uint16(um.ID)) new_vod[200-choppedLengthOffset] = bcdEncoded[0] new_vod[201-choppedLengthOffset] = bcdEncoded[1] //Year 202 bit 0 bit 15 2 bytes BCD bcdEncoded = bcd.FromUint16(uint16(versionInfo.Year)) new_vod[202-choppedLengthOffset] = bcdEncoded[0] new_vod[203-choppedLengthOffset] = bcdEncoded[1] //Month 204 bit 0 bit 7 1 byte BCD var bcdByte byte bcdByte = bcd.FromUint8(uint8(versionInfo.Month)) new_vod[204-choppedLengthOffset] = bcdByte //Major V. 205 bit 0 bit 7 1 byte BCD bcdByte = bcd.FromUint8(uint8(versionInfo.MajorVersion)) new_vod[205-choppedLengthOffset] = bcdByte //Minor V. 206 bit 0 bit 7 1 byte BCD bcdByte = bcd.FromUint8(uint8(versionInfo.MinorVersion)) new_vod[206-choppedLengthOffset] = bcdByte //Eng. Rls. 207 bit 0 bit 7 1 byte binary: 0x00 Formal, 0x01 Engineering if versionInfo.EngineeringRelease { new_vod[207-choppedLengthOffset] = 0x01 } helper := v.NewVODHelper(lengthInCRC, crcInLength, lengthInLength) new_vod = helper.AddLengthAndCRC(new_vod) um.VOD = hex.EncodeToString(new_vod) return } // Given 2023.05.01.02.E parses that up. // No ID in beginning type version struct { Year int Month int MajorVersion int MinorVersion int EngineeringRelease bool // True if .E is present } func (um *UpdateManifest) parseVersionNumber() (v version, err error) { vals := strings.Split(um.SUMS, ".") if len(vals) < 4 { err = errors.Errorf("invalid version %s", um.Version) return } v.Year, err = strconv.Atoi(vals[0]) if err != nil { return } v.Month, err = strconv.Atoi(vals[1]) if err != nil { return } v.MajorVersion, err = strconv.Atoi(vals[2]) if err != nil { return } v.MinorVersion, err = strconv.Atoi(vals[3]) if err != nil { return } if len(vals) > 4 && vals[4] == "E" { v.EngineeringRelease = true } return } func (um *UpdateManifest) GetECUs() []string { ecus := make([]string, len(um.ECUs)) for i, ecu := range um.ECUs { ecus[i] = ecu.ECU } return ecus } func (um *UpdateManifest) getECU(name string) *UpdateManifestECU { for i := range um.ECUs { if um.ECUs[i].ECU == name { return um.ECUs[i] } } return nil } func (um *UpdateManifest) AddECUECCKeys(keys []ECCKeys) { for i, key := range keys { ecu := um.getECU(key.ECU) if ecu != nil { ecu.ECCKeys = &keys[i] } } } func (um *UpdateManifest) Clone() UpdateManifest { copy := UpdateManifest{} js, _ := json.Marshal(um) json.Unmarshal(js, ©) return copy } // I think this code is a bit silly func (um *UpdateManifest) ToUpdateConfigManifest() (configManifest UpdateConfigManifest) { configManifest = UpdateConfigManifest{ CarUpdateID: um.CarUpdateID, ECUs: []*UpdateConfigManifestECU{}, Type: um.Type, VOD: um.VOD, } for _, ecu := range um.ECUs { v := ecu.ToUpdateConfigManifestECU() configManifest.ECUs = append(configManifest.ECUs, &v) } return } type UpdateManifestSearch struct { Search string `json:"search" validate:"max=1024"` UpdateManifest } func getDefaultUpdateDuration() int { i, err := strconv.Atoi(envtool.GetEnv("DEFAULT_UPDATE_DURATION", "30")) if err != nil { return 30 } return i } // FilterCompatibleECUs Expects the UpdateManifest to have a filled VOD field // If the VOD field is not filled, this will return improper results func (um *UpdateManifest)FilterCompatibleECUs(vin string){ fileFilter := ECUFileFilter{} info, ok := vindecoder.DecodeVIN(vin) if !ok { // Hanlde that we are not okay return } fileFilter.Trim, ok = vinTrimToFileTrim(info.Trim) if !ok { // Hanlde that we are not okay return } side, ok := vod_decoder.GetDriverSideFromVOD(um.VOD) if !ok { return } fileFilter.DriveSide, ok = vodDriveSideToFileDriveSide(side) if !ok { return } for _, ecu := range um.ECUs { ecu.FilterECUFiles(fileFilter) } }