Initial cloud-services repo - gateway service + pkg modules
This commit is contained in:
535
pkg/common/updatemanifest.go
Normal file
535
pkg/common/updatemanifest.go
Normal file
@@ -0,0 +1,535 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v "fiskerinc.com/modules/utils/vod"
|
||||
"fiskerinc.com/modules/vindecoder"
|
||||
"fiskerinc.com/modules/vod_decoder"
|
||||
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
"fiskerinc.com/modules/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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user