package handlers import ( "encoding/base64" "encoding/json" "errors" "fmt" "net/http" "otaupdate/services" "github.com/fiskerinc/cloud-services/pkg/common" "github.com/fiskerinc/cloud-services/pkg/common/actionlogger" "github.com/fiskerinc/cloud-services/pkg/httphandlers" "github.com/fiskerinc/cloud-services/pkg/logger" "github.com/fiskerinc/cloud-services/pkg/loggerdataresp" "github.com/fiskerinc/cloud-services/pkg/utils" ) var OTAUpdateECUReplacement = map[string]string{ "TBOX": "MCU", "PDU": "OBC", "EPS": "EPS1", } // Replace PDX ECUs to the names known by T.Rex. func transformECUNames(pdxEcu []string) { for i := 0; i < len(pdxEcu); i++ { ecu := pdxEcu[i] if replacementECU, exists := OTAUpdateECUReplacement[ecu]; exists { pdxEcu[i] = replacementECU } } } var ModelToECUSecOC = map[string][]string{ // '1' - Fisker Ocean One. "1": {"TBOX" /*T.Rex's MCU*/, "GW", "MCU_R" /*It's called EDU 'Electtric Drive Unit'*/, "MCU_F", "BMS", "PDU" /*T.Rex's OBC*/, "VCU", "ECC", "ADAS", "EPS", "BCM", "PKC", "FCM", "MRR", "CMRR_FL", "CMRR_FR", "CMRR_RL", "CMRR_RR"}, } var parseRequest = func(request common.RemoteDiagnosticCommandRequest, w http.ResponseWriter) (interface{}, error) { if request.Command == "can_network" { var args common.RemoteCANNetworkCommandArgs args.Action = request.CANNetAction args.Timeout = request.Timeout return args, nil } if request.Command == "remote_ignition" { var args common.RemoteIgnitionCommandArgs args.Action = request.IgnitionAction args.Timeout = request.Timeout return args, nil } if request.Command == "read_ecu_versions" { var args common.RemoteReadVersionsCommandArgs args.ECUName = request.ECU return args, nil } var args common.RemoteResetDiagnosticCommandArgs args.ECUName = request.ECU //Don't need any keys for reset TBOX if request.ECU != "TBOX" { //XXX TBOX ECUs were divided in to MCU, T.Rex, and NAD. //Keys had already been inserted in DB with the name 'TBOX' for what is referred to as 'MCU' and so we must pull the key by that name. if request.ECU == "MCU" { request.ECU = "TBOX" } eccKeys, err := services.GetDB().GetECCKeys().SelectPrivateKeysByECUsEnv([]string{request.ECU}, "") if loggerdataresp.BadDataErrorResp(w, err, http.StatusNotFound, loggerdataresp.PostgresNoRowsErrorCheck) { return nil, errors.New("") } if len(eccKeys) != 1 { utils.RespError(w, http.StatusNotFound, "ECC keys not found") return nil, errors.New("") } args.UDSKeys = &eccKeys[0] args.UDSKeys.ECU = "" } return args, nil } // HandleVehicleDiagnosticCommand godoc // @Summary Send diagnostic command to car // @Description Send diagnostic command to car // @Accept json // @Produce json // @Param Authorization header string false "Bearer " // @Param Api-Key header string false "" // @Param Command body common.RemoteDiagnosticCommandRequest true "Diagnostic command to send to cars" // @Success 200 {object} common.JSONMessage // @Failure 400 {object} common.JSONError "Bad request" // @Failure 401 {object} common.JSONError "Unauthorized" // @Failure 503 {object} common.JSONError "Service unavailable" // @Router /vehiclediagnosticcommand [post] func HandleVehicleDiagnosticCommand(w http.ResponseWriter, r *http.Request) { var request common.RemoteDiagnosticCommandRequest err := httphandlers.ParseRequest(r, &request) if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) { return } client := services.RedisClientPool().GetFromPool() defer client.Close() b, _ := json.Marshal(request) alDB := services.GetDB().GetActionLog() go func() { actionLog := actionlogger.ActionLog{ VIN: "", Action: actionlogger.CarUpdate, UserIdentifier: httphandlers.GetClientID(r), CallLocation: "github.com/fiskerinc/cloud-services/services/ota_update_go/handlers/vehicle_diagnostic_command.go", Description: string(b), } for _, vin := range request.VINs { actionLog.VIN = vin err = alDB.Insert(actionLog) if err != nil { logger.Err(err).Msg("failed to insert action log inside HandleCarUpdateCancel") } } }() // Since this uses publish, if the car is not awake, the message is never received by the car SendToVin := func(vin string, data interface{}) { err = client.SafePublishMessage( common.TRex.Key(vin), common.Message{ Handler: request.Command, Data: data, }, ) loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) } if request.Command == "write_secoc_key" { for _, vin := range request.VINs { var args common.RemoteUpdateSecOCCommandArgs // Fourth character in VIN is model. var ecus []string for _, ecu := range ModelToECUSecOC[string(vin[3])] { ecus = append(ecus, ecu) } eccKeys, err := services.GetDB().GetECCKeys().SelectPrivateKeysByECUsEnv(ecus, "") transformECUNames(ecus) args.ECUs = ecus if loggerdataresp.BadDataErrorResp(w, err, http.StatusNotFound, loggerdataresp.PostgresNoRowsErrorCheck) { continue } args.UDSKeys = eccKeys for i := 0; i < len(args.UDSKeys); i++ { ecu := args.UDSKeys[i].ECU if replacementECU, exists := OTAUpdateECUReplacement[ecu]; exists { args.UDSKeys[i].ECU = replacementECU } } symKeys, err := services.GetDB().GetSymKeys().SelectByVIN(vin) if loggerdataresp.BadDataErrorResp(w, err, http.StatusNotFound, loggerdataresp.PostgresNoRowsErrorCheck) { continue } args.KeyBase64 = base64.StdEncoding.EncodeToString(symKeys.SecOC.Bytes()) SendToVin(vin, args) } utils.RespJSON(w, http.StatusOK, &common.JSONMessage{ Message: fmt.Sprintf("remote diagnostic command sent to %d vehicles", len(request.VINs)), }) return } args, err := parseRequest(request, w) if err != nil {// TODO: need to write an error to request return } for _, vin := range request.VINs { SendToVin(vin, args) } utils.RespJSON(w, http.StatusOK, &common.JSONMessage{ Message: fmt.Sprintf("remote diagnostic command sent to %d vehicles", len(request.VINs)), }) }