/// <summary>
        /// Create patch twin model to upload
        /// </summary>
        /// <param name="existing"></param>
        /// <param name="update"></param>
        protected static DeviceTwinModel Patch(
            BaseRegistration existing, BaseRegistration update)
        {
            var twin = new DeviceTwinModel {
                Etag       = existing?.Etag,
                Tags       = new Dictionary <string, JToken>(),
                Properties = new TwinPropertiesModel {
                    Desired = new Dictionary <string, JToken>()
                }
            };

            // Tags

            if (update?.ApplicationId != null &&
                update.ApplicationId != existing?.ApplicationId)
            {
                twin.Tags.Add(nameof(ApplicationId), update.ApplicationId);
            }

            if (update?.IsDisabled != null &&
                update.IsDisabled != existing?.IsDisabled)
            {
                twin.Tags.Add(nameof(IsDisabled), (update?.IsDisabled ?? false) ?
                              true : (bool?)null);
                twin.Tags.Add(nameof(NotSeenSince), (update?.IsDisabled ?? false) ?
                              DateTime.UtcNow : (DateTime?)null);
            }

            if (update?.SiteOrSupervisorId != existing?.SiteOrSupervisorId)
            {
                twin.Tags.Add(nameof(SiteOrSupervisorId), update?.SiteOrSupervisorId);
            }

            if (update?.SupervisorId != existing?.SupervisorId)
            {
                twin.Tags.Add(nameof(SupervisorId), update?.SupervisorId);
            }

            if (update?.SiteId != existing?.SiteId)
            {
                twin.Tags.Add(nameof(SiteId), update?.SiteId);
            }

            var certUpdate = update?.Certificate.DecodeAsByteArray().SequenceEqualsSafe(
                existing?.Certificate.DecodeAsByteArray());

            if (!(certUpdate ?? true))
            {
                twin.Tags.Add(nameof(Certificate), update?.Certificate == null ?
                              null : JToken.FromObject(update.Certificate));
                twin.Tags.Add(nameof(Thumbprint),
                              update?.Certificate?.DecodeAsByteArray()?.ToSha1Hash());
            }

            twin.Tags.Add(nameof(DeviceType), update?.DeviceType);
            return(twin);
        }
        public void TestEqualIsEqualWithDeviceModel()
        {
            var r1 = CreateRegistration();
            var m  = SupervisorRegistration.Patch(null, r1);
            var r2 = BaseRegistration.ToRegistration(m);

            Assert.Equal(r1, r2);
            Assert.Equal(r1.GetHashCode(), r2.GetHashCode());
            Assert.True(r1 == r2);
            Assert.False(r1 != r2);
        }
        public void TestEqualIsNotEqualWithDeviceModel()
        {
            var r1 = CreateRegistration();
            var m  = SupervisorRegistration.Patch(null, r1);

            m.Properties.Desired["AddressRangesToScan"] = null;
            var r2 = BaseRegistration.ToRegistration(m);

            Assert.NotEqual(r1, r2);
            Assert.NotEqual(r1.GetHashCode(), r2.GetHashCode());
            Assert.True(r1 != r2);
            Assert.False(r1 == r2);
        }
        public void TestEqualIsNotEqualWithDeviceModel()
        {
            var r1 = CreateRegistration();
            var m  = EndpointRegistration.Patch(null, r1);

            m.Properties.Desired["Credential"] = "password";
            var r2 = BaseRegistration.ToRegistration(m);

            Assert.NotEqual(r1, r2);
            Assert.NotEqual(r1.GetHashCode(), r2.GetHashCode());
            Assert.True(r1 != r2);
            Assert.False(r1 == r2);
        }
        public void TestEqualIsNotEqualWithDeviceModel()
        {
            var r1 = CreateRegistration();
            var m  = ApplicationRegistration.Patch(null, r1);

            m.Tags["DiscoveryProfileUri"] = null;
            var r2 = BaseRegistration.ToRegistration(m);

            Assert.NotEqual(r1, r2);
            Assert.NotEqual(r1.GetHashCode(), r2.GetHashCode());
            Assert.True(r1 != r2);
            Assert.False(r1 == r2);
        }
        /// <summary>
        /// Create patch twin model to upload
        /// </summary>
        /// <param name="existing"></param>
        /// <param name="update"></param>
        public static DeviceTwinModel Patch(
            EndpointRegistration existing,
            EndpointRegistration update)
        {
            var twin = BaseRegistration.Patch(existing, update);

            // Tags

            if (update?.EndpointRegistrationUrl != null &&
                update.EndpointRegistrationUrl != existing?.EndpointRegistrationUrl)
            {
                twin.Tags.Add(nameof(EndpointUrlLC),
                              update.EndpointUrlLC);
                twin.Tags.Add(nameof(EndpointRegistrationUrl),
                              update.EndpointRegistrationUrl);
            }

            if (update?.SecurityLevel != existing?.SecurityLevel)
            {
                twin.Tags.Add(nameof(SecurityLevel), update?.SecurityLevel == null ?
                              null : JToken.FromObject(update?.SecurityLevel));
            }

            if (update?.Activated != null &&
                update.Activated != existing?.Activated)
            {
                twin.Tags.Add(nameof(Activated), update?.Activated);
            }

            var methodEqual = update?.AuthenticationMethods.DecodeAsList().SetEqualsSafe(
                existing?.AuthenticationMethods?.DecodeAsList(), JToken.DeepEquals);

            if (!(methodEqual ?? true))
            {
                twin.Tags.Add(nameof(AuthenticationMethods), update?.AuthenticationMethods == null ?
                              null : JToken.FromObject(update.AuthenticationMethods,
                                                       new JsonSerializer {
                    NullValueHandling = NullValueHandling.Ignore
                }));
            }

            // Endpoint Property

            if (update?.EndpointUrl != null &&
                update.EndpointUrl != existing?.EndpointUrl)
            {
                twin.Properties.Desired.Add(nameof(EndpointUrl),
                                            update.EndpointUrl);
            }

            var urlsEqual = update?.AlternativeUrls.DecodeAsList().ToHashSetSafe().SetEqualsSafe(
                existing?.AlternativeUrls?.DecodeAsList());

            if (!(urlsEqual ?? true))
            {
                twin.Properties.Desired.Add(nameof(AlternativeUrls), update?.AlternativeUrls == null ?
                                            null : JToken.FromObject(update.AlternativeUrls));
            }

            if (update?.SecurityMode != null &&
                update.SecurityMode != existing?.SecurityMode)
            {
                twin.Properties.Desired.Add(nameof(SecurityMode),
                                            JToken.FromObject(update.SecurityMode));
            }

            if (update?.SecurityPolicy != null &&
                update?.SecurityPolicy != existing?.SecurityPolicy)
            {
                twin.Properties.Desired.Add(nameof(SecurityPolicy),
                                            update.SecurityPolicy);
            }

            if (update?.CredentialType != existing?.CredentialType)
            {
                twin.Properties.Desired.Add(nameof(CredentialType), update?.CredentialType == null ?
                                            null : JToken.FromObject(update?.CredentialType));
            }

            if (!JToken.DeepEquals(update?.Credential, existing?.Credential))
            {
                twin.Properties.Desired.Add(nameof(Credential), update?.Credential);
            }

            var thumbEqual = update?.ServerThumbprint.DecodeAsByteArray().SequenceEqualsSafe(
                existing?.ServerThumbprint.DecodeAsByteArray());

            if (!(thumbEqual ?? true))
            {
                twin.Properties.Desired.Add(nameof(ServerThumbprint), update?.ServerThumbprint == null ?
                                            null : JToken.FromObject(update.ServerThumbprint));
            }

            var certEqual = update?.ClientCertificate.DecodeAsByteArray().SequenceEqualsSafe(
                existing?.ClientCertificate.DecodeAsByteArray());

            if (update?.ClientCertificate != null && !(certEqual ?? true))
            {
                twin.Properties.Desired.Add(nameof(ClientCertificate), update?.ClientCertificate == null ?
                                            null : JToken.FromObject(update.ClientCertificate));
            }

            // Recalculate identity

            var reportedEndpointUrl = existing?.EndpointRegistrationUrl;

            if (update?.EndpointRegistrationUrl != null)
            {
                reportedEndpointUrl = update.EndpointRegistrationUrl;
            }
            if (reportedEndpointUrl == null)
            {
                throw new ArgumentException(nameof(EndpointUrl));
            }
            var applicationId = existing?.ApplicationId;

            if (update?.ApplicationId != null)
            {
                applicationId = update.ApplicationId;
            }
            if (applicationId == null)
            {
                throw new ArgumentException(nameof(ApplicationId));
            }
            var securityMode = existing?.SecurityMode;

            if (update?.SecurityMode != null)
            {
                securityMode = update.SecurityMode;
            }
            var securityPolicy = existing?.SecurityPolicy;

            if (update?.SecurityPolicy != null)
            {
                securityPolicy = update.SecurityPolicy;
            }

            twin.Id = EndpointInfoModelEx.CreateEndpointId(
                applicationId, reportedEndpointUrl, securityMode, securityPolicy);

            if (existing?.DeviceId != twin.Id)
            {
                twin.Etag = null; // Force creation of new identity
            }
            return(twin);
        }