diff --git a/OICNet.Tests/OicMessageSerialiserTests.cs b/OICNet.Tests/OicMessageSerialiserTests.cs index c2d55a3..d0fa660 100644 --- a/OICNet.Tests/OicMessageSerialiserTests.cs +++ b/OICNet.Tests/OicMessageSerialiserTests.cs @@ -20,6 +20,7 @@ public static void Setup() mockResolver.Setup(c => c.GetResourseType("oic.r.core")).Returns(typeof(OicCoreResource)); mockResolver.Setup(c => c.GetResourseType("oic.r.audio")).Returns(typeof(ResourceTypes.Audio)); + mockResolver.Setup(c => c.GetResourseType("oic.wk.res")).Returns(typeof(OicResource.DiscoverableResources)); _resolver = mockResolver.Object; } @@ -37,7 +38,17 @@ public IOicResource DeserialiseOicResourceCore(byte[] input, OicMessageContentTy { // Arrange var serialiser = new OicMessageSerialiser(_resolver); - return serialiser.Deserialise(input, type); + + //Only worried about the first result + return serialiser.Deserialise(input, type).First(); + } + + [Test, TestCaseSource(typeof(SerialiserTestCaseData), nameof(SerialiserTestCaseData.DeserialiseArrayTestCases))] + public IList DeserialiseOicResourceCoreArray(byte[] input, OicMessageContentType type) + { + // Arrange + var serialiser = new OicMessageSerialiser(_resolver); + return serialiser.Deserialise(input, type).ToList(); } } @@ -166,5 +177,113 @@ public static IEnumerable DeserialiseTestCases }); } } + + public static IEnumerable DeserialiseArrayTestCases + { + get + { + yield return new TestCaseData( + Encoding.UTF8.GetBytes( + @"[{""rt"": [""oic.wk.res""],""di"": ""0685B960-736F-46F7-BEC0-9E6CBD61ADC1"",""links"":[{""href"": ""/res"",""rel"": ""self"",""rt"": [""oic.r.collection""],""if"": [""oic.if.ll""]},{""href"": ""/smartDevice"",""rel"": ""contained"",""rt"": [""oic.d.smartDevice""],""if"": [""oic.if.a""]}]},{""rt"": [""oic.wk.res""],""di"": ""0685B960-736F-46F7-BEC0-9E6CBD61ADC1"",""links"":[{""href"": ""/res"",""rel"": ""self"",""rt"": [""oic.r.collection""],""if"": [""oic.if.ll""]},{""href"": ""/smartDevice"",""rel"": ""contained"",""rt"": [""oic.d.smartDevice""],""if"": [""oic.if.a""]}]}]"), + OicMessageContentType.ApplicationJson) + .Returns(new List + { + new OicResource.DiscoverableResources + { + ResourceTypes = new List {"oic.wk.res"}, + DeviceId = new Guid("0685B960-736F-46F7-BEC0-9E6CBD61ADC1"), + Links = new List + { + new OicResource.Link + { + Href = new Uri("/res", UriKind.Relative), + Rel = "self", + ResourceTypes = new List {"oic.r.collection"}, + Interfaces = new List {OicResourceInterface.LinkLists}, + }, + new OicResource.Link + { + Href = new Uri("/smartDevice", UriKind.Relative), + Rel = "contained", + ResourceTypes = new List {"oic.d.smartDevice"}, + Interfaces = new List {OicResourceInterface.Actuator}, + } + } + }, + new OicResource.DiscoverableResources + { + ResourceTypes = new List {"oic.wk.res"}, + DeviceId = new Guid("0685B960-736F-46F7-BEC0-9E6CBD61ADC1"), + Links = new List + { + new OicResource.Link + { + Href = new Uri("/res", UriKind.Relative), + Rel = "self", + ResourceTypes = new List {"oic.r.collection"}, + Interfaces = new List {OicResourceInterface.LinkLists}, + }, + new OicResource.Link + { + Href = new Uri("/smartDevice", UriKind.Relative), + Rel = "contained", + ResourceTypes = new List {"oic.d.smartDevice"}, + Interfaces = new List {OicResourceInterface.Actuator}, + } + } + } + }); + yield return new TestCaseData( + new byte[]{ 0x82, 0xA3, 0x62, 0x72, 0x74, 0x81, 0x6A, 0x6F, 0x69, 0x63, 0x2E, 0x77, 0x6B, 0x2E, 0x72, 0x65, 0x73, 0x62, 0x64, 0x69, 0x78, 0x24, 0x30, 0x36, 0x38, 0x35, 0x42, 0x39, 0x36, 0x30, 0x2D, 0x37, 0x33, 0x36, 0x46, 0x2D, 0x34, 0x36, 0x46, 0x37, 0x2D, 0x42, 0x45, 0x43, 0x30, 0x2D, 0x39, 0x45, 0x36, 0x43, 0x42, 0x44, 0x36, 0x31, 0x41, 0x44, 0x43, 0x31, 0x65, 0x6C, 0x69, 0x6E, 0x6B, 0x73, 0x82, 0xA4, 0x64, 0x68, 0x72, 0x65, 0x66, 0x64, 0x2F, 0x72, 0x65, 0x73, 0x63, 0x72, 0x65, 0x6C, 0x64, 0x73, 0x65, 0x6C, 0x66, 0x62, 0x72, 0x74, 0x81, 0x70, 0x6F, 0x69, 0x63, 0x2E, 0x72, 0x2E, 0x63, 0x6F, 0x6C, 0x6C, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x62, 0x69, 0x66, 0x81, 0x69, 0x6F, 0x69, 0x63, 0x2E, 0x69, 0x66, 0x2E, 0x6C, 0x6C, 0xA4, 0x64, 0x68, 0x72, 0x65, 0x66, 0x6C, 0x2F, 0x73, 0x6D, 0x61, 0x72, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x63, 0x72, 0x65, 0x6C, 0x69, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x65, 0x64, 0x62, 0x72, 0x74, 0x81, 0x71, 0x6F, 0x69, 0x63, 0x2E, 0x64, 0x2E, 0x73, 0x6D, 0x61, 0x72, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x62, 0x69, 0x66, 0x81, 0x68, 0x6F, 0x69, 0x63, 0x2E, 0x69, 0x66, 0x2E, 0x61, 0xA3, 0x62, 0x72, 0x74, 0x81, 0x6A, 0x6F, 0x69, 0x63, 0x2E, 0x77, 0x6B, 0x2E, 0x72, 0x65, 0x73, 0x62, 0x64, 0x69, 0x78, 0x24, 0x30, 0x36, 0x38, 0x35, 0x42, 0x39, 0x36, 0x30, 0x2D, 0x37, 0x33, 0x36, 0x46, 0x2D, 0x34, 0x36, 0x46, 0x37, 0x2D, 0x42, 0x45, 0x43, 0x30, 0x2D, 0x39, 0x45, 0x36, 0x43, 0x42, 0x44, 0x36, 0x31, 0x41, 0x44, 0x43, 0x31, 0x65, 0x6C, 0x69, 0x6E, 0x6B, 0x73, 0x82, 0xA4, 0x64, 0x68, 0x72, 0x65, 0x66, 0x64, 0x2F, 0x72, 0x65, 0x73, 0x63, 0x72, 0x65, 0x6C, 0x64, 0x73, 0x65, 0x6C, 0x66, 0x62, 0x72, 0x74, 0x81, 0x70, 0x6F, 0x69, 0x63, 0x2E, 0x72, 0x2E, 0x63, 0x6F, 0x6C, 0x6C, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x62, 0x69, 0x66, 0x81, 0x69, 0x6F, 0x69, 0x63, 0x2E, 0x69, 0x66, 0x2E, 0x6C, 0x6C, 0xA4, 0x64, 0x68, 0x72, 0x65, 0x66, 0x6C, 0x2F, 0x73, 0x6D, 0x61, 0x72, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x63, 0x72, 0x65, 0x6C, 0x69, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x65, 0x64, 0x62, 0x72, 0x74, 0x81, 0x71, 0x6F, 0x69, 0x63, 0x2E, 0x64, 0x2E, 0x73, 0x6D, 0x61, 0x72, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x62, 0x69, 0x66, 0x81, 0x68, 0x6F, 0x69, 0x63, 0x2E, 0x69, 0x66, 0x2E, 0x61 }, + OicMessageContentType.ApplicationCbor) + .Returns(new List + { + new OicResource.DiscoverableResources + { + ResourceTypes = new List {"oic.wk.res"}, + DeviceId = new Guid("0685B960-736F-46F7-BEC0-9E6CBD61ADC1"), + Links = new List + { + new OicResource.Link + { + Href = new Uri("/res", UriKind.Relative), + Rel = "self", + ResourceTypes = new List {"oic.r.collection"}, + Interfaces = new List {OicResourceInterface.LinkLists}, + }, + new OicResource.Link + { + Href = new Uri("/smartDevice", UriKind.Relative), + Rel = "contained", + ResourceTypes = new List {"oic.d.smartDevice"}, + Interfaces = new List {OicResourceInterface.Actuator}, + } + } + }, + new OicResource.DiscoverableResources + { + ResourceTypes = new List {"oic.wk.res"}, + DeviceId = new Guid("0685B960-736F-46F7-BEC0-9E6CBD61ADC1"), + Links = new List + { + new OicResource.Link + { + Href = new Uri("/res", UriKind.Relative), + Rel = "self", + ResourceTypes = new List {"oic.r.collection"}, + Interfaces = new List {OicResourceInterface.LinkLists}, + }, + new OicResource.Link + { + Href = new Uri("/smartDevice", UriKind.Relative), + Rel = "contained", + ResourceTypes = new List {"oic.d.smartDevice"}, + Interfaces = new List {OicResourceInterface.Actuator}, + } + } + } + }); + } + } } } diff --git a/OICNet/OICNet.csproj b/OICNet/OICNet.csproj index 0849b07..06d9d6b 100644 --- a/OICNet/OICNet.csproj +++ b/OICNet/OICNet.csproj @@ -30,7 +30,7 @@ More details about OIC here: https://openconnectivity.org/ - + diff --git a/OICNet/OicMessageSerialiser.cs b/OICNet/OicMessageSerialiser.cs index 3463372..c7131d4 100644 --- a/OICNet/OicMessageSerialiser.cs +++ b/OICNet/OicMessageSerialiser.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Cbor; +using Newtonsoft.Json.Cbor.Linq; using Newtonsoft.Json.Linq; namespace OICNet @@ -29,41 +30,40 @@ public OicMessageSerialiser(IResourceTypeResolver resolver) /// /// Deserialses a OIC message into a object based on the message's resource-type ("rt") property - /// /// Todo: Support multiple resource types some how... Currently only supports the first resource type, any subsequent types are ignored. /// /// + /// /// - public IOicResource Deserialise(byte[] message, OicMessageContentType contentType) + public IEnumerable Deserialise(byte[] message, OicMessageContentType contentType) { - var serialiser = new JsonSerializer(); var stream = new MemoryStream(message); - OicCoreResource coreResource; - Type type; + JToken token; + switch (contentType) { case OicMessageContentType.ApplicationJson: - { - coreResource = - serialiser.Deserialize(new JsonTextReader(new StreamReader(stream))); - type = _resolver.GetResourseType(coreResource.ResourceTypes.FirstOrDefault()); - - stream.Seek(0, SeekOrigin.Begin); - return (IOicResource)serialiser.Deserialize(new JsonTextReader(new StreamReader(stream)), type); - } + token = JToken.ReadFrom(new JsonTextReader(new StreamReader(stream))); + break; case OicMessageContentType.ApplicationCbor: - { - coreResource = serialiser.Deserialize(new CborDataReader(stream)); - type = _resolver.GetResourseType(coreResource.ResourceTypes.FirstOrDefault()); - - stream.Seek(0, SeekOrigin.Begin); - return (IOicResource)serialiser.Deserialize(new CborDataReader(stream), type); - } + token = JToken.ReadFrom(new CborDataReader(stream)); + break; default: throw new NotImplementedException(); } + if (token.Type == JTokenType.Array) + token = token.First; + while (token != null) + { + var rt = (string) token["rt"].FirstOrDefault(); + var type = _resolver.GetResourseType(rt); + yield return (IOicResource) token.ToObject(type); + + token = token.Next; + } } + //Todo: support serialising multiple IOicResouces in an array/list public byte[] Serialise(IOicResource resource, OicMessageContentType contentType) { var writer = new MemoryStream(); diff --git a/OICNet/OicResolver.cs b/OICNet/OicResolver.cs index 8bbf054..9fb8953 100644 --- a/OICNet/OicResolver.cs +++ b/OICNet/OicResolver.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using OICNet.OicResource; namespace OICNet { @@ -15,13 +16,14 @@ public interface IResourceTypeResolver /// (Will) Support all OIC v1.1.0 defined resource-types public class OicResolver : IResourceTypeResolver { - private Dictionary _resourceTypes; + private readonly Dictionary _resourceTypes; public OicResolver() { // List of built in resource-types will go here (OIC v1.1.0) _resourceTypes = new Dictionary { + { "oic.wk.res" ,typeof(DiscoverableResources) }, // Todo: In .Net Standard 2.0, replace hardcoded references with reflection, looking for classes with OicResourceTypeAttribute { "oic.r.core", typeof(OicCoreResource) }, { "oic.r.audio", typeof(ResourceTypes.Audio) }, diff --git a/OICNet/OicResource.cs b/OICNet/OicResource.cs index ef44fda..8f12805 100644 --- a/OICNet/OicResource.cs +++ b/OICNet/OicResource.cs @@ -94,7 +94,7 @@ public Task RetrieveAsync() [MinLength(1), StringLength(64)] public List ResourceTypes { get; set; } - [JsonProperty("if", ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter)), JsonRequired()] + [JsonProperty("if", ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter))] public List Interfaces { get; set; } [JsonProperty("n", NullValueHandling=NullValueHandling.Ignore)] @@ -105,6 +105,7 @@ public Task RetrieveAsync() #endregion + internal OicCoreResource(OicDevice device) { Device = device; @@ -115,6 +116,8 @@ public OicCoreResource() } + public virtual bool ShouldSerializeInterfaces() { return true; } + public override bool Equals(object obj) { var other = obj as OicCoreResource; diff --git a/OICNet/OicResource/DiscoverableResources.cs b/OICNet/OicResource/DiscoverableResources.cs new file mode 100644 index 0000000..c348eaf --- /dev/null +++ b/OICNet/OicResource/DiscoverableResources.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; + +using Newtonsoft.Json; +using OICNet.ResourceTypes; + +namespace OICNet.OicResource +{ +#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() + [OicResourceType("oic.wk.res")] + public class DiscoverableResources : OicCoreResource + { + // Hack to get around required "if" property in base-class + public override bool ShouldSerializeInterfaces() { return false; } + + [JsonProperty("di", Required = Required.Always, Order = 10)] + public Guid DeviceId { get; set; } + + /// + /// Supported messaging protocols + /// + [JsonProperty("mpro", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore, Order = 10), StringLength(64)] + public string MessagingProtocols { get; set; } + + [JsonProperty("links", Required = Required.Always, Order = 11)] + public List Links { get; set; } + + public override bool Equals(object obj) + { + var other = obj as DiscoverableResources; + if (other == null) + return false; + if (!base.Equals(obj)) + return false; + if (DeviceId != other.DeviceId) + return false; + if (MessagingProtocols != other.MessagingProtocols) + return false; + if (!Links.SequenceEqual(other.Links)) + return false; + return true; + } + } +#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() +} diff --git a/OICNet/OicResource/Link.cs b/OICNet/OicResource/Link.cs new file mode 100644 index 0000000..b9da7ae --- /dev/null +++ b/OICNet/OicResource/Link.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OICNet; + +namespace OICNet.OicResource +{ +#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() + public class Link + { + public class LinkPolicies + { + /// + /// Specifies the framework policies on the Resource referenced by the target URI for e.g. observable and discoverable + /// + // Todo: Create a flag enum + [JsonProperty("bm", Required = Required.Always)] + public int Policies { get; set; } + + /// + /// Specifies if security needs to be turned on when looking to interact with the Resource + /// + [JsonProperty("sec")] + public bool IsSecure { get; set; } + + /// + /// Secure port to be used for connection + /// + [JsonProperty("port")] + public int SecurePort { get; set; } + + public override bool Equals(object obj) + { + var other = obj as LinkPolicies; + return other != null && Policies == other.Policies && IsSecure == other.IsSecure && + SecurePort == other.SecurePort; + } + } + + /// + /// This is the target URI, it can be specified as a Relative Reference or fully-qualified URI. Relative Reference should be used along with the di parameter to make it unique. + /// + [JsonProperty("href", Required = Required.Always), StringLength(256)] + public Uri Href { get; set; } + + /// + /// The relation of the target URI referenced by the link to the context URI + /// + [JsonProperty("rel", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore), StringLength(64)] + public string Rel { get; set; } = "hosts"; + + [JsonProperty("rt"), JsonRequired()] + [MinLength(1), StringLength(64)] + public List ResourceTypes { get; set; } + + /// + /// The interface set supported by this resource + /// + [JsonProperty("if", ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter)), JsonRequired()] + public List Interfaces { get; set; } + + /// + /// The Device ID on which the Relative Reference in href is to be resolved on. Base URI should be used in preference where possible + /// + [JsonProperty("di", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + public Guid DeviceId { get; set; } + + /// + /// The base URI used to fully qualify a Relative Reference in the href parameter. Use the OCF Schema for URI + /// + [JsonProperty("buri", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore), StringLength(256)] + public Uri BaseUri { get; set; } + + /// + /// Specifies the framework policies on the Resource referenced by the target URI + /// + [JsonProperty("p", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + public LinkPolicies Policies { get; set; } + + /// + /// Batch Parameters: URI parameters to use with an batch request using this link + /// + [JsonProperty("bp", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + public string BatchParameters { get; set; } + + /// + /// A title for the link relation. Can be used by the UI to provide a context + /// + [JsonProperty("title", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore), StringLength(64)] + public string Title { get; set; } + + /// + /// This is used to override the context URI e.g. override the URI of the containing collection + /// + [JsonProperty("anchor", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore), StringLength(256)] + public Uri Anchor { get; set; } + + /// + /// The instance identifier for this web link in an array of web links - used in collections + /// - Use UUID for universal uniqueness - used in /oic/res to identify the device + /// - Any unique string including a URI + /// - An ordinal number that is not repeated - must be unique in the collection context + /// + // Todo: verify this type is either a GUID, Uri(maxLen=256) or int + [JsonProperty("ins", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + public JValue InstanceId { get; set; } + + /// + /// A hint at the representation of the resource referenced by the target URI. This represents the media types that are used for both accepting and emitting + /// + [JsonProperty("type", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + public List TypeHints { get; set; } = new List{ "application/cbor" }; + + public override bool Equals(object obj) + { + var other = obj as Link; + if (other == null) + return false; + //if (!base.Equals(obj)) + // return false; + if (Href != other.Href) + return false; + if (Rel != other.Rel) + return false; + if (!ResourceTypes.SequenceEqual(other.ResourceTypes)) + return false; + if (!Interfaces.SequenceEqual(other.Interfaces)) + return false; + if (DeviceId != other.DeviceId) + return false; + if (BaseUri != other.BaseUri) + return false; + if (Policies != other.Policies) + return false; + if (BatchParameters != other.BatchParameters) + return false; + if (Title != other.Title) + return false; + if (Anchor != other.Anchor) + return false; + if (InstanceId?.Value != other.InstanceId?.Value) + return false; + if (!TypeHints.SequenceEqual(other.TypeHints)) + return false; + + return true; + } + } +}