package vehicleconfig import ( "bytes" "crypto/tls" "encoding/base64" "encoding/json" "encoding/xml" "fmt" "io" "net/http" "time" "github.com/pkg/errors" "fiskerinc.com/modules/common" "fiskerinc.com/modules/common/staticerrors" "fiskerinc.com/modules/logger" "fiskerinc.com/modules/utils/envtool" ) func NewSAPService() SAPServiceInterface { return &SAPService{ sapUser: envtool.GetEnv("SAP_USER", "REPLACE_ME"), sapPass: envtool.GetEnv("SAP_PASS", "REPLACE_ME"), sapURL: envtool.GetEnv("SAP_URL", "REPLACE_ME"), } } type SAPServiceInterface interface { GetSAPOrder(vin string) (order common.VehicleOrder, err error) GetECUVersions(vin string) (versions map[string]string, err error) UpdateECUVersions(vin string, versions map[string]string) (err error) GetFeatureCodes(vin string) (common.SAPResponse, error) SubmitResult(vin string, success bool) (err error) SubmitCarFlashpackVersion(vin string, previousFlashpack string, flashpack string) error } // This is currently a mock of the actual calls to the SAP and the ODX parser services type SAPService struct { sapUser string sapPass string sapURL string } type SAPSubmitResultBody struct { VIN string `json:"vin" validate:"required,vin"` DateTime string `json:"dateTime"` Flag string `json:"flag"` } type SAPSubmitCarFlashpackVersionBody struct { VIN string `json:"vin" validate:"required,vin"` CreatedOn string `json:"createdOn"` CurrentSWV string `json:"currentSWV"` PreviousSWV string `json:"previousSWV"` ModeOfSWU string `json:"modeOfSWU"` } // Returns the current vehicle order from SAP func (cs *SAPService) GetSAPOrder(vin string) (order common.VehicleOrder, err error) { var result common.OrderUpdated err = xml.Unmarshal([]byte(vehicleOrder), &result) if err != nil { return } order = result.VehicleOrder return } // Returns the current ECU versions for a car func (cs *SAPService) GetECUVersions(vin string) (versions map[string]string, err error) { versions = map[string]string{} for _, ecu := range ecus { versions[ecu] = "10000" } return } // Submits the updated ECU versions for a car back to SAP func (cs *SAPService) UpdateECUVersions(vin string, versions map[string]string) (err error) { return } func (cs *SAPService) GetFeatureCodes(vin string) (common.SAPResponse, error) { headers := http.Header{} headers.Add("Authorization", cs.createToken()) url := fmt.Sprintf("%s/vehicleSpec?vin=%s", cs.sapURL, vin) request, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return common.SAPResponse{}, errors.WithStack(err) } client := createHttpClient() request.Header = headers resp, err := client.Do(request) if err != nil { return common.SAPResponse{}, errors.WithStack(err) } if resp.StatusCode != http.StatusOK { return common.SAPResponse{}, errors.WithStack(fmt.Errorf(staticerrors.ErrorSAPFailedCall+" with status: %s", resp.Status)) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return common.SAPResponse{}, errors.WithStack(err) } respPayload := common.SAPResponse{} err = json.Unmarshal(respBody, &respPayload) if err != nil { return common.SAPResponse{}, errors.WithStack(err) } return respPayload, nil } func (cs *SAPService) SubmitResult(vin string, success bool) (err error) { headers := http.Header{} headers.Add("Authorization", cs.createToken()) headers.Add("Content-Type", "application/json") timeNow := time.Now() formattedTime := timeNow.Format("2006-01-02T15:04:05") body := &SAPSubmitResultBody{ VIN: vin, DateTime: formattedTime, Flag: "not completed", } if success { body.Flag = "completed" } bodyJSON, err := json.Marshal(body) if err != nil { return errors.WithStack(err) } return cs.submit("vehicleSpecAck", bodyJSON, http.StatusOK) } func (cs *SAPService) SubmitCarFlashpackVersion(vin string, previousFlashpack string, flashpack string) error { timeNow := time.Now() formattedTime := timeNow.Format("2006-01-02T15:04:05.999999Z") body := &SAPSubmitCarFlashpackVersionBody{ VIN: vin, CreatedOn: formattedTime, PreviousSWV: previousFlashpack, CurrentSWV: flashpack, ModeOfSWU: "OTA", } bodyJSON, err := json.Marshal(body) if err != nil { return errors.WithStack(err) } return cs.submit("swVersionUpdate", bodyJSON, http.StatusCreated) } func (cs *SAPService) submit(endpoint string, bodyJSON []byte, successStatus int) error { headers := http.Header{} headers.Add("Authorization", cs.createToken()) headers.Add("Content-Type", "application/json") url := fmt.Sprintf("%s/%s", cs.sapURL, endpoint) request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(bodyJSON)) if err != nil { return errors.WithStack(err) } client := createHttpClient() request.Header = headers resp, err := client.Do(request) if err != nil { return errors.WithStack(err) } if resp.StatusCode != successStatus { return errors.WithStack(fmt.Errorf(staticerrors.ErrorSAPFailedCall+" with status: %s and payload: %s", resp.Status, string(bodyJSON))) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return errors.WithStack(err) } logger.Info().Msg(fmt.Sprintf("sap service has been notified at %s of car update: %s", endpoint, string(respBody))) return nil } func (cs *SAPService) createToken() string { token := base64.StdEncoding.EncodeToString([]byte(cs.sapUser + ":" + cs.sapPass)) return fmt.Sprintf("Basic %s", token) } func createHttpClient() http.Client { transport := http.DefaultTransport.(*http.Transport) transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} return http.Client{ Transport: transport, } }