SAIC: simplify (#27130)
This commit is contained in:
parent
26f79c4be5
commit
64b415c36d
12 changed files with 282 additions and 366 deletions
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/util/transport"
|
||||
"github.com/evcc-io/evcc/vehicle/saic/requests"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -56,19 +55,13 @@ func NewAPI(log *util.Logger, identity *Identity) *API {
|
|||
}
|
||||
|
||||
func (v *API) doRepeatedRequest(path string, event_id string) error {
|
||||
var req *http.Request
|
||||
|
||||
answer := requests.Answer{
|
||||
Data: &v.request.Result,
|
||||
}
|
||||
|
||||
token, err := v.identity.Token()
|
||||
if err != nil {
|
||||
v.request.Status = StatInvalid
|
||||
return err
|
||||
}
|
||||
|
||||
req, err = requests.CreateRequest(
|
||||
req, err := requests.CreateRequest(
|
||||
v.identity.baseUrl,
|
||||
path,
|
||||
http.MethodGet,
|
||||
|
|
@ -81,9 +74,13 @@ func (v *API) doRepeatedRequest(path string, event_id string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = v.DoRequest(req, &answer)
|
||||
var res requests.Answer[requests.ChargeStatus]
|
||||
_, err = doRequest(v, req, &res)
|
||||
if err == nil {
|
||||
v.request.Status = StatValid
|
||||
v.request = ConcurrentRequest{
|
||||
Status: StatValid,
|
||||
Result: res.Data,
|
||||
}
|
||||
} else if err != api.ErrMustRetry {
|
||||
v.request.Status = StatInvalid
|
||||
}
|
||||
|
|
@ -92,11 +89,10 @@ func (v *API) doRepeatedRequest(path string, event_id string) error {
|
|||
|
||||
// This is running concurrently
|
||||
func (v *API) repeatRequest(path string, event_id string) {
|
||||
var err error
|
||||
var count int
|
||||
|
||||
v.request.Status = StatRunning
|
||||
for err = api.ErrMustRetry; err == api.ErrMustRetry && count < 20; {
|
||||
for err := api.ErrMustRetry; err == api.ErrMustRetry && count < 20; {
|
||||
time.Sleep(2 * time.Second)
|
||||
v.log.TRACE.Printf("Starting repeated query. Count: %d", count)
|
||||
err = v.doRepeatedRequest(path, event_id)
|
||||
|
|
@ -108,20 +104,17 @@ func (v *API) repeatRequest(path string, event_id string) {
|
|||
if v.request.Status == StatRunning {
|
||||
v.request.Status = StatInvalid
|
||||
}
|
||||
v.log.TRACE.Printf("Exiting repeated query. Count: %d", count)
|
||||
}
|
||||
|
||||
func (v *API) DoRequest(req *http.Request, result *requests.Answer) (string, error) {
|
||||
var body []byte
|
||||
|
||||
func doRequest[T any](v *API, req *http.Request, result *requests.Answer[T]) (string, error) {
|
||||
resp, err := v.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusUnauthorized {
|
||||
v.log.TRACE.Printf("DoRequest: %s", resp.Status)
|
||||
v.log.TRACE.Printf("doRequest: %s", resp.Status)
|
||||
v.identity.Login()
|
||||
return "", api.ErrMustRetry
|
||||
}
|
||||
|
|
@ -129,17 +122,17 @@ func (v *API) DoRequest(req *http.Request, result *requests.Answer) (string, err
|
|||
event_id := resp.Header.Get("event-id")
|
||||
|
||||
if result != nil {
|
||||
body, err = requests.DecryptAnswer(resp)
|
||||
if err == nil {
|
||||
if err2 := json.Unmarshal(body, result); err2 == nil && result.Code != 0 {
|
||||
if result.Code == 4 {
|
||||
err = api.ErrMustRetry
|
||||
} else {
|
||||
err = fmt.Errorf("%d: %s", result.Code, result.Message)
|
||||
}
|
||||
body, err2 := requests.DecodeResponse(resp)
|
||||
if err2 != nil {
|
||||
return event_id, fmt.Errorf("decrypt: %w", err2)
|
||||
}
|
||||
|
||||
if err2 := json.Unmarshal(body, result); err2 == nil && result.Code != 0 {
|
||||
if result.Code == 4 {
|
||||
err = api.ErrMustRetry
|
||||
} else {
|
||||
err = fmt.Errorf("%d: %s", result.Code, result.Message)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("decrypt: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,17 +149,13 @@ func (v *API) Vehicles() ([]Vehicle, error) {
|
|||
*/
|
||||
|
||||
func (v *API) Wakeup(vin string) error {
|
||||
var req *http.Request
|
||||
var err error
|
||||
var token *oauth2.Token
|
||||
|
||||
token, err = v.identity.Token()
|
||||
token, err := v.identity.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := "vehicle/status?vin=" + requests.Sha256(vin)
|
||||
req, err = requests.CreateRequest(
|
||||
req, err := requests.CreateRequest(
|
||||
v.identity.baseUrl,
|
||||
path,
|
||||
http.MethodGet,
|
||||
|
|
@ -178,21 +167,14 @@ func (v *API) Wakeup(vin string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
v.DoRequest(req, nil)
|
||||
doRequest[any](v, req, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status implements the /user/vehicles/<vin>/status api
|
||||
func (v *API) Status(vin string) (requests.ChargeStatus, error) {
|
||||
var req *http.Request
|
||||
var res requests.ChargeStatus
|
||||
var event_id string
|
||||
var err error
|
||||
var token *oauth2.Token
|
||||
answer := requests.Answer{
|
||||
Data: &res,
|
||||
}
|
||||
var zero requests.ChargeStatus
|
||||
|
||||
// Check if we are already running in the background
|
||||
if v.request.Status == StatValid {
|
||||
|
|
@ -203,18 +185,18 @@ func (v *API) Status(vin string) (requests.ChargeStatus, error) {
|
|||
}
|
||||
if v.request.Status == StatRunning {
|
||||
v.log.TRACE.Printf("StatRunning. Exiting")
|
||||
return res, api.ErrMustRetry
|
||||
return zero, api.ErrMustRetry
|
||||
}
|
||||
v.log.TRACE.Printf("StatInvalid. Starting query")
|
||||
|
||||
token, err = v.identity.Token()
|
||||
token, err := v.identity.Token()
|
||||
if err != nil {
|
||||
return res, err
|
||||
return zero, err
|
||||
}
|
||||
|
||||
path := "vehicle/charging/mgmtData?vin=" + requests.Sha256(vin)
|
||||
// get charging status of vehicle
|
||||
req, err = requests.CreateRequest(
|
||||
req, err := requests.CreateRequest(
|
||||
v.identity.baseUrl,
|
||||
path,
|
||||
http.MethodGet,
|
||||
|
|
@ -223,18 +205,18 @@ func (v *API) Status(vin string) (requests.ChargeStatus, error) {
|
|||
token.AccessToken,
|
||||
"")
|
||||
if err != nil {
|
||||
return res, err
|
||||
return zero, err
|
||||
}
|
||||
|
||||
event_id, err = v.DoRequest(req, &answer)
|
||||
var res requests.Answer[requests.ChargeStatus]
|
||||
event_id, err := doRequest(v, req, &res)
|
||||
if err != nil {
|
||||
v.log.TRACE.Printf("Getting event id failed")
|
||||
return res, err
|
||||
return zero, err
|
||||
}
|
||||
|
||||
if event_id == "" {
|
||||
v.log.TRACE.Printf("Answer without event ID")
|
||||
return res, api.ErrMustRetry
|
||||
return zero, api.ErrMustRetry
|
||||
}
|
||||
|
||||
req, err = requests.CreateRequest(
|
||||
|
|
@ -246,20 +228,17 @@ func (v *API) Status(vin string) (requests.ChargeStatus, error) {
|
|||
token.AccessToken,
|
||||
event_id)
|
||||
if err != nil {
|
||||
v.log.TRACE.Printf("Could not create request %s", err.Error())
|
||||
return res, err
|
||||
return zero, err
|
||||
}
|
||||
|
||||
_, err = v.DoRequest(req, &answer)
|
||||
_, err = doRequest(v, req, &res)
|
||||
|
||||
// Continue checking....
|
||||
if err == api.ErrMustRetry {
|
||||
v.request.Status = StatRunning
|
||||
v.log.TRACE.Printf(" No answer yet. Continue status query in background")
|
||||
go v.repeatRequest(path, event_id)
|
||||
} else if err != nil {
|
||||
v.log.TRACE.Printf("doRequest failed with %s", err.Error())
|
||||
}
|
||||
|
||||
return res, err
|
||||
return res.Data, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,11 +62,6 @@ func (v *Identity) Login() error {
|
|||
}
|
||||
|
||||
func (v *Identity) retrieveToken(data url.Values) (*oauth2.Token, error) {
|
||||
var loginData requests.LoginData
|
||||
answer := requests.Answer{
|
||||
Data: &loginData,
|
||||
}
|
||||
|
||||
data.Set("deviceId", v.deviceId)
|
||||
data.Set("response_type", "code")
|
||||
data.Set("scope", "all")
|
||||
|
|
@ -92,24 +87,27 @@ func (v *Identity) retrieveToken(data url.Values) (*oauth2.Token, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var body []byte
|
||||
body, err = requests.DecryptAnswer(resp)
|
||||
if err == nil && len(body) != 0 {
|
||||
err = json.Unmarshal(body, &answer)
|
||||
if err == nil && answer.Code != 0 {
|
||||
err = fmt.Errorf("%d: %s", answer.Code, answer.Message)
|
||||
}
|
||||
|
||||
tok := oauth2.Token{
|
||||
AccessToken: loginData.Access_token,
|
||||
RefreshToken: loginData.Refresh_token,
|
||||
TokenType: loginData.Token_type,
|
||||
}
|
||||
tok.Expiry = time.Now().Add(time.Second * time.Duration(loginData.Expires_in))
|
||||
return &tok, err
|
||||
body, err := requests.DecodeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
var res requests.Answer[requests.LoginData]
|
||||
if err := json.Unmarshal(body, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Code != 0 {
|
||||
return nil, fmt.Errorf("%d: %s", res.Code, res.Message)
|
||||
}
|
||||
|
||||
tok := oauth2.Token{
|
||||
AccessToken: res.Data.Access_token,
|
||||
RefreshToken: res.Data.Refresh_token,
|
||||
TokenType: res.Data.Token_type,
|
||||
Expiry: time.Now().Add(time.Second * time.Duration(res.Data.Expires_in)),
|
||||
}
|
||||
|
||||
return &tok, nil
|
||||
}
|
||||
|
||||
func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) {
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ func (v *Provider) Range() (int64, error) {
|
|||
val := res.RvsChargeStatus.FuelRangeElec
|
||||
if val < 10 {
|
||||
// Ok, 0 would be possible, but it's more likely that it's an invalid answer.
|
||||
return val, fmt.Errorf("invalid raw range value: %d: %w", val, api.ErrMustRetry)
|
||||
return 0, api.ErrMustRetry
|
||||
}
|
||||
return val / 10, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package requests
|
||||
|
||||
const (
|
||||
CONTENT_ENCRYPTED = "1"
|
||||
PARAM_AUTHENTICATION = "Basic c3dvcmQ6c3dvcmRfc2VjcmV0"
|
||||
TENANT_ID = "459771"
|
||||
USER_TYPE = "app"
|
||||
)
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
func PKCS5Trimming(encrypt []byte) []byte {
|
||||
padding := encrypt[len(encrypt)-1]
|
||||
return encrypt[:len(encrypt)-int(padding)]
|
||||
}
|
||||
|
||||
func Decrypt(cipherText, hexKey, hexIV string) string {
|
||||
if len(cipherText) == 0 || len(hexKey) == 0 || len(hexIV) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
secretKey, _ := hex.DecodeString(hexKey)
|
||||
ivParameter, _ := hex.DecodeString(hexIV)
|
||||
block, err := aes.NewCipher(secretKey)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
decrypter := cipher.NewCBCDecrypter(block, ivParameter)
|
||||
text, _ := hex.DecodeString(cipherText)
|
||||
decrypted := make([]byte, len(text))
|
||||
decrypter.CryptBlocks(decrypted, text)
|
||||
|
||||
return string(PKCS5Trimming(decrypted))
|
||||
}
|
||||
|
||||
func Encrypt(plainText, hexKey, hexIV string) string {
|
||||
if len(plainText) == 0 || len(hexKey) == 0 || len(hexIV) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
secretKey, _ := hex.DecodeString(hexKey)
|
||||
ivParameter, _ := hex.DecodeString(hexIV)
|
||||
block, err := aes.NewCipher(secretKey)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
content := PKCS5Padding([]byte(plainText), block.BlockSize())
|
||||
encrypter := cipher.NewCBCEncrypter(block, ivParameter)
|
||||
|
||||
ciphertext := make([]byte, len(content))
|
||||
encrypter.CryptBlocks(ciphertext, content)
|
||||
|
||||
return hex.EncodeToString(ciphertext)
|
||||
}
|
||||
|
|
@ -1,87 +1,60 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func CalculateRequestVerification(
|
||||
resourcePath string,
|
||||
sendDate int64,
|
||||
tenant,
|
||||
contentType,
|
||||
bodyEncrypted,
|
||||
token string,
|
||||
) string {
|
||||
dateString := strconv.FormatInt(sendDate, 10)
|
||||
str9 := resourcePath + tenant + token + USER_TYPE
|
||||
a2 := Md5(str9)
|
||||
str10 := dateString + CONTENT_ENCRYPTED + contentType
|
||||
a3 := Md5(a2 + str10)
|
||||
str11 := resourcePath + tenant + token + USER_TYPE + dateString + CONTENT_ENCRYPTED + contentType + bodyEncrypted
|
||||
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
a5 := Md5(a3 + dateString)
|
||||
func PKCS5Trimming(encrypt []byte) []byte {
|
||||
padding := encrypt[len(encrypt)-1]
|
||||
return encrypt[:len(encrypt)-int(padding)]
|
||||
}
|
||||
|
||||
if len(a5) != 0 && len(str11) != 0 {
|
||||
return HmacSha256(a5, str11)
|
||||
} else {
|
||||
func Decrypt(cipherText, hexKey, hexIV string) string {
|
||||
if len(cipherText) == 0 || len(hexKey) == 0 || len(hexIV) == 0 {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func DecryptResponse(timeStamp, contentType, cipherText string) string {
|
||||
str4 := timeStamp + CONTENT_ENCRYPTED + contentType
|
||||
a2 := Md5(str4)
|
||||
hashedTimeStamp := Md5(timeStamp)
|
||||
|
||||
if len(cipherText) != 0 {
|
||||
return Decrypt(cipherText, a2, hashedTimeStamp)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func CalculateResponseVerification(str, str2, str3 string) string {
|
||||
str4 := str + CONTENT_ENCRYPTED + str2
|
||||
a2 := Md5(str4)
|
||||
str5 := str + CONTENT_ENCRYPTED + str2 + str3
|
||||
a4 := Md5(a2 + str)
|
||||
return HmacSha256(a4, str5)
|
||||
}
|
||||
|
||||
func EncryptRequest(path string, time int64, tenant, token, body, contentType string) string {
|
||||
sendDate := strconv.FormatInt(time, 10)
|
||||
// tenant
|
||||
resourcePath := ""
|
||||
if len(path) != 0 {
|
||||
resourcePath = "/" + path
|
||||
secretKey, _ := hex.DecodeString(hexKey)
|
||||
ivParameter, _ := hex.DecodeString(hexIV)
|
||||
block, err := aes.NewCipher(secretKey)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
encryptedBody := ""
|
||||
decrypter := cipher.NewCBCDecrypter(block, ivParameter)
|
||||
text, _ := hex.DecodeString(cipherText)
|
||||
decrypted := make([]byte, len(text))
|
||||
decrypter.CryptBlocks(decrypted, text)
|
||||
|
||||
if len(body) != 0 {
|
||||
sb3 := Md5(resourcePath+tenant+token+USER_TYPE) + sendDate + CONTENT_ENCRYPTED + contentType
|
||||
a2 := Md5(sb3)
|
||||
a3 := Md5(sendDate)
|
||||
if len(body) != 0 && len(a2) != 0 && len(a3) != 0 {
|
||||
encryptedBody = Encrypt(body, a2, a3)
|
||||
}
|
||||
}
|
||||
|
||||
return encryptedBody
|
||||
return string(PKCS5Trimming(decrypted))
|
||||
}
|
||||
|
||||
func DecryptRequest(path string, time int64, tenant, token, body, contentType string) string {
|
||||
timeStamp := strconv.FormatInt(time, 10)
|
||||
resourcePath := ""
|
||||
|
||||
if len(path) != 0 {
|
||||
resourcePath = "/" + path
|
||||
func Encrypt(plainText, hexKey, hexIV string) string {
|
||||
if len(plainText) == 0 || len(hexKey) == 0 || len(hexIV) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(body) != 0 {
|
||||
sb3 := Md5(resourcePath+tenant+token+USER_TYPE) + timeStamp + CONTENT_ENCRYPTED + contentType
|
||||
a2 := Md5(sb3)
|
||||
timeStampHash := Md5(timeStamp)
|
||||
return Decrypt(body, a2, timeStampHash)
|
||||
secretKey, _ := hex.DecodeString(hexKey)
|
||||
ivParameter, _ := hex.DecodeString(hexIV)
|
||||
block, err := aes.NewCipher(secretKey)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
|
||||
content := PKCS5Padding([]byte(plainText), block.BlockSize())
|
||||
encrypter := cipher.NewCBCEncrypter(block, ivParameter)
|
||||
|
||||
ciphertext := make([]byte, len(content))
|
||||
encrypter.CryptBlocks(ciphertext, content)
|
||||
|
||||
return hex.EncodeToString(ciphertext)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func Md5(value string) string {
|
||||
if len(value) == 0 {
|
||||
return ""
|
||||
}
|
||||
result := md5.Sum([]byte(value))
|
||||
return hex.EncodeToString(result[:])
|
||||
}
|
||||
|
||||
func Sha1(value string) string {
|
||||
if len(value) == 0 {
|
||||
return ""
|
||||
}
|
||||
result := sha1.Sum([]byte(value))
|
||||
return hex.EncodeToString(result[:])
|
||||
}
|
||||
|
||||
func Sha256(value string) string {
|
||||
if len(value) == 0 {
|
||||
return ""
|
||||
}
|
||||
result := sha256.Sum256([]byte(value))
|
||||
return hex.EncodeToString(result[:])
|
||||
}
|
||||
40
vehicle/saic/requests/helper.go
Normal file
40
vehicle/saic/requests/helper.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
)
|
||||
|
||||
func sum(hash hash.Hash, value string) string {
|
||||
if len(value) == 0 {
|
||||
return ""
|
||||
}
|
||||
hash.Write([]byte(value))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
||||
func Md5(value string) string {
|
||||
return sum(md5.New(), value)
|
||||
}
|
||||
|
||||
func Sha1(value string) string {
|
||||
return sum(sha1.New(), value)
|
||||
}
|
||||
|
||||
func Sha256(value string) string {
|
||||
return sum(sha256.New(), value)
|
||||
}
|
||||
|
||||
func HmacSha256(secret string, message string) string {
|
||||
if len(secret) == 0 || len(message) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write([]byte(message))
|
||||
return hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func HmacSha256(secret string, message string) string {
|
||||
if len(secret) == 0 || len(message) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
key := []byte(secret)
|
||||
h := hmac.New(sha256.New, key)
|
||||
h.Write([]byte(message))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
135
vehicle/saic/requests/request.go
Normal file
135
vehicle/saic/requests/request.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Decorate(req *http.Request) error {
|
||||
req.Header.Set("tenant-id", TENANT_ID)
|
||||
req.Header.Set("user-type", USER_TYPE)
|
||||
req.Header.Set("app-content-encrypted", CONTENT_ENCRYPTED)
|
||||
req.Header.Set("Authorization", PARAM_AUTHENTICATION)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encryptRequest(resourcePath string, sendDate int64, tenant, token, body, contentType string) string {
|
||||
if len(body) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
dateString := strconv.FormatInt(sendDate, 10)
|
||||
|
||||
// tenant
|
||||
if len(resourcePath) != 0 {
|
||||
resourcePath = "/" + resourcePath
|
||||
}
|
||||
|
||||
sb3 := Md5(resourcePath+tenant+token+USER_TYPE) + dateString + CONTENT_ENCRYPTED + contentType
|
||||
a2 := Md5(sb3)
|
||||
a3 := Md5(dateString)
|
||||
|
||||
return Encrypt(body, a2, a3)
|
||||
}
|
||||
|
||||
func calculateRequestVerification(
|
||||
resourcePath string,
|
||||
sendDate int64,
|
||||
tenant, contentType, bodyEncrypted, token string,
|
||||
) string {
|
||||
dateString := strconv.FormatInt(sendDate, 10)
|
||||
str9 := resourcePath + tenant + token + USER_TYPE
|
||||
a2 := Md5(str9)
|
||||
str10 := dateString + CONTENT_ENCRYPTED + contentType
|
||||
a3 := Md5(a2 + str10)
|
||||
str11 := resourcePath + tenant + token + USER_TYPE + dateString + CONTENT_ENCRYPTED + contentType + bodyEncrypted
|
||||
|
||||
a5 := Md5(a3 + dateString)
|
||||
|
||||
return HmacSha256(a5, str11)
|
||||
}
|
||||
|
||||
func CreateRequest(baseUrl, path, httpMethod, request, contentType, token, eventId string) (*http.Request, error) {
|
||||
sendDate := time.Now().UnixMilli()
|
||||
|
||||
endpoint := baseUrl + path
|
||||
|
||||
if len(request) != 0 {
|
||||
request = encryptRequest(
|
||||
path,
|
||||
sendDate,
|
||||
TENANT_ID,
|
||||
token,
|
||||
request,
|
||||
contentType)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(httpMethod, endpoint, bytes.NewReader([]byte(request)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Decorate(req)
|
||||
req.Header.Set("app-send-date", strconv.FormatInt(sendDate, 10))
|
||||
req.Header.Set("original-content-type", contentType)
|
||||
|
||||
if len(token) != 0 {
|
||||
req.Header.Set("blade-auth", token)
|
||||
}
|
||||
if len(eventId) != 0 {
|
||||
req.Header.Set("event-id", eventId)
|
||||
}
|
||||
|
||||
replace := strings.Replace(endpoint, baseUrl, "/", -1)
|
||||
|
||||
req.Header.Set("app-verification-string",
|
||||
calculateRequestVerification(
|
||||
replace,
|
||||
sendDate,
|
||||
TENANT_ID,
|
||||
contentType,
|
||||
request,
|
||||
token))
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decryptResponse(timeStamp, contentType, cipherText string) string {
|
||||
if len(cipherText) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
str4 := timeStamp + CONTENT_ENCRYPTED + contentType
|
||||
a2 := Md5(str4)
|
||||
hashedTimeStamp := Md5(timeStamp)
|
||||
|
||||
return Decrypt(cipherText, a2, hashedTimeStamp)
|
||||
}
|
||||
|
||||
func DecodeResponse(resp *http.Response) ([]byte, error) {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(resp.Status)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return body, err
|
||||
}
|
||||
|
||||
if resp.Header.Get("app-content-encrypted") != "1" {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
decoded := decryptResponse(
|
||||
resp.Header.Get("app-send-date"),
|
||||
resp.Header.Get("original-content-type"),
|
||||
string(body))
|
||||
|
||||
return []byte(decoded), nil
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Decorate(req *http.Request) error {
|
||||
req.Header.Set("tenant-id", TENANT_ID)
|
||||
req.Header.Set("user-type", USER_TYPE)
|
||||
req.Header.Set("app-content-encrypted", CONTENT_ENCRYPTED)
|
||||
req.Header.Set("Authorization", PARAM_AUTHENTICATION)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateRequest(
|
||||
baseUrl string,
|
||||
path string,
|
||||
httpMethod string,
|
||||
request string,
|
||||
contentType string,
|
||||
token string,
|
||||
eventId string,
|
||||
) (*http.Request, error) {
|
||||
appSendDate := time.Now().UnixMilli()
|
||||
|
||||
endpoint := baseUrl + path
|
||||
|
||||
if len(request) != 0 {
|
||||
request = EncryptRequest(
|
||||
path,
|
||||
appSendDate,
|
||||
TENANT_ID,
|
||||
token,
|
||||
request,
|
||||
contentType)
|
||||
}
|
||||
|
||||
bodyReader := bytes.NewReader([]byte(request))
|
||||
req, err := http.NewRequest(httpMethod, endpoint, bodyReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Decorate(req)
|
||||
req.Header.Set("app-send-date", strconv.FormatInt(appSendDate, 10))
|
||||
req.Header.Set("original-content-type", contentType)
|
||||
|
||||
if len(token) != 0 {
|
||||
req.Header.Set("blade-auth", token)
|
||||
}
|
||||
if len(eventId) != 0 {
|
||||
req.Header.Set("event-id", eventId)
|
||||
}
|
||||
|
||||
replace := strings.Replace(endpoint, baseUrl, "/", -1)
|
||||
|
||||
req.Header.Set("app-verification-string",
|
||||
CalculateRequestVerification(
|
||||
replace,
|
||||
appSendDate,
|
||||
TENANT_ID,
|
||||
contentType,
|
||||
request,
|
||||
token))
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func DecryptAnswer(resp *http.Response) ([]byte, error) {
|
||||
var body string
|
||||
var bodyRaw []byte
|
||||
var err error
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
bodyRaw, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return bodyRaw, err
|
||||
}
|
||||
if resp.Header.Get("app-content-encrypted") == "1" {
|
||||
body = DecryptResponse(
|
||||
resp.Header.Get("app-send-date"),
|
||||
resp.Header.Get("original-content-type"),
|
||||
string(bodyRaw))
|
||||
} else {
|
||||
body = string(bodyRaw)
|
||||
}
|
||||
} else {
|
||||
err = errors.New(resp.Status)
|
||||
}
|
||||
|
||||
return []byte(body), err
|
||||
}
|
||||
|
|
@ -1,5 +1,12 @@
|
|||
package requests
|
||||
|
||||
const (
|
||||
CONTENT_ENCRYPTED = "1"
|
||||
PARAM_AUTHENTICATION = "Basic c3dvcmQ6c3dvcmRfc2VjcmV0"
|
||||
TENANT_ID = "459771"
|
||||
USER_TYPE = "app"
|
||||
)
|
||||
|
||||
type ChargeStatus struct {
|
||||
RvsChargeStatus struct {
|
||||
MileageSinceLastCharge int
|
||||
|
|
@ -91,8 +98,8 @@ type LoginData struct {
|
|||
Jti string
|
||||
}
|
||||
|
||||
type Answer struct {
|
||||
type Answer[T any] struct {
|
||||
Code int `json:"code"`
|
||||
Data any `json:"data,omitempty"`
|
||||
Message string `json:"message"`
|
||||
Data T `json:"data,omitempty"`
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue