535 lines
15 KiB
Go
535 lines
15 KiB
Go
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)
|
|
}
|
|
} |