diff --git a/GM.Api/GMClientBase.cs b/GM.Api/GMClientBase.cs
index a70e812..f9adfb4 100644
--- a/GM.Api/GMClientBase.cs
+++ b/GM.Api/GMClientBase.cs
@@ -191,7 +191,7 @@ namespace GM.Api
///
///
///
- async Task VehicleConnect()
+ async Task VehicleConnect()
{
if (ActiveVehicle == null) throw new InvalidOperationException("ActiveVehicle must be populated");
using (var response = await PostAsync(ActiveVehicle.GetCommand("connect").Url, new StringContent("{}", Encoding.UTF8, "application/json")))
@@ -199,9 +199,9 @@ namespace GM.Api
if (response.IsSuccessStatusCode)
{
var respString = await response.Content.ReadAsStringAsync();
- var respObj = JsonConvert.DeserializeObject(respString);
- if (respObj == null || respObj.commandResponse == null) return null;
- return respObj.commandResponse;
+ var respObj = JsonConvert.DeserializeObject(respString);
+ if (respObj == null || respObj.CommandResponse == null) return null;
+ return respObj.CommandResponse;
}
else
{
@@ -384,7 +384,7 @@ namespace GM.Api
/// OnStar PIN
/// command name
///
- async Task InitiateCommand(string command, JObject requestParameters)
+ async Task InitiateCommand(string command, JObject requestParameters)
{
if (ActiveVehicle == null) throw new InvalidOperationException("ActiveVehicle must be populated");
@@ -427,9 +427,9 @@ namespace GM.Api
return null;
}
- var commandResult = await response.Content.ReadAsAsync();
+ var commandResult = await response.Content.ReadAsAsync();
- return commandResult.commandResponse;
+ return commandResult.CommandResponse;
}
}
@@ -439,7 +439,7 @@ namespace GM.Api
///
/// statusUrl returned when the command was initiated
/// Response from final poll
- async Task WaitForCommandCompletion(string statusUrl)
+ async Task WaitForCommandCompletion(string statusUrl)
{
int nullResponseCount = 0;
@@ -452,16 +452,16 @@ namespace GM.Api
nullResponseCount++;
if (nullResponseCount > 5) return null;
}
- if ("inProgress".Equals(result.status, StringComparison.OrdinalIgnoreCase)) continue;
+ if ("inProgress".Equals(result.Status, StringComparison.OrdinalIgnoreCase)) continue;
return result;
}
}
- protected async Task InitiateCommandAndWait(string command, JObject requestParameters)
+ protected async Task InitiateCommandAndWait(string command, JObject requestParameters)
{
var result = await InitiateCommand(command, requestParameters);
- var endStatus = await WaitForCommandCompletion(result.url);
+ var endStatus = await WaitForCommandCompletion(result.Url);
return endStatus;
}
@@ -469,7 +469,7 @@ namespace GM.Api
{
var result = await InitiateCommandAndWait(command, requestParameters);
if (result == null) return false;
- if ("success".Equals(result.status, StringComparison.OrdinalIgnoreCase))
+ if ("success".Equals(result.Status, StringComparison.OrdinalIgnoreCase))
{
return true;
}
@@ -480,14 +480,14 @@ namespace GM.Api
}
- async Task PollCommandStatus(string statusUrl)
+ async Task PollCommandStatus(string statusUrl)
{
var response = await GetAsync($"{statusUrl}?units=METRIC");
if (response.IsSuccessStatusCode)
{
- var result = await response.Content.ReadAsAsync();
- return result.commandResponse;
+ var result = await response.Content.ReadAsAsync();
+ return result.CommandResponse;
}
else
{
diff --git a/GM.Api/GenericGMClient.cs b/GM.Api/GenericGMClient.cs
index b189cc6..aa465b8 100644
--- a/GM.Api/GenericGMClient.cs
+++ b/GM.Api/GenericGMClient.cs
@@ -17,7 +17,7 @@ namespace GM.Api
}
- public async Task GetDiagnostics()
+ public async Task GetDiagnostics()
{
var cmdInfo = ActiveVehicle.GetCommand("diagnostics");
@@ -28,9 +28,9 @@ namespace GM.Api
var result = await InitiateCommandAndWait("diagnostics", reqObj);
if (result == null) return null;
- if ("success".Equals(result.status, StringComparison.OrdinalIgnoreCase))
+ if ("success".Equals(result.Status, StringComparison.OrdinalIgnoreCase))
{
- return result.body.diagnosticResponse;
+ return result.Body.DiagnosticResponse;
}
else
{
@@ -40,7 +40,7 @@ namespace GM.Api
- public async Task IssueCommand(string commandName, JObject parameters = null)
+ public async Task IssueCommand(string commandName, JObject parameters = null)
{
return await InitiateCommandAndWait(commandName, parameters);
}
diff --git a/GM.Api/Models/CommandResponse.cs b/GM.Api/Models/CommandResponse.cs
index e9f54b2..fe7f8e7 100644
--- a/GM.Api/Models/CommandResponse.cs
+++ b/GM.Api/Models/CommandResponse.cs
@@ -1,23 +1,63 @@
-using System;
+using Newtonsoft.Json;
+using System;
using System.Collections.Generic;
using System.Text;
namespace GM.Api.Models
{
- public class CommandResponseRoot
+ ///
+ /// Root object returned by a command request, or a call to a status url
+ ///
+ public class CommandRequestResponse
{
- public Commandresponse commandResponse { get; set; }
+ ///
+ /// Inner response
+ ///
+ [JsonProperty("commandResponse")]
+ public CommandResponse CommandResponse { get; set; }
}
-
- public class Commandresponse
+ ///
+ /// Command Response Object
+ ///
+ public class CommandResponse
{
- public DateTime requestTime { get; set; }
- public DateTime completionTime { get; set; }
- public string url { get; set; }
- public string status { get; set; } //inProgress, success
- public string type { get; set; }
- public ResponseBody body { get; set; }
+ ///
+ /// Timestamp the request was received by the server
+ ///
+ [JsonProperty("requestTime")]
+ public DateTime RequestTime { get; set; }
+
+ ///
+ /// Timestamp the server completed the request
+ ///
+ [JsonProperty("completionTime")]
+ public DateTime CompletionTime { get; set; }
+
+ ///
+ /// Status URL to be polled for updates (commands are async)
+ ///
+ [JsonProperty("url")]
+ public string Url { get; set; }
+
+ ///
+ /// Current status of the command request
+ /// (e.g. "inProgress", "success")
+ ///
+ [JsonProperty("status")]
+ public string Status { get; set; } //inProgress, success
+
+ ///
+ /// Probably refers to the type of the response body
+ ///
+ [JsonProperty("type")]
+ public string Type { get; set; }
+
+ ///
+ /// Response boldy for commands that include a response (e.g. diagnostics, location)
+ ///
+ [JsonProperty("body")]
+ public ResponseBody Body { get; set; }
}
diff --git a/GM.Api/Models/Configuration.cs b/GM.Api/Models/Configuration.cs
index 20cfb33..fe75ffb 100644
--- a/GM.Api/Models/Configuration.cs
+++ b/GM.Api/Models/Configuration.cs
@@ -5,55 +5,168 @@ using System.Text;
namespace GM.Api.Models
{
+ ///
+ /// Model of the encrypted Andorid configuration file
+ ///
public class GmConfiguration
{
- public Dictionary brand_client_info { get; set; }
- public ApiConfig[] configs { get; set; }
- public Telenav_Config telenav_config { get; set; }
- public string equip_key { get; set; }
- public string key_store_password { get; set; }
- public string key_password { get; set; }
- public Dictionary certs { get; set; }
+ ///
+ /// Client Credentials by GM Brand
+ ///
+ [JsonProperty("brand_client_info")]
+ public Dictionary BrandClientInfo { get; set; }
+
+ ///
+ /// Endpoint Configuration collection
+ ///
+ [JsonProperty("configs")]
+ public ApiConfig[] Configs { get; set; }
+
+ ///
+ /// Presumably configuration used for navigation
+ ///
+ [JsonProperty("telenav_config")]
+ public TelenavConfig TelenavConfig { get; set; }
+
+ ///
+ /// Unknown
+ ///
+ [JsonProperty("equip_key")]
+ public string EquipKey { get; set; }
+
+ ///
+ /// Probably the key used to encrypt the saved OnStar PINs
+ ///
+ [JsonProperty("key_store_password")]
+ public string KeyStorePassword { get; set; }
+
+ ///
+ /// Unknown
+ ///
+ [JsonProperty("key_password")]
+ public string KeyPassword { get; set; }
+
+ ///
+ /// Certificate pinning information used to prevent SSL spoofing
+ ///
+ [JsonProperty("certs")]
+ public Dictionary Certs { get; set; }
}
+ ///
+ /// Client Credentials for a given GM brand
+ ///
public class BrandClientInfo
{
- public string client_id { get; set; }
- public string client_secret { get; set; }
- public string debug_client_id { get; set; }
- public string debug_client_secret { get; set; }
+ ///
+ /// OAuth Client ID
+ ///
+ [JsonProperty("client_id")]
+ public string ClientId { get; set; }
+
+ ///
+ /// OAuth Client Secret
+ ///
+ [JsonProperty("client_secret")]
+ public string ClientSecret { get; set; }
+
+ ///
+ /// Debug environment Oauth Client ID
+ ///
+ [JsonProperty("debug_client_id")]
+ public string DebugClientId { get; set; }
+
+ ///
+ /// Debug environment Oauth Client Secret
+ ///
+ [JsonProperty("debug_client_secret")]
+ public string DebugClientSecret { get; set; }
}
-
+ ///
+ /// API configuration for a given GM brand
+ ///
public class ApiConfig
{
- public string name { get; set; }
- public string url { get; set; }
- public string required_client_scope { get; set; }
- public string optional_client_scope { get; set; }
///
- /// do not use this
+ /// GM Brand name
///
- public string client_id { get; set; }
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
///
- /// do not use this
+ /// Base API endpoint URL (eg "https://api.gm.com/api")
///
- public string client_secret { get; set; }
+ [JsonProperty("url")]
+ public string Url { get; set; }
+
+ ///
+ /// Space separated scopes required for login
+ ///
+ [JsonProperty("required_client_scope")]
+ public string RequiredClientScope { get; set; }
+
+ ///
+ /// Space separated scopes optional for login
+ ///
+ [JsonProperty("optional_client_scope")]
+ public string OptionalClientScope { get; set; }
+
+ ///
+ /// Use the Brand config Client ID instead
+ ///
+ [JsonProperty("client_id")]
+ public string ClientId { get; set; }
+
+ ///
+ /// Use the Brand config Client Secret instead
+ ///
+ [JsonProperty("client_secret")]
+ public string ClientSecret { get; set; }
}
-
- public class Telenav_Config
+ ///
+ /// Client credentials for Telenav system
+ ///
+ public class TelenavConfig
{
- public string name { get; set; }
- public string client_id { get; set; }
- public string client_secret { get; set; }
+ ///
+ /// Name
+ ///
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ ///
+ /// OAuth Client ID
+ ///
+ [JsonProperty("client_id")]
+ public string ClientId { get; set; }
+
+ ///
+ /// OAuth Client Secret
+ ///
+ [JsonProperty("client_secret")]
+ public string ClientSecret { get; set; }
}
+ ///
+ /// Container for certificate pinning info
+ ///
public class RegionCert
{
- public string pattern { get; set; }
- public string[] certificate_pins { get; set; }
+ ///
+ /// Pattern used by the expected certificate. Should match the CN I'm guessing
+ ///
+ [JsonProperty("pattern")]
+ public string Pattern { get; set; }
+
+ ///
+ /// A list of certificate pins. There are usually 3 and only one matches the actual SSL cert of the server
+ /// The other two do not match the intermediate / root certs - not sure what they are
+ ///
+ [JsonProperty("certificate_pins")]
+ public string[] CertificatePins { get; set; }
}
}
diff --git a/GM.Api/Models/Diagnostic.cs b/GM.Api/Models/Diagnostic.cs
index d875dab..e61edf8 100644
--- a/GM.Api/Models/Diagnostic.cs
+++ b/GM.Api/Models/Diagnostic.cs
@@ -2,27 +2,45 @@
using System.Collections.Generic;
using System.Text;
using System.Linq;
+using Newtonsoft.Json;
namespace GM.Api.Models
{
+ ///
+ /// Response Body
+ /// Note: this only contains a diagnostic response. there are likely others.
+ ///
public class ResponseBody
{
- public Diagnosticresponse[] diagnosticResponse { get; set; }
+ [JsonProperty("diagnosticResponse")]
+ public DiagnosticResponse[] DiagnosticResponse { get; set; }
}
- public class Diagnosticresponse
+ public class DiagnosticResponse
{
- public string name { get; set; }
- public Diagnosticelement[] diagnosticElement { get; set; }
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("diagnosticElement")]
+ public DiagnosticElement[] DiagnosticElement { get; set; }
}
- public class Diagnosticelement
+ public class DiagnosticElement
{
- public string name { get; set; }
- public string status { get; set; }
- public string message { get; set; }
- public string value { get; set; }
- public string unit { get; set; }
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("status")]
+ public string Status { get; set; }
+
+ [JsonProperty("message")]
+ public string Message { get; set; }
+
+ [JsonProperty("value")]
+ public string Value { get; set; }
+
+ [JsonProperty("unit")]
+ public string Unit { get; set; }
}
}
diff --git a/GM.Api/Models/Vehicle.cs b/GM.Api/Models/Vehicle.cs
index c3d1924..f9d8dd4 100644
--- a/GM.Api/Models/Vehicle.cs
+++ b/GM.Api/Models/Vehicle.cs
@@ -16,33 +16,64 @@ namespace GM.Api.Models
public class Vehicles
{
+ ///
+ /// Size of the Vehicle array, or full size. One would need to have more than 10 cars to find out...
+ ///
[JsonProperty("size")]
public string Size { get; set; }
+ ///
+ /// List of vehicles associated with the account
+ /// Note that there is paging and by default the page size is 10
+ ///
[JsonProperty("vehicle")]
public Vehicle[] Vehicle { get; set; }
}
+ ///
+ /// Vehicle description
+ ///
public class Vehicle
{
+ ///
+ /// Vehicle VIN
+ ///
[JsonProperty("vin")]
public string Vin { get; set; }
+ ///
+ /// Vehicle Make
+ ///
[JsonProperty("make")]
public string Make { get; set; }
+ ///
+ /// Vehicle Model
+ ///
[JsonProperty("model")]
public string Model { get; set; }
+ ///
+ /// Vehicle Year
+ ///
[JsonProperty("year")]
public string Year { get; set; }
+ ///
+ /// Vehicle Manufacturer - not sure why this is required...
+ ///
[JsonProperty("manufacturer")]
public string Manufacturer { get; set; }
+ ///
+ /// (e.g. car, maybe truck)
+ ///
[JsonProperty("bodyStyle")]
public string BodyStyle { get; set; }
+ ///
+ /// Vehicle cellular / OnStar phone number
+ ///
[JsonProperty("phone")]
public string Phone { get; set; }
@@ -52,6 +83,9 @@ namespace GM.Api.Models
[JsonProperty("onstarStatus")]
public string OnStarStatus { get; set; }
+ ///
+ /// Base URL for API calls regarding this vehicle
+ ///
[JsonProperty("url")]
public string Url { get; set; }
@@ -64,12 +98,21 @@ namespace GM.Api.Models
[JsonProperty("enrolledInContinuousCoverage")]
public bool? EnrolledInContinuousCoverage { get; set; }
+ ///
+ /// Details on supported commands
+ ///
[JsonProperty("commands")]
public Commands Commands { get; set; }
+ ///
+ /// Details on available modules
+ ///
[JsonProperty("modules")]
public Modules Modules { get; set; }
+ ///
+ /// Details on available entitlements
+ ///
[JsonProperty("entitlements")]
public Entitlements Entitlements { get; set; }
@@ -102,24 +145,46 @@ namespace GM.Api.Models
public class Commands
{
+ ///
+ /// List of commands supported by the vehicle
+ ///
[JsonProperty("command")]
public Command[] Command { get; set; }
}
+ ///
+ /// Details about a supported command
+ ///
public class Command
{
+ ///
+ /// Command name
+ ///
[JsonProperty("name")]
public string Name { get; set; }
+ ///
+ /// Description of what the command does
+ ///
[JsonProperty("description")]
public string Description { get; set; }
+ ///
+ /// API URL to be used for issuing the command
+ /// This SDK uses this url rather than constructing it
+ ///
[JsonProperty("url")]
public string Url { get; set; }
+ ///
+ /// True or False if the command requires the token to be upgraded with an OnStar PIN
+ ///
[JsonProperty("isPrivSessionRequired")]
public bool? IsPrivSessionRequired { get; set; }
+ ///
+ /// For commands with additional data such as diagnostics
+ ///
[JsonProperty("commandData")]
public CommandData CommandData { get; set; }
}
@@ -132,12 +197,18 @@ namespace GM.Api.Models
public class SupportedDiagnostics
{
+ ///
+ /// List of the diagnostic elements that may be requsted for the vehicle
+ ///
[JsonProperty("supportedDiagnostic")]
public string[] SupportedDiagnostic { get; set; }
}
public class Modules
{
+ ///
+ /// List of modules - not much here
+ ///
[JsonProperty("module")]
public Module[] Module { get; set; }
}
@@ -153,23 +224,43 @@ namespace GM.Api.Models
public class Entitlements
{
+ ///
+ /// List of entitlements - features and activities vehicles are capable of
+ /// List contains things the vehicle or account may or may not support
+ /// Check the Elligible flag
+ ///
[JsonProperty("entitlement")]
public Entitlement[] Entitlement { get; set; }
}
+ ///
+ /// Details about an Entitlement
+ ///
public class Entitlement
{
+ ///
+ /// ID or name of entitlement
+ ///
[JsonProperty("id")]
public string Id { get; set; }
+ ///
+ /// True or false if the entitlement is available on this vehicle or account
+ ///
[JsonProperty("eligible")]
public bool? Eligible { get; set; }
+ ///
+ /// Reason for inelligibility (whether the car is incapable or the owner isn't subscribed)
+ ///
[JsonProperty("ineligibleReasonCode")]
public string IneligibleReasonCode { get; set; }
+ ///
+ /// True or false if the entitlement can send notifications
+ ///
[JsonProperty("notificationCapable")]
- public string NotificationCapable { get; set; }
+ public bool? NotificationCapable { get; set; }
}
diff --git a/GM.WindowsUI/BrandWindow.xaml.cs b/GM.WindowsUI/BrandWindow.xaml.cs
index 2a54f78..98df45a 100644
--- a/GM.WindowsUI/BrandWindow.xaml.cs
+++ b/GM.WindowsUI/BrandWindow.xaml.cs
@@ -33,7 +33,7 @@ namespace GM.WindowsUI
_config = configuration;
InitializeComponent();
- foreach (var brandName in _config.brand_client_info.Keys.OrderBy((val) => val, StringComparer.OrdinalIgnoreCase))
+ foreach (var brandName in _config.BrandClientInfo.Keys.OrderBy((val) => val, StringComparer.OrdinalIgnoreCase))
{
lstBrands.Items.Add(brandName.Substring(0, 1).ToUpperInvariant() + brandName.Substring(1));
}
diff --git a/GM.WindowsUI/MainWindow.xaml.cs b/GM.WindowsUI/MainWindow.xaml.cs
index 7d2bc3b..de4a7d2 100644
--- a/GM.WindowsUI/MainWindow.xaml.cs
+++ b/GM.WindowsUI/MainWindow.xaml.cs
@@ -60,7 +60,7 @@ namespace GM.WindowsUI
}
//todo: maybe the client reads the config and takes the brand and device id as param?
- _client = new GenericGMClient(_clientCredentials.client_id, Properties.Settings.Default.DeviceId, _clientCredentials.client_secret, _apiConfig.url);
+ _client = new GenericGMClient(_clientCredentials.ClientId, Properties.Settings.Default.DeviceId, _clientCredentials.ClientSecret, _apiConfig.Url);
_client.TokenUpdateCallback = TokenUpdateHandler;
if (!string.IsNullOrEmpty(Properties.Settings.Default.LoginData))
@@ -119,8 +119,8 @@ namespace GM.WindowsUI
Title = _brandDisplay + " Vehicle Control";
- _clientCredentials = _globalConfig.brand_client_info[_brand];
- _apiConfig = (from f in _globalConfig.configs where f.name.Equals(_brand, StringComparison.OrdinalIgnoreCase) select f).FirstOrDefault();
+ _clientCredentials = _globalConfig.BrandClientInfo[_brand];
+ _apiConfig = (from f in _globalConfig.Configs where f.Name.Equals(_brand, StringComparison.OrdinalIgnoreCase) select f).FirstOrDefault();
}
void LoadConfiguration()
@@ -393,7 +393,7 @@ namespace GM.WindowsUI
lblStatus.Content = "Getting Diagnostics (Please Wait)...";
var details = await _client.GetDiagnostics();
txtOutput.Text = JsonConvert.SerializeObject(details, Formatting.Indented);
-
+ lblStatus.Content = "Getting Diagnostics Complete";
grpActions.IsEnabled = true;
btnLogin.IsEnabled = true;
}
diff --git a/README.md b/README.md
index c24bcc7..e719aa1 100644
--- a/README.md
+++ b/README.md
@@ -25,16 +25,11 @@ VERY IMPORTANT: Unless you want an international incident on your hands DO NOT S
# TODO
This is very early, unpolished, incomplete code. No judgement please.
+* Implement more commands
+* Analyze and implement vehicle location capability
+* consider using MS JWT implementation
+* Implement secure means of saving onstar pin. If possible.
+* recognize response from calling priv'd command without upgrade and trigger upgrade using saved pin.
+Notes: The android app saves the onstar pin using biometrics to unlock - no difference in the api calls. It does not use a different token refresh mechanism after elevating permissions, but the elevation persists across a refresh. The upgrade request does not specify an expiration. Testing will be required to determine the lifespan of token upgrades.
-TODO: implement lots more actions
-
-TODO: Complete updating JSON model property names
-
-Note: the android app saves the onstar pin using biometrics to unlock - no difference in the api calls
-Note: the android app does not use a different token refresh mechanism after elevating permissions, but the elevation persists across a refresh. The upgrade request does not specify an expiration. Testing will be required to determine the lifespan of token upgrades.
-
-TODO: Implement secure means of saving onstar pin. If possible.
-TODO: recognize response from calling priv'd command without upgrade and trigger upgrade using saved pin.
-
-TODO: consider using MS JWT implementation