diff --git a/client/client-1.0/Javascript/appclient_commands.txt b/client/client-1.0/Javascript/appclient_commands.txt index 3d3fcc31..363a1510 100644 --- a/client/client-1.0/Javascript/appclient_commands.txt +++ b/client/client-1.0/Javascript/appclient_commands.txt @@ -75,13 +75,14 @@ URL: Vehicle/ADAS/ABS?filter={"type":"static-metadata", "parameter":""} // AGTserver POST input @agtclient.html: agtserver -{"vin":"GEO001", "context":"Independent+OEM+Cloud", "proof":"ABC", "key":"DEF"} +{"action": "agt-request", "vin":"GEO001", "context":"Independent+OEM+Cloud", "proof":"ABC", "key":"DEF"} // ATserver POST input @atclient.html: atserver -{"token":"ag-token", "purpose":"fuel-status", "pop":""} +{"action": "at-request", "agToken":"xyz", "purpose":"fuel-status", "pop":""} +{"action": "at-inquiry", "sessionId":"xxx"} //VISSv2 server request for fuel-status use case: wsclient_uncompressed.html: @@ -90,5 +91,5 @@ wsclient_uncompressed.html: http_client.html: URL: Vehicle/Cabin/Door/Row1/Right/IsOpen -Access token: from ATS request +Access token: from at-request response diff --git a/go.mod b/go.mod index 60ee3523..c7287f28 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ go 1.18 replace github.com/w3c/automotive-viss2/utils => ./utils replace ( - github.com/COVESA/vss-tools/binary/go_parser/datamodel => github.com/UlfBj/vss-tools/binary/go_parser/datamodel v0.0.0-20231219145220-847ed46699dc - github.com/COVESA/vss-tools/binary/go_parser/parserlib => github.com/UlfBj/vss-tools/binary/go_parser/parserlib v0.0.0-20231219145220-847ed46699dc +// github.com/COVESA/vss-tools/binary/go_parser/datamodel => github.com/UlfBj/vss-tools/binary/go_parser/datamodel v0.0.0-20231219145220-847ed46699dc +// github.com/COVESA/vss-tools/binary/go_parser/parserlib => github.com/UlfBj/vss-tools/binary/go_parser/parserlib v0.0.0-20231219145220-847ed46699dc github.com/w3c/automotive-viss2/grpc_pb => ./grpc_pb github.com/w3c/automotive-viss2/server/vissv2server/atServer => ./server/vissv2server/atServer github.com/w3c/automotive-viss2/server/vissv2server/grpcMgr => ./server/vissv2server/grpcMgr @@ -21,8 +21,8 @@ replace ( //replace github.com/w3c/automotive-viss2/protobuf/protoc-out => ./protobuf/protoc-out require ( - github.com/COVESA/vss-tools/binary/go_parser/datamodel v0.0.0-20231218135229-3cd370821711 - github.com/COVESA/vss-tools/binary/go_parser/parserlib v0.0.0-20231218135229-3cd370821711 + github.com/COVESA/vss-tools/binary/go_parser/datamodel v0.0.0-20231222140420-5343e9c0884d + github.com/COVESA/vss-tools/binary/go_parser/parserlib v0.0.0-20231222140420-5343e9c0884d github.com/akamensky/argparse v1.4.0 github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/go-redis/redis v6.15.9+incompatible @@ -38,8 +38,6 @@ require ( google.golang.org/protobuf v1.31.0 ) -require github.com/golang/protobuf v1.5.3 - require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect diff --git a/go.sum b/go.sum index 703b9025..007c1b60 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,8 @@ -github.com/UlfBj/vss-tools/binary/go_parser/datamodel v0.0.0-20231219145220-847ed46699dc h1:IhfqGgTj5bMuyxrpriL/ijQ83n8ieUcCRwhCO61oGt4= -github.com/UlfBj/vss-tools/binary/go_parser/datamodel v0.0.0-20231219145220-847ed46699dc/go.mod h1:g2N97BLJNriANqodWem0usF9fKwwUFHP+VxG1dfSsZQ= -github.com/UlfBj/vss-tools/binary/go_parser/parserlib v0.0.0-20231219145220-847ed46699dc h1:h2Pml3Esx5Wikvw/rjLbBj6ob3KLUGdSqx8LsEU1sUw= -github.com/UlfBj/vss-tools/binary/go_parser/parserlib v0.0.0-20231219145220-847ed46699dc/go.mod h1:rWjamgjPuXRJ5kKJrwibrR1Pe771seoUO/9OauCu/Y4= +github.com/COVESA/vss-tools/binary/go_parser/datamodel v0.0.0-20211207094201-7208d48f32b6/go.mod h1:g2N97BLJNriANqodWem0usF9fKwwUFHP+VxG1dfSsZQ= +github.com/COVESA/vss-tools/binary/go_parser/datamodel v0.0.0-20231222140420-5343e9c0884d h1:xDbVda6eQvHk6MotDq4iKnKO/zCmSpLdkDtTkyljBV4= +github.com/COVESA/vss-tools/binary/go_parser/datamodel v0.0.0-20231222140420-5343e9c0884d/go.mod h1:g2N97BLJNriANqodWem0usF9fKwwUFHP+VxG1dfSsZQ= +github.com/COVESA/vss-tools/binary/go_parser/parserlib v0.0.0-20231222140420-5343e9c0884d h1:/m3tP0SJlrfGVnX52eNu/oSo/aFFH2u4NtmcqRCdl/E= +github.com/COVESA/vss-tools/binary/go_parser/parserlib v0.0.0-20231222140420-5343e9c0884d/go.mod h1:rWjamgjPuXRJ5kKJrwibrR1Pe771seoUO/9OauCu/Y4= github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc= github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= diff --git a/server/agt_server/agt_server.go b/server/agt_server/agt_server.go index 214b272b..92601778 100644 --- a/server/agt_server/agt_server.go +++ b/server/agt_server/agt_server.go @@ -45,7 +45,7 @@ type Payload struct { Context string `json:"context"` Proof string `json:"proof"` //Key utils.JsonWebKey `json:"key"` - Key string `json:"key"` + Key string `json:"key"` } // Handles the request depending on the url and the method for the request @@ -143,7 +143,7 @@ func generateResponse(input string, pop string) string { err := json.Unmarshal([]byte(input), &payload) if err != nil { utils.Error.Printf("generateResponse:error input=%s", input) - return `{"error": "Client request malformed"}` + return `{"action": "agt-request", "error": "Client request malformed"}` } if authenticateClient(payload) { if pop != "" { @@ -151,7 +151,7 @@ func generateResponse(input string, pop string) string { } return generateAgt(payload) // In case no pop claim appears, an ST AGT is issued } - return `{"error": "Client authentication failed"}` + return `{"action": "agt-request", "error": "Client authentication failed"}` } // Client roles checking @@ -223,27 +223,27 @@ func generateLTAgt(payload Payload, pop string) string { err := popToken.Unmarshal(pop) if err != nil { utils.Error.Printf("generateLTAgt: Error unmarshalling pop, err = %s", err) - return `{"error": "Client request malformed"}` + return `{"action": "agt-request", "error": "Client request malformed"}` } if !addCheckJti(popToken.PayloadClaims["jti"]) { utils.Error.Printf("generateLTAgt: JTI used") - return `{"error": "Repeated JTI"}` + return `{"action": "agt-request", "error": "Repeated JTI"}` } err = popToken.CheckSignature() if err != nil { utils.Info.Printf("generateLTAgt: Invalid POP signature") - return `{"error": "Invalid POP signature"}` + return `{"action": "agt-request", "error": "Invalid POP signature"}` } if ok, info := popToken.Validate(payload.Key, "vissv2/agts", GAP, LIFETIME); !ok { utils.Info.Printf("generateLTAgt: Not valid POP Token: %s", info) - return `{"error": "Invalid POP Token"}` + return `{"action": "agt-request", "error": "Invalid POP Token"}` } // Generates the response token var jwtoken utils.JsonWebToken var unparsedId uuid.UUID if unparsedId, err = uuid.NewRandom(); err != nil { // Better way to generate uuid than calling an ext program utils.Error.Printf("generateAgt:Error generating uuid, err=%s", err) - return `{"error": "Internal error"}` + return `{"action": "agt-request", "error": "Internal error"}` } iat := int(time.Now().Unix()) exp := iat + LT_DURATION // defined by const @@ -259,7 +259,7 @@ func generateLTAgt(payload Payload, pop string) string { //utils.Info.Printf("generateAgt:jwtPayload=%s", jwtoken.GetPayload()) jwtoken.Encode() jwtoken.AssymSign(privKey) - return `{"token":"` + jwtoken.GetFullToken() + `"}` + return `{"action": "agt-request", "token":"` + jwtoken.GetFullToken() + `"}` } // Generates an AGT (short term) @@ -268,7 +268,7 @@ func generateAgt(payload Payload) string { uuid, err := exec.Command("uuidgen").Output() if err != nil { utils.Error.Printf("generateAgt:Error generating uuid, err=%s", err) - return `{"error": "Internal error"}` + return `{"action": "agt-request", "error": "Internal error"}` } uuid = uuid[:len(uuid)-1] // remove '\n' char iat := int(time.Now().Unix()) @@ -285,7 +285,7 @@ func generateAgt(payload Payload) string { utils.Info.Printf("generateAgt:jwtPayload=%s", jwtoken.GetPayload()) jwtoken.Encode() jwtoken.AssymSign(privKey) - return `{"token":"` + jwtoken.GetFullToken() + `"}` + return `{"action": "agt-request", "token":"` + jwtoken.GetFullToken() + `"}` } func main() { diff --git a/server/server b/server/server deleted file mode 100755 index d88d05e0..00000000 Binary files a/server/server and /dev/null differ diff --git a/server/vissv2server/atServer/atServer.go b/server/vissv2server/atServer/atServer.go index 6a22cb77..b2a02436 100644 --- a/server/vissv2server/atServer/atServer.go +++ b/server/vissv2server/atServer/atServer.go @@ -70,24 +70,13 @@ type AtValidatePayload struct { } type AtGenPayload struct { - Token string `json:"token"` + Token string `json:"agToken"` Purpose string `json:"purpose"` Pop string `json:"pop"` Agt utils.ExtendedJwt PopTk utils.PopToken } -/***** No needed, as we use utils.JsonWebToken -type AgToken struct { - Vin string `json:"vin"` - Iat int `json:"iat"` - Exp int `json:"exp"` - Context string `json:"clx"` - Key string `json:"pub"` - Audience string `json:"aud"` - JwtId string `json:"jti"` -} -*****/ var purposeList map[string]interface{} var pList []PurposeElement @@ -121,12 +110,6 @@ type ScopeElement struct { NoAccess []string } -/*type TokenCacheElem struct { - GatingId int - Token string - Handle string -}*/ - type PendingListElem struct { GatingId int Consent string @@ -142,23 +125,10 @@ type ActiveListElem struct { } const LISTSIZE = 100 -//var tokenCache []TokenCacheElem var pendingList []PendingListElem var activeList []ActiveListElem var expiryTicker *time.Ticker -func initVssFile() bool { - filePath := "vss_vissv2.binary" - VSSTreeRoot = golib.VSSReadTree(filePath) - - if VSSTreeRoot == nil { - utils.Error.Println("Tree file not found") - return false - } - - return true -} - // Initializes AGT Server public key for AGT checking func initAgtKey() { err := utils.ImportRsaPubKey(AGT_PUB_KEY_DIRECTORY, &agtKey) @@ -254,9 +224,11 @@ func reDialer(dialer websocket.Dialer, sessionUrl url.URL) *websocket.Conn { utils.Error.Printf("Data session dial error:%s\n", err) time.Sleep(2 * time.Second) } else { + utils.Info.Printf("ECF dial success.\n") return conn } } + utils.Error.Printf("ECF dial failure.\n") return nil } @@ -295,18 +267,22 @@ func generateParentResponse(input string) string { } func generateClientResponse(input string, ecfSendChan chan string, ecfAvailable bool) string { - if strings.Contains(input, "purpose") { // Request for access token + if strings.Contains(input, "at-request") { return accessTokenResponse(input, ecfSendChan, ecfAvailable) - } else { // consent inquiry request + } else if strings.Contains(input, "at-inquiry") { return consentInquiryResponse(input) + } else { + return `{"action":"unknown","status":"401-Bad request"}` } } func generateEcfResponse(input string, vissChan chan string) string { - if strings.Contains(input, "consent-reply") { // Consent reply request + if strings.Contains(input, "consent-reply") { return consentReplyResponse(input) - } else { // consent cancel request + } else if strings.Contains(input, "consent-cancel") { return consentCancelResponse(input, vissChan) + } else { + return `{"action":"unknown","status":"401-Bad request"}` } } @@ -353,7 +329,7 @@ func consentCancelResponse(request string, vissChan chan string) string { } } for i := 0; i < LISTSIZE; i++ { - if pendingList[i].GatingId == gatingId { + if activeList[i].GatingId == gatingId { removeFromActiveList(i) vissChan <- requestMap["messageId"].(string) // remove eventual subscription return `{"action":"consent-cancel", "status":"200-OK"}` @@ -472,9 +448,6 @@ func noScopeResponse(input string) string { return `{"no_access":` + res + `}` } -// Validates an access token, returns validation message. -// The only validation done is the one regarding the Access Token List - func tokenValidationResponse(input string) string { var inputMap map[string]interface{} err := json.Unmarshal([]byte(input), &inputMap) @@ -484,9 +457,7 @@ func tokenValidationResponse(input string) string { } var atValidatePayload AtValidatePayload extractAtValidatePayloadLevel1(inputMap, &atValidatePayload) -// var isCached bool -// atValidatePayload.Token, isCached = searchCache(atValidatePayload.Token) - atValidatePayload.Token = getTokenSaveHandle(atValidatePayload.Token) + atValidatePayload.Token = getCompleteToken(atValidatePayload.Token) err = utils.VerifyTokenSignature(atValidatePayload.Token, theAtSecret) if err != nil { utils.Info.Printf("tokenValidationResponse:invalid signature, error= %s, token=%s", err, atValidatePayload.Token) @@ -503,8 +474,7 @@ func tokenValidationResponse(input string) string { utils.Info.Printf("validateTokenExpiry fails with result=%d", res) return `{"validation":"` + strconv.Itoa(res) + `"}` } -// tokenHandle := cacheToken(atValidatePayload.Token, isCached) - tokenHandle, gatingId := getGatingIdAndTokenHandle(atValidatePayload.Token) + gatingId, tokenHandle := getGatingIdAndTokenHandle(atValidatePayload.Token) if tokenHandle != "" { return `{"validation":"0", "gatingId":"` + gatingId + `", "handle":"` + tokenHandle + `"}` } else { @@ -512,17 +482,9 @@ func tokenValidationResponse(input string) string { } } -func getTokenSaveHandle(token string) string { //input token may be handle or complete token. If handle then save it. Return complete token. -/* for i := 0; i < len(tokenCache); i++ { - if token == tokenCache[i].Token || token == tokenCache[i].Handle { - return tokenCache[i].Token, true - } - }*/ +func getCompleteToken(token string) string { //input token may be handle or complete token. Return complete token. for i := 0; i < LISTSIZE; i++ { if token == activeList[i].Atoken || token == activeList[i].AtokenHandle { - if token != activeList[i].AtokenHandle { - activeList[i].AtokenHandle = extractSignature(token) - } return activeList[i].Atoken } } @@ -530,20 +492,6 @@ func getTokenSaveHandle(token string) string { //input token may be handle or c } func getGatingIdAndTokenHandle(token string) (string, string) { -/*func cacheToken(token string, isCached bool) string { - for i := 0; i < len(tokenCache); i++ { - if isCached { - if token == tokenCache[i].Token { - return tokenCache[i].Handle - } - } else { - if tokenCache[i].Token == "" || validateTokenExpiry(token) != 0 { - tokenCache[i].Token = token - tokenCache[i].Handle = extractSignature(token) - return tokenCache[i].Handle - } - } - }*/ for i := 0; i < LISTSIZE; i++ { if token == activeList[i].Atoken { return strconv.Itoa(activeList[i].GatingId), activeList[i].AtokenHandle @@ -633,18 +581,18 @@ func accessTokenResponse(request string, ecfSendChan chan string, ecfAvailable b err := json.Unmarshal([]byte(request), &payload) // Unmarshalls the request if err != nil { utils.Error.Printf("accessTokenResponse:error request=%s", request) - return `{"error": "Client request malformed"}` + return `{"action": "at-request", "error": "Client request malformed"}` } err = payload.Agt.DecodeFromFull(payload.Token) // Decodes the AGT included in the request if err != nil { utils.Error.Printf("accessTokenResponse: error decoding token=%s", payload.Token) - return `{"error":"AGT Malformed"}` + return `{"action": "at-request", "error":"AGT Malformed"}` } if payload.Pop != "" { // Checks for POP token and decodes if exists err = payload.PopTk.Unmarshal(payload.Pop) if err != nil { utils.Error.Printf("accessTokenResponse: error decoding pop, error=%s, pop=%s", err, payload.Agt.PayloadClaims["pop"]) - return `{"error":"POP malformed"}` + return `{"action": "at-request", "error":"POP malformed"}` } } valid, errResponse := validateRequest(payload) // Validates the request @@ -655,20 +603,36 @@ func accessTokenResponse(request string, ecfSendChan chan string, ecfAvailable b if ecfAvailable { writeToPendingList(gatingId, payload) utils.Info.Printf("requesting ECF about consent") - ecfSendChan<-`{"action": "consent-ask", "purpose": "`+payload.Purpose+`", "user-roles": "`+payload.Agt.PayloadClaims["clx"]+`", "messageId": "`+strconv.Itoa(gatingId) + `"}` - return `{"sessionId":"` + strconv.Itoa(gatingId) + `"}` +// ecfSendChan<-`{"action": "consent-ask", "purpose": "`+ payload.Purpose + `", "user-roles": "`+ payload.Agt.PayloadClaims["clx"] + + ecfSendChan<-`{"action": "consent-ask", "user-roles": "`+ payload.Agt.PayloadClaims["clx"] + `", "purpose": "`+ payload.Purpose + + `", "signal_access":` + getSignalAccess(payload.Purpose) + `, "messageId": "`+strconv.Itoa(gatingId) + `"}` + return `{"action": "at-request", "sessionId":"` + strconv.Itoa(gatingId) + `", "consent":"NOT_SET"}` } else { - return `{"error":"consent framework not accessible"}` + return `{"action": "at-request", "error":"consent framework not accessible"}` } } else { at := generateAt(payload) writeToActiveList(gatingId, at) - return at + return `{"action": "at-request", "aToken":"` + at + `"}` } } return errResponse } +func getSignalAccess(purpose string) string { + for i := 0; i < len(pList); i++ { + if pList[i].Short == purpose { + signalAccess, err := json.Marshal(pList[i].Access) + if err != nil { + utils.Error.Printf("getSignalAccess:Marshall error=%s", err) + return "" + } + return string(signalAccess) + } + } + return "" +} + func checkifConsent(purpose string) bool { for i := 0; i < len(pList); i++ { //utils.Info.Printf("validatePurpose:purposeList[%d].Short=%s", i, pList[i].Short) @@ -700,29 +664,24 @@ func consentInquiryResponse(input string) string { for i := 0; i < LISTSIZE; i++ { if pendingList[i].GatingId == gatingId { if pendingList[i].Consent == "NOT_SET" { - return `{"sessionId":` + strconv.Itoa(gatingId) + `", "consent":"NOT_SET"}` + return `{"action": "at-inquiry", "sessionId":` + strconv.Itoa(gatingId) + `", "consent":"NOT_SET"}` } else if pendingList[i].Consent == "NO" { removeFromPendingList(i) - return `{"consent":"NO"}` + return `{"action": "at-inquiry", "consent":"NO"}` } else { // YES or IN_VEHICLE atGenData := removeFromPendingList(i) at := generateAt(atGenData) writeToActiveList(gatingId, at) - return `{"token":"` + at + `", "consent":"` + pendingList[i].Consent + `"}` + return `{"action": "at-inquiry", "aToken":"` + at + `", "consent":"` + pendingList[i].Consent + `"}` } } } - return `{"error":"404 - Not-found"}` + return `{"action": "at-inquiry", "error":"404 - Not-found"}` } func extractGatingId(input string) int { - var inputMap map[string]interface{} - err := json.Unmarshal([]byte(input), &inputMap) - if err != nil { - utils.Error.Printf("extractGatingId:error input=%s", err) - return -1 - } - gatingId, err := strconv.Atoi(inputMap["sessionId"].(string)) + gatingIdStr := extractKeyValue("sessionId", input) + gatingId, err := strconv.Atoi(gatingIdStr) if err != nil { utils.Error.Printf("extractGatingId:error converting id=%s", err) return -1 @@ -730,6 +689,16 @@ func extractGatingId(input string) int { return gatingId } +func extractKeyValue(key string, input string) string { + var inputMap map[string]interface{} + err := json.Unmarshal([]byte(input), &inputMap) + if err != nil { + utils.Error.Printf("extractKeyValue:error input=%s", err) + return "" + } + return inputMap[key].(string) +} + func validateTokenTimestamps(iat int, exp int) bool { now := time.Now() if now.Before(time.Unix(int64(iat), 0)) { @@ -829,31 +798,31 @@ func deleteJti(jti string) { func validatePop(payload AtGenPayload) (bool, string) { // Check jti if !addCheckJti(payload.PopTk.PayloadClaims["jti"]) { - utils.Error.Printf("validateRequest: JTI used") + utils.Error.Printf("validatePop: JTI used") return false, `{"error": "Repeated JTI"}` } // Check signaure if err := payload.PopTk.CheckSignature(); err != nil { - utils.Info.Printf("validateRequest: Invalid POP signature: %s", err) + utils.Info.Printf("validatePop: Invalid POP signature: %s", err) return false, `{"error": "Cannot validate POP signature"}` } // Check exp: no need, iat will be used instead // Check iat if ok, cause := payload.PopTk.CheckIat(GAP, LIFETIME); !ok { - utils.Info.Printf("validateRequest: Invalid POP iat: %s", cause) + utils.Info.Printf("validatePop: Invalid POP iat: %s", cause) return false, `{"error": "Cannot validate POP iat"}` } // Check that pub (thumprint) corresponds with pop key if ok, _ := payload.PopTk.CheckThumb(payload.Agt.PayloadClaims["pub"]); !ok { - utils.Info.Printf("validateRequest: PubKey in POP is not same as in AGT") + utils.Info.Printf("validatePop: PubKey in POP is not same as in AGT") return false, `{"error": "Keys in POP and AGToken are not matching"}` } // Check aud if ok, _ := payload.PopTk.CheckAud("vissv2/agts"); !ok { - utils.Info.Printf("validateRequest: Aud in POP not valid") + utils.Info.Printf("validatePop: Aud in POP not valid") return false, `{"error": "Invalid aud"}` } - //utils.Info.Printf("validateRequest:Proof of possession of key pair failed") + //utils.Info.Printf("validatePop:Proof of possession of key pair failed") //return false, `{"error": "Proof of possession of key pair failed"}` return true, "" } @@ -915,7 +884,7 @@ func generateAt(payload AtGenPayload) string { utils.Info.Printf("generateAt:jwtPayload=%s", jwtoken.GetPayload()) jwtoken.Encode() jwtoken.SymmSign(theAtSecret) - return `{"token":"` + jwtoken.GetFullToken() + `"}` + return jwtoken.GetFullToken() } func initPurposelist() { @@ -1221,11 +1190,9 @@ func extractScopeElementsL4NoAccessL1(index int, noAccessElem []interface{}) { } func initLists() { -// tokenCache = make([]TokenCacheElem, LISTSIZE) pendingList = make([]PendingListElem, LISTSIZE) activeList = make([]ActiveListElem, LISTSIZE) for i := 0; i < LISTSIZE; i++ { -// tokenCache[i].Token = "" pendingList[i].GatingId = -1 activeList[i].GatingId = -1 } @@ -1249,6 +1216,7 @@ func writeToActiveList(gatingId int, at string) { if activeList[i].GatingId == -1 { activeList[i].GatingId = gatingId activeList[i].Atoken = at + activeList[i].AtokenHandle = extractSignature(activeList[i].Atoken) activeList[i].AtExpiryTime = utils.ExtractFromToken(at, "exp") setExpiryTicker() return @@ -1297,9 +1265,10 @@ func purgeLists() string { return "" } if now.After(time.Unix(int64(listExpiry), 0)) { + gatingId := activeList[i].GatingId removeFromActiveList(i) setExpiryTicker() - return strconv.Itoa(activeList[i].GatingId) + return strconv.Itoa(gatingId) } } return "" @@ -1316,7 +1285,7 @@ func setExpiryTicker() { listExpiryStr = pendingList[i].AgtExpiryTime listExpiry, err := strconv.Atoi(listExpiryStr) if err != nil { - utils.Error.Print("Error reading expiry time. ", err) + utils.Error.Print("Error reading expiry time.", err) return } if minExpiry.After(time.Unix(int64(listExpiry), 0)) { @@ -1331,7 +1300,7 @@ func setExpiryTicker() { listExpiryStr = activeList[i].AtExpiryTime listExpiry, err := strconv.Atoi(listExpiryStr) if err != nil { - utils.Error.Print("Error reading expiry time. ", err) + utils.Error.Print("Error reading expiry time.", err) return } if minExpiry.After(time.Unix(int64(listExpiry), 0)) { @@ -1340,12 +1309,16 @@ func setExpiryTicker() { } } tickerValue := minExpiry.Sub(time.Now()) +// utils.Info.Print("Expiry ticker period=%s", tickerValue) if tickerValue > 0 && isUpdated { expiryTicker.Reset(tickerValue) + } else { + expiryTicker.Stop() } } -func AtServerInit(viss2Chan chan string, viss2CancelChan chan string, consentSupport bool) { +func AtServerInit(viss2Chan chan string, viss2CancelChan chan string, vssRootReference *gomodel.Node_t, consentSupport bool) { + VSSTreeRoot = vssRootReference clientChan := make(chan string) ecfReceiveChan := make(chan string) ecfSendChan := make(chan string) @@ -1353,7 +1326,6 @@ func AtServerInit(viss2Chan chan string, viss2CancelChan chan string, consentSup initPurposelist() initScopeList() - initVssFile() initAgtKey() initLists() initGatingId() @@ -1393,7 +1365,7 @@ func AtServerInit(viss2Chan chan string, viss2CancelChan chan string, consentSup gatingId := purgeLists() if gatingId != "" { viss2CancelChan <- gatingId - } + } else } } } diff --git a/server/vissv2server/atServer/ecfSim/ecfSimulator.go b/server/vissv2server/atServer/ecfSim/ecfSimulator.go index 4a015c40..869da36c 100644 --- a/server/vissv2server/atServer/ecfSim/ecfSimulator.go +++ b/server/vissv2server/atServer/ecfSim/ecfSimulator.go @@ -30,6 +30,8 @@ var statusIndex int var replyStatus [3]string = [3]string{`“status”: “200-OK”`, `“status”: “401-Bad request”`, `“status”: “404-Not found”`} var postponeTicker *time.Ticker var postponedRequest string +var cancelTicker *time.Ticker +var cancelRequest string func initEcfComm(receiveChan chan string, sendChan chan string, muxServer *http.ServeMux) { ecfHandler := makeEcfHandler(receiveChan, sendChan) @@ -83,15 +85,14 @@ func ecfSender(conn *websocket.Conn, sendChan chan string) { } func dispatchResponse(request string, sendChan chan string) { - var response string var requestMap map[string]interface{} + errorIndex := statusIndex err := json.Unmarshal([]byte(request), &requestMap) if err != nil { fmt.Printf("dispatchResponse:Request unmarshal error=%s", err) - response = `{"error":"request malformed"}` - } else { - response = `{"action":"` + requestMap["action"].(string) + `", "status":"` + replyStatus[statusIndex] + `"}` + errorIndex = 1 //bad request } + response := `{"action":"` + requestMap["action"].(string) + `", "status":"` + replyStatus[errorIndex] + `"}` sendChan <- response } @@ -99,9 +100,9 @@ func uiDialogue(request string) string { var actionNum string var newstatusIndex int fmt.Printf("\nCurrent response to all requests=%s\n", replyStatus[statusIndex]) - fmt.Printf("Change to 0:200-OK / 1:401-Bad request / 2:404-Not found / 4:Keep current response: ") + fmt.Printf("Change to 0:200-OK / 1:401-Bad request / 2:404-Not found / 3:Keep current response: ") fmt.Scanf("%d", &newstatusIndex) - if newstatusIndex >= 0 && newstatusIndex <= 3 { + if newstatusIndex >= 0 && newstatusIndex <= 2 { statusIndex = newstatusIndex } fmt.Printf("\natServer request=%s\n", request) @@ -109,6 +110,13 @@ func uiDialogue(request string) string { fmt.Scanf("%s", &actionNum) switch actionNum { case "0": + if prepareCancelRequest(request) { + var cancelSecs int + fmt.Printf("Time to activate event to cancel request in seconds: ") + fmt.Scanf("%d", &cancelSecs) + cancelTicker.Reset(time.Duration(cancelSecs) * time.Second) + cancelRequest = request + } return createReply(request, true) case "1": return createReply(request, false) @@ -126,8 +134,32 @@ func uiDialogue(request string) string { return "" } +func prepareCancelRequest(request string) bool { + var cancelDecision string + fmt.Printf("Request= %s", request) + fmt.Printf("Activate event for allowing cancelling of this request (yes/no): ") + fmt.Scanf("%s", &cancelDecision) + if cancelDecision == "yes" { + return true + } + return false +} + +func extractMessageId(request string) string { + var requestMap map[string]interface{} + err := json.Unmarshal([]byte(request), &requestMap) + if err != nil { + fmt.Printf("extractMessageId:Request unmarshal error=%s", err) + return "" + } + if requestMap["messageId"] == nil { + fmt.Printf("extractMessageId:Missing messageId key in request=%s", request) + return "" + } + return requestMap["messageId"].(string) +} + func createReply(request string, consent bool) string { - var reply string var requestMap map[string]interface{} yesNo := "NO" if consent { @@ -136,11 +168,10 @@ func createReply(request string, consent bool) string { err := json.Unmarshal([]byte(request), &requestMap) if err != nil { fmt.Printf("createReply:Request unmarshal error=%s", err) - reply = `{"error":"request malformed"}` + return "" } else { - reply = `{"action":"consent-reply", "consent":"` + yesNo + `", "messageId":"` + requestMap["messageId"].(string) + `"}` + return `{"action":"consent-reply", "consent":"` + yesNo + `", "messageId":"` + requestMap["messageId"].(string) + `"}` } - return reply } func main() { @@ -148,6 +179,7 @@ func main() { sendChan := make(chan string) statusIndex = 0 postponeTicker = time.NewTicker(24 * time.Hour) + cancelTicker = time.NewTicker(24 * time.Hour) go initEcfComm(receiveChan, sendChan, muxServer[0]) fmt.Printf("ECF simulator started. Waiting for request from Access Token server...") @@ -158,20 +190,27 @@ func main() { fmt.Printf("Message received=%s\n", message) if !strings.Contains(message, "status\":") { dispatchResponse(message, sendChan) - response := uiDialogue(message) - if response != "" { - fmt.Printf("Response to atServer=%s\n", response) - sendChan <- response + reply := uiDialogue(message) + if reply != "" { + fmt.Printf("Reply to atServer=%s\n", reply) + sendChan <- reply } } case <-postponeTicker.C: fmt.Printf("postpone ticker triggered") - response := uiDialogue(postponedRequest) - if response != "" { - fmt.Printf("Postponed response to atServer=%s\n", response) - sendChan <- response + reply := uiDialogue(postponedRequest) + if reply != "" { + fmt.Printf("Postponed reply to atServer=%s\n", reply) + sendChan <- reply + } + case <-cancelTicker.C: + fmt.Printf("Cancel ticker triggered") + messageId := extractMessageId(cancelRequest) + if messageId != "" { + request := `{"action":"consent-cancel", "messageId":"` + messageId + `"}` + fmt.Printf("Cancel request to atServer=%s\n", request) + sendChan <- request } } } - } diff --git a/server/vissv2server/serviceMgr/README.md b/server/vissv2server/serviceMgr/README.md index 936c88fa..28041474 100644 --- a/server/vissv2server/serviceMgr/README.md +++ b/server/vissv2server/serviceMgr/README.md @@ -20,27 +20,7 @@ The figure shows the internal architecture of the service manager when it comes Each request for a curve logging subscription instantiates a Go routine that handles the request. An unsubscribe request kills the Go routine.
## Historic data -A Go routine for handling of historic data is spawned at server start up. The vehicle system can via the History control interface control the saving of data for one o more signals via a Unix Domain Socket command with the socket address /var/tmp/vissv2/histctrlserver.sock.
-The write commands available are:
-1. {"action":"create", "path": X, "buf-size":"Y"}
-2. {"action":"start", "path": X, "freq":"Z"}
-3. {"action":"stop", "path": X}
-4. {"action":"delete", "path": X}
-where X can be a single path "x.y.z", or an array of paths ["a.b.c", ..., "x.y.z"], Y is the max number of samples that can be buffered, which must be less than 65535, and Z is the capture frequency in captures per hour, which must be less than 65535.
- -The create request leads to the creation of a buffer of the size requested.
-The start request initates capture of samples at the set frequency until the buffer is full.
-The stop request halts the capture of samples.
-the delete request discards the buffer.
- -If a client issues a request for historic data, specifying a period from now and backwards in time, then the service manager will check if there is historic data saved, and select the part that matches the requested period. If there is no data saved, then the response will only contain the latest data point. - -This architecture supports a use case where a high frequency capture rate is applied to the battery voltage during cranking of the starter motor. The vehile can then start the saving of this data at a high capture frequency, and then issue a stop command when the motor has started. This data can then be available for some time so that a client has a resonable time to issue a request for it.
- -Another use case could be that the vehicle temporarily loses its connection, maybe due to passage through a tunnel. If this is detected by the vehicle telematics unit, it may issue a request over the History control interface to start saving multiple selected signals, but with buf-size set to zero. The later means that the buffer size is automatically increased when it becomes full. -The saving of data will automatically stop at some max limit if no stop command is issued before that. - -A third use case could be that data related to electrical charging shall be saved, the vehicle system then uses the start and stop commands to record the appropriate signals during the charging session. +See the README in the histCtrlSim directory. ## State storage The VISSv2 service manager is started with input on which state storage implementation to use: diff --git a/server/vissv2server/serviceMgr/histctrlSim/README.md b/server/vissv2server/serviceMgr/histctrlSim/README.md new file mode 100644 index 00000000..63de4404 --- /dev/null +++ b/server/vissv2server/serviceMgr/histctrlSim/README.md @@ -0,0 +1,36 @@ +**(C) 2023 Ford Motor Company**
+**(C) 2020 Geotab Inc**
+ +All files and artifacts in this repository are licensed under the provisions of the license provided by the LICENSE file in this repository. + +# Historic data +A Go routine for handling of historic data is spawned at server start up. The Unix domain socket server that it realizes can be used by a vehicle subsystem to control the recording of data for one o more signals. The Unix domain socket file address is /var/tmp/vissv2/histctrlserver.sock.
+The payload structures that is available for a client to issue to the server are:
+1. {"action":"create", "path": X, "buf-size":"Y"}
+2. {"action":"start", "path": X, "freq":"Z"}
+3. {"action":"stop", "path": X}
+4. {"action":"delete", "path": X}
+where X can be a single path "x.y.z", or an array of paths ["a.b.c", ..., "x.y.z"], Y is the max number of samples that can be buffered, which must be less than 65535, and Z is the capture frequency in captures per hour, which must be less than 65535.
+ +The create request leads to the creation of a buffer of the size requested.
+The start request initates capture of samples at the set frequency until the buffer is full.
+The stop request halts the capture of samples.
+The delete request discards the buffer.
+ +If a VISSv2 client issues a request for historic data, specifying a period from now and backwards in time, then the service manager will check if there is historic data saved, and select the part that matches the requested period. If there is no data saved, then the response will only contain the latest data point. + +This architecture supports a use case where a high frequency capture rate is applied to the battery voltage during cranking of the starter motor. The vehile can then start the saving of this data at a high capture frequency, and then issue a stop command when the motor has started. This data can then be available for some time so that a client has a resonable time to issue a request for it.
+ +Another use case could be that the vehicle temporarily loses its connection, maybe due to passage through a tunnel. If this is detected by the vehicle telematics unit, it may issue a request over the History control interface to start saving multiple selected signals, but with buf-size set to zero. The later means that the buffer size is automatically increased when it becomes full. +The saving of data will automatically stop at some max limit if no stop command is issued before that. + +A third use case could be that data related to electrical charging shall be saved, the vehicle system then uses the start and stop commands to record the appropriate signals during the charging session. + +# History control simulator +The hist_ctrl_client.go can be used to simulate a vehicle subsystem that is responsible for controlling the VISSv2 server's functionality for recording historic data. +Its UI can be used to issue the commands described above to control the server to record historic data that can then be retrieved by a client issuing a historic data request. + +To use the History control simulator, open a terminal window, go to the directory where it resides, build it by issuing
+go build
+and then start it by the command
+./histctrlSim diff --git a/server/hist_ctrl_client.go b/server/vissv2server/serviceMgr/histctrlSim/hist_ctrl_client.go similarity index 100% rename from server/hist_ctrl_client.go rename to server/vissv2server/serviceMgr/histctrlSim/hist_ctrl_client.go diff --git a/server/vissv2server/serviceMgr/serviceMgr.go b/server/vissv2server/serviceMgr/serviceMgr.go index fa082c89..c78feb05 100644 --- a/server/vissv2server/serviceMgr/serviceMgr.go +++ b/server/vissv2server/serviceMgr/serviceMgr.go @@ -1085,12 +1085,19 @@ func ServiceMgrInit(mgrId int, serviceMgrChan chan string, stateStorageType stri case "internal-killsubscriptions": isRemoved := true for isRemoved == true { - isRemoved, subscriptionList = scanAndRemoveListItem(subscriptionList, requestMap["RouterId"].(string), "") + isRemoved, subscriptionList = scanAndRemoveListItem(subscriptionList, requestMap["RouterId"].(string)) } case "internal-cancelsubscription": - utils.SetErrorResponse(requestMap, errorResponseMap, "401", "Token expired.", "") - dataChan <- utils.FinalizeMessage(errorResponseMap) - _, subscriptionList = scanAndRemoveListItem(subscriptionList, "", requestMap["gatingId"].(string)) + routerId, subscriptionId := getSubscriptionData(subscriptionList, requestMap["gatingId"].(string)) + if routerId != "" { + requestMap["RouterId"] = routerId + requestMap["action"] = "subscription" + requestMap["requestId"] = nil + requestMap["subscriptionId"] = subscriptionId + utils.SetErrorResponse(requestMap, errorResponseMap, "401", "Token expired or consent cancelled.", "") + dataChan <- utils.FinalizeMessage(errorResponseMap) + _, subscriptionList = scanAndRemoveListItem(subscriptionList, routerId) + } default: utils.SetErrorResponse(requestMap, errorResponseMap, "400", "Unknown action.", "") dataChan <- utils.FinalizeMessage(errorResponseMap) @@ -1147,22 +1154,22 @@ func ServiceMgrInit(mgrId int, serviceMgrChan chan string, stateStorageType stri } // for } -func scanAndRemoveListItem(subscriptionList []SubscriptionState, routerId string, gatingId string) (bool, []SubscriptionState) { +func getSubscriptionData(subscriptionList []SubscriptionState, gatingId string) (string, string) { + for i := 0; i < len(subscriptionList); i++ { + if subscriptionList[i].GatingId == gatingId { + return subscriptionList[i].RouterId, strconv.Itoa(subscriptionList[i].SubscriptionId) + } + } + utils.Error.Printf("getSubscriptionData: gatingId = %s not on subscription list", gatingId) + return "", "" +} + +func scanAndRemoveListItem(subscriptionList []SubscriptionState, routerId string) (bool, []SubscriptionState) { removed := false doRemove := false - isRouterId := true - if routerId == "" && gatingId != "" { - isRouterId = false - } for i := 0; i < len(subscriptionList); i++ { - if isRouterId { - if subscriptionList[i].RouterId == routerId { - doRemove = true - } - } else { - if subscriptionList[i].GatingId == gatingId { - doRemove = true - } + if subscriptionList[i].RouterId == routerId { + doRemove = true } if doRemove { _, subscriptionList = deactivateSubscription(subscriptionList, strconv.Itoa(subscriptionList[i].SubscriptionId)) diff --git a/server/vissv2server/vissv2server.go b/server/vissv2server/vissv2server.go index 16d9ed67..188e8a5b 100644 --- a/server/vissv2server/vissv2server.go +++ b/server/vissv2server/vissv2server.go @@ -226,66 +226,15 @@ func getTokenErrorMessage(index int) string { func setTokenErrorResponse(reqMap map[string]interface{}, errorCode int) { utils.SetErrorResponse(reqMap, errorResponseMap, "400", "Bad Request", getTokenErrorMessage(errorCode)) - // errMsg := "" - // bitValid := 1 - // for i := 0; i < 8; i++ { - // if errorCode&bitValid == bitValid { - // errMsg += getTokenErrorMessage(i) - // } - // bitValid = bitValid << 1 - // } - // utils.SetErrorResponse(reqMap, errorResponseMap, "400", "Bad Request", errMsg) } // Sends a message to the Access Token Server to validate the Access Token paths and permissions -func verifyToken(token string, action string, paths string, validation int) (int, string, int) { +func verifyToken(token string, action string, paths string, validation int) (int, string, string) { handle := "" - gatingId := -1 -/* - hostIp := utils.GetServerIP() - var url string - if utils.SecureConfiguration.TransportSec == "yes" { - url = "https://" + hostIp + ":" + utils.SecureConfiguration.AtsSecPort + "/ats" - } else { - url = "http://" + hostIp + ":8600/ats" - } - utils.Info.Printf("verifyToken: Sending validation request to AT, url = %s", url) - - data := []byte(`{"token":"` + token + `","paths":` + paths + `,"action":"` + action + `","validation":"` + strconv.Itoa(validation) + `"}`) - - req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) - if err != nil { - utils.Error.Print("verifyToken: Error generating request. ", err) - return 42, handle - } - - // Set headers - req.Header.Set("Access-Control-Allow-Origin", "*") - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Host", hostIp+":8600") - - // Set client timeout - client := &http.Client{Timeout: time.Second * 10} - - // Send request - resp, err := client.Do(req) - if err != nil { - utils.Error.Print("verifyToken: Can not stablish connection with ATS ", err) - return 40, handle - } - defer resp.Body.Close() -*/ - request := `{"token":"` + token + `","paths":` + paths + `,"action":"` + action + `","validation":"` + strconv.Itoa(validation) + `"}` + gatingId := "" + request := `{"token":"` + token + `","paths":"` + paths + `","action":"` + action + `","validation":"` + strconv.Itoa(validation) + `"}` atsChannel[0] <- request -/* - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - utils.Error.Print("verifyToken: Error reading response. ", err) - return 41, handle - } -*/ body := <- atsChannel[0] - // Unmarshall json body to map containing the validation claim var bdy map[string]interface{} var err error if err = json.Unmarshal([]byte(body), &bdy); err != nil { @@ -307,11 +256,7 @@ func verifyToken(token string, action string, paths string, validation int) (int handle = bdy["handle"].(string) } if bdy["gatingId"] != nil { - gatingId, err = strconv.Atoi(bdy["gatingId"].(string)) - if err != nil { - utils.Error.Print("verifyToken: Error converting gatingId to int. ", err) - gatingId = -1 - } + gatingId = bdy["gatingId"].(string) } } return atsValidation, handle, gatingId @@ -530,10 +475,10 @@ func validRequest(request string, action string) bool { return isValidSubscribeParams(request) case "unsubscribe": return isValidUnsubscribeParams(request) - default: - if action == "internal-killsubscriptions" { - return true - } + case "internal-killsubscriptions": + return true + case "internal-cancelsubscription": + return true } return false } @@ -765,7 +710,7 @@ func issueServiceRequest(requestMap map[string]interface{}, tDChanIndex int, sDC } tokenHandle := "" - gatingId := -1 + gatingId := "" switch maxValidation%10 { case 0: // validation not required case 1: @@ -775,7 +720,7 @@ func issueServiceRequest(requestMap map[string]interface{}, tDChanIndex int, sDC if requestMap["authorization"] == nil { errorCode = 2 } else { - if requestMap["action"] == "set" || maxValidation == 2 { // no validation for get/subscribe when validation is 1 (write-only) + if requestMap["action"] == "set" || maxValidation%10 == 2 { // no validation for get/subscribe when validation is 1 (write-only) // checks if requestmap authorization is a string if authToken, ok := requestMap["authorization"].(string); !ok { errorCode = 1 @@ -798,7 +743,7 @@ func issueServiceRequest(requestMap map[string]interface{}, tDChanIndex int, sDC if tokenHandle != "" { requestMap["handle"] = tokenHandle } - if gatingId != -1 { + if gatingId != "" { requestMap["gatingId"] = gatingId } serviceDataChan[sDChanIndex] <- utils.FinalizeMessage(requestMap) @@ -917,7 +862,7 @@ func main() { go serviceMgr.ServiceMgrInit(0, serviceMgrChannel[0], *stateDB, *udsPath, *dbFile) go serviceDataSession(serviceMgrChannel[0], serviceDataChan[0], backendChan) case "atServer": - go atServer.AtServerInit(atsChannel[0], atsChannel[1], *consentSupport) + go atServer.AtServerInit(atsChannel[0], atsChannel[1], VSSTreeRoot, *consentSupport) } } diff --git a/tutorial/content/server/Access-control-servers/_index.md b/tutorial/content/server/Access-control-servers/_index.md index 1ac5b5a2..3e75981a 100644 --- a/tutorial/content/server/Access-control-servers/_index.md +++ b/tutorial/content/server/Access-control-servers/_index.md @@ -68,7 +68,7 @@ To configure the VISSv2 server to try to connect to an ECF, it must be started w The figure below shows the the different steps in the dataflow that is necessary when a client wants to initiate a subscription of data that is access controlled and require consent from the data owner. -![VISSv2 consent subscribe dataflow](/automotive-viss2/images/VISSv2-consent-subscribe-dataflow.jpg?width=25pc) +![VISSv2 consent subscribe dataflow](/automotive-viss2/images/VISSv2-consent-subscribe-dataflow.jpg?width=40pc) The dataflow describes a scenario when the client successfully subscribes to data the require both access control and consent by the data owner. Consent can only be required in combination with requiring access control, please see the [Access Control Model](https://raw.githack.com/w3c/automotive/gh-pages/spec/VISSv2_Core.html#access-control-model) chapter. @@ -120,8 +120,7 @@ B. The ECF issues a consent cancellation request to the ATS. C. The AT expiry time is reached. Alternative A: -The service manager will delete the entry on the subscription list, and issue a request to the ATS to delete the entry on the Active list corresponding to the -reference Id from its deleted entry. +The service manager will delete the entry on the subscription list. The corresponding entry on the Active list will remain. Alternative B: The ATS will delete the entry on the Active list, and issue a request to the service manager to delete the entry on the subscription list corresponding to the @@ -133,30 +132,39 @@ and issue a request to the service manager to delete the entry on the subscripti #### Payload syntax for the messages in the client-to-ATGS communication: -AGT request: {"vin":"pseudo-vin", "context":"triplet-sub-roles-see-spec", "proof":"ABC", "key":"DEF"} +AGT request: {“action”: “agt-request”, "vin":"pseudo-vin", "context":"triplet-sub-roles-see-spec", "proof":"ABC", "key":"DEF"} -AGT response: {"token":"xxx"} // if successful validation +AGT response: {“action”: “agt-request”, "token":"x.y.z"} // if successful validation -AGT response: {"error":"error-reason"} // if unsuccessful validation +Error response: {“action”: “agt-request”, "error":"error-reason"} // if unsuccessful validation #### Payload syntax for the messages in the client-to-ATS communication: -AT request: {"agToken":"xyz", "purpose":"purpose-description", "pop":""} // pop shall be an empty string for access control short term flow. +AT request: {“action”: “at-request”, "agToken":"x.y.z", "purpose":"purpose-description", "pop":""} // pop shall be an empty string for access control short term flow. + +AT response 1: {“action”: “at-request”, "aToken":"x.y.z"} ATS->Client // Consent is not required. +. +AT response 2: {“action”: “at-request”, "sessionId ":"reference-Id", “consent”:”NOT_SET”} // Consent is required, and consent reply not obtained yet from ECF. + +AT inquiry request: {“action”: “at-inquiry”, "sessionId":"reference-Id"} // may need to be issued multiple times until consent is provided by ECF. + +AT inquiry response 1: {“action”: “at-inquiry”, "aToken":"x.y.z", “consent”:”YES”} ATS->Client // ECF has provided a positive consent. -AT response: {"aToken":"", “consent”:””} ATS->Client // consent only if consent required, error message if fail on token or consent validation +AT inquiry response 2: {“action”: “at-inquiry”, "sessionId ":"reference-Id", “consent”:”NOT_SET”} // Reply is not obtained yet from ECF. -AT response: {" sessionId ":""} // if consent required, and consent reply not obtained yet from ECF +AT inquiry response 3: {“action”: “at-inquiry”, “consent”:”NO”} // ECF has provided a negative consent. -AT inquiry request: {"sessionId":""} // may need to be issued multiple times until consent is provided by ECF +Error response: {“action”: “same-as-in-request”, "error":"error-reason"} #### Payload syntax for the messages in the ATS-to-ECF communication: -Consent request: {“action”: “consent-ask”, “purpose”: “purpose-description”, “user-roles”: “triplet-sub-roles-see-spec”, “messageId”: ”reference-Id”} +Consent request: {“action”: “consent-ask”, “user-roles”: “triplet-sub-roles-see-spec”, “purpose”: “purpose-description”, "signal_access": [{},..{}], “messageId”: ”reference-Id”} Consent reply request: {“action”: “consent-reply”, “consent”: “YES/NO”, “messageId”: ”reference-Id”} Consent cancellation request: {“action”: “consent-cancel”, “messageId”: ”reference-Id”} -Response to above requests: {“action”: “x”, “status”: “200-OK/404-Not found/401-Bad request”} // action must have same value as in corresponding request. Status is one of the three shown. +Response to above requests: {“action”: “same-as-in-request”, “status”: “200-OK/404-Not found/401-Bad request”} // Status is one of the three examples shown. +See the specification chapter[Purpose list](https://raw.githack.com/w3c/automotive/gh-pages/spec/VISSv2_Core.html#purpose-list) for the definition of the signal_access object. diff --git a/utils/common.go b/utils/common.go index 3a6d9113..25700271 100644 --- a/utils/common.go +++ b/utils/common.go @@ -163,6 +163,8 @@ func SetErrorResponse(reqMap map[string]interface{}, errRespMap map[string]inter } if reqMap["requestId"] != nil { errRespMap["requestId"] = reqMap["requestId"] + } else { + delete(errRespMap, "requestId") } if reqMap["subscriptionId"] != nil { errRespMap["subscriptionId"] = reqMap["subscriptionId"]