Files
cloud-services/services/ota_update_go/handlers/vehicle_diagnostic_command.go

186 lines
5.9 KiB
Go

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 <ID token>"
// @Param Api-Key header string false "<API token>"
// @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)),
})
}