diff --git a/HapSharp.Core.Example/HapSharp.Core.Example.csproj b/HapSharp.Core.Example/HapSharp.Core.Example.csproj new file mode 100644 index 0000000..d17a820 --- /dev/null +++ b/HapSharp.Core.Example/HapSharp.Core.Example.csproj @@ -0,0 +1,43 @@ + + + + Debug + AnyCPU + {3C9CBFBB-1DE6-4599-A293-C0CC7DAAA852} + Exe + HapSharp.Core.Example + HapSharp.Core.Example + v4.7 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + + + true + bin\Release + prompt + 4 + true + + + + + + + + + + + {24CB3D20-D49D-4361-8B78-92726D8477BD} + HapSharp.Core + + + + \ No newline at end of file diff --git a/HapSharp.Core.Example/Program.cs b/HapSharp.Core.Example/Program.cs new file mode 100755 index 0000000..06402a3 --- /dev/null +++ b/HapSharp.Core.Example/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace HapSharp.Core.Example +{ + class MainClass + { + public static void Main (string[] args) + { + Console.WriteLine ("Hello World!"); + } + } +} diff --git a/HapSharp.Core.Example/Properties/AssemblyInfo.cs b/HapSharp.Core.Example/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..68641e8 --- /dev/null +++ b/HapSharp.Core.Example/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("HapSharp.Core.Example")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("${AuthorCopyright}")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion ("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/HapSharp.Core.Tests/Chacha20poly1305_Tests.cs b/HapSharp.Core.Tests/Chacha20poly1305_Tests.cs new file mode 100644 index 0000000..673d7a0 --- /dev/null +++ b/HapSharp.Core.Tests/Chacha20poly1305_Tests.cs @@ -0,0 +1,39 @@ +using NUnit.Framework; + +namespace HapSharp.Core.Tests +{ + class Chacha20poly1305_Tests + { + [Test ()] + public void load32_test () + { + var test = new int[16]; + test[1] = 1; + test[2] = 2; + test[3] = 2; + var result = Chacha20poly1305.load32 (test, 0); + Assert.AreEqual (33685760, result); + } + + [Test ()] + public void store32_test () + { + var test = new int[16]; + test[1] = 1; + test[2] = 2; + test[3] = 2; + + Chacha20poly1305.store32 (test, 0, 2); + Assert.AreEqual (2, test[0]); + Assert.AreEqual (0, test[1]); + Assert.AreEqual (0, test[2]); + } + + [Test ()] + public void plus_test () + { + var result = Chacha20poly1305.plus (5, 6); + Assert.AreEqual (11, result); + } + } +} diff --git a/HapSharp.Core.Tests/HapSharp.Core.Tests.csproj b/HapSharp.Core.Tests/HapSharp.Core.Tests.csproj new file mode 100644 index 0000000..bbed39b --- /dev/null +++ b/HapSharp.Core.Tests/HapSharp.Core.Tests.csproj @@ -0,0 +1,48 @@ + + + + Debug + AnyCPU + {7FDBCF30-37C2-43A4-9B0A-6EBE5E340D14} + Library + HapSharp.Core.Tests + HapSharp.Core.Tests + v4.7 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + + + true + bin\Release + prompt + 4 + + + + + ..\packages\NUnit.2.6.4\lib\nunit.framework.dll + + + + + + + + + + + + + {24CB3D20-D49D-4361-8B78-92726D8477BD} + HapSharp.Core + + + + \ No newline at end of file diff --git a/HapSharp.Core.Tests/Test.cs b/HapSharp.Core.Tests/Test.cs new file mode 100755 index 0000000..88016d3 --- /dev/null +++ b/HapSharp.Core.Tests/Test.cs @@ -0,0 +1,48 @@ +using NUnit.Framework; +using System; +namespace HapSharp.Core.Tests +{ + [TestFixture ()] + public class AccessoryTests + { + readonly Service service; + + [Test ()] + public void Bridge_CheckInitialValues () + { + var bridgeGuid = Guid.NewGuid (); + var bridge = new Bridge ("bridge", bridgeGuid); + Assert.IsTrue (bridge._isBridge); + } + + [Test ()] + public void Accessory_CheckInitialValues () + { + string accessoryName = "test"; + var accessoryGuid = Guid.NewGuid (); + + var accessory = new Accessory (accessoryName, accessoryGuid); + Assert.AreEqual (accessoryGuid, accessory.UUID); + Assert.AreEqual (AccessoryCategory.OTHER, accessory.Category); + Assert.AreEqual (0, accessory.BridgedAccessories.Count); + + Assert.IsFalse (accessory._isBridge); + + Assert.AreEqual (5, accessory.serviceCharacteristic.Count); + Assert.IsTrue (accessory.serviceCharacteristic.TryGetValue (ServiceCharacteristicType.Manufacturer, out string manufacturer)); + Assert.AreEqual ("Default-Manufacturer", manufacturer); + + Assert.IsTrue (accessory.serviceCharacteristic.TryGetValue (ServiceCharacteristicType.Name, out string displayName)); + Assert.AreEqual (accessoryName, displayName); + + Assert.IsTrue (accessory.serviceCharacteristic.TryGetValue (ServiceCharacteristicType.Model, out string model)); + Assert.AreEqual ("Default-Model", model); + + Assert.IsTrue (accessory.serviceCharacteristic.TryGetValue (ServiceCharacteristicType.SerialNumber, out string serial)); + Assert.AreEqual ("Default-SerialNumber", serial); + + Assert.IsTrue (accessory.serviceCharacteristic.TryGetValue (ServiceCharacteristicType.FirmwareRevision, out string firmwareRevision)); + Assert.AreEqual ("1.0", firmwareRevision); + } + } +} diff --git a/HapSharp.Core.Tests/UUID_Tests.cs b/HapSharp.Core.Tests/UUID_Tests.cs new file mode 100644 index 0000000..a873e40 --- /dev/null +++ b/HapSharp.Core.Tests/UUID_Tests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using System; +namespace HapSharp.Core.Tests +{ + [TestFixture ()] + class UUID_Tests + { + [Test ()] + public void Bridge_CheckInitialValues () + { + var bridgeGuid = Guid.NewGuid (); + var bridge = new Bridge ("bridge", bridgeGuid); + Assert.IsTrue (bridge._isBridge); + } + } +} diff --git a/HapSharp.Core.Tests/packages.config b/HapSharp.Core.Tests/packages.config new file mode 100755 index 0000000..01c187c --- /dev/null +++ b/HapSharp.Core.Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/HapSharp.Core.sln b/HapSharp.Core.sln new file mode 100644 index 0000000..9d970e6 --- /dev/null +++ b/HapSharp.Core.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HapSharp.Core", "HapSharp.Core\HapSharp.Core.csproj", "{24CB3D20-D49D-4361-8B78-92726D8477BD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HapSharp.Core.Example", "HapSharp.Core.Example\HapSharp.Core.Example.csproj", "{3C9CBFBB-1DE6-4599-A293-C0CC7DAAA852}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HapSharp.Core.Tests", "HapSharp.Core.Tests\HapSharp.Core.Tests.csproj", "{7FDBCF30-37C2-43A4-9B0A-6EBE5E340D14}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {24CB3D20-D49D-4361-8B78-92726D8477BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24CB3D20-D49D-4361-8B78-92726D8477BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24CB3D20-D49D-4361-8B78-92726D8477BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24CB3D20-D49D-4361-8B78-92726D8477BD}.Release|Any CPU.Build.0 = Release|Any CPU + {3C9CBFBB-1DE6-4599-A293-C0CC7DAAA852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C9CBFBB-1DE6-4599-A293-C0CC7DAAA852}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C9CBFBB-1DE6-4599-A293-C0CC7DAAA852}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C9CBFBB-1DE6-4599-A293-C0CC7DAAA852}.Release|Any CPU.Build.0 = Release|Any CPU + {7FDBCF30-37C2-43A4-9B0A-6EBE5E340D14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FDBCF30-37C2-43A4-9B0A-6EBE5E340D14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FDBCF30-37C2-43A4-9B0A-6EBE5E340D14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FDBCF30-37C2-43A4-9B0A-6EBE5E340D14}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/HapSharp.Core/Accessory.cs b/HapSharp.Core/Accessory.cs new file mode 100755 index 0000000..44a6725 --- /dev/null +++ b/HapSharp.Core/Accessory.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace HapSharp.Core +{ + public enum AccessoryCategory + { + OTHER = 1, + BRIDGE = 2, + FAN = 3, + GARAGE_DOOR_OPENER = 4, + LIGHTBULB = 5, + DOOR_LOCK = 6, + OUTLET = 7, + SWITCH = 8, + THERMOSTAT = 9, + SENSOR = 10, + ALARM_SYSTEM = 11, + SECURITY_SYSTEM = 11, //Added to conform to HAP naming + DOOR = 12, + WINDOW = 13, + WINDOW_COVERING = 14, + PROGRAMMABLE_SWITCH = 15, + RANGE_EXTENDER = 16, + CAMERA = 17, + IP_CAMERA = 17, //Added to conform to HAP naming + VIDEO_DOORBELL = 18, + AIR_PURIFIER = 19, + AIR_HEATER = 20, //Not in HAP Spec + AIR_CONDITIONER = 21, //Not in HAP Spec + AIR_HUMIDIFIER = 22, //Not in HAP Spec + AIR_DEHUMIDIFIER = 23, // Not in HAP Spec + APPLE_TV = 24, + SPEAKER = 26, + AIRPORT = 27, + SPRINKLER = 28, + FAUCET = 29, + SHOWER_HEAD = 30 + } + + public class Accessory + { + object aid; + internal bool _isBridge; + bool bridged; + bool reachable; + bool shouldPurgeUnusedIDs; + + public string DisplayName { get; protected set; } + public Guid UUID { get; private set; } + + public ServiceType ServiceType { get; set; } + public AccessoryCategory Category { get; private set; } + + // If we are a Bridge, these are the Accessories we are bridging + readonly public List BridgedAccessories = new List (); + readonly public List Services = new List (); + internal Dictionary serviceCharacteristic = new Dictionary(); + + public virtual void Identify () + { + + } + + public Accessory (string displayName, Guid UUID) + { + if (displayName == null) + throw new Exception ("Accessories must be created with a non-empty displayName."); + if (UUID == null) + throw new Exception ("Accessories must be created with a valid UUID."); + + this.DisplayName = displayName; + this.UUID = UUID; + this.aid = null; // assigned by us in assignIDs() or by a Bridge + this._isBridge = false; // true if we are a Bridge (creating a new instance of the Bridge subclass sets this to true) + this.bridged = false; // true if we are hosted "behind" a Bridge Accessory + this.reachable = true; + this.Category = AccessoryCategory.OTHER; + //this.services = []; // of Service + //this.cameraSource = null; + this.shouldPurgeUnusedIDs = true; // Purge unused ids by default + + // create our initial "Accessory Information" Service that all Accessories are expected to have + serviceCharacteristic.Add (ServiceCharacteristicType.Name, displayName); + serviceCharacteristic.Add (ServiceCharacteristicType.Manufacturer, "Default-Manufacturer"); + serviceCharacteristic.Add (ServiceCharacteristicType.Model, "Default-Model"); + serviceCharacteristic.Add (ServiceCharacteristicType.SerialNumber, "Default-SerialNumber"); + serviceCharacteristic.Add (ServiceCharacteristicType.FirmwareRevision, "1.0"); + + // sign up for when iOS attempts to "set" the Identify characteristic - this means a paired device wishes + } + + + public void OnIdentificationRequest () + { + Console.WriteLine ("Identification request {0}", DisplayName); + // allow implementors to identify this Accessory in whatever way is appropriate, and pass along + // the standard callback for completion. + //this.emit ('identify', paired, callback); + // debug("[%s] Identification request ignored; no listeners to 'identify' event", this.displayName); + //callback (); + } + + public void AddService (Service service) + { + //TODO: we need to check UUID, subtype + if (Services.Contains (service)) { + return; + } + + Services.Add (service); + + if (bridged) { + _updateConfiguration (); + + } else { + this.Emit (MessageType.ServiceConfigurationChange, new Tuple (this, service)); + } + + service.MessageReceived += (s, message) => { + if (message == MessageType.ServiceConfigurationChange) { + if (!this.bridged) { + this._updateConfiguration (); + } else { + this.Emit (MessageType.ServiceConfigurationChange, new Tuple (this, service)); + } + } else if (message == MessageType.CharacteristicChange) { + this.Emit (MessageType.ServiceCharacteristicChange, new Tuple (this, service)); + + // if we're not bridged, when we'll want to process this event through our HAPServer + if (!this.bridged) + this._handleCharacteristicChange (); + } + }; + + // listen for changes in characteristics and bubble them up + + service.MessageReceived += (s, message) => { + if (!this.bridged) { + this._updateConfiguration (); + } else { + this.Emit (MessageType.ServiceConfigurationChange, new Tuple (this, service)); + } + }; + } + + public void UpdateReachability (bool reachable) + { + if (!this.bridged) + throw new Exception ("Cannot update reachability on non-bridged accessory!"); + this.reachable = reachable; + + Console.WriteLine ("Reachability update is no longer being supported."); + } + + public void AddBridgedAccessory (Accessory accessory, bool deferUpdate) + { + if (accessory._isBridge) + throw new Exception ("Cannot Bridge another Bridge!"); + + if (BridgedAccessories.Any (s => s.UUID == accessory.UUID)) { + throw new Exception ("Cannot add a bridged Accessory with the same UUID as another bridged Accessory: " + accessory.UUID); + } + //accessory. + } + + private void _handleCharacteristicChange () + { + throw new NotImplementedException (); + } + + public Service GetService (string name) + { + throw new NotImplementedException (); + } + + public void Emit (MessageType order, object args) + { + throw new NotImplementedException (); + } + + private void _updateConfiguration () + { + throw new NotImplementedException (); + } + } +} diff --git a/HapSharp.Core/Bridge.cs b/HapSharp.Core/Bridge.cs new file mode 100644 index 0000000..239c1e2 --- /dev/null +++ b/HapSharp.Core/Bridge.cs @@ -0,0 +1,13 @@ +using System; +namespace HapSharp.Core +{ + public class Bridge : Accessory + { + readonly public Guid SerialNumber; + + public Bridge (string displayName, Guid serialNumber) : base (displayName, serialNumber) + { + _isBridge = true; + } + } +} diff --git a/HapSharp.Core/Chacha20poly1305.cs b/HapSharp.Core/Chacha20poly1305.cs new file mode 100644 index 0000000..1036500 --- /dev/null +++ b/HapSharp.Core/Chacha20poly1305.cs @@ -0,0 +1,547 @@ +using System; + +namespace HapSharp.Core +{ + public static class BufferShim + { + public static int[] Alloc (int test) { + throw new NotImplementedException (); + } + } + + public class Chacha20poly1305 + { + #region Chacha20 + + public class Chacha20Cxt + { + public int[] input; + public int leftover; + public int[] buffer; + } + + const int Chacha20KeySize = 32; + const int Chacha20NonceSize = 8; + + public Chacha20Cxt Chacha20Ctx () + { + var ctx = new Chacha20Cxt { + input = new int[16], + leftover = 0, + buffer = new int[64] + }; + return ctx; + } + + public static int load32 (int[] x, int i) + { + return x[i] | (x[i + 1] << 8) | (x[i + 2] << 16) | (x[i + 3] << 24); + } + + public static void store32 (int[] x, int i, int u) + { + x[i] = u & 0xff; + u = u.GreaterShiftRightOperator (8); + x[i + 1] = u & 0xff; + u = u.GreaterShiftRightOperator (8); + x[i + 2] = u & 0xff; + u = u.GreaterShiftRightOperator (8); + x[i + 3] = u & 0xff; + } + + public static int plus (int v, int w) + { + return (v + w).GreaterShiftRightOperator (0); + } + + public static int rotl32 (int v, int c) + { + return ((v << c).GreaterShiftRightOperator (0)) | (v.GreaterShiftRightOperator (32 - c)); + } + + public static void quarterRound (int[] x, int a, int b, int c, int d) + { + x[a] = plus (x[a], x[b]); x[d] = rotl32 (x[d] ^ x[a], 16); + x[c] = plus (x[c], x[d]); x[b] = rotl32 (x[b] ^ x[c], 12); + x[a] = plus (x[a], x[b]); x[d] = rotl32 (x[d] ^ x[a], 8); + x[c] = plus (x[c], x[d]); x[b] = rotl32 (x[b] ^ x[c], 7); + } + + public static void chacha20_keysetup (Chacha20Cxt ctx, int[] key) + { + ctx.input[0] = 1634760805; + ctx.input[1] = 857760878; + ctx.input[2] = 2036477234; + ctx.input[3] = 1797285236; + for (var i = 0; i < 8; i++) { + ctx.input[i + 4] = load32 (key, i * 4); + } + } + + public static void chacha20_ivsetup (Chacha20Cxt ctx, int[] iv) + { + ctx.input[12] = 0; + ctx.input[13] = 0; + ctx.input[14] = load32 (iv, 0); + ctx.input[15] = load32 (iv, 4); + } + + public static void chacha20_encrypt (Chacha20Cxt ctx, int[] dst, int[] src, int len) + { + var x = new int[16]; + var buf = new int[64]; + int i = 0, dpos = 0, spos = 0; + + while (len > 0) { + for (i = 16; i != 0; i--) x[i] = ctx.input[i]; + for (i = 20; i > 0; i -= 2) { + quarterRound (x, 0, 4, 8, 12); + quarterRound (x, 1, 5, 9, 13); + quarterRound (x, 2, 6, 10, 14); + quarterRound (x, 3, 7, 11, 15); + quarterRound (x, 0, 5, 10, 15); + quarterRound (x, 1, 6, 11, 12); + quarterRound (x, 2, 7, 8, 13); + quarterRound (x, 3, 4, 9, 14); + } + for (i = 16; i != 0; i--) x[i] += ctx.input[i]; + for (i = 16; i != 0; i--) store32 (buf, 4 * i, x[i]); + + ctx.input[12] = plus (ctx.input[12], 1); + if (ctx.input[12] != 0) { + ctx.input[13] = plus (ctx.input[13], 1); + } + if (len <= 64) { + for (i = len; i != 0; i--) { + dst[i + dpos] = src[i + spos] ^ buf[i]; + } + return; + } + for (i = 64; i != 0; i--) { + dst[i + dpos] = src[i + spos] ^ buf[i]; + } + len -= 64; + spos += 64; + dpos += 64; + } + } + + public static void chacha20_decrypt (Chacha20Cxt ctx, int[] dst, int[] src, int len) + { + chacha20_encrypt (ctx, dst, src, len); + } + + public static int chacha20_update (Chacha20Cxt ctx, int[] dst, int[] src, int inlen) + { + var bytes = 0; + var out_start = 0; + var out_inc = 0; + + if ((ctx.leftover + inlen) >= 64) { + + if (ctx.leftover != 0) { + bytes = 64 - ctx.leftover; + + if (src.Length > 0) { + src.Copy (ctx.buffer, ctx.leftover, 0, bytes); + src = src.Slice (bytes, src.Length); + } + + chacha20_encrypt (ctx, dst, ctx.buffer, 64); + inlen -= bytes; + dst = dst.Slice (64, dst.Length); + out_inc += 64; + ctx.leftover = 0; + } + + bytes = (inlen & (~63)); + if (bytes > 0) { + chacha20_encrypt (ctx, dst, src, bytes); + inlen -= bytes; + src = src.Slice (bytes, src.Length); + dst = dst.Slice (bytes, dst.Length); + out_inc += bytes; + } + + } + + if (inlen > 0) { + if (src.Length > 0) { + src.Copy (ctx.buffer, ctx.leftover, 0, src.Length); + } + //else { + // var zeros = bufferShim.Alloc (inlen); + // zeros.Copy (ctx.buffer, ctx.leftover, 0, inlen); + //} + ctx.leftover += inlen; + } + + return out_inc - out_start; + } + + + public static int chacha20_final (Chacha20Cxt ctx, int[] dst) + { + if (ctx.leftover != 0) { + chacha20_encrypt (ctx, dst, ctx.buffer, 64); + } + + return ctx.leftover; + } + + public static void chacha20_keystream (Chacha20Cxt ctx, int[] dst, int len) + { + for (var i = 0; i < len; ++i) dst[i] = 0; + chacha20_encrypt (ctx, dst, dst, len); + } + #endregion + + #region poly1305 + /* + // Written in 2014 by Devi Mandiri. Public domain. + // + // Implementation derived from poly1305-donna-16.h + // See for details: https://github.com/floodyberry/poly1305-donna + */ + + const int Poly1305KeySize = 32; + const int Poly1305TagSize = 16; + + public Poly1305Ctx CreatePoly1305Ctx () + { + var ctx = new Poly1305Ctx { + buffer = new int[Poly1305TagSize], + leftover = 0, + r = new int[10], + h = new int[10], + pad = new int[8], + finished = 0 + }; + return ctx; + } + + public class Poly1305Ctx + { + public int[] r; + public int[] h; + public int[] pad; + public int finished; + public int leftover; + public int[] buffer; + } + + public static int U8TO16 (int[] p, int pos) + { + return ((p[pos] & 0xff) & 0xffff) | (((p[pos + 1] & 0xff) & 0xffff) << 8); + } + + public static void U16TO8 (int[] p, int pos, int v) + { + p[pos] = (v) & 0xff; + p[pos + 1] = (v.GreaterShiftRightOperator (8)) & 0xff; + } + + public static void poly1305_init (Poly1305Ctx ctx, int[] key) + { + int[] t = new int[1000]; + int i = 0; + + for (i = 8; i != 0; i--) t[i] = U8TO16 (key, i * 2); + + ctx.r[0] = t[0] & 0x1fff; + ctx.r[1] = ((t[0].GreaterShiftRightOperator (13)) | (t[1] << 3)) & 0x1fff; + ctx.r[2] = ((t[1].GreaterShiftRightOperator (10)) | (t[2] << 6)) & 0x1f03; + ctx.r[3] = ((t[2].GreaterShiftRightOperator (7)) | (t[3] << 9)) & 0x1fff; + ctx.r[4] = ((t[3].GreaterShiftRightOperator (4)) | (t[4] << 12)) & 0x00ff; + ctx.r[5] = (t[4].GreaterShiftRightOperator (1)) & 0x1ffe; + ctx.r[6] = ((t[4].GreaterShiftRightOperator (14)) | (t[5] << 2)) & 0x1fff; + ctx.r[7] = ((t[5].GreaterShiftRightOperator (11)) | (t[6] << 5)) & 0x1f81; + ctx.r[8] = ((t[6].GreaterShiftRightOperator (8)) | (t[7] << 8)) & 0x1fff; + ctx.r[9] = (t[7].GreaterShiftRightOperator (5)) & 0x007f; + + for (i = 8; i != 0; i--) { + ctx.h[i] = 0; + ctx.pad[i] = U8TO16 (key, 16 + (2 * i)); + } + ctx.h[8] = 0; + ctx.h[9] = 0; + ctx.leftover = 0; + ctx.finished = 0; + } + + public static void poly1305_blocks (Poly1305Ctx ctx, int[] m, int mpos, int bytes) + { + int hibit = ctx.finished == 0 ? 0 : (1 << 11); + int[] t = new int[1000], d = new int[1000]; + int c = 0, i = 0, j = 0; + + while (bytes >= Poly1305TagSize) { + for (i = 8; i != 0; i--) t[i] = U8TO16 (m, i * 2 + mpos); + + ctx.h[0] += t[0] & 0x1fff; + ctx.h[1] += ((t[0].GreaterShiftRightOperator (13)) | (t[1] << 3)) & 0x1fff; + ctx.h[2] += ((t[1].GreaterShiftRightOperator (10)) | (t[2] << 6)) & 0x1fff; + ctx.h[3] += ((t[2].GreaterShiftRightOperator (7)) | (t[3] << 9)) & 0x1fff; + ctx.h[4] += ((t[3].GreaterShiftRightOperator (4)) | (t[4] << 12)) & 0x1fff; + ctx.h[5] += (t[4].GreaterShiftRightOperator (1)) & 0x1fff; + ctx.h[6] += ((t[4].GreaterShiftRightOperator (14)) | (t[5] << 2)) & 0x1fff; + ctx.h[7] += ((t[5].GreaterShiftRightOperator (11)) | (t[6] << 5)) & 0x1fff; + ctx.h[8] += ((t[6].GreaterShiftRightOperator (8)) | (t[7] << 8)) & 0x1fff; + ctx.h[9] += (t[7].GreaterShiftRightOperator (7)) | hibit; + + for (i = 0, c = 0; i < 10; i++) { + d[i] = c; + for (j = 0; j < 10; j++) { + d[i] += (int)(ctx.h[j] & 0xffffffff) * ((j <= i) ? ctx.r[i - j] : (5 * ctx.r[i + 10 - j])); + if (j == 4) { + c = (d[i].GreaterShiftRightOperator (13)); + d[i] &= 0x1fff; + } + } + c += (d[i].GreaterShiftRightOperator (13)); + d[i] &= 0x1fff; + } + c = ((c << 2) + c); + c += d[0]; + d[0] = ((c & 0xffff) & 0x1fff); + c = (c.GreaterShiftRightOperator (13)); + d[1] += c; + + for (i = 10; i != 0; i--) ctx.h[i] = d[i] & 0xffff; + + mpos += Poly1305TagSize; + bytes -= Poly1305TagSize; + } + } + + public static void poly1305_update (Poly1305Ctx ctx, int[] m, int bytes) + { + int want = 0, i = 0, mpos = 0; + + if (ctx.leftover == 0) { + want = (Poly1305TagSize - ctx.leftover); + if (want > bytes) + want = bytes; + for (i = want; i != 0; i--) { + ctx.buffer[ctx.leftover + i] = m[i + mpos]; + } + bytes -= want; + mpos += want; + ctx.leftover += want; + if (ctx.leftover < Poly1305TagSize) + return; + poly1305_blocks (ctx, ctx.buffer, 0, Poly1305TagSize); + ctx.leftover = 0; + } + + if (bytes >= Poly1305TagSize) { + want = (bytes & ~(Poly1305TagSize - 1)); + poly1305_blocks (ctx, m, mpos, want); + mpos += want; + bytes -= want; + } + + if (bytes == 0) { + for (i = bytes; i != 0; i--) { + ctx.buffer[ctx.leftover + i] = m[i + mpos]; + } + ctx.leftover += bytes; + } + } + + public static void poly1305_finish (Poly1305Ctx ctx, int[] mac) + { + var g = new int[1000]; + int c = 0, mask = 0, f = 0, i = 0; + + if (ctx.leftover == 0) { + i = ctx.leftover; + ctx.buffer[i++] = 1; + for (; i < Poly1305TagSize; i++) { + ctx.buffer[i] = 0; + } + ctx.finished = 1; + poly1305_blocks (ctx, ctx.buffer, 0, Poly1305TagSize); + } + + c = ctx.h[1].GreaterShiftRightOperator (13); + ctx.h[1] &= 0x1fff; + for (i = 2; i < 10; i++) { + ctx.h[i] += c; + c = ctx.h[i].GreaterShiftRightOperator (13); + ctx.h[i] &= 0x1fff; + } + ctx.h[0] += (c * 5); + c = ctx.h[0].GreaterShiftRightOperator (13); + ctx.h[0] &= 0x1fff; + ctx.h[1] += c; + c = ctx.h[1].GreaterShiftRightOperator (13); + ctx.h[1] &= 0x1fff; + ctx.h[2] += c; + + g[0] = ctx.h[0] + 5; + c = g[0].GreaterShiftRightOperator (13); + g[0] &= 0x1fff; + for (i = 1; i < 10; i++) { + g[i] = ctx.h[i] + c; + c = g[i].GreaterShiftRightOperator (13); + g[i] &= 0x1fff; + } + g[9] -= (1 << 13); + g[9] &= 0xffff; + + mask = (g[9].GreaterShiftRightOperator (15)) - 1; + for (i = 10; i != 0; i--) g[i] &= mask; + mask = ~mask; + for (i = 10; i != 0; i--) { + ctx.h[i] = (ctx.h[i] & mask) | g[i]; + } + + ctx.h[0] = ((ctx.h[0]) | (ctx.h[1] << 13)) & 0xffff; + ctx.h[1] = ((ctx.h[1] >> 3) | (ctx.h[2] << 10)) & 0xffff; + ctx.h[2] = ((ctx.h[2] >> 6) | (ctx.h[3] << 7)) & 0xffff; + ctx.h[3] = ((ctx.h[3] >> 9) | (ctx.h[4] << 4)) & 0xffff; + ctx.h[4] = ((ctx.h[4] >> 12) | (ctx.h[5] << 1) | (ctx.h[6] << 14)) & 0xffff; + ctx.h[5] = ((ctx.h[6] >> 2) | (ctx.h[7] << 11)) & 0xffff; + ctx.h[6] = ((ctx.h[7] >> 5) | (ctx.h[8] << 8)) & 0xffff; + ctx.h[7] = ((ctx.h[8] >> 8) | (ctx.h[9] << 5)) & 0xffff; + + f = (int)(ctx.h[0] & 0xffffffff) + ctx.pad[0]; + ctx.h[0] = f & 0xffff; + for (i = 1; i < 8; i++) { + f = ((int)(ctx.h[i] & 0xffffffff)) + ctx.pad[i] + (f.GreaterShiftRightOperator (16)); + ctx.h[i] = f & 0xffff; + } + + for (i = 8; i != 0; i--) { + U16TO8 (mac, i * 2, ctx.h[i]); + ctx.pad[i] = 0; + } + for (i = 10; i != 0; i--) { + ctx.h[i] = 0; + ctx.r[i] = 0; + } + } + + public static void poly1305_auth (int[] mac, int[] m, int bytes, int[] key) + { + var ctx = new Poly1305Ctx (); + poly1305_init (ctx, key); + poly1305_update (ctx, m, bytes); + poly1305_finish (ctx, mac); + } + + public static int poly1305_verify (int[] mac1, int[] mac2) + { + var dif = 0; + for (var i = 0; i < 16; i++) { + dif |= (mac1[i] ^ mac2[i]); + } + dif = (dif - 1).GreaterShiftRightOperator (31); + return (dif & 1); + } + + #endregion + + #region AEAD + + public static AeadCtx CreateAeadCtx (int[] key) + { + var ctx = new AeadCtx (); + ctx.key = key; + return ctx; + } + + public class AeadCtx + { + public int[] key; + } + + public static int[] aead_init (Chacha20Cxt c20ctx, int[] key,int[] nonce) + { + chacha20_keysetup (c20ctx, key); + chacha20_ivsetup (c20ctx, nonce); + + var subkey = new int [64]; + chacha20_keystream (c20ctx, subkey, subkey.Length); + + return subkey.Slice (0, 32); + } + + public static void store64 (int[] dst,int pos,int num) + { + int hi = 0, lo = num.GreaterShiftRightOperator (0); + if ((+(Math.Abs (num))) >= 1) { + if (num > 0) { + var floor = (int) Math.Floor (num / 4294967296f); + var number = (int) Math.Min ((+(floor)), 4294967295); + hi = (number | 0).GreaterShiftRightOperator (0); + } else { + var ceiling = (int) Math.Ceiling ((num - +(((~~(num))).GreaterShiftRightOperator (0))) / 4294967296f); + hi = (~~(+ceiling)).GreaterShiftRightOperator (0); + } + } + dst[pos] = lo & 0xff; lo = lo.GreaterShiftRightOperator (8); + dst[pos + 1] = lo & 0xff; lo = lo.GreaterShiftRightOperator (8); + dst[pos + 2] = lo & 0xff; lo = lo.GreaterShiftRightOperator (8); + dst[pos + 3] = lo & 0xff; + dst[pos + 4] = hi & 0xff; lo = lo.GreaterShiftRightOperator (8); + dst[pos + 5] = hi & 0xff; lo = lo.GreaterShiftRightOperator (8); + dst[pos + 6] = hi & 0xff; lo = lo.GreaterShiftRightOperator (8); + dst[pos + 7] = hi & 0xff; + } + + public static int[] aead_mac (int[] key,int[] ciphertext,int[] data) + { + var clen = ciphertext.Length; + var dlen = data.Length; + var m = new int[clen + dlen + 16]; + var i = dlen; + + for (; i != 0; i--) m[i] = data[i]; + store64 (m, dlen, dlen); + + for (i = clen; i != 0; i--) m[dlen + 8 + i] = ciphertext[i]; + store64 (m, clen + dlen + 8, clen); + + var mac = new int[1000]; + poly1305_auth (mac, m, m.Length, key); + + return mac; + } + + + public static int[] aead_encrypt (AeadCtx ctx,int[] nonce,int[] input,int[] ad) + { + var c = new Chacha20Cxt (); + var key = aead_init (c, ctx.key, nonce); + + var ciphertext = new int [1000]; + chacha20_encrypt (c, ciphertext, input, input.Length); + + var mac = aead_mac (key, ciphertext, ad); + + var outArray = ciphertext.Concat (mac); + //outArray = ciphertext. Array. outArray. concat (ciphertext, mac); + + return outArray; + } + + public static int[] aead_decrypt (AeadCtx ctx,int[] nonce,int[] ciphertext,int[] ad) + { + var c = new Chacha20Cxt (); + var key = aead_init (c, ctx.key, nonce); + var clen = ciphertext.Length - Poly1305TagSize; + var digest = ciphertext.Slice (clen); + var mac = aead_mac (key, ciphertext.Slice (0, clen), ad); + + if (poly1305_verify (digest, mac) != 1) return null; + + var outArray = new int [1000]; + chacha20_decrypt (c, outArray, ciphertext, clen); + return outArray; + } + + #endregion + } +} diff --git a/HapSharp.Core/Characteristic.cs b/HapSharp.Core/Characteristic.cs new file mode 100644 index 0000000..a027050 --- /dev/null +++ b/HapSharp.Core/Characteristic.cs @@ -0,0 +1,16 @@ +using System; +namespace HapSharp.Core +{ + public class Characteristic + { + string UUID; + string displayName; + + public Characteristic (string displayName, string UUID, object props) + { + this.displayName = displayName; + this.UUID = UUID; + //this.iid = null; // assigned by our containing Service + } + } +} diff --git a/HapSharp.Core/Definitions.cs b/HapSharp.Core/Definitions.cs new file mode 100644 index 0000000..c59ee9d --- /dev/null +++ b/HapSharp.Core/Definitions.cs @@ -0,0 +1,82 @@ +using System; +namespace HapSharp.Core +{ + public interface IUUID + { + string Generate (string data); + bool IsValid (string UUID); + string Unparse (string bug, int offset); + } + + public enum EventService + { + CharacteristicChange, + ServiceConfigurationChange, + } + + public interface IEventEmitterAccessory + { + void AddListener (EventAccessory eventAccessory, Action handler); + void On (EventAccessory eventAccessory, Action handler); + void Once (EventAccessory eventAccessory, Action handler); + void RemoveListener (EventAccessory eventAccessory, Action handler); + void RemoveAllListeners (EventAccessory eventAccessory); + void SetMaxListeners (int number); + int GetMaxListeners (); + } + + public interface IService : IEventEmitterAccessory + { + //new (displayName: string, UUID: string, subtype: string): Service; + string displayName { get; set; } + string UUID { get; set; } + string subtype { get; set; } + string iid { get; set; } + + ICharacteristic[] characteristics { get; set; } + ICharacteristic[] optionalCharacteristics { get; set; } + + ICharacteristic addCharacteristic (ICharacteristic characteristic, Action handler); + + void removeCharacteristic (ICharacteristic characteristic); + + ICharacteristic getCharacteristic (string characteristic, string value); + + bool testCharacteristic (string characteristic); + bool setCharacteristic (string characteristic); + + IService updateCharacteristic (string name, string value); + void addOptionalCharacteristic (ICharacteristic characteristic); + ICharacteristic getCharacteristicByIID (string iid); + + string toHAP (object any); + } + + public interface ICharacteristic + { + + } + + public enum EventAccessory + { + ServiceConfigurationChange, + ServiceCharacteristicChange, + Identify + } + + public enum EventCharacteristic + { + Get, + Set, + } + + public enum ServiceType + { + AccessoryInformation + } + + public enum ServiceCharacteristicType + { + Name, Manufacturer, Model, SerialNumber, FirmwareRevision, Identify + } +} diff --git a/HapSharp.Core/Encription.cs b/HapSharp.Core/Encription.cs new file mode 100644 index 0000000..c94d921 --- /dev/null +++ b/HapSharp.Core/Encription.cs @@ -0,0 +1,331 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace HapSharp.Core +{ + public enum EncriptionType + { + SHA1 + } + + public static class Curve25519 + { + //TODO: TO IMPLEMENT + public static void MakeSecretKey (int[] data) + { + throw new NotImplementedException (); + } + //TODO: TO IMPLEMENT + internal static int[] deriveSharedSecret (int[] secKey, int[] pubKey) + { + throw new NotImplementedException (); + } + //TODO: TO IMPLEMENT + internal static int[] derivePublicKey (int[] secKey) + { + throw new NotImplementedException (); + } + } + + public class EncriptionHelper + { + public static string CreateHash (EncriptionType type, string input) + { + if (type == EncriptionType.SHA1) { + return CreateHashSha1 (input); + } + throw new NotImplementedException (); + } + + static string CreateHashSha1 (string input) + { + using (SHA1Managed sha1 = new SHA1Managed ()) { + var hash = sha1.ComputeHash (Encoding.UTF8.GetBytes (input)); + var sb = new StringBuilder (hash.Length * 2); + + foreach (byte b in hash) { + // can be "x2" if you want lowercase + sb.Append (b.ToString ("X2")); + } + + return sb.ToString (); + } + } + } + + public class Encription + { + //TODO: TO IMPLEMENT + public static int[] FromHex (string h) + { + //h.replace (/ ([^ 0 - 9a - f]) / g, ''); + //var out = [], len = h.length, w = ''; + //for (var i = 0; i < len; i += 2) { + // w = h[i]; + // if (((i + 1) >= len) || typeof h[i + 1] === 'undefined') { + // w += '0'; + // } else { + // w += h[i + 1]; + // } + // out.push (parseInt (w, 16)); + //} + //return out; + throw new NotImplementedException (); + } + + //TODO: TO IMPLEMENT + public static int[] generateCurve25519SecretKey () + { + var secretKey = BufferShim.Alloc (32); + Curve25519.MakeSecretKey (secretKey); + return secretKey; + } + + //TODO: TO IMPLEMENT + public static int[] generateCurve25519PublicKeyFromSecretKey (int[] secKey) + { + var publicKey = Curve25519.derivePublicKey (secKey); + return publicKey; + } + + //TODO: TO IMPLEMENT + public static int[] generateCurve25519SharedSecKey (int[] secKey, int[] pubKey) + { + var sharedSec = Curve25519.deriveSharedSecret (secKey, pubKey); + return sharedSec; + } + + //Security Layer Enc/Dec + //TODO: TO IMPLEMENT + public static int[] layerEncrypt (int[] data, int[] count, int[] key) + { + // var result = BufferShim.Alloc (0); + // var total = data.Length; + // for (var offset = 0; offset < total;) { + // var length = Math.Min (total - offset, 0x400); + // var leLength = BufferShim.Alloc (2); + // leLength.writeUInt16LE (length, 0); + + // var nonce = BufferShim.Alloc (8); + // writeUInt64LE (count.Value++, nonce, 0); + + // var result_Buffer = bufferShim.alloc (length); + // var result_mac = bufferShim.alloc (16); + // encryptAndSeal (key, nonce, data.slice (offset, offset + length), + // leLength, result_Buffer, result_mac); + + // offset += length; + + // result = Buffer.Concat ([result, leLength, result_Buffer, result_mac]); + //} + //return result; + throw new NotImplementedException (); + } + + //TODO: TO IMPLEMENT + public static int[] layerDecrypt (int[] packet,int count,int[] key,int[] extraInfo) + { + // Handle Extra Info + // if (extraInfo.leftoverData != undefined) { + // packet = Buffer.concat ([extraInfo.leftoverData, packet]); + // } + + // var result = bufferShim.alloc (0); + // var total = packet.length; + + // for (var offset = 0; offset < total;) { + // var realDataLength = packet.slice (offset, offset + 2).readUInt16LE (0); + + // var availableDataLength = total - offset - 2 - 16; + // if (realDataLength > availableDataLength) { + // // Fragmented packet + // extraInfo.leftoverData = packet.slice (offset); + // break; + // } else { + // extraInfo.leftoverData = undefined; + // } + + // var nonce = bufferShim.alloc (8); + // writeUInt64LE (count.value++, nonce, 0); + + // var result_Buffer = bufferShim.alloc (realDataLength); + + // if (verifyAndDecrypt (key, nonce, packet.slice (offset + 2, offset + 2 + realDataLength), + // packet.slice (offset + 2 + realDataLength, offset + 2 + realDataLength + 16), + // packet.slice (offset, offset + 2), result_Buffer)) { + // result = Buffer.concat ([result, result_Buffer]); + // offset += (18 + realDataLength); + // } else { + // debug ('Layer Decrypt fail!'); + // debug ('Packet: %s', packet.toString('hex')); + // return 0; + // } + //} + + //return result; + throw new NotImplementedException (); + } + + //General Enc/Dec + //TODO: TO IMPLEMENT + public static int[] verifyAndDecrypt (int[] key,int[] nonce,int[] ciphertext,int[] mac,int[] addData,int[] plaintext) + { + //var ctx = new chacha20poly1305.Chacha20Ctx (); + //chacha20poly1305.chacha20_keysetup (ctx, key); + //chacha20poly1305.chacha20_ivsetup (ctx, nonce); + //var poly1305key = bufferShim.alloc (64); + //var zeros = bufferShim.alloc (64); + //chacha20poly1305.chacha20_update (ctx, poly1305key, zeros, zeros.length); + + //var poly1305_contxt = new chacha20poly1305.Poly1305Ctx (); + //chacha20poly1305.poly1305_init (poly1305_contxt, poly1305key); + + //var addDataLength = 0; + //if (addData != undefined) { + // addDataLength = addData.length; + // chacha20poly1305.poly1305_update (poly1305_contxt, addData, addData.length); + // if ((addData.length % 16) != 0) { + // chacha20poly1305.poly1305_update (poly1305_contxt, bufferShim.alloc (16 - (addData.length % 16)), 16 - (addData.length % 16)); + // } + //} + + //chacha20poly1305.poly1305_update (poly1305_contxt, ciphertext, ciphertext.length); + //if ((ciphertext.length % 16) != 0) { + // chacha20poly1305.poly1305_update (poly1305_contxt, bufferShim.alloc (16 - (ciphertext.length % 16)), 16 - (ciphertext.length % 16)); + //} + + //var leAddDataLen = bufferShim.alloc (8); + //writeUInt64LE (addDataLength, leAddDataLen, 0); + //chacha20poly1305.poly1305_update (poly1305_contxt, leAddDataLen, 8); + + //var leTextDataLen = bufferShim.alloc (8); + //writeUInt64LE (ciphertext.length, leTextDataLen, 0); + //chacha20poly1305.poly1305_update (poly1305_contxt, leTextDataLen, 8); + + //var poly_out = []; + //chacha20poly1305.poly1305_finish (poly1305_contxt, poly_out); + + //if (chacha20poly1305.poly1305_verify (mac, poly_out) != 1) { + // debug ('Verify Fail'); + // return false; + //} else { + // var written = chacha20poly1305.chacha20_update (ctx, plaintext, ciphertext, ciphertext.length); + // chacha20poly1305.chacha20_final (ctx, plaintext.slice (written, ciphertext.length)); + // return true; + //} + + throw new NotImplementedException (); + } + + //TODO: TO IMPLEMENT + public static int[] encryptAndSeal (int[] key,int[] nonce, int[] plaintext,int[] addData,int[] ciphertext,int[] mac) + { + //var ctx = new chacha20poly1305.Chacha20Ctx (); + //chacha20poly1305.chacha20_keysetup (ctx, key); + //chacha20poly1305.chacha20_ivsetup (ctx, nonce); + //var poly1305key = bufferShim.alloc (64); + //var zeros = bufferShim.alloc (64); + //chacha20poly1305.chacha20_update (ctx, poly1305key, zeros, zeros.length); + + //var written = chacha20poly1305.chacha20_update (ctx, ciphertext, plaintext, plaintext.length); + //chacha20poly1305.chacha20_final (ctx, ciphertext.slice (written, plaintext.length)); + + //var poly1305_contxt = new chacha20poly1305.Poly1305Ctx (); + //chacha20poly1305.poly1305_init (poly1305_contxt, poly1305key); + + //var addDataLength = 0; + //if (addData != undefined) { + // addDataLength = addData.length; + // chacha20poly1305.poly1305_update (poly1305_contxt, addData, addData.length); + // if ((addData.length % 16) != 0) { + // chacha20poly1305.poly1305_update (poly1305_contxt, bufferShim.alloc (16 - (addData.length % 16)), 16 - (addData.length % 16)); + // } + //} + + //chacha20poly1305.poly1305_update (poly1305_contxt, ciphertext, ciphertext.length); + //if ((ciphertext.length % 16) != 0) { + // chacha20poly1305.poly1305_update (poly1305_contxt, bufferShim.alloc (16 - (ciphertext.length % 16)), 16 - (ciphertext.length % 16)); + //} + + //var leAddDataLen = bufferShim.alloc (8); + //writeUInt64LE (addDataLength, leAddDataLen, 0); + //chacha20poly1305.poly1305_update (poly1305_contxt, leAddDataLen, 8); + + //var leTextDataLen = bufferShim.alloc (8); + //writeUInt64LE (ciphertext.length, leTextDataLen, 0); + //chacha20poly1305.poly1305_update (poly1305_contxt, leTextDataLen, 8); + + //chacha20poly1305.poly1305_finish (poly1305_contxt, mac); + throw new NotImplementedException (); + } + + const ulong MAX_UINT32 = 0x00000000FFFFFFFF; + const ulong MAX_INT53 = 0x001FFFFFFFFFFFFF; + + public static ulong onesComplement (ulong number) + { + number = ~number; + if (number < 0) { + number = (number & 0x7FFFFFFF) + 0x80000000; + } + return number; + } + + //TODO: TO IMPLEMENT + public static ulong[] uintHighLow (ulong number) + { + if (((long) number) > -1 && number <= MAX_INT53) { + throw new IndexOutOfRangeException (); + } + + ulong high = 0; + ulong signbit = number & 0xFFFFFFFF; + var low = signbit < 0 ? (number & 0x7FFFFFFF) + 0x80000000 : signbit; + if (number > MAX_UINT32) { + high = (number - low) / (MAX_UINT32 + 1); + + } + return new[] { high, low }; + } + + //TODO: TO IMPLEMENT + public static ulong[] intHighLow (int number) + { + if (number > -1) { + return uintHighLow ((ulong)number); + } + var hl = uintHighLow ((ulong)-number); + var high = onesComplement (hl[0]); + var low = onesComplement (hl[1]); + if (low == MAX_UINT32) { + high += 1; + low = 0; + } else { + low += 1; + } + return new[] { high, low }; + } + + //TODO: TO IMPLEMENT + public static ulong[] writeUInt64BE (ulong number,int buffer,ulong offset) + { + // offset = offset || 0; + //var hl = uintHighLow (number) + //buffer.writeUInt32BE (hl[0], offset) + //buffer.writeUInt32BE (hl[1], offset + 4) + throw new NotImplementedException (); + } + + //TODO: TO IMPLEMENT + public static ulong[] writeUInt64LE (int number,int buffer,int offset) + { + //offset = offset || 0; + //var hl = uintHighLow (number) + //buffer.writeUInt32LE (hl[1], offset) + //buffer.writeUInt32LE (hl[0], offset + 4) + throw new NotImplementedException (); + } + + } +} diff --git a/HapSharp.Core/HapServer.cs b/HapSharp.Core/HapServer.cs new file mode 100755 index 0000000..1abaa44 --- /dev/null +++ b/HapSharp.Core/HapServer.cs @@ -0,0 +1,117 @@ +/* + * The actual HAP server that iOS devices talk to. + * + * Notes + * ----- + * It turns out that the IP-based version of HomeKit's HAP protocol operates over a sort of pseudo-HTTP. + * Accessories are meant to host a TCP socket server that initially behaves exactly as an HTTP/1.1 server. + * So iOS devices will open up a long-lived connection to this server and begin issuing HTTP requests. + * So far, this conforms with HTTP/1.1 Keepalive. However, after the "pairing" process is complete, the + * connection is expected to be "upgraded" to support full-packet encryption of both HTTP headers and data. + * This encryption is NOT SSL. It is a customized ChaCha20+Poly1305 encryption layer. + * + * Additionally, this "HTTP Server" supports sending "event" responses at any time without warning. The iOS + * device simply keeps the connection open after it's finished with HTTP request/response traffic, and while + * the connection is open, the server can elect to issue "EVENT/1.0 200 OK" HTTP-style responses. These are + * typically sent to inform the iOS device of a characteristic change for the accessory (like "Door was Unlocked"). + * + * See eventedhttp.js for more detail on the implementation of this protocol. + * + * @event 'listening' => function() { } + * Emitted when the server is fully set up and ready to receive connections. + * + * @event 'identify' => function(callback(err)) { } + * Emitted when a client wishes for this server to identify itself before pairing. You must call the + * callback to respond to the client with success. + * + * @event 'pair' => function(username, publicKey, callback(err)) { } + * This event is emitted when a client completes the "pairing" process and exchanges encryption keys. + * Note that this does not mean the "Add Accessory" process in iOS has completed. You must call the + * callback to complete the process. + * + * @event 'verify' => function() { } + * This event is emitted after a client successfully completes the "verify" process, thereby authenticating + * itself to an Accessory as a known-paired client. + * + * @event 'unpair' => function(username, callback(err)) { } + * This event is emitted when a client has requested us to "remove their pairing info", or basically to unpair. + * You must call the callback to complete the process. + * + * @event 'accessories' => function(callback(err, accessories)) { } + * This event is emitted when a client requests the complete representation of Accessory data for + * this Accessory (for instance, what services, characteristics, etc. are supported) and any bridged + * Accessories in the case of a Bridge Accessory. The listener must call the provided callback function + * when the accessory data is ready. We will automatically JSON.stringify the data. + * + * @event 'get-characteristics' => function(data, events, callback(err, characteristics), remote, connectionID) { } + * This event is emitted when a client wishes to retrieve the current value of one or more characteristics. + * The listener must call the provided callback function when the values are ready. iOS clients can typically + * wait up to 10 seconds for this call to return. We will automatically JSON.stringify the data (which must + * be an array) and wrap it in an object with a top-level "characteristics" property. + * + * @event 'set-characteristics' => function(data, events, callback(err), remote, connectionID) { } + * This event is emitted when a client wishes to set the current value of one or more characteristics and/or + * subscribe to one or more events. The 'events' param is an initially-empty object, associated with the current + * connection, on which you may store event registration keys for later processing. The listener must call + * the provided callback when the request has been processed. + */ + +namespace HapSharp.Core +{ + class HAP_TYPES + { + public const int REQUEST_TYPE = 0x00, + USERNAME = 0x01, + SALT = 0x02, + PUBLIC_KEY = 0x03, + PASSWORD_PROOF = 0x04, + ENCRYPTED_DATA = 0x05, + SEQUENCE_NUM = 0x06, + ERROR_CODE = 0x07, + PROOF = 0x0a; + } + + class HAP_ERRORCODES + { + public const int INVALID_REQUEST = 0x02, + INVALID_SIGNATURE = 0x04; + } + + class HAP_STATUS + { + public const int SUCCESS = 0, + INSUFFICIENT_PRIVILEGES = -70401, + SERVICE_COMMUNICATION_FAILURE = -70402, + RESOURCE_BUSY = -70403, + READ_ONLY_CHARACTERISTIC = -70404, + WRITE_ONLY_CHARACTERISTIC = -70405, + NOTIFICATION_NOT_SUPPORTED = -70406, + OUT_OF_RESOURCE = -70407, + OPERATION_TIMED_OUT = -70408, + RESOURCE_DOES_NOT_EXIST = -70409, + INVALID_VALUE_IN_REQUEST = -70410; + } + + class HapServer + { + + System.Timers.Timer timer = new System.Timers.Timer (1000 * 60 * 10); + + public HapServer () + { + // so iOS is very reluctant to actually disconnect HAP connections (as in, sending a FIN packet). + // For instance, if you turn off wifi on your phone, it will not close the connection, instead + // it will leave it open and hope that it's still valid when it returns to the network. And Node, + // by itself, does not ever "discover" that the connection has been closed behind it, until a + // potentially very long system-level socket timeout (like, days). To work around this, we have + // invented a manual "keepalive" mechanism where we send "empty" events perodicially, such that + // when Node attempts to write to the socket, it discovers that it's been disconnected after + // an additional one-minute timeout (this timeout appears to be hardcoded). + + timer.Start (); + timer.Elapsed += (s, e) => { + //this._keepAliveTimerID = setInterval (this._onKeepAliveTimerTick.bind (this), 1000 * 60 * 10); // send keepalive every 10 minutes + }; + } + } +} diff --git a/HapSharp.Core/HapSharp.Core.csproj b/HapSharp.Core/HapSharp.Core.csproj new file mode 100644 index 0000000..16b4e48 --- /dev/null +++ b/HapSharp.Core/HapSharp.Core.csproj @@ -0,0 +1,48 @@ + + + + Debug + AnyCPU + {24CB3D20-D49D-4361-8B78-92726D8477BD} + Library + HapSharp.Core + HapSharp.Core + v4.7 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + true + bin\Release + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HapSharp.Core/MessageType.cs b/HapSharp.Core/MessageType.cs new file mode 100755 index 0000000..5aa56fb --- /dev/null +++ b/HapSharp.Core/MessageType.cs @@ -0,0 +1,14 @@ +namespace HapSharp.Core +{ + public enum MessageType + { + //service-configurationChange + ServiceConfigurationChange, + + //characteristic-change + CharacteristicChange, + + //service-characteristic-change + ServiceCharacteristicChange + } +} diff --git a/HapSharp.Core/OperationExpensions.cs b/HapSharp.Core/OperationExpensions.cs new file mode 100644 index 0000000..bdafc53 --- /dev/null +++ b/HapSharp.Core/OperationExpensions.cs @@ -0,0 +1,82 @@ +using System; + +namespace HapSharp.Core +{ + public static class OperationExpensions + { + public static int GreaterSignedOperator (this int sender, int value) + { + return sender >> value; + } + public static int MinorSignedOperator (this int sender, int value) + { + return sender << value; + } + + public static int MinorShiftRightOperator (this int sender, int value) + { + return (int)(((uint)sender) << value); + } + + public static int GreaterShiftRightOperator (this int sender, int value) + { + return (int)(((uint)sender) >> value); + } + + /// + /// Get the array slice between the two indexes. + /// ... Inclusive for start index, exclusive for end index. + /// + public static T[] Slice (this T[] source, int len) + { + // Handles negative ends. + T[] res = new T[len]; + for (int i = 0; i < len; i++) { + res[i] = source[i]; + } + return res; + } + + /// + /// Get the array slice between the two indexes. + /// ... Inclusive for start index, exclusive for end index. + /// + public static T[] Slice (this T[] source, int start, int end) + { + // Handles negative ends. + if (end < 0) { + end = source.Length + end; + } + int len = end - start; + + // Return new array. + T[] res = new T[len]; + for (int i = 0; i < len; i++) { + res[i] = source[i + start]; + } + return res; + } + + + /// + /// Get the array slice between the two indexes. + /// ... Inclusive for start index, exclusive for end index. + /// + public static void Copy (this Array source, Array target, int targetStart, int sourceStart, int sourceEnd) + { + Array.Copy (source, sourceStart, target, targetStart, sourceEnd - sourceStart); + } + + /// + /// Get the array slice between the two indexes. + /// ... Inclusive for start index, exclusive for end index. + /// + public static int[] Concat (this int[] x, int[] y) + { + var z = new int[x.Length + y.Length]; + x.CopyTo (z, 0); + y.CopyTo (z, x.Length); + return z; + } + } +} diff --git a/HapSharp.Core/Properties/AssemblyInfo.cs b/HapSharp.Core/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..642c37b --- /dev/null +++ b/HapSharp.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("HapSharp.Core")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("${AuthorCopyright}")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion ("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] +[assembly: InternalsVisibleTo ("HapSharp.Core.Tests")] \ No newline at end of file diff --git a/HapSharp.Core/Service.cs b/HapSharp.Core/Service.cs new file mode 100755 index 0000000..0efc0db --- /dev/null +++ b/HapSharp.Core/Service.cs @@ -0,0 +1,57 @@ +/* +* Service represents a set of grouped values necessary to provide a logical function. For instance, a +* "Door Lock Mechanism" service might contain two values, one for the "desired lock state" and one for the +* "current lock state". A particular Service is distinguished from others by its "type", which is a UUID. +* HomeKit provides a set of known Service UUIDs defined in HomeKitTypes.js along with a corresponding +* concrete subclass that you can instantiate directly to setup the necessary values. These natively-supported +* Services are expected to contain a particular set of Characteristics. +* +* Unlike Characteristics, where you cannot have two Characteristics with the same UUID in the same Service, +* you can actually have multiple Services with the same UUID in a single Accessory. For instance, imagine +* a Garage Door Opener with both a "security light" and a "backlight" for the display. Each light could be +* a "Lightbulb" Service with the same UUID. To account for this situation, we define an extra "subtype" +* property on Service, that can be a string or other string-convertible object that uniquely identifies the +* Service among its peers in an Accessory. For instance, you might have `service1.subtype = 'security_light'` +* for one and `service2.subtype = 'backlight'` for the other. +* +* You can also define custom Services by providing your own UUID for the type that you generate yourself. +* Custom Services can contain an arbitrary set of Characteristics, but Siri will likely not be able to +* work with these. +* +* @event 'characteristic-change' => function({characteristic, oldValue, newValue, context}) { } +* Emitted after a change in the value of one of our Characteristics has occurred. +*/ + +using System; +using System.Collections.Generic; + +namespace HapSharp.Core +{ + public class Service + { + public event EventHandler Set; + + List Characteristics = new List (); + + public event EventHandler MessageReceived; + + public Service (string displayName, string UUID, string subtype) { + + } + + public void Add () + { + + } + + public void SetCharacteristic () + { + + } + + public Service GetCharacteristic (ServiceCharacteristicType characteristic) + { + throw new NotImplementedException (); + } + } +} diff --git a/HapSharp.Core/Types.cs b/HapSharp.Core/Types.cs new file mode 100644 index 0000000..f5702ca --- /dev/null +++ b/HapSharp.Core/Types.cs @@ -0,0 +1,28 @@ +using System; +namespace HapSharp.Core +{ + public enum Utf8AsciiLatin1Encoding + { + UTF8, ASCII, LATIN1 + } + + public enum HexBase64Latin1Encoding + { + LATIN1, HEX, BASE64 + } + + public enum Utf8AsciiBinaryEncoding + { + UTF8, ASCII, BINARY + } + + public enum HexBase64BinaryEncoding + { + BINARY, BASE64, HEX + } + + public enum ECDHKeyFormat + { + Compressed, Uncompressed, Hybrid + } +} diff --git a/HapSharp.Core/UUID.cs b/HapSharp.Core/UUID.cs new file mode 100644 index 0000000..be0b5db --- /dev/null +++ b/HapSharp.Core/UUID.cs @@ -0,0 +1,14 @@ +using System; +namespace HapSharp.Core +{ + class UUID + { + public Guid guid; + readonly public string Name; + public UUID (string name) + { + Name = name; + guid = Guid.NewGuid (); + } + } +}