Skip to content
This repository has been archived by the owner on Sep 19, 2022. It is now read-only.

Commit

Permalink
Initial push for Pint support #6
Browse files Browse the repository at this point in the history
  • Loading branch information
COM8 committed Dec 27, 2019
1 parent 9775d59 commit 74ffa08
Show file tree
Hide file tree
Showing 11 changed files with 526 additions and 118 deletions.
17 changes: 9 additions & 8 deletions Component_Tests/Classes/TestOnewheelHandshake.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OnewheelBluetooth.Classes;
using OnewheelBluetooth.Classes.UnlockHelper;
using System.Linq;

namespace Component_Tests.Classes
Expand All @@ -11,10 +12,10 @@ public class TestOnewheelHandshake
[TestMethod]
public void Test_Handshake1()
{
OnewheelUnlockHelper unlockHelper = new OnewheelUnlockHelper(null);
DefaultGeminiUnlock unlock = new DefaultGeminiUnlock();
byte[] inData = Utils.HexStringToByteArray("43:52:58:7f:9e:5c:14:df:42:e2:62:82:62:62:62:62:62:77:f6:9c".Replace(":", ""));
byte[] outRefData = Utils.HexStringToByteArray("43:52:58:d8:82:11:d1:26:96:5f:9f:aa:72:fc:de:92:f3:25:3d:20".Replace(":", ""));
byte[] outData = unlockHelper.CalcResponse(inData);
byte[] outData = unlock.CalcResponse(inData);

Assert.IsTrue(outData.Length == 20);
Assert.IsTrue(outData.SequenceEqual(outRefData));
Expand All @@ -23,10 +24,10 @@ public void Test_Handshake1()
[TestMethod]
public void Test_Handshake2()
{
OnewheelUnlockHelper unlockHelper = new OnewheelUnlockHelper(null);
DefaultGeminiUnlock unlock = new DefaultGeminiUnlock();
byte[] inData = Utils.HexStringToByteArray("43:52:58:7f:8e:0c:4c:17:7a:22:a2:b2:e2:e2:e2:e2:e2:f8:77:ca".Replace(":", ""));
byte[] outRefData = Utils.HexStringToByteArray("43:52:58:4a:8d:4c:93:ca:9c:75:bc:ba:73:87:53:e9:10:4b:49:28".Replace(":", ""));
byte[] outData = unlockHelper.CalcResponse(inData);
byte[] outData = unlock.CalcResponse(inData);

Assert.IsTrue(outData.Length == 20);
Assert.IsTrue(outData.SequenceEqual(outRefData));
Expand All @@ -35,10 +36,10 @@ public void Test_Handshake2()
[TestMethod]
public void Test_Handshake3()
{
OnewheelUnlockHelper unlockHelper = new OnewheelUnlockHelper(null);
DefaultGeminiUnlock unlock = new DefaultGeminiUnlock();
byte[] inData = Utils.HexStringToByteArray("43:52:58:be:3c:45:5d:2d:90:38:f8:78:38:38:38:38:38:4e:0c:ac".Replace(":", ""));
byte[] outRefData = Utils.HexStringToByteArray("43:52:58:c8:4b:77:d2:1d:fa:5c:a1:ab:7e:ee:1f:c8:2f:fa:19:55".Replace(":", ""));
byte[] outData = unlockHelper.CalcResponse(inData);
byte[] outData = unlock.CalcResponse(inData);

Assert.IsTrue(outData.Length == 20);
Assert.IsTrue(outData.SequenceEqual(outRefData));
Expand All @@ -47,10 +48,10 @@ public void Test_Handshake3()
[TestMethod]
public void Test_Handshake4()
{
OnewheelUnlockHelper unlockHelper = new OnewheelUnlockHelper(null);
DefaultGeminiUnlock unlock = new DefaultGeminiUnlock();
byte[] inData = Utils.HexStringToByteArray("43:52:58:ff:fe:cb:12:dd:3f:b7:b7:b7:57:57:57:57:57:6c:6b:94".Replace(":", ""));
byte[] outRefData = Utils.HexStringToByteArray("43:52:58:bd:26:ed:86:75:c3:be:b7:ab:7f:78:8c:0b:b6:3c:85:22".Replace(":", ""));
byte[] outData = unlockHelper.CalcResponse(inData);
byte[] outData = unlock.CalcResponse(inData);

Assert.IsTrue(outData.Length == 20);
Assert.IsTrue(outData.SequenceEqual(outRefData));
Expand Down
46 changes: 46 additions & 0 deletions DataManager/Classes/OnewheelApiCredentials.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
namespace DataManager.Classes
{
public class OnewheelApiCredentials
{
//--------------------------------------------------------Attributes:-----------------------------------------------------------------\\
#region --Attributes--
public string DEVICE_NAME;
public string apiKey;
public string apiToken;

#endregion
//--------------------------------------------------------Constructor:----------------------------------------------------------------\\
#region --Constructors--
public OnewheelApiCredentials(string deviceName)
{
DEVICE_NAME = deviceName;
}

#endregion
//--------------------------------------------------------Set-, Get- Methods:---------------------------------------------------------\\
#region --Set-, Get- Methods--


#endregion
//--------------------------------------------------------Misc Methods:---------------------------------------------------------------\\
#region --Misc Methods (Public)--


#endregion

#region --Misc Methods (Private)--


#endregion

#region --Misc Methods (Protected)--


#endregion
//--------------------------------------------------------Events:---------------------------------------------------------------------\\
#region --Events--


#endregion
}
}
87 changes: 87 additions & 0 deletions DataManager/Classes/Vault.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using Logging;
using System;
using System.Collections.Generic;
using Windows.Security.Credentials;

namespace DataManager.Classes
{
public static class Vault
{
//--------------------------------------------------------Attributes:-----------------------------------------------------------------\\
#region --Attributes--
private static readonly PasswordVault PASSWORD_VAULT = new PasswordVault();

#endregion
//--------------------------------------------------------Constructor:----------------------------------------------------------------\\
#region --Constructors--


#endregion
//--------------------------------------------------------Set-, Get- Methods:---------------------------------------------------------\\
#region --Set-, Get- Methods--


#endregion
//--------------------------------------------------------Misc Methods:---------------------------------------------------------------\\
#region --Misc Methods (Public)--
public static bool LoadCredentials(OnewheelApiCredentials credentials)
{
try
{
IReadOnlyList<PasswordCredential> pwCredentials = PASSWORD_VAULT.FindAllByResource(credentials.DEVICE_NAME);
if (pwCredentials.Count >= 1)
{
pwCredentials[0].RetrievePassword();
credentials.apiKey = pwCredentials[0].UserName;
credentials.apiToken = pwCredentials[0].Password;
return !string.IsNullOrEmpty(credentials.apiKey) && !string.IsNullOrEmpty(credentials.apiToken);
}
}
catch (Exception e)
{
Logger.Error("Failed to retrieve credentials for: " + credentials.DEVICE_NAME, e);
}
return false;
}

public static void StoreCredentials(OnewheelApiCredentials credentials)
{
// Delete existing password vaults:
DeleteAllVaults();

// Store the new password:
if (!string.IsNullOrEmpty(credentials.apiToken))
{
PASSWORD_VAULT.Add(new PasswordCredential(credentials.DEVICE_NAME, credentials.apiKey, credentials.apiToken));
}
}

/// <summary>
/// Deletes all vaults.
/// </summary>
public static void DeleteAllVaults()
{
foreach (PasswordCredential item in PASSWORD_VAULT.RetrieveAll())
{
PASSWORD_VAULT.Remove(item);
}
}

#endregion

#region --Misc Methods (Private)--


#endregion

#region --Misc Methods (Protected)--


#endregion
//--------------------------------------------------------Events:---------------------------------------------------------------------\\
#region --Events--


#endregion
}
}
2 changes: 2 additions & 0 deletions DataManager/DataManager.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@
<Compile Include="Classes\DBTables\BatteryTable.cs" />
<Compile Include="Classes\DBTables\DBTableConsts.cs" />
<Compile Include="Classes\DBTables\SpeedTable.cs" />
<Compile Include="Classes\OnewheelApiCredentials.cs" />
<Compile Include="Classes\Settings.cs" />
<Compile Include="Classes\SettingsConsts.cs" />
<Compile Include="Classes\Vault.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
9 changes: 9 additions & 0 deletions OnewheelBluetooth/Classes/OnewheelBoard.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Logging;
using OnewheelBluetooth.Classes.UnlockHelper;
using System;
using System.Collections.Generic;
using System.Text;
Expand Down Expand Up @@ -105,6 +106,14 @@ public async Task<GattWriteResult> WriteShortAsync(Guid uuid, short data)
return await WriteBytesAsync(uuid, dataArr);
}

public async Task<GattWriteResult> WriteUShortAsync(Guid uuid, ushort data)
{
byte[] dataArr = BitConverter.GetBytes(data);
Utils.ReverseByteOrderIfNeeded(dataArr);

return await WriteBytesAsync(uuid, dataArr);
}

public async Task<GattWriteResult> WriteStringAsync(Guid uuid, string data)
{
return await WriteBytesAsync(uuid, Encoding.ASCII.GetBytes(data));
Expand Down
3 changes: 2 additions & 1 deletion OnewheelBluetooth/Classes/OnewheelType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public enum OnewheelType
{
ONEWHEEL = 0,
ONEWHEEL_PLUS = 1,
ONEWHEEL_PLUS_XR = 2
ONEWHEEL_PLUS_XR = 2,
ONEWHEEL_PINT = 3
}
}
54 changes: 54 additions & 0 deletions OnewheelBluetooth/Classes/UnlockHelper/AbstractOnewheelUnlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace OnewheelBluetooth.Classes.UnlockHelper
{
public abstract class AbstractOnewheelUnlock
{
//--------------------------------------------------------Attributes:-----------------------------------------------------------------\\
#region --Attributes--
/// <summary>
/// The first three bytes of a challenge message from the Onewheel.
/// </summary>
protected readonly byte[] CHALLENGE_FIRST_BYTES = new byte[] { 0x43, 0x52, 0x58 };

#endregion
//--------------------------------------------------------Constructor:----------------------------------------------------------------\\
#region --Constructors--


#endregion
//--------------------------------------------------------Set-, Get- Methods:---------------------------------------------------------\\
#region --Set-, Get- Methods--


#endregion
//--------------------------------------------------------Misc Methods:---------------------------------------------------------------\\
#region --Misc Methods (Public)--
public abstract Task CalcAndSendResponseAsync(List<byte> serialReadCache, OnewheelBoard onewheel);

public bool CheckIfFirstChallengeBytesMatch(List<byte> serialReadCache)
{
return serialReadCache.Count >= 3
&& serialReadCache[0] == CHALLENGE_FIRST_BYTES[0]
&& serialReadCache[1] == CHALLENGE_FIRST_BYTES[1]
&& serialReadCache[2] == CHALLENGE_FIRST_BYTES[2];
}
#endregion

#region --Misc Methods (Private)--


#endregion

#region --Misc Methods (Protected)--


#endregion
//--------------------------------------------------------Events:---------------------------------------------------------------------\\
#region --Events--


#endregion
}
}
89 changes: 89 additions & 0 deletions OnewheelBluetooth/Classes/UnlockHelper/DefaultGeminiUnlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using Logging;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Threading.Tasks;

namespace OnewheelBluetooth.Classes.UnlockHelper
{
public class DefaultGeminiUnlock : AbstractOnewheelUnlock
{
//--------------------------------------------------------Attributes:-----------------------------------------------------------------\\
#region --Attributes--
/// <summary>
/// The android challenge response password.
/// Source: https://github.com/ponewheel/android-ponewheel/issues/86#issuecomment-440809066
/// </summary>
private readonly byte[] CHALLENGE_RESPONSE_PASSWORD = new byte[] { 0xD9, 0x25, 0x5F, 0x0F, 0x23, 0x35, 0x4E, 0x19, 0xBA, 0x73, 0x9C, 0xCD, 0xC4, 0xA9, 0x17, 0x65 };

#endregion
//--------------------------------------------------------Constructor:----------------------------------------------------------------\\
#region --Constructors--


#endregion
//--------------------------------------------------------Set-, Get- Methods:---------------------------------------------------------\\
#region --Set-, Get- Methods--


#endregion
//--------------------------------------------------------Misc Methods:---------------------------------------------------------------\\
#region --Misc Methods (Public)--
public override async Task CalcAndSendResponseAsync(List<byte> serialReadCache, OnewheelBoard onewheel)
{
byte[] challenge = serialReadCache.ToArray();
byte[] response = CalcResponse(challenge);

await onewheel.WriteBytesAsync(OnewheelCharacteristicsCache.CHARACTERISTIC_UART_SERIAL_WRITE, response);
Logger.Info("Sent Gemini unlock response to Onewheel challenge.");
}

/// <summary>
/// Calculates the response for the given challenge.
/// Source: https://github.com/ponewheel/android-ponewheel/issues/86#issuecomment-440809066
/// </summary>
/// <param name="challenge">The challenge send by the Onewheel.</param>
/// <returns>The response for the given challenge.</returns>
public byte[] CalcResponse(byte[] challenge)
{
List<byte> response = new List<byte>(20);
response.AddRange(CHALLENGE_FIRST_BYTES);

byte[] md5In = new byte[16 + CHALLENGE_RESPONSE_PASSWORD.Length];
Buffer.BlockCopy(challenge, 3, md5In, 0, 16);
Buffer.BlockCopy(CHALLENGE_RESPONSE_PASSWORD, 0, md5In, 16, CHALLENGE_RESPONSE_PASSWORD.Length);

MD5 md5 = MD5.Create();
byte[] md5Out = md5.ComputeHash(md5In);
response.AddRange(md5Out);

// Calculate the validation byte:
byte checkByte = 0;
for (int i = 0; i < response.Count; i++)
{
checkByte = ((byte)(response[i] ^ checkByte));
}
response.Add(checkByte);

return response.ToArray();
}

#endregion

#region --Misc Methods (Private)--


#endregion

#region --Misc Methods (Protected)--


#endregion
//--------------------------------------------------------Events:---------------------------------------------------------------------\\
#region --Events--



#endregion
}
}
Loading

0 comments on commit 74ffa08

Please sign in to comment.