├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── GM.Api
├── Brands.cs
├── GM.Api.csproj
├── GMClient.cs
├── GMClientBase.cs
├── GMClientNoKey.cs
├── Models
│ ├── CommandResponse.cs
│ ├── Diagnostic.cs
│ ├── HotspotInfo.cs
│ ├── Location.cs
│ ├── TbtDestination.cs
│ └── Vehicle.cs
├── Tokens
│ ├── Base32.cs
│ ├── JwtTool.cs
│ ├── LoginData.cs
│ ├── LoginRequest.cs
│ └── SortedJsonSerializer.cs
└── helpers.cs
├── GM.WindowsUI
├── App.config
├── App.xaml
├── App.xaml.cs
├── BrandWindow.xaml
├── BrandWindow.xaml.cs
├── GM.WindowsUI.csproj
├── LoginWindow.xaml
├── LoginWindow.xaml.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
└── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── GM.sln
├── LICENSE
├── README.md
└── _config.yml
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at j8byz4by@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Feel free to issue PRs with improvements, bug fixes, enhancements, etc.
2 | Guidelines:
3 | * Please avoid the inclusion of non-essential third-party libraries.
4 | * Please avoid significant changes to the design without very good reason and include explanation in PR comments.
5 | * Please avoid unnecessary complexity, or style changes that make the code harder to follow without good reason. (e.g. DI, IoC, stupid method names, etc...)
6 | * Please include code docs on all public methods and types, and comments explaining things that aren't obvious
7 | * Be nice
8 |
--------------------------------------------------------------------------------
/GM.Api/Brands.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace GM.Api
6 | {
7 |
8 | public enum Brand
9 | {
10 | Opel,
11 | Vauxhall,
12 | Chevrolet,
13 | OnStar,
14 | Cadillac,
15 | Buick,
16 | Gmc
17 | }
18 |
19 |
20 | public static class BrandHelpers
21 | {
22 |
23 | public static string GetDisplayName(this Brand brand)
24 | {
25 | return brand.ToString();
26 | }
27 |
28 | public static string GetName(this Brand brand)
29 | {
30 | switch (brand)
31 | {
32 | case Brand.Opel:
33 | return "opel";
34 | case Brand.Vauxhall:
35 | return "vauxhall";
36 | case Brand.Chevrolet:
37 | return "chevrolet";
38 | case Brand.OnStar:
39 | return "onstar";
40 | case Brand.Cadillac:
41 | return "cadillac";
42 | case Brand.Buick:
43 | return "buick";
44 | case Brand.Gmc:
45 | return "gmc";
46 | default:
47 | throw new InvalidOperationException("Unknown Brand");
48 | }
49 | }
50 |
51 | public static string GetUrl(this Brand brand)
52 | {
53 | switch (brand)
54 | {
55 | case Brand.Opel:
56 | return "https://api.eur.onstar.com/api";
57 | case Brand.Vauxhall:
58 | return "https://api.eur.onstar.com/api";
59 | case Brand.Chevrolet:
60 | return "https://api.gm.com/api";
61 | case Brand.OnStar:
62 | return "https://api.gm.com/api";
63 | case Brand.Cadillac:
64 | return "https://api.gm.com/api";
65 | case Brand.Buick:
66 | return "https://api.gm.com/api";
67 | case Brand.Gmc:
68 | return "https://api.gm.com/api";
69 | default:
70 | throw new InvalidOperationException("Unknown Brand");
71 | }
72 | }
73 |
74 |
75 | public static Brand GetBrand(string brandName)
76 | {
77 | var cleanName = brandName.ToLowerInvariant();
78 |
79 | switch (cleanName)
80 | {
81 | case "opel":
82 | return Brand.Opel;
83 | case "vauxhall":
84 | return Brand.Vauxhall;
85 | case "chevrolet":
86 | return Brand.Chevrolet;
87 | case "onstar":
88 | return Brand.OnStar;
89 | case "cadillac":
90 | return Brand.Cadillac;
91 | case "buick":
92 | return Brand.Buick;
93 | case "gmc":
94 | return Brand.Gmc;
95 | default:
96 | throw new InvalidOperationException("Unknown Brand");
97 | }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/GM.Api/GM.Api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | j8byz4by
6 | j8byz4by
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/GM.Api/GMClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 | using GM.Api.Tokens;
6 |
7 | namespace GM.Api
8 | {
9 | ///
10 | /// GM Client implementation to be used when you have local access to the Client ID and Client Secret
11 | ///
12 | public class GMClient : GMClientBase
13 | {
14 | string _clientId;
15 | JwtTool _jwtTool;
16 |
17 |
18 | public GMClient(string deviceId, Brand brand, string clientId, string clientSecret) : base(deviceId, brand)
19 | {
20 | _clientId = clientId;
21 | _jwtTool = new JwtTool(clientSecret);
22 | }
23 |
24 | protected override async Task EncodeLoginRequest(LoginRequest request)
25 | {
26 | request.ClientId = _clientId;
27 | return _jwtTool.EncodeToken(request);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/GM.Api/GMClientBase.cs:
--------------------------------------------------------------------------------
1 | using GM.Api.Models;
2 | using GM.Api.Tokens;
3 | using JWT;
4 | using JWT.Algorithms;
5 | using Newtonsoft.Json;
6 | using Newtonsoft.Json.Linq;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Net.Http;
10 | using System.Security.Cryptography;
11 | using System.Text;
12 | using System.Threading.Tasks;
13 |
14 | namespace GM.Api
15 | {
16 | ///
17 | /// Base class API client for GM web services
18 | ///
19 | public abstract class GMClientBase
20 | {
21 | public static int RetryCount { get; set; } = 3;
22 |
23 | //TODO: consistent exception throwing
24 | protected Brand _brand;
25 | protected string _deviceId;
26 | protected string _apiUrl;
27 |
28 | HttpClient _client;
29 |
30 | ///
31 | /// If the current login token has been upgraded
32 | /// Note: it is not known how long this lasts
33 | ///
34 | public bool IsUpgraded { get; private set; } = false;
35 |
36 | bool _isConnected = false;
37 |
38 | ///
39 | /// Contents of the received login token
40 | /// May be populated from a cached token
41 | /// Refreshed automatically
42 | ///
43 | public LoginData LoginData { get; set; } = null;
44 |
45 | ///
46 | /// Active vehicle configuration
47 | /// Must be populated to initiate commands against a car
48 | ///
49 | public Vehicle ActiveVehicle { get; set; }
50 |
51 |
52 | ///
53 | /// Callback called when LoginData is updated
54 | /// Intended to facilitate updating the stored token
55 | ///
56 | public Func TokenUpdateCallback { get; set; }
57 |
58 |
59 | ///
60 | /// Create a new GMClient
61 | ///
62 | /// Device ID (should be in the format of a GUID)
63 | /// One of the supported brands from
64 | public GMClientBase(string deviceId, Brand brand)
65 | {
66 | Setup(deviceId, brand);
67 | }
68 |
69 | void Setup(string deviceId, Brand brand)
70 | {
71 | _brand = brand;
72 | _deviceId = deviceId;
73 | _apiUrl = brand.GetUrl();
74 | var uri = new Uri(_apiUrl);
75 | _client = CreateClient(uri.Host);
76 | }
77 |
78 |
79 | static HttpClient CreateClient(string host)
80 | {
81 | var client = new HttpClient(new HttpClientHandler() { AllowAutoRedirect = true, AutomaticDecompression = System.Net.DecompressionMethods.GZip });
82 |
83 | client.DefaultRequestHeaders.AcceptEncoding.SetValue("gzip");
84 | client.DefaultRequestHeaders.Accept.SetValue("application/json");
85 | client.DefaultRequestHeaders.AcceptLanguage.SetValue("en-US");
86 | client.DefaultRequestHeaders.UserAgent.ParseAdd("okhttp/3.9.0");
87 | client.DefaultRequestHeaders.Host = host;
88 | client.DefaultRequestHeaders.MaxForwards = 10;
89 | client.DefaultRequestHeaders.ExpectContinue = false;
90 | return client;
91 | }
92 |
93 |
94 | protected abstract Task EncodeLoginRequest(LoginRequest request);
95 |
96 | LoginData DecodeLoginData(string token)
97 | {
98 | IJsonSerializer serializer = new SortedJsonSerializer();
99 | IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
100 | IDateTimeProvider dateTimeProvider = new UtcDateTimeProvider();
101 | IJwtValidator validator = new JwtValidator(serializer, dateTimeProvider);
102 | var decoder = new JwtDecoder(serializer, validator, urlEncoder);
103 | return decoder.DecodeToObject(token);
104 | }
105 |
106 |
107 | #region Client Helpers
108 |
109 | ///
110 | /// Helper wrapper for SendAsync that handles token updates and retries
111 | ///
112 | ///
113 | ///
114 | ///
115 | async Task SendAsync(HttpRequestMessage request, bool noAuth = false)
116 | {
117 | if (!noAuth)
118 | {
119 | if (LoginData == null)
120 | {
121 | throw new InvalidOperationException("Not Logged in");
122 | }
123 | if (LoginData.IsExpired)
124 | {
125 | var result = await RefreshToken();
126 | if (!result)
127 | {
128 | throw new InvalidOperationException("Token refresh failed");
129 | }
130 | }
131 | }
132 | else
133 | {
134 | request.Headers.Authorization = null;
135 | }
136 |
137 | int attempt = 0;
138 | while (attempt < RetryCount)
139 | {
140 | attempt++;
141 | HttpResponseMessage resp = null;
142 | try
143 | {
144 | resp = await _client.SendAsync(request);
145 | }
146 | catch (Exception ex)
147 | {
148 | //todo: only catch transient errors
149 | //todo: log this
150 | continue;
151 | }
152 |
153 | if (!resp.IsSuccessStatusCode)
154 | {
155 | if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized || resp.StatusCode == System.Net.HttpStatusCode.Forbidden)
156 | {
157 | var result = await RefreshToken();
158 | if (!result)
159 | {
160 | throw new InvalidOperationException("Token refresh failed");
161 | }
162 | continue;
163 | }
164 | else if (resp.StatusCode == System.Net.HttpStatusCode.BadGateway || resp.StatusCode == System.Net.HttpStatusCode.Conflict || resp.StatusCode == System.Net.HttpStatusCode.GatewayTimeout || resp.StatusCode == System.Net.HttpStatusCode.InternalServerError || resp.StatusCode == System.Net.HttpStatusCode.RequestTimeout || resp.StatusCode == System.Net.HttpStatusCode.ResetContent || resp.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable)
165 | {
166 |
167 | var respMessage = (await resp.Content.ReadAsStringAsync()) ?? "";
168 | int f = 5;
169 | //possible transient errors
170 | //todo: log this
171 | await Task.Delay(500);
172 | continue;
173 | }
174 | else
175 | {
176 | var respMessage = (await resp.Content.ReadAsStringAsync()) ?? "";
177 | throw new InvalidOperationException("Request error. StatusCode: " + resp.StatusCode.ToString() + ", msg: " + respMessage);
178 | }
179 | }
180 | else
181 | {
182 | return resp;
183 | }
184 | }
185 | //todo: include more info
186 | throw new InvalidOperationException("Request failed too many times");
187 | }
188 |
189 |
190 | async Task PostAsync(string requestUri, HttpContent content, bool noAuth = false)
191 | {
192 | return await SendAsync(new HttpRequestMessage(HttpMethod.Post, requestUri) { Content = content }, noAuth);
193 | }
194 |
195 | async Task GetAsync(string requestUri, bool noAuth = false)
196 | {
197 | return await SendAsync(new HttpRequestMessage(HttpMethod.Get, requestUri), noAuth);
198 | }
199 |
200 | #endregion
201 |
202 |
203 | ///
204 | /// Connect to vehicle. Must be called before issuing commands
205 | ///
206 | ///
207 | ///
208 | async Task VehicleConnect()
209 | {
210 | if (ActiveVehicle == null) throw new InvalidOperationException("ActiveVehicle must be populated");
211 | using (var response = await PostAsync(ActiveVehicle.GetCommand("connect").Url, new StringContent("{}", Encoding.UTF8, "application/json")))
212 | {
213 | if (response.IsSuccessStatusCode)
214 | {
215 | var respString = await response.Content.ReadAsStringAsync();
216 | var respObj = JsonConvert.DeserializeObject(respString);
217 | if (respObj == null || respObj.CommandResponse == null) return null;
218 | return respObj.CommandResponse;
219 | }
220 | else
221 | {
222 | var error = await response.Content.ReadAsStringAsync();
223 | return null;
224 | }
225 | }
226 | }
227 |
228 | ///
229 | /// Upgrade the token using OnStar PIN
230 | /// Allows the execution of privileged commands on the vehicle
231 | ///
232 | /// OnStar PIN
233 | /// Success or not
234 | public async Task UpgradeLogin(string onStarPin)
235 | {
236 | var payload = new LoginRequest()
237 | {
238 | //ClientId = _clientId,
239 | DeviceId = _deviceId,
240 | Credential = onStarPin,
241 | CredentialType = "PIN",
242 | Nonce = helpers.GenerateNonce(),
243 | Timestamp = DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffK")
244 | };
245 |
246 | //var token = _jwtTool.EncodeToken(payload);
247 | var token = await EncodeLoginRequest(payload);
248 |
249 | using (var response = await PostAsync($"{_apiUrl}/v1/oauth/token/upgrade", new StringContent(token, Encoding.UTF8, "text/plain")))
250 | {
251 | if (response.IsSuccessStatusCode)
252 | {
253 | IsUpgraded = true;
254 | return true;
255 | }
256 | else
257 | {
258 | var error = await response.Content.ReadAsStringAsync();
259 | return false;
260 | }
261 | }
262 | }
263 |
264 |
265 | ///
266 | /// Login to the API via Username and Password
267 | /// These credentials are not stored; only exchanged for a token
268 | /// The token is maintained by the client
269 | ///
270 | /// GM account username
271 | /// GM Account password
272 | ///
273 | public async Task Login(string username, string password)
274 | {
275 | var payload = new LoginRequest()
276 | {
277 | //ClientId = _clientId,
278 | DeviceId = _deviceId,
279 | GrantType = "password",
280 | Nonce = helpers.GenerateNonce(),
281 | Password = password,
282 | Scope = "onstar gmoc commerce user_trailer msso",
283 | Timestamp = DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffK"),
284 | Username = username
285 | };
286 |
287 | //var token = _jwtTool.EncodeToken(payload);
288 | var token = await EncodeLoginRequest(payload);
289 |
290 | using (var response = await PostAsync($"{_apiUrl}/v1/oauth/token", new StringContent(token, Encoding.UTF8, "text/plain"), true))
291 | {
292 | string rawResponseToken = null;
293 |
294 | if (response.IsSuccessStatusCode)
295 | {
296 | rawResponseToken = await response.Content.ReadAsStringAsync();
297 | }
298 | else
299 | {
300 | var error = await response.Content.ReadAsStringAsync();
301 | }
302 |
303 | if (string.IsNullOrEmpty(rawResponseToken))
304 | {
305 | return false;
306 | }
307 |
308 | //var loginTokenData = _jwtTool.DecodeTokenToObject(rawResponseToken);
309 | var loginTokenData = DecodeLoginData(rawResponseToken);
310 |
311 | LoginData = loginTokenData;
312 | _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", LoginData.AccessToken);
313 |
314 | //todo: should this be a copy rather than a reference?
315 | await TokenUpdateCallback?.Invoke(LoginData);
316 | return true;
317 | }
318 | }
319 |
320 | ///
321 | /// Manually refresh access token
322 | ///
323 | /// Success tru or false
324 | public async Task RefreshToken()
325 | {
326 | if (LoginData == null) return false;
327 |
328 | var payload = new LoginRequest()
329 | {
330 | DeviceId = _deviceId,
331 | GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer",
332 | Nonce = helpers.GenerateNonce(),
333 | Scope = "onstar gmoc commerce user_trailer",
334 | Timestamp = DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffK"),
335 | Assertion = LoginData.IdToken
336 | };
337 |
338 | var token = await EncodeLoginRequest(payload);
339 |
340 | using (var response = await PostAsync($"{_apiUrl}/v1/oauth/token", new StringContent(token, Encoding.UTF8, "text/plain"), true))
341 | {
342 |
343 | string rawResponseToken = null;
344 |
345 | if (response.IsSuccessStatusCode)
346 | {
347 | rawResponseToken = await response.Content.ReadAsStringAsync();
348 | }
349 | else
350 | {
351 | var error = await response.Content.ReadAsStringAsync();
352 | }
353 |
354 | if (string.IsNullOrEmpty(rawResponseToken))
355 | {
356 | return false;
357 | }
358 |
359 |
360 | /*{
361 | "access_token": ,
362 | "token_type": "Bearer",
363 | "expires_in": 1800,
364 | "scope": "user_trailer onstar commerce gmoc role_owner",
365 | "user_info": {
366 | "RemoteUserId": "",
367 | "country": ""
368 | }
369 | }*/
370 | // Not sure if the scope needs to be updated, as msso has been removed in the refresh request
371 |
372 | var refreshData = DecodeLoginData(rawResponseToken);
373 |
374 | LoginData.AccessToken = refreshData.AccessToken;
375 | LoginData.IssuedAtUtc = refreshData.IssuedAtUtc;
376 | LoginData.ExpiresIn = refreshData.ExpiresIn;
377 |
378 | //should we assume the upgrade status is broken?
379 |
380 |
381 | _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", LoginData.AccessToken);
382 |
383 | //todo: should this be a copy rather than a reference?
384 | await TokenUpdateCallback?.Invoke(LoginData);
385 |
386 | return true;
387 | }
388 | }
389 |
390 |
391 |
392 | #region Commands
393 |
394 |
395 | ///
396 | /// Submit the initial call for a command
397 | /// NOTE: this will be changing to use the URLs defined in vehicle configuration
398 | ///
399 | /// Vehicle VIN
400 | /// OnStar PIN
401 | /// command name
402 | ///
403 | async Task InitiateCommand(string command, JObject requestParameters)
404 | {
405 | if (ActiveVehicle == null) throw new InvalidOperationException("ActiveVehicle must be populated");
406 |
407 | var cmdInfo = ActiveVehicle.GetCommand(command);
408 |
409 | if (cmdInfo == null) throw new InvalidOperationException("Unsupported command");
410 |
411 | if (cmdInfo.IsPrivSessionRequired.GetValueOrDefault())
412 | {
413 | if (!IsUpgraded)
414 | {
415 | //TODO: need to determine how long an upgrade lasts - do we reset it when refreshing the token?
416 | // Also if the android app saves the PIN, should we save the PIN?
417 | throw new InvalidOperationException("Command requires upgraded login");
418 | }
419 | }
420 |
421 | if (!_isConnected)
422 | {
423 | await VehicleConnect();
424 | _isConnected = true;
425 | }
426 |
427 |
428 | JObject reqObj = requestParameters;
429 |
430 | if (reqObj == null)
431 | {
432 | reqObj = new JObject();
433 | }
434 |
435 |
436 |
437 | using (var response = await PostAsync(cmdInfo.Url, new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(reqObj), Encoding.UTF8, "application/json")))
438 | {
439 | if (!response.IsSuccessStatusCode)
440 | {
441 | var error = await response.Content.ReadAsStringAsync();
442 | //todo: is this needed with the fancy post?
443 | return null;
444 | }
445 |
446 | var commandResult = await response.Content.ReadAsAsync();
447 |
448 | return commandResult.CommandResponse;
449 | }
450 | }
451 |
452 |
453 | ///
454 | /// Periodically poll the status of a command, only returning after it succeeds or fails
455 | ///
456 | /// statusUrl returned when the command was initiated
457 | /// Response from final poll
458 | async Task WaitForCommandCompletion(string statusUrl)
459 | {
460 | int nullResponseCount = 0;
461 |
462 | while (true)
463 | {
464 | await Task.Delay(5000);
465 | var result = await PollCommandStatus(statusUrl);
466 | if (result == null)
467 | {
468 | nullResponseCount++;
469 | if (nullResponseCount > 5) return null;
470 | }
471 | if ("inProgress".Equals(result.Status, StringComparison.OrdinalIgnoreCase)) continue;
472 | return result;
473 | }
474 | }
475 |
476 | ///
477 | /// Initiate a command and wait for completion, returning the Command Response
478 | ///
479 | /// Command Name
480 | /// Command request parameters
481 | /// Command Response
482 | protected async Task InitiateCommandAndWait(string command, JObject requestParameters)
483 | {
484 | var result = await InitiateCommand(command, requestParameters);
485 | var endStatus = await WaitForCommandCompletion(result.Url);
486 | return endStatus;
487 | }
488 |
489 |
490 | ///
491 | /// Initiate a command and wait for completion, parsing the response for success flag
492 | ///
493 | /// Command Name
494 | /// Command request parameters
495 | /// True or false if the command succeeded
496 | protected async Task InitiateCommandAndWaitForSuccess(string command, JObject requestParameters)
497 | {
498 | var result = await InitiateCommandAndWait(command, requestParameters);
499 | if (result == null) return false;
500 | if ("success".Equals(result.Status, StringComparison.OrdinalIgnoreCase))
501 | {
502 | return true;
503 | }
504 | else
505 | {
506 | return false;
507 | }
508 | }
509 |
510 | ///
511 | /// Call the status URL for a command
512 | ///
513 | ///
514 | ///
515 | async Task PollCommandStatus(string statusUrl)
516 | {
517 | var response = await GetAsync($"{statusUrl}?units=METRIC");
518 |
519 | if (response.IsSuccessStatusCode)
520 | {
521 | var result = await response.Content.ReadAsAsync();
522 | return result.CommandResponse;
523 | }
524 | else
525 | {
526 | return null;
527 | }
528 | }
529 |
530 |
531 | ///
532 | /// Get the list of vehicle configurations for the first 10 vehicles on the account
533 | /// Result of this request is required to use vehicle-specific commands
534 | ///
535 | /// Collection of Vehicle configurations
536 | public async Task> GetVehicles()
537 | {
538 | //these could be parameterized, but we better stick with what the app does
539 | var resp = await GetAsync($"{_apiUrl}/v1/account/vehicles?offset=0&limit=10&includeCommands=true&includeEntitlements=true&includeModules=true");
540 |
541 | if (resp.IsSuccessStatusCode)
542 | {
543 | var outerResult = await resp.Content.ReadAsAsync();
544 | if (outerResult.Vehicles != null && outerResult.Vehicles.Vehicle != null && outerResult.Vehicles.Vehicle.Length > 0)
545 | {
546 | return outerResult.Vehicles.Vehicle;
547 | }
548 | }
549 |
550 | return null;
551 | }
552 |
553 |
554 |
555 |
556 |
557 | #endregion
558 |
559 |
560 | #region Command Implementations
561 |
562 | ///
563 | /// Retrieve Diagnostic data for the active vehicle
564 | ///
565 | ///
566 | public async Task GetDiagnostics()
567 | {
568 | var cmdInfo = ActiveVehicle.GetCommand("diagnostics");
569 |
570 | var reqObj = new JObject()
571 | {
572 | ["diagnosticsRequest"] = new JObject()
573 | {
574 | ["diagnosticItem"] = new JArray(cmdInfo.CommandData.SupportedDiagnostics.SupportedDiagnostic)
575 | }
576 | };
577 |
578 | var result = await InitiateCommandAndWait("diagnostics", reqObj);
579 | if (result == null) return null;
580 | if ("success".Equals(result.Status, StringComparison.OrdinalIgnoreCase))
581 | {
582 | return result.Body.DiagnosticResponse;
583 | }
584 | else
585 | {
586 | return null;
587 | }
588 | }
589 |
590 |
591 | ///
592 | /// Issue an arbitrary command
593 | ///
594 | /// Name of the command. Must exists in the vehicle's configuration
595 | /// JSON parameters for the command
596 | ///
597 | public async Task IssueCommand(string commandName, JObject parameters = null)
598 | {
599 | return await InitiateCommandAndWait(commandName, parameters);
600 | }
601 |
602 | ///
603 | /// Lock the active vehicles's doors and wait for completion
604 | /// Privileged Command
605 | ///
606 | /// True or false for success
607 | public async Task LockDoor()
608 | {
609 |
610 | var reqObj = new JObject()
611 | {
612 | ["lockDoorRequest"] = new JObject()
613 | {
614 | ["delay"] = 0
615 | }
616 | };
617 |
618 | return await InitiateCommandAndWaitForSuccess("lockDoor", reqObj);
619 | }
620 |
621 |
622 | ///
623 | /// Fails when the hotspot is off...
624 | /// Note: the app uses diagnotics that also fail when the hotpot is off
625 | ///
626 | ///
627 | public async Task GetHotspotInfo()
628 | {
629 | var resp = await InitiateCommandAndWait("getHotspotInfo", null);
630 | return resp.Body.HotspotInfo;
631 | }
632 |
633 |
634 | ///
635 | /// Send a turn-by-turn destination to the vehicle
636 | /// Requires both coordinates and address info
637 | /// Vehicle may not respond if turned off or may take a very long time to respond
638 | ///
639 | ///
640 | ///
641 | public async Task SendTBTRoute(TbtDestination destination)
642 | {
643 | var reqObj = new JObject()
644 | {
645 | ["tbtDestination"] = new JObject(destination)
646 | };
647 |
648 | return await InitiateCommandAndWaitForSuccess("sendTBTRoute", reqObj);
649 | }
650 |
651 |
652 | ///
653 | /// Unlock the active vehicles's doors and wait for completion
654 | /// Privileged Command
655 | ///
656 | /// True or false for success
657 | public async Task UnlockDoor()
658 | {
659 | var reqObj = new JObject()
660 | {
661 | ["unlockDoorRequest"] = new JObject()
662 | {
663 | ["delay"] = 0
664 | }
665 | };
666 |
667 | return await InitiateCommandAndWaitForSuccess("unlockDoor", reqObj);
668 | }
669 |
670 | ///
671 | /// Remote start the active vehicle and wait for completion
672 | /// Privileged Command
673 | ///
674 | /// True or false for success
675 | public async Task Start()
676 | {
677 | return await InitiateCommandAndWaitForSuccess("start", null);
678 | }
679 |
680 | ///
681 | /// Remote stop the active vehicle and wait for completion
682 | /// Privileged Command
683 | ///
684 | /// True or false for success
685 | public async Task CancelStart()
686 | {
687 | return await InitiateCommandAndWaitForSuccess("cancelStart", null);
688 | }
689 |
690 |
691 | ///
692 | /// Set off remote alarm on the active vehicle and wait for completion
693 | /// Privileged Command
694 | ///
695 | /// True or false for success
696 | public async Task Alert()
697 | {
698 | var reqObj = new JObject()
699 | {
700 | ["alertRequest"] = new JObject()
701 | {
702 | ["action"] = new JArray() { "Honk", "Flash" },
703 | ["delay"] = 0,
704 | ["duration"] = 1,
705 | ["override"] = new JArray() { "DoorOpen", "IgnitionOn" }
706 | }
707 | };
708 |
709 | return await InitiateCommandAndWaitForSuccess("alert", reqObj);
710 | }
711 |
712 | ///
713 | /// Stop remote alarm on the active vehicle and wait for completion
714 | /// Privileged Command
715 | ///
716 | /// True or false for success
717 | public async Task CancelAlert()
718 | {
719 | return await InitiateCommandAndWaitForSuccess("cancelAlert", null);
720 | }
721 |
722 | #endregion
723 |
724 |
725 |
726 | }
727 | }
728 |
--------------------------------------------------------------------------------
/GM.Api/GMClientNoKey.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net.Http;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using GM.Api.Tokens;
7 |
8 | namespace GM.Api
9 | {
10 | ///
11 | /// GM Client implementation that uses a web service to sign the JWT tokens required for authentication
12 | /// Use this if you do not have access to the Client ID and Client Secret
13 | ///
14 | public class GMClientNoKey : GMClientBase
15 | {
16 | string _tokenSignUrl;
17 | HttpClient _tokenClient = new HttpClient();
18 |
19 | ///
20 | /// Create new GM Client
21 | ///
22 | /// deviceId = string representation of a GUID
23 | /// API is segmented by brand
24 | /// URL for webservice that will sign JWT tokens (e.g. "https://gmsigner.herokuapp.com/")
25 | public GMClientNoKey(string deviceId, Brand brand, string tokenSignUrl) : base(deviceId, brand)
26 | {
27 | _tokenSignUrl = tokenSignUrl;
28 | }
29 |
30 | protected override async Task EncodeLoginRequest(LoginRequest request)
31 | {
32 | var resp = await _tokenClient.PostAsJsonAsync($"{_tokenSignUrl}?brand={_brand.GetName()}", request);
33 |
34 | if (resp.IsSuccessStatusCode)
35 | {
36 | return await resp.Content.ReadAsStringAsync();
37 | }
38 | else
39 | {
40 | string errorText = await resp.Content.ReadAsStringAsync();
41 | throw new InvalidOperationException("Token sign failure: " + errorText);
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/GM.Api/Models/CommandResponse.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace GM.Api.Models
7 | {
8 | ///
9 | /// Root object returned by a command request, or a call to a status url
10 | ///
11 | public class CommandRequestResponse
12 | {
13 | ///
14 | /// Inner response
15 | ///
16 | [JsonProperty("commandResponse")]
17 | public CommandResponse CommandResponse { get; set; }
18 | }
19 |
20 | ///
21 | /// Command Response Object
22 | ///
23 | public class CommandResponse
24 | {
25 | ///
26 | /// Timestamp the request was received by the server
27 | ///
28 | [JsonProperty("requestTime")]
29 | public DateTime RequestTime { get; set; }
30 |
31 | ///
32 | /// Timestamp the server completed the request
33 | ///
34 | [JsonProperty("completionTime")]
35 | public DateTime CompletionTime { get; set; }
36 |
37 | ///
38 | /// Status URL to be polled for updates (commands are async)
39 | ///
40 | [JsonProperty("url")]
41 | public string Url { get; set; }
42 |
43 | ///
44 | /// Current status of the command request
45 | /// (e.g. "inProgress", "success")
46 | ///
47 | [JsonProperty("status")]
48 | public string Status { get; set; } //inProgress, success
49 |
50 | ///
51 | /// Probably refers to the type of the response body
52 | ///
53 | [JsonProperty("type")]
54 | public string Type { get; set; }
55 |
56 | ///
57 | /// Response body for commands that include a response (e.g. diagnostics, location)
58 | ///
59 | [JsonProperty("body")]
60 | public ResponseBody Body { get; set; }
61 |
62 | }
63 |
64 |
65 |
66 | ///
67 | /// Response Body
68 | /// Note: this only contains a diagnostic response. there are likely others.
69 | ///
70 | public class ResponseBody
71 | {
72 | ///
73 | /// Populated for diagnostics command
74 | ///
75 | [JsonProperty("diagnosticResponse")]
76 | public DiagnosticResponse[] DiagnosticResponse { get; set; }
77 |
78 | ///
79 | /// populated for location command
80 | ///
81 | [JsonProperty("location")]
82 | public Location Location { get; set; }
83 |
84 | ///
85 | /// placeholder - not yet tested
86 | ///
87 | [JsonProperty("hotspotInfo")]
88 | public HotspotInfo HotspotInfo { get; set; }
89 |
90 | }
91 |
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/GM.Api/Models/Diagnostic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Linq;
5 | using Newtonsoft.Json;
6 |
7 | namespace GM.Api.Models
8 | {
9 | public class DiagnosticResponse
10 | {
11 | [JsonProperty("name")]
12 | public string Name { get; set; }
13 |
14 | [JsonProperty("diagnosticElement")]
15 | public DiagnosticElement[] DiagnosticElement { get; set; }
16 | }
17 |
18 | public class DiagnosticElement
19 | {
20 | [JsonProperty("name")]
21 | public string Name { get; set; }
22 |
23 | [JsonProperty("status")]
24 | public string Status { get; set; }
25 |
26 | [JsonProperty("message")]
27 | public string Message { get; set; }
28 |
29 | [JsonProperty("value")]
30 | public string Value { get; set; }
31 |
32 | [JsonProperty("unit")]
33 | public string Unit { get; set; }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/GM.Api/Models/HotspotInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace GM.Api.Models
6 | {
7 | ///
8 | /// Placeholder
9 | ///
10 | public class HotspotInfo
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/GM.Api/Models/Location.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace GM.Api.Models
7 | {
8 | public class Location
9 | {
10 | [JsonProperty("lat")]
11 | public float? Latitude { get; set; }
12 |
13 | [JsonProperty("long")]
14 | public float? Longitude { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/GM.Api/Models/TbtDestination.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace GM.Api.Models
7 | {
8 |
9 | public class TbtDestination
10 | {
11 | [JsonProperty("additionalDestinationInfo")]
12 | public AdditionalDestinationInfo AdditionalDestinationInfo { get; set; }
13 |
14 | [JsonProperty("destinationLocation")]
15 | public Location DestinationLocation { get; set; }
16 | }
17 |
18 | public class AdditionalDestinationInfo
19 | {
20 | [JsonProperty("destinationAddress")]
21 | public DestinationAddress DestinationAddress { get; set; }
22 |
23 | [JsonProperty("destinationType")]
24 | public string DestinationType { get; set; }
25 | }
26 |
27 | public class DestinationAddress
28 | {
29 | [JsonProperty("city")]
30 | public string City { get; set; }
31 |
32 | [JsonProperty("country")]
33 | public string Country { get; set; }
34 |
35 | [JsonProperty("state")]
36 | public string State { get; set; }
37 |
38 | [JsonProperty("street")]
39 | public string Street { get; set; }
40 |
41 | [JsonProperty("streetNo")]
42 | public string StreetNo { get; set; }
43 |
44 | [JsonProperty("zipCode")]
45 | public string ZipCode { get; set; }
46 | }
47 |
48 | public class DestinationLocation
49 | {
50 | public void SetLatitude(float value)
51 | {
52 | Latitude = value.ToString("###.#####");
53 | }
54 |
55 | public void SetLongitude(float value)
56 | {
57 | Longitude = value.ToString("###.#####");
58 | }
59 |
60 | [JsonProperty("lat")]
61 | public string Latitude { get; set; }
62 |
63 | [JsonProperty("long")]
64 | public string Longitude { get; set; }
65 | }
66 |
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/GM.Api/Models/Vehicle.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace GM.Api.Models
8 | {
9 |
10 |
11 | public class VehiclesResponse
12 | {
13 | [JsonProperty("vehicles")]
14 | public Vehicles Vehicles { get; set; }
15 | }
16 |
17 | public class Vehicles
18 | {
19 | ///
20 | /// Size of the Vehicle array, or full size. One would need to have more than 10 cars to find out...
21 | ///
22 | [JsonProperty("size")]
23 | public string Size { get; set; }
24 |
25 | ///
26 | /// List of vehicles associated with the account
27 | /// Note that there is paging and by default the page size is 10
28 | ///
29 | [JsonProperty("vehicle")]
30 | public Vehicle[] Vehicle { get; set; }
31 | }
32 |
33 | ///
34 | /// Vehicle description
35 | ///
36 | public class Vehicle
37 | {
38 | ///
39 | /// Vehicle VIN
40 | ///
41 | [JsonProperty("vin")]
42 | public string Vin { get; set; }
43 |
44 | ///
45 | /// Vehicle Make
46 | ///
47 | [JsonProperty("make")]
48 | public string Make { get; set; }
49 |
50 | ///
51 | /// Vehicle Model
52 | ///
53 | [JsonProperty("model")]
54 | public string Model { get; set; }
55 |
56 | ///
57 | /// Vehicle Year
58 | ///
59 | [JsonProperty("year")]
60 | public string Year { get; set; }
61 |
62 | ///
63 | /// Vehicle Manufacturer - not sure why this is required...
64 | ///
65 | [JsonProperty("manufacturer")]
66 | public string Manufacturer { get; set; }
67 |
68 | ///
69 | /// (e.g. car, maybe truck)
70 | ///
71 | [JsonProperty("bodyStyle")]
72 | public string BodyStyle { get; set; }
73 |
74 | ///
75 | /// Vehicle cellular / OnStar phone number
76 | ///
77 | [JsonProperty("phone")]
78 | public string Phone { get; set; }
79 |
80 | [JsonProperty("unitType")]
81 | public string UnitType { get; set; }
82 |
83 | [JsonProperty("onstarStatus")]
84 | public string OnStarStatus { get; set; }
85 |
86 | ///
87 | /// Base URL for API calls regarding this vehicle
88 | ///
89 | [JsonProperty("url")]
90 | public string Url { get; set; }
91 |
92 | [JsonProperty("isInPreActivation")]
93 | public bool? IsInPreActivation { get; set; }
94 |
95 | [JsonProperty("insuranceInfo")]
96 | public InsuranceInfo InsuranceInfo { get; set; }
97 |
98 | [JsonProperty("enrolledInContinuousCoverage")]
99 | public bool? EnrolledInContinuousCoverage { get; set; }
100 |
101 | ///
102 | /// Details on supported commands
103 | ///
104 | [JsonProperty("commands")]
105 | public Commands Commands { get; set; }
106 |
107 | ///
108 | /// Details on available modules
109 | ///
110 | [JsonProperty("modules")]
111 | public Modules Modules { get; set; }
112 |
113 | ///
114 | /// Details on available entitlements
115 | ///
116 | [JsonProperty("entitlements")]
117 | public Entitlements Entitlements { get; set; }
118 |
119 | [JsonProperty("propulsionType")]
120 | public string PropulsionType { get; set; }
121 |
122 |
123 | public Command GetCommand(string name)
124 | {
125 | return (from f in Commands.Command where f.Name.Equals(name, StringComparison.Ordinal) select f).FirstOrDefault();
126 | }
127 |
128 | }
129 |
130 | public class InsuranceInfo
131 | {
132 | [JsonProperty("insuranceAgent")]
133 | public InsuranceAgent InsuranceAgent { get; set; }
134 | }
135 |
136 | public class InsuranceAgent
137 | {
138 | [JsonProperty("phone")]
139 | public Phone Phone { get; set; }
140 | }
141 |
142 | public class Phone
143 | {
144 | }
145 |
146 | public class Commands
147 | {
148 | ///
149 | /// List of commands supported by the vehicle
150 | ///
151 | [JsonProperty("command")]
152 | public Command[] Command { get; set; }
153 | }
154 |
155 | ///
156 | /// Details about a supported command
157 | ///
158 | public class Command
159 | {
160 | ///
161 | /// Command name
162 | ///
163 | [JsonProperty("name")]
164 | public string Name { get; set; }
165 |
166 | ///
167 | /// Description of what the command does
168 | ///
169 | [JsonProperty("description")]
170 | public string Description { get; set; }
171 |
172 | ///
173 | /// API URL to be used for issuing the command
174 | /// This SDK uses this url rather than constructing it
175 | ///
176 | [JsonProperty("url")]
177 | public string Url { get; set; }
178 |
179 | ///
180 | /// True or False if the command requires the token to be upgraded with an OnStar PIN
181 | ///
182 | [JsonProperty("isPrivSessionRequired")]
183 | public bool? IsPrivSessionRequired { get; set; }
184 |
185 | ///
186 | /// For commands with additional data such as diagnostics
187 | ///
188 | [JsonProperty("commandData")]
189 | public CommandData CommandData { get; set; }
190 | }
191 |
192 | public class CommandData
193 | {
194 | [JsonProperty("supportedDiagnostics")]
195 | public SupportedDiagnostics SupportedDiagnostics { get; set; }
196 | }
197 |
198 | public class SupportedDiagnostics
199 | {
200 | ///
201 | /// List of the diagnostic elements that may be requsted for the vehicle
202 | ///
203 | [JsonProperty("supportedDiagnostic")]
204 | public string[] SupportedDiagnostic { get; set; }
205 | }
206 |
207 | public class Modules
208 | {
209 | ///
210 | /// List of modules - not much here
211 | ///
212 | [JsonProperty("module")]
213 | public Module[] Module { get; set; }
214 | }
215 |
216 | public class Module
217 | {
218 | [JsonProperty("moduleType")]
219 | public string ModuleType { get; set; }
220 |
221 | [JsonProperty("moduleCapability")]
222 | public string ModuleCapability { get; set; }
223 | }
224 |
225 | public class Entitlements
226 | {
227 | ///
228 | /// List of entitlements - features and activities vehicles are capable of
229 | /// List contains things the vehicle or account may or may not support
230 | /// Check the Elligible flag
231 | ///
232 | [JsonProperty("entitlement")]
233 | public Entitlement[] Entitlement { get; set; }
234 | }
235 |
236 | ///
237 | /// Details about an Entitlement
238 | ///
239 | public class Entitlement
240 | {
241 | ///
242 | /// ID or name of entitlement
243 | ///
244 | [JsonProperty("id")]
245 | public string Id { get; set; }
246 |
247 | ///
248 | /// True or false if the entitlement is available on this vehicle or account
249 | ///
250 | [JsonProperty("eligible")]
251 | public bool? Eligible { get; set; }
252 |
253 | ///
254 | /// Reason for inelligibility (whether the car is incapable or the owner isn't subscribed)
255 | ///
256 | [JsonProperty("ineligibleReasonCode")]
257 | public string IneligibleReasonCode { get; set; }
258 |
259 | ///
260 | /// True or false if the entitlement can send notifications
261 | ///
262 | [JsonProperty("notificationCapable")]
263 | public bool? NotificationCapable { get; set; }
264 | }
265 |
266 |
267 | }
268 |
--------------------------------------------------------------------------------
/GM.Api/Tokens/Base32.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace GM.Api.Tokens
6 | {
7 | ///
8 | /// Base32 Implementation
9 | /// https://stackoverflow.com/a/641437
10 | ///
11 | sealed class Base32
12 | {
13 |
14 | // the valid chars for the encoding
15 | private static string ValidChars = "QAZ2WSX3" + "EDC4RFV5" + "TGB6YHN7" + "UJM8K9LP";
16 |
17 | ///
18 | /// Converts an array of bytes to a Base32-k string.
19 | /// TODO: reference source give credit
20 | ///
21 | public static string ToBase32String(byte[] bytes)
22 | {
23 | StringBuilder sb = new StringBuilder(); // holds the base32 chars
24 | byte index;
25 | int hi = 5;
26 | int currentByte = 0;
27 |
28 | while (currentByte < bytes.Length)
29 | {
30 | // do we need to use the next byte?
31 | if (hi > 8)
32 | {
33 | // get the last piece from the current byte, shift it to the right
34 | // and increment the byte counter
35 | index = (byte)(bytes[currentByte++] >> (hi - 5));
36 | if (currentByte != bytes.Length)
37 | {
38 | // if we are not at the end, get the first piece from
39 | // the next byte, clear it and shift it to the left
40 | index = (byte)(((byte)(bytes[currentByte] << (16 - hi)) >> 3) | index);
41 | }
42 |
43 | hi -= 3;
44 | }
45 | else if (hi == 8)
46 | {
47 | index = (byte)(bytes[currentByte++] >> 3);
48 | hi -= 3;
49 | }
50 | else
51 | {
52 |
53 | // simply get the stuff from the current byte
54 | index = (byte)((byte)(bytes[currentByte] << (8 - hi)) >> 3);
55 | hi += 5;
56 | }
57 |
58 | sb.Append(ValidChars[index]);
59 | }
60 |
61 | return sb.ToString();
62 | }
63 |
64 |
65 | ///
66 | /// Converts a Base32-k string into an array of bytes.
67 | ///
68 | ///
69 | /// Input string s contains invalid Base32-k characters.
70 | ///
71 | public static byte[] FromBase32String(string str)
72 | {
73 | int numBytes = str.Length * 5 / 8;
74 | byte[] bytes = new Byte[numBytes];
75 |
76 | // all UPPERCASE chars
77 | str = str.ToUpper();
78 |
79 | int bit_buffer;
80 | int currentCharIndex;
81 | int bits_in_buffer;
82 |
83 | if (str.Length < 3)
84 | {
85 | bytes[0] = (byte)(ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5);
86 | return bytes;
87 | }
88 |
89 | bit_buffer = (ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5);
90 | bits_in_buffer = 10;
91 | currentCharIndex = 2;
92 | for (int i = 0; i < bytes.Length; i++)
93 | {
94 | bytes[i] = (byte)bit_buffer;
95 | bit_buffer >>= 8;
96 | bits_in_buffer -= 8;
97 | while (bits_in_buffer < 8 && currentCharIndex < str.Length)
98 | {
99 | bit_buffer |= ValidChars.IndexOf(str[currentCharIndex++]) << bits_in_buffer;
100 | bits_in_buffer += 5;
101 | }
102 | }
103 |
104 | return bytes;
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/GM.Api/Tokens/JwtTool.cs:
--------------------------------------------------------------------------------
1 | using JWT;
2 | using JWT.Algorithms;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 |
9 | namespace GM.Api.Tokens
10 | {
11 | public class JwtTool
12 | {
13 | public IJwtEncoder Encoder { get; private set; }
14 |
15 | public IJwtDecoder Decoder { get; private set; }
16 |
17 | byte[] _key;
18 |
19 | public JwtTool(string key)
20 | {
21 | _key = Encoding.ASCII.GetBytes(key);
22 |
23 | IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
24 | IJsonSerializer serializer = new SortedJsonSerializer();
25 | IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
26 | Encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
27 |
28 | IDateTimeProvider dateTimeProvider = new UtcDateTimeProvider();
29 | IJwtValidator validator = new JwtValidator(serializer, dateTimeProvider);
30 | Decoder = new JwtDecoder(serializer, validator, urlEncoder);
31 |
32 | }
33 |
34 |
35 | public string EncodeToken(object claim)
36 | {
37 | return Encoder.Encode(claim, _key);
38 | }
39 |
40 |
41 | public string DecodeToken(string token)
42 | {
43 | return Decoder.Decode(token);
44 | }
45 |
46 | public T DecodeTokenToObject(string token)
47 | {
48 | return Decoder.DecodeToObject(token);
49 | }
50 | }
51 |
52 |
53 |
54 |
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/GM.Api/Tokens/LoginData.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace GM.Api.Tokens
7 | {
8 | ///
9 | /// Data contained within the login response JWT or as updated when refreshed
10 | ///
11 | public class LoginData
12 | {
13 | ///
14 | /// Access token used as bearer token
15 | ///
16 | [JsonProperty("access_token")]
17 | public string AccessToken { get; set; }
18 |
19 | ///
20 | /// Bearer
21 | ///
22 | [JsonProperty("token_type")]
23 | public string TokenType { get; set; }
24 |
25 | ///
26 | /// Token expiration in seconds
27 | /// (Note: I have seen the tokens expire quicker)
28 | ///
29 | [JsonProperty("expires_in")]
30 | public int ExpiresIn { get; set; }
31 |
32 | ///
33 | /// List of scopes
34 | ///
35 | [JsonProperty("scope")]
36 | public string Scope { get; set; }
37 |
38 | ///
39 | /// Information about the OnStar account
40 | ///
41 | [JsonProperty("onstar_account_info")]
42 | public Onstar_Account_Info OnStarAccountInfo { get; set; }
43 |
44 | ///
45 | /// Information about the user
46 | ///
47 | [JsonProperty("user_info")]
48 | public User_Info UserInfo { get; set; }
49 |
50 | ///
51 | /// RSA hashed itentity token (JWT) used in refresh assertion
52 | ///
53 | [JsonProperty("id_token")]
54 | public string IdToken { get; set; }
55 |
56 | ///
57 | /// Timestamp the token was recieved
58 | ///
59 | [JsonIgnore]
60 | public DateTime IssuedAtUtc { get; set; } = DateTime.UtcNow;
61 |
62 | ///
63 | /// Approximate timestamp the token expires
64 | ///
65 | [JsonIgnore]
66 | public DateTime ExpiresAtUtc => (IssuedAtUtc + TimeSpan.FromSeconds(ExpiresIn - 2));
67 |
68 | ///
69 | /// Check if the token is expired based on timestamp
70 | ///
71 | [JsonIgnore]
72 | public bool IsExpired => (DateTime.UtcNow >= ExpiresAtUtc);
73 |
74 |
75 | }
76 |
77 | public class Onstar_Account_Info
78 | {
79 | [JsonProperty("country_code")]
80 | public string CountryCode { get; set; }
81 |
82 | [JsonProperty("account_no")]
83 | public string AccountNo { get; set; }
84 | }
85 |
86 | public class User_Info
87 | {
88 | [JsonProperty("RemoteUserId")]
89 | public string RemoteUserId { get; set; }
90 |
91 | [JsonProperty("country")]
92 | public string Country { get; set; }
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/GM.Api/Tokens/LoginRequest.cs:
--------------------------------------------------------------------------------
1 | using JWT;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Linq;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 |
9 | namespace GM.Api.Tokens
10 | {
11 |
12 | ///
13 | /// Body that is encoded into the login and upgrade JWT
14 | ///
15 | public class LoginRequest
16 | {
17 | ///
18 | /// Client ID from configuration
19 | ///
20 | [JsonProperty("client_id", DefaultValueHandling = DefaultValueHandling.Ignore)]
21 | public string ClientId { get; set; }
22 |
23 | ///
24 | /// A GUID represented as a string, specific to the device
25 | /// (randomly generated once and saved)
26 | ///
27 | [JsonProperty("device_id", DefaultValueHandling = DefaultValueHandling.Ignore)]
28 | public string DeviceId { get; set; }
29 |
30 | ///
31 | /// OAuth grant type
32 | ///
33 | [JsonProperty("grant_type", DefaultValueHandling = DefaultValueHandling.Ignore)]
34 | public string GrantType { get; set; }
35 |
36 | ///
37 | /// Random string generated by a specifi method
38 | ///
39 | [JsonProperty("nonce", DefaultValueHandling = DefaultValueHandling.Ignore)]
40 | public string Nonce { get; set; }
41 |
42 | ///
43 | /// User's password
44 | ///
45 | [JsonProperty("password", DefaultValueHandling = DefaultValueHandling.Ignore)]
46 | public string Password { get; set; }
47 |
48 | ///
49 | /// Scope
50 | /// ex: onstar gmoc commerce user_trailer msso
51 | ///
52 | [JsonProperty("scope", DefaultValueHandling = DefaultValueHandling.Ignore)]
53 | public string Scope { get; set; }
54 |
55 | ///
56 | /// Current timestamp in UTC using "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffK" format string
57 | ///
58 | [JsonProperty("timestamp", DefaultValueHandling = DefaultValueHandling.Ignore)]
59 | public string Timestamp { get; set; }
60 |
61 | ///
62 | /// Username for GM account
63 | ///
64 | [JsonProperty("username", DefaultValueHandling = DefaultValueHandling.Ignore)]
65 | public string Username { get; set; }
66 |
67 |
68 | ///
69 | /// OnStar PIN used to upgrade token
70 | ///
71 | [JsonProperty("credential", DefaultValueHandling = DefaultValueHandling.Ignore)]
72 | public string Credential { get; set; }
73 |
74 | ///
75 | /// "PIN" for onstar pin
76 | ///
77 | [JsonProperty("credential_type", DefaultValueHandling = DefaultValueHandling.Ignore)]
78 | public string CredentialType { get; set; }
79 |
80 | ///
81 | /// IdToken from login payload - used for refreshing
82 | ///
83 | [JsonProperty("assertion", DefaultValueHandling = DefaultValueHandling.Ignore)]
84 | public string Assertion { get; set; }
85 |
86 |
87 | }
88 |
89 |
90 |
91 |
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/GM.Api/Tokens/SortedJsonSerializer.cs:
--------------------------------------------------------------------------------
1 | using JWT;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Linq;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 |
9 | namespace GM.Api.Tokens
10 | {
11 | ///
12 | /// Custom JSON serialized used with JWT.net
13 | /// JWT.net's JWT header is not alphebetized by default...
14 | ///
15 | public class SortedJsonSerializer : IJsonSerializer
16 | {
17 | public string Serialize(object obj)
18 | {
19 | return NormalizeJsonString(Newtonsoft.Json.JsonConvert.SerializeObject(obj));
20 | }
21 |
22 | public T Deserialize(string json)
23 | {
24 | return JsonConvert.DeserializeObject(json);
25 | }
26 |
27 |
28 | public static string NormalizeJsonString(string json)
29 | {
30 | // Parse json string into JObject.
31 | var parsedObject = JObject.Parse(json);
32 |
33 | // Sort properties of JObject.
34 | var normalizedObject = SortPropertiesAlphabetically(parsedObject);
35 |
36 | // Serialize JObject .
37 | return JsonConvert.SerializeObject(normalizedObject);
38 | }
39 |
40 | private static JObject SortPropertiesAlphabetically(JObject original)
41 | {
42 | var result = new JObject();
43 |
44 | foreach (var property in original.Properties().ToList().OrderBy(p => p.Name))
45 | {
46 | var value = property.Value as JObject;
47 |
48 | if (value != null)
49 | {
50 | value = SortPropertiesAlphabetically(value);
51 | result.Add(property.Name, value);
52 | }
53 | else
54 | {
55 | result.Add(property.Name, property.Value);
56 | }
57 | }
58 |
59 | return result;
60 | }
61 |
62 |
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/GM.Api/helpers.cs:
--------------------------------------------------------------------------------
1 | using JWT;
2 | using JWT.Algorithms;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Net.Http.Headers;
6 | using System.Security.Cryptography;
7 | using System.Text;
8 |
9 | namespace GM.Api
10 | {
11 | static class helpers
12 | {
13 | public static string GenerateNonce()
14 | {
15 | //17.25 bytes = 130 bits
16 | //return new BigInteger(130, new SecureRandom()).toString(32);
17 |
18 | RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
19 | var byteArray = new byte[17];
20 |
21 | provider.GetBytes(byteArray);
22 | var nonce = Tokens.Base32.ToBase32String(byteArray);
23 | return nonce.ToLower().Substring(0, 26);
24 | }
25 |
26 | ///
27 | /// Set an HTTP header to a single value, clearing any existing values
28 | ///
29 | ///
30 | ///
31 | ///
32 | public static void SetValue(this HttpHeaderValueCollection headerValue, string value) where T: class
33 | {
34 | headerValue.Clear();
35 | headerValue.ParseAdd(value);
36 | }
37 |
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/GM.WindowsUI/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | https://gmsigner.herokuapp.com/
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/GM.WindowsUI/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/GM.WindowsUI/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace GM.WindowsUI
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/GM.WindowsUI/BrandWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/GM.WindowsUI/BrandWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using GM.Api;
2 | using GM.Api.Models;
3 | using Newtonsoft.Json;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using System.Windows;
11 | using System.Windows.Controls;
12 | using System.Windows.Data;
13 | using System.Windows.Documents;
14 | using System.Windows.Input;
15 | using System.Windows.Media;
16 | using System.Windows.Media.Imaging;
17 | using System.Windows.Shapes;
18 |
19 | namespace GM.WindowsUI
20 | {
21 | ///
22 | /// Interaction logic for BrandWindow.xaml
23 | ///
24 | public partial class BrandWindow : Window
25 | {
26 | public Brand? SelectedBrand { get; set; } = null;
27 |
28 | public BrandWindow()
29 | {
30 | InitializeComponent();
31 |
32 | var brandNames = Enum.GetNames(typeof(Brand));
33 |
34 | foreach (var brandName in brandNames.OrderBy((val) => val, StringComparer.OrdinalIgnoreCase))
35 | {
36 | lstBrands.Items.Add(brandName);
37 | }
38 | }
39 |
40 | private void BtnOk_Click(object sender, RoutedEventArgs e)
41 | {
42 | if (lstBrands.SelectedItem == null) return;
43 | SelectedBrand = BrandHelpers.GetBrand((string)lstBrands.SelectedItem);
44 | this.Close();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/GM.WindowsUI/GM.WindowsUI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {40A84AE5-3E7F-4838-A8E8-E33D772B1D8A}
8 | WinExe
9 | GM.WindowsUI
10 | GM.WindowsUI
11 | v4.7.2
12 | 512
13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 4
15 | true
16 | true
17 |
18 |
19 | AnyCPU
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 |
39 | ..\Assemblies\GM.SettingsReader.dll
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 4.0
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | MSBuild:Compile
59 | Designer
60 |
61 |
62 | Designer
63 | MSBuild:Compile
64 |
65 |
66 | Designer
67 | MSBuild:Compile
68 |
69 |
70 | MSBuild:Compile
71 | Designer
72 |
73 |
74 | App.xaml
75 | Code
76 |
77 |
78 | BrandWindow.xaml
79 |
80 |
81 | LoginWindow.xaml
82 |
83 |
84 | MainWindow.xaml
85 | Code
86 |
87 |
88 |
89 |
90 | Code
91 |
92 |
93 | True
94 | True
95 | Resources.resx
96 |
97 |
98 | True
99 | Settings.settings
100 | True
101 |
102 |
103 | ResXFileCodeGenerator
104 | Designer
105 | Resources.Designer.cs
106 |
107 |
108 | SettingsSingleFileGenerator
109 | Settings.Designer.cs
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | {95bf6a76-8e35-41dd-be3d-90a41014f1af}
118 | GM.Api
119 |
120 |
121 |
122 |
123 | 5.2.3
124 |
125 |
126 | 5.2.7
127 |
128 |
129 | 12.0.2
130 |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/GM.WindowsUI/LoginWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/GM.WindowsUI/LoginWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using GM.Api;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Shapes;
15 |
16 | namespace GM.WindowsUI
17 | {
18 | ///
19 | /// Interaction logic for LoginWindow.xaml
20 | ///
21 | public partial class LoginWindow : Window
22 | {
23 | GMClientBase _client;
24 |
25 | public bool Success { get; private set; } = false;
26 |
27 | public LoginWindow(GMClientBase client)
28 | {
29 | _client = client;
30 | InitializeComponent();
31 | }
32 |
33 | private async void BtnLogin_Click(object sender, RoutedEventArgs e)
34 | {
35 | var success = await _client.Login(txtUsername.Text, txtPassword.Password);
36 |
37 | if (success)
38 | {
39 | Success = true;
40 | this.Close();
41 | return;
42 | }
43 |
44 | MessageBox.Show("Login failed");
45 |
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/GM.WindowsUI/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/GM.WindowsUI/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using GM.Api;
2 | using GM.Api.Models;
3 | using GM.Api.Tokens;
4 | using Newtonsoft.Json;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using System.Windows;
12 | using System.Windows.Controls;
13 | using System.Windows.Data;
14 | using System.Windows.Documents;
15 | using System.Windows.Input;
16 | using System.Windows.Media;
17 | using System.Windows.Media.Imaging;
18 | using System.Windows.Navigation;
19 | using System.Windows.Shapes;
20 |
21 | namespace GM.WindowsUI
22 | {
23 | ///
24 | /// Interaction logic for MainWindow.xaml
25 | ///
26 | public partial class MainWindow : Window
27 | {
28 | GMClientBase _client;
29 |
30 | Brand _brand;
31 |
32 | Vehicle[] _vehicles = null;
33 |
34 | //Vehicle _selectedVehicle;
35 |
36 | public MainWindow()
37 | {
38 | InitializeComponent();
39 | LoadBrand();
40 | CreateClient();
41 | grpActions.IsEnabled = false;
42 | }
43 |
44 |
45 | void CreateClient()
46 | {
47 | if (string.IsNullOrEmpty(Properties.Settings.Default.DeviceId))
48 | {
49 | Properties.Settings.Default.DeviceId = Guid.NewGuid().ToString();
50 | Properties.Settings.Default.Save();
51 | }
52 |
53 | //todo: maybe the client reads the config and takes the brand and device id as param?
54 | _client = new GMClientNoKey(Properties.Settings.Default.DeviceId, _brand, Properties.Settings.Default.TokenSignerUrl);
55 | _client.TokenUpdateCallback = TokenUpdateHandler;
56 |
57 | if (!string.IsNullOrEmpty(Properties.Settings.Default.LoginData))
58 | {
59 | LoginData ld = null;
60 |
61 | try
62 | {
63 | ld = JsonConvert.DeserializeObject(Properties.Settings.Default.LoginData);
64 | }
65 | catch (Exception ex)
66 | {
67 | Properties.Settings.Default.LoginData = null;
68 | Properties.Settings.Default.Save();
69 | }
70 |
71 | if (ld != null)
72 | {
73 | _client.LoginData = ld;
74 | }
75 | }
76 |
77 | }
78 |
79 |
80 | async Task TokenUpdateHandler(LoginData loginData)
81 | {
82 | if (loginData != null)
83 | {
84 | Properties.Settings.Default.LoginData = JsonConvert.SerializeObject(loginData);
85 | Properties.Settings.Default.Save();
86 | }
87 | }
88 |
89 |
90 | void LoadBrand()
91 | {
92 | if (string.IsNullOrEmpty(Properties.Settings.Default.Brand))
93 | {
94 | var bw = new BrandWindow();
95 | bw.ShowDialog();
96 |
97 | if (!bw.SelectedBrand.HasValue)
98 | {
99 | MessageBox.Show("You must select a brand!");
100 | Environment.Exit(100);
101 | return;
102 | }
103 |
104 | Properties.Settings.Default.Brand = bw.SelectedBrand.Value.GetName();
105 | Properties.Settings.Default.Save();
106 | }
107 |
108 | _brand = BrandHelpers.GetBrand(Properties.Settings.Default.Brand);
109 |
110 | Title = _brand.GetDisplayName() + " Vehicle Control";
111 | }
112 |
113 |
114 | async Task LoadVehicles()
115 | {
116 |
117 |
118 | IEnumerable vehicles = null;
119 | try
120 | {
121 | vehicles = await _client.GetVehicles();
122 | }
123 | catch (Exception)
124 | {
125 | throw;
126 | }
127 |
128 | if (vehicles == null)
129 | {
130 | MessageBox.Show("There are no vehicles on your account!");
131 | return;
132 | }
133 |
134 | _vehicles = vehicles.ToArray();
135 |
136 | foreach (var vehicle in _vehicles)
137 | {
138 | cmbVehicle.Items.Add($"{vehicle.Year} {vehicle.Model} ({vehicle.Vin})");
139 | }
140 |
141 | if (!string.IsNullOrEmpty(Properties.Settings.Default.Vin))
142 | {
143 | bool found = false;
144 | for (int i = 0; i < _vehicles.Length; i++)
145 | {
146 | if (_vehicles[i].Vin.Equals(Properties.Settings.Default.Vin, StringComparison.OrdinalIgnoreCase))
147 | {
148 | found = true;
149 | cmbVehicle.SelectedIndex = i;
150 | break;
151 | }
152 | }
153 | if (!found)
154 | {
155 | cmbVehicle.SelectedIndex = 0;
156 | }
157 | }
158 | else
159 | {
160 | cmbVehicle.SelectedIndex = 0;
161 | }
162 |
163 |
164 |
165 | }
166 |
167 |
168 | private async void BtnLogin_Click(object sender, RoutedEventArgs e)
169 | {
170 | var wind = new LoginWindow(_client);
171 | wind.ShowDialog();
172 | if (!wind.Success)
173 | {
174 | return;
175 | }
176 |
177 | await LoadVehicles();
178 | lblStatus.Content = "Connected";
179 | grpActions.IsEnabled = true;
180 | btnLogin.IsEnabled = false;
181 | }
182 |
183 |
184 | async Task HandleUpgrade()
185 | {
186 | if (!_client.IsUpgraded)
187 | {
188 | if (string.IsNullOrEmpty(txtPin.Password))
189 | {
190 | MessageBox.Show("OnStar PIN required");
191 | return false;
192 | }
193 |
194 | var result = await _client.UpgradeLogin(txtPin.Password);
195 | if (!result)
196 | {
197 | MessageBox.Show("Login upgrade failed!");
198 | return false;
199 | }
200 | }
201 | return true;
202 | }
203 |
204 | private async void BtnLock_Click(object sender, RoutedEventArgs e)
205 | {
206 | if (!await HandleUpgrade()) return;
207 |
208 | grpActions.IsEnabled = false;
209 | btnLogin.IsEnabled = false;
210 | lblStatus.Content = "Locking (Please wait)";
211 | var success = await _client.LockDoor();
212 | if (success)
213 | {
214 | lblStatus.Content = "Locked Successfully";
215 | }
216 | else
217 | {
218 | lblStatus.Content = "Locking Failed";
219 | }
220 |
221 | grpActions.IsEnabled = true;
222 | btnLogin.IsEnabled = true;
223 | }
224 |
225 | private async void BtnUnlock_Click(object sender, RoutedEventArgs e)
226 | {
227 | if (!await HandleUpgrade()) return;
228 | grpActions.IsEnabled = false;
229 | btnLogin.IsEnabled = false;
230 | lblStatus.Content = "Unlocking (Please wait)";
231 | var success = await _client.UnlockDoor();
232 | if (success)
233 | {
234 | lblStatus.Content = "Unlocked Successfully";
235 | }
236 | else
237 | {
238 | lblStatus.Content = "Unlocking Failed";
239 | }
240 | grpActions.IsEnabled = true;
241 | btnLogin.IsEnabled = true;
242 | }
243 |
244 | private async void BtnStart_Click(object sender, RoutedEventArgs e)
245 | {
246 | if (!await HandleUpgrade()) return;
247 | grpActions.IsEnabled = false;
248 | btnLogin.IsEnabled = false;
249 | lblStatus.Content = "Starting (Please wait)";
250 | var success = await _client.Start();
251 | if (success)
252 | {
253 | lblStatus.Content = "Started Successfully";
254 | }
255 | else
256 | {
257 | lblStatus.Content = "Starting Failed";
258 | }
259 | grpActions.IsEnabled = true;
260 | btnLogin.IsEnabled = true;
261 | }
262 |
263 | private async void BtnStop_Click(object sender, RoutedEventArgs e)
264 | {
265 | if (!await HandleUpgrade()) return;
266 | grpActions.IsEnabled = false;
267 | btnLogin.IsEnabled = false;
268 | lblStatus.Content = "Stopping (Please wait)";
269 | var success = await _client.CancelStart();
270 | if (success)
271 | {
272 | lblStatus.Content = "Stopped Successfully";
273 | }
274 | else
275 | {
276 | lblStatus.Content = "Stopping Failed";
277 | }
278 | grpActions.IsEnabled = true;
279 | btnLogin.IsEnabled = true;
280 | }
281 |
282 | private async void BtnAlert_Click(object sender, RoutedEventArgs e)
283 | {
284 | if (!await HandleUpgrade()) return;
285 | grpActions.IsEnabled = false;
286 | btnLogin.IsEnabled = false;
287 | lblStatus.Content = "Alarming (Please wait)";
288 | var success = await _client.Alert();
289 | if (success)
290 | {
291 | lblStatus.Content = "Alarmed Successfully";
292 | }
293 | else
294 | {
295 | lblStatus.Content = "Alarming Failed";
296 | }
297 | grpActions.IsEnabled = true;
298 | btnLogin.IsEnabled = true;
299 | }
300 |
301 | private async void BtnCancelAlert_Click(object sender, RoutedEventArgs e)
302 | {
303 | if (!await HandleUpgrade()) return;
304 | grpActions.IsEnabled = false;
305 | btnLogin.IsEnabled = false;
306 | lblStatus.Content = "Stopping Alarm (Please wait)";
307 | var success = await _client.CancelAlert();
308 | if (success)
309 | {
310 | lblStatus.Content = "Alarmed Stopped Successfully";
311 | }
312 | else
313 | {
314 | lblStatus.Content = "Alarm Stopping Failed";
315 | }
316 | grpActions.IsEnabled = true;
317 | btnLogin.IsEnabled = true;
318 | }
319 |
320 | private async void Window_Loaded(object sender, RoutedEventArgs e)
321 | {
322 | if (await _client.RefreshToken())
323 | {
324 | await LoadVehicles();
325 | lblStatus.Content = "Connected";
326 | grpActions.IsEnabled = true;
327 | btnLogin.IsEnabled = false;
328 | }
329 |
330 | }
331 |
332 | private void CmbVehicle_SelectionChanged(object sender, SelectionChangedEventArgs e)
333 | {
334 | if (_vehicles == null || _vehicles.Length == 0 || cmbVehicle.SelectedIndex < 0)
335 | {
336 | _client.ActiveVehicle = null;
337 | return;
338 | }
339 |
340 | _client.ActiveVehicle = _vehicles[cmbVehicle.SelectedIndex];
341 |
342 | Properties.Settings.Default.Vin = _client.ActiveVehicle.Vin;
343 | Properties.Settings.Default.Save();
344 |
345 | //todo: populate available actions
346 | //todo: update client state instead of local variable?
347 |
348 | }
349 |
350 | private async void BtnDiagnostics_Click(object sender, RoutedEventArgs e)
351 | {
352 | if (!await HandleUpgrade()) return;
353 | grpActions.IsEnabled = false;
354 | btnLogin.IsEnabled = false;
355 | lblStatus.Content = "Getting Diagnostics (Please Wait)...";
356 | var details = await _client.GetDiagnostics();
357 | txtOutput.Text = JsonConvert.SerializeObject(details, Formatting.Indented);
358 | lblStatus.Content = "Getting Diagnostics Complete";
359 | grpActions.IsEnabled = true;
360 | btnLogin.IsEnabled = true;
361 | }
362 | }
363 | }
364 |
--------------------------------------------------------------------------------
/GM.WindowsUI/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("GM.WindowsUI")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("j8byz4by")]
14 | [assembly: AssemblyProduct("GM.WindowsUI")]
15 | [assembly: AssemblyCopyright("Copyright © 2019")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/GM.WindowsUI/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace GM.WindowsUI.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GM.WindowsUI.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/GM.WindowsUI/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/GM.WindowsUI/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace GM.WindowsUI.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.2.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 |
26 | [global::System.Configuration.UserScopedSettingAttribute()]
27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
28 | [global::System.Configuration.DefaultSettingValueAttribute("")]
29 | public string Brand {
30 | get {
31 | return ((string)(this["Brand"]));
32 | }
33 | set {
34 | this["Brand"] = value;
35 | }
36 | }
37 |
38 | [global::System.Configuration.UserScopedSettingAttribute()]
39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
40 | [global::System.Configuration.DefaultSettingValueAttribute("")]
41 | public string DeviceId {
42 | get {
43 | return ((string)(this["DeviceId"]));
44 | }
45 | set {
46 | this["DeviceId"] = value;
47 | }
48 | }
49 |
50 | [global::System.Configuration.UserScopedSettingAttribute()]
51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
52 | [global::System.Configuration.DefaultSettingValueAttribute("")]
53 | public string VehicleCache {
54 | get {
55 | return ((string)(this["VehicleCache"]));
56 | }
57 | set {
58 | this["VehicleCache"] = value;
59 | }
60 | }
61 |
62 | [global::System.Configuration.UserScopedSettingAttribute()]
63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
64 | [global::System.Configuration.DefaultSettingValueAttribute("")]
65 | public string LoginData {
66 | get {
67 | return ((string)(this["LoginData"]));
68 | }
69 | set {
70 | this["LoginData"] = value;
71 | }
72 | }
73 |
74 | [global::System.Configuration.UserScopedSettingAttribute()]
75 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
76 | [global::System.Configuration.DefaultSettingValueAttribute("")]
77 | public string Vin {
78 | get {
79 | return ((string)(this["Vin"]));
80 | }
81 | set {
82 | this["Vin"] = value;
83 | }
84 | }
85 |
86 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
87 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
88 | [global::System.Configuration.DefaultSettingValueAttribute("https://gmsigner.herokuapp.com/")]
89 | public string TokenSignerUrl {
90 | get {
91 | return ((string)(this["TokenSignerUrl"]));
92 | }
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/GM.WindowsUI/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | https://gmsigner.herokuapp.com/
22 |
23 |
24 |
--------------------------------------------------------------------------------
/GM.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29123.88
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GM.Api", "GM.Api\GM.Api.csproj", "{95BF6A76-8E35-41DD-BE3D-90A41014F1AF}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GM.WindowsUI", "GM.WindowsUI\GM.WindowsUI.csproj", "{40A84AE5-3E7F-4838-A8E8-E33D772B1D8A}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8DADF6E3-2511-4EE4-849C-CC71C89CBD7E}"
11 | ProjectSection(SolutionItems) = preProject
12 | README.md = README.md
13 | EndProjectSection
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {95BF6A76-8E35-41DD-BE3D-90A41014F1AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {95BF6A76-8E35-41DD-BE3D-90A41014F1AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {95BF6A76-8E35-41DD-BE3D-90A41014F1AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {95BF6A76-8E35-41DD-BE3D-90A41014F1AF}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {40A84AE5-3E7F-4838-A8E8-E33D772B1D8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {40A84AE5-3E7F-4838-A8E8-E33D772B1D8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {40A84AE5-3E7F-4838-A8E8-E33D772B1D8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {40A84AE5-3E7F-4838-A8E8-E33D772B1D8A}.Release|Any CPU.Build.0 = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(SolutionProperties) = preSolution
31 | HideSolutionNode = FALSE
32 | EndGlobalSection
33 | GlobalSection(ExtensibilityGlobals) = postSolution
34 | SolutionGuid = {1612C7A6-1844-45C0-9EB3-6CDB436BC481}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GM-Vehicle-API
2 | Remote API for supported General Motors vehicles in C#, .NET Standard
3 | Includes a WPF demo app.
4 |
5 | This is the API used by the myChevrolet, myBuick, myGMC, myCadillac, My Vauxhall and myOpel apps.
6 |
7 | Reverse engineered C# API for accessing General Motors vehicles supporting OnStar connected services.
8 | Obviously this is unsanctioned. Use at your own risk.
9 |
10 | GM announced that they would be releasing the API in 2013. 6 years later they still refuse to respond to developer requests.
11 | The API has been reverse engineered via decompilation and protocol interception in order to facilitate interoperability. No copywritten works have been duplicated.
12 |
13 | You are accepting all responsibility and liability for the use of this content.
14 |
15 | # Client Credentials
16 | To use this API you will require a valid client id and client secret. The correct approach would be to request access from GM at https://developer.gm.com/ or by emailing them at developer.gm.com.
17 |
18 | I have a utility that is capable of extracting the client credentials from the Android APK but I am _very_ hesitant to share. The client secrets get out and GM will freak out. So...
19 |
20 | I have implemented a very small, very simple web service hosted with heroku (https://gmsigner.herokuapp.com/) that will sign token requests for you,
21 | and I have implemented a version of the client that uses this service.
22 | (Please note that you will be sending your login credentials to this service. I would highly advise reviewing the service source code here: https://github.com/q39JzrRa/GM-Vehicle-API-AuthUtil . It is deployed to Heroku using CD from the master branch so the code you see is the code that runs)
23 |
24 | Quick note: I have purged the extraction code from the repo. Sorry about that :-P
25 |
26 |
27 | # Quick Start
28 | If you prefer not to use the Windows UI or examine how it works, here is how you might start your car if you only have one.
29 |
30 | ```
31 | // generate Device ID (GUID formatted as a string)
32 |
33 | var client = new GMClientNoKey("{deviceId}", Brand.Chevrolet, "https://gmsigner.herokuapp.com/");
34 | if (!await _client.Login("{ your username}", "{your password}")) { throw new InvalidOperationException("Login Failed"); }
35 | var vehicles = await _client.GetVehicles();
36 | if (vehicles == null || !vehicles.Any()) { throw new InvalidOperationException("No Vehicles on acccount"); }
37 | client.ActiveVehicle = vehicles.FirstOrDefault();
38 | if (!await client.UpgradeLogin("{Your OnStar PIN}")) { throw new InvalidOperationException("Login upgrade failed"); }
39 | if (!await client.Start()) { throw new InvalidOperationException("Start failed"); }
40 | Console.WriteLine("Start Success");
41 | ```
42 |
43 |
44 | # Implemented Functionality
45 | * Login
46 | * Elevate credentials
47 | * Enumerate Vehicles and vehicle capabilities
48 | * Remote Lock and Unlock
49 | * Remote Start and Stop
50 | * Remote Alarm
51 | * Get Diagnostics (including charge status and charge level)
52 | * Get vehicle location
53 | * Send turn-by-turn route
54 |
55 | # References
56 |
57 | PYTHON Project doing something similar: https://github.com/sdague/mychevy
58 |
59 |
60 | # TODO
61 | This is very early, unpolished, incomplete code. No judgement please.
62 |
63 | * Implement more commands
64 | * consider using MS JWT implementation
65 | * Implement secure means of saving onstar pin. If possible.
66 | * recognize response from calling priv'd command without upgrade and trigger upgrade using saved pin.
67 |
68 | 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.
69 |
70 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------