Skip to content

Commit

Permalink
improve request logic and simplify protocol error handling logic
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasAlxDmy authored and sethterashima committed Dec 9, 2023
1 parent 12678f4 commit 71d7881
Show file tree
Hide file tree
Showing 10 changed files with 645 additions and 515 deletions.
4 changes: 2 additions & 2 deletions examples/unlock/unlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func main() {

// Specify the user-agent header value used in HTTP requests to Tesla's servers. The default
// value is constructed from your package name and account.LibraryVersion.
account.UserAgent = "example-unlock/1.0.0"
userAgent := "example-unlock/1.0.0"

if vin == "" {
logger.Printf("Must specify VIN")
Expand Down Expand Up @@ -61,7 +61,7 @@ func main() {

// This example program sends commands over the Internet, which requires a Tesla account login
// token. The protocol can also work over BLE; see other programs in the example directory.
acct, err := account.New(string(oauthToken))
acct, err := account.New(string(oauthToken), userAgent)
if err != nil {
logger.Printf("Authentication error: %s", err)
return
Expand Down
44 changes: 22 additions & 22 deletions pkg/account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@ import (
var (
//go:embed version.txt
libraryVersion string
// UserAgent is sent in HTTP requests.
//
// The default value is "<main package name>/<commit hash> tesla-sdk/<version>".
UserAgent = buildUserAgent()
)

func buildUserAgent() string {
func buildUserAgent(app string) string {
library := strings.TrimSpace("tesla-sdk/" + libraryVersion)
build, ok := debug.ReadBuildInfo()
if !ok {
Expand All @@ -40,26 +36,29 @@ func buildUserAgent() string {
if len(path) == 0 {
return library
}
app := path[len(path)-1]

var version string
if build.Main.Version != "(devel)" && build.Main.Version != "" {
version = build.Main.Version
} else {
for _, info := range build.Settings {
if info.Key == "vcs.revision" {
if len(info.Value) > 8 {
version = info.Value[0:8]

if app == "" {
app = path[len(path)-1]
var version string
if build.Main.Version != "(devel)" && build.Main.Version != "" {
version = build.Main.Version
} else {
for _, info := range build.Settings {
if info.Key == "vcs.revision" {
if len(info.Value) > 8 {
version = info.Value[0:8]
}
break
}
break
}
}
}

if version == "" {
return fmt.Sprintf("%s %s", app, library)
if version != "" {
app = fmt.Sprintf("%s/%s", app, version)
}
}
return fmt.Sprintf("%s/%s %s", app, version, library)

return fmt.Sprintf("%s %s", app, library)
}

// Account allows interaction with a Tesla account.
Expand Down Expand Up @@ -114,7 +113,8 @@ func (p *oauthPayload) domain() string {
}

// New returns an [Account] that can be used to fetch a [vehicle.Vehicle].
func New(oauthToken string) (*Account, error) {
// Optional userAgent can be passed in - otherwise it will be generated from code
func New(oauthToken, userAgent string) (*Account, error) {
parts := strings.Split(oauthToken, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("client provided malformed OAuth token")
Expand All @@ -133,7 +133,7 @@ func New(oauthToken string) (*Account, error) {
return nil, fmt.Errorf("client provided OAuth token with invalid audiences")
}
return &Account{
UserAgent: UserAgent,
UserAgent: buildUserAgent(userAgent),
authHeader: "Bearer " + strings.TrimSpace(oauthToken),
Host: domain,
}, nil
Expand Down
20 changes: 10 additions & 10 deletions pkg/account/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,29 @@ func b64Encode(payload string) string {

func TestNewAccount(t *testing.T) {
validDomain := "fleet-api.example.tesla.com"
if _, err := New(""); err == nil {
if _, err := New("", ""); err == nil {
t.Error("Returned success empty JWT")
}
if _, err := New(b64Encode(validDomain)); err == nil {
if _, err := New(b64Encode(validDomain), ""); err == nil {
t.Error("Returned success on one-field JWT")
}
if _, err := New("x." + b64Encode(validDomain)); err == nil {
if _, err := New("x."+b64Encode(validDomain), ""); err == nil {
t.Error("Returned success on two-field JWT")
}
if _, err := New("x." + b64Encode(validDomain) + "y.z"); err == nil {
if _, err := New("x."+b64Encode(validDomain)+"y.z", ""); err == nil {
t.Error("Returned success on four-field JWT")
}
if _, err := New("x." + validDomain + ".y"); err == nil {
if _, err := New("x."+validDomain+".y", ""); err == nil {
t.Error("Returned success on non-base64 encoded JWT")
}
if _, err := New("x." + b64Encode("{\"aud\": \"example.com\"}") + ".y"); err == nil {
if _, err := New("x."+b64Encode("{\"aud\": \"example.com\"}")+".y", ""); err == nil {
t.Error("Returned success on untrusted domain")
}
if _, err := New("x." + b64Encode(fmt.Sprintf("{\"aud\": \"%s\"}", validDomain)) + ".y"); err == nil {
if _, err := New("x."+b64Encode(fmt.Sprintf("{\"aud\": \"%s\"}", validDomain))+".y", ""); err == nil {
t.Error("Returned when aud field not a list")
}

acct, err := New("x." + b64Encode(fmt.Sprintf("{\"aud\": [\"%s\"]}", validDomain)) + ".y")
acct, err := New("x."+b64Encode(fmt.Sprintf("{\"aud\": [\"%s\"]}", validDomain))+".y", "")
if err != nil {
t.Fatalf("Returned error on valid JWT: %s", err)
}
Expand All @@ -49,7 +49,7 @@ func TestDomainDefault(t *testing.T) {
Audiences: []string{"https://auth.tesla.com/nts"},
}

acct, err := New(makeTestJWT(payload))
acct, err := New(makeTestJWT(payload), "")
if err != nil {
t.Fatalf("Returned error on valid JWT: %s", err)
}
Expand All @@ -64,7 +64,7 @@ func TestDomainExtraction(t *testing.T) {
OUCode: "EU",
}

acct, err := New(makeTestJWT(payload))
acct, err := New(makeTestJWT(payload), "")
if err != nil {
t.Fatalf("Returned error on valid JWT: %s", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ func (c *Config) Account() (*account.Account, error) {
if err != nil {
return nil, err
}
return account.New(token)
return account.New(token, "")
}

// SavePrivateKey writes skey to the system keyring or file, depending on what options are
Expand Down
6 changes: 2 additions & 4 deletions pkg/connector/inet/inet.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,8 @@ func SendFleetAPICommand(ctx context.Context, client *http.Client, userAgent, au
switch result.StatusCode {
case http.StatusOK:
return body, nil
case http.StatusUnprocessableEntity:
if bytes.Contains(body, []byte("vehicle does not support signed commands")) {
return nil, protocol.ErrProtocolNotSupported
}
case http.StatusUnprocessableEntity: // HTTP: 422 on commands endpoint means protocol is not supported (fallback to regular commands)
return nil, protocol.ErrProtocolNotSupported
case http.StatusServiceUnavailable:
return nil, ErrVehicleNotAwake
case http.StatusRequestTimeout:
Expand Down
Loading

0 comments on commit 71d7881

Please sign in to comment.