/// <summary>
        /// Copies values from signed activity information to properties of this instance of the activity.
        /// </summary>
        /// <param name="SignedActivity">Signed description of the activity.</param>
        /// <param name="PrimaryServerId">In case of NeighborActivity, this is identifier of the primary proximity server of the activity.</param>
        public void CopyFromSignedActivityInformation(SignedActivityInformation SignedActivity, byte[] PrimaryServerId = null)
        {
            ActivityInformation activity         = SignedActivity.Activity;
            GpsLocation         activityLocation = new GpsLocation(activity.Latitude, activity.Longitude);

            byte[] pubKey     = activity.OwnerPublicKey.ToByteArray();
            byte[] identityId = Crypto.Sha256(pubKey);

            this.Version                       = new SemVer(activity.Version).ToByteArray();
            this.ActivityId                    = activity.Id;
            this.OwnerIdentityId               = identityId;
            this.OwnerPublicKey                = pubKey;
            this.OwnerProfileServerId          = activity.ProfileServerContact.NetworkId.ToByteArray();
            this.OwnerProfileServerIpAddress   = activity.ProfileServerContact.IpAddress.ToByteArray();
            this.OwnerProfileServerPrimaryPort = (ushort)activity.ProfileServerContact.PrimaryPort;
            this.Type              = activity.Type;
            this.LocationLatitude  = activityLocation.Latitude;
            this.LocationLongitude = activityLocation.Longitude;
            this.PrecisionRadius   = activity.Precision;
            this.StartTime         = ProtocolHelper.UnixTimestampMsToDateTime(activity.StartTime).Value;
            this.ExpirationTime    = ProtocolHelper.UnixTimestampMsToDateTime(activity.ExpirationTime).Value;
            this.Signature         = SignedActivity.Signature.ToByteArray();
            this.ExtraData         = activity.ExtraData;
            if (this is NeighborActivity)
            {
                (this as NeighborActivity).PrimaryServerId = PrimaryServerId;
            }
        }
        /// <summary>
        /// Creates a new instance of identity from SignedActivityInformation data structure.
        /// </summary>
        /// <param name="SignedActivity">Signed description of the activity.</param>
        /// <param name="PrimaryServerId">In case of NeighborActivity, this is identifier of the primary proximity server of the activity.</param>
        /// <returns>New instance of the activity.</returns>
        public static T FromSignedActivityInformation <T>(SignedActivityInformation SignedProfile, byte[] PrimaryServerId = null) where T : ActivityBase, new()
        {
            T res = new T();

            res.CopyFromSignedActivityInformation(SignedProfile, PrimaryServerId);

            return(res);
        }
        /// <summary>
        /// Checks whether the update activity request is valid.
        /// <para>This function does not verify whether the activity exists.
        /// It also does not verify whether a closer proximity server exists to which the activity should be migrated.
        /// It also does not verify whether the changed activity's start time will be before expiration time.</para>
        /// </summary>
        /// <param name="UpdateActivityRequest">Update activity request part of the client's request message.</param>
        /// <param name="Client">Client that sent the request.</param>
        /// <param name="RequestMessage">Full request message from client.</param>
        /// <param name="ErrorResponse">If the function fails, this is filled with error response message that is ready to be sent to the client.</param>
        /// <returns>true if the update activity request can be applied, false otherwise.</returns>
        public static bool ValidateUpdateActivityRequest(UpdateActivityRequest UpdateActivityRequest, IncomingClient Client, ProxProtocolMessage RequestMessage, out ProxProtocolMessage ErrorResponse)
        {
            log.Trace("()");

            bool res = false;

            ErrorResponse = null;
            string details = null;

            if (UpdateActivityRequest == null)
            {
                UpdateActivityRequest = new UpdateActivityRequest();
            }
            if (UpdateActivityRequest.Activity == null)
            {
                UpdateActivityRequest.Activity = new ActivityInformation();
            }

            SignedActivityInformation signedActivity = new SignedActivityInformation()
            {
                Activity  = UpdateActivityRequest.Activity,
                Signature = RequestMessage.Request.ConversationRequest.Signature
            };

            ProxMessageBuilder messageBuilder = Client.MessageBuilder;

            if (ValidateSignedActivityInformation(signedActivity, Client.PublicKey, messageBuilder, RequestMessage, "", false, out ErrorResponse))
            {
                if (details == null)
                {
                    int index = 0;
                    foreach (ByteString serverId in UpdateActivityRequest.IgnoreServerIds)
                    {
                        if (serverId.Length != ProtocolHelper.NetworkIdentifierLength)
                        {
                            log.Debug("Ignored server ID #{0} is not a valid network ID as its length is {1} bytes.", index, serverId.Length);
                            details = "ignoreServerIds";
                            break;
                        }

                        index++;
                    }
                }

                if (details == null)
                {
                    res = true;
                }
                else
                {
                    ErrorResponse = messageBuilder.CreateErrorInvalidValueResponse(RequestMessage, details);
                }
            }

            log.Trace("(-):{0}", res);
            return(res);
        }
        /// <summary>
        /// Creates SignedActivityInformation representation of the activity.
        /// </summary>
        /// <returns>SignedActivityInformation structure describing the activity.</returns>
        public SignedActivityInformation ToSignedActivityInformation()
        {
            SignedActivityInformation res = new SignedActivityInformation()
            {
                Activity  = this.ToActivityInformation(),
                Signature = ProtocolHelper.ByteArrayToByteString(this.Signature != null ? this.Signature : new byte[0])
            };

            return(res);
        }
        /// <summary>
        /// Checks whether a signed activity information is valid.
        /// </summary>
        /// <param name="SignedActivity">Signed activity information to check.</param>
        /// <param name="OwnerIdentityPublicKey">Public key of the activity's owner identity.</param>
        /// <param name="MessageBuilder">Client's network message builder.</param>
        /// <param name="RequestMessage">Full request message from client.</param>
        /// <param name="ErrorPrefix">Prefix to add to the validation error details.</param>
        /// <param name="InvalidSignatureToDetails">If set to true, invalid signature error will be reported as invalid value in signature field.</param>
        /// <param name="ErrorResponse">If the function fails, this is filled with error response message that is ready to be sent to the client.</param>
        /// <returns>true if the signed activity information is valid and signed correctly by the given identity, false otherwise.</returns>
        public static bool ValidateSignedActivityInformation(SignedActivityInformation SignedActivity, byte[] OwnerIdentityPublicKey, ProxMessageBuilder MessageBuilder, ProxProtocolMessage RequestMessage, string ErrorPrefix, bool InvalidSignatureToDetails, out ProxProtocolMessage ErrorResponse)
        {
            log.Trace("()");
            ErrorResponse = null;

            if (SignedActivity == null)
            {
                SignedActivity = new SignedActivityInformation();
            }
            if (SignedActivity.Activity == null)
            {
                SignedActivity.Activity = new ActivityInformation();
            }

            bool res = false;

            if (ValidateActivityInformation(SignedActivity.Activity, OwnerIdentityPublicKey, MessageBuilder, RequestMessage, ErrorPrefix + "activity.", out ErrorResponse))
            {
                // ActivityBase.InternalInvalidActivityType is a special internal type of activity that we use to prevent problems in the network.
                // This is not an elegant solution.
                // See NeighborhoodActionProcessor.NeighborhoodActivityUpdateAsync case NeighborhoodActionType.AddActivity for more information.
                if (SignedActivity.Activity.Type != ActivityBase.InternalInvalidActivityType)
                {
                    byte[] signature = SignedActivity.Signature.ToByteArray();
                    byte[] data      = SignedActivity.Activity.ToByteArray();

                    if (Ed25519.Verify(signature, data, OwnerIdentityPublicKey))
                    {
                        res = true;
                    }
                    else
                    {
                        ErrorResponse = InvalidSignatureToDetails ? MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, ErrorPrefix + "signature") : MessageBuilder.CreateErrorInvalidSignatureResponse(RequestMessage);
                    }
                }
            }

            log.Trace("(-):{0}", res);
            return(res);
        }
        /// <summary>
        /// Checks whether the create activity request is valid.
        /// <para>This function does not verify the uniqueness of the activity identifier.
        /// It also does not verify whether a closer proximity server exists.</para>
        /// </summary>
        /// <param name="CreateActivityRequest">Create activity request part of the client's request message.</param>
        /// <param name="Client">Client that sent the request.</param>
        /// <param name="RequestMessage">Full request message from client.</param>
        /// <param name="ErrorResponse">If the function fails, this is filled with error response message that is ready to be sent to the client.</param>
        /// <returns>true if the create activity request can be applied, false otherwise.</returns>
        public static bool ValidateCreateActivityRequest(CreateActivityRequest CreateActivityRequest, IncomingClient Client, ProxProtocolMessage RequestMessage, out ProxProtocolMessage ErrorResponse)
        {
            log.Trace("()");

            bool res = false;

            ErrorResponse = null;
            string details = null;

            if (CreateActivityRequest == null)
            {
                CreateActivityRequest = new CreateActivityRequest();
            }
            if (CreateActivityRequest.Activity == null)
            {
                CreateActivityRequest.Activity = new ActivityInformation();
            }
            if (CreateActivityRequest.Activity.ProfileServerContact == null)
            {
                CreateActivityRequest.Activity.ProfileServerContact = new ServerContactInfo();
            }

            SignedActivityInformation signedActivity = new SignedActivityInformation()
            {
                Activity  = CreateActivityRequest.Activity,
                Signature = RequestMessage.Request.ConversationRequest.Signature
            };


            if (ValidateSignedActivityInformation(signedActivity, Client.PublicKey, Client.MessageBuilder, RequestMessage, "", false, out ErrorResponse))
            {
                byte[] ownerPubKey = CreateActivityRequest.Activity.OwnerPublicKey.ToByteArray();

                if (!ByteArrayComparer.Equals(Client.PublicKey, ownerPubKey))
                {
                    log.Debug("Client's public key '{0}' does not match activity owner's public key '{1}'.", Client.PublicKey.ToHex(), ownerPubKey.ToHex());
                    details = "activity.ownerPublicKey";
                }


                if (details == null)
                {
                    int index = 0;
                    foreach (ByteString serverId in CreateActivityRequest.IgnoreServerIds)
                    {
                        if (serverId.Length != ProtocolHelper.NetworkIdentifierLength)
                        {
                            log.Debug("Ignored server ID #{0} is not a valid network ID as its length is {1} bytes.", index, serverId.Length);
                            details = "ignoreServerIds";
                            break;
                        }

                        index++;
                    }
                }

                if (details == null)
                {
                    res = true;
                }
                else
                {
                    ErrorResponse = Client.MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, details);
                }
            }

            log.Trace("(-):{0}", res);
            return(res);
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Updates an existing activity in the database. Then a new neighborhood action is created to propagate the changes to the neighborhood
        /// if this is required. If the update is rejected, the action is deleted from the database and this is propagated to the neighborhood.
        /// <para>Note that the change in activity is not propagated if the client sets no propagation flag in the request, or if only the activity
        /// expiration date or its location is changed.</para>
        /// </summary>
        /// <param name="UpdateRequest">Update request received from the client.</param>
        /// <param name="Signature">Signature of the updated activity data from the client.</param>
        /// <param name="OwnerIdentityId">Network ID of the client who requested the update.</param>
        /// <param name="CloserServerId">If the result is Status.ErrorRejected, this is filled with network identifier of a neighbor server that is closer to the target location.</param>
        /// <returns>Status.Ok if the function succeeds,
        /// Status.ErrorNotFound if the activity to update does not exist,
        /// Status.ErrorRejected if the update was rejected and the client should migrate the activity to closest proximity server,
        /// Status.ErrorInvalidValue if the update attempted to change activity's type,
        /// Status.ErrorInternal otherwise.</returns>
        public async Task <Status> UpdateAndPropagateAsync(UpdateActivityRequest UpdateRequest, byte[] Signature, byte[] OwnerIdentityId, StrongBox <byte[]> CloserServerId)
        {
            log.Trace("(UpdateRequest.Activity.Id:{0},OwnerIdentityId:'{1}')", UpdateRequest.Activity.Id, OwnerIdentityId.ToHex());

            Status res = Status.ErrorInternal;

            bool success = false;
            bool signalNeighborhoodAction           = false;
            bool migrateActivity                    = false;
            ActivityInformation activityInformation = UpdateRequest.Activity;

            DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.PrimaryActivityLock, UnitOfWork.FollowerLock, UnitOfWork.NeighborhoodActionLock };
            using (IDbContextTransaction transaction = await unitOfWork.BeginTransactionWithLockAsync(lockObjects))
            {
                try
                {
                    PrimaryActivity existingActivity = (await GetAsync(a => (a.ActivityId == activityInformation.Id) && (a.OwnerIdentityId == OwnerIdentityId))).FirstOrDefault();
                    if (existingActivity != null)
                    {
                        // First, we check whether the activity should be migrated to closer proximity server.
                        GpsLocation oldLocation     = existingActivity.GetLocation();
                        GpsLocation newLocation     = new GpsLocation(activityInformation.Latitude, activityInformation.Longitude);
                        bool        locationChanged = !oldLocation.Equals(newLocation);

                        bool error = false;
                        if (locationChanged)
                        {
                            List <byte[]> ignoreServerIds = new List <byte[]>(UpdateRequest.IgnoreServerIds.Select(i => i.ToByteArray()));
                            if (!await unitOfWork.NeighborRepository.IsServerNearestToLocationAsync(newLocation, ignoreServerIds, CloserServerId, ProxMessageBuilder.ActivityMigrationDistanceTolerance))
                            {
                                if (CloserServerId.Value != null)
                                {
                                    migrateActivity = true;
                                    log.Debug("Activity's new location is outside the reach of this proximity server, the activity will be deleted from the database.");
                                }
                                else
                                {
                                    error = true;
                                }
                            }
                            // else No migration needed
                        }

                        if (!error)
                        {
                            // If it should not be migrated, we update the activity in our database.
                            if (!migrateActivity)
                            {
                                SignedActivityInformation signedActivityInformation = new SignedActivityInformation()
                                {
                                    Activity  = activityInformation,
                                    Signature = ProtocolHelper.ByteArrayToByteString(Signature)
                                };

                                PrimaryActivity updatedActivity = ActivityBase.FromSignedActivityInformation <PrimaryActivity>(signedActivityInformation);
                                ActivityChange  changes         = existingActivity.CompareChangeTo(updatedActivity);

                                if ((changes & ActivityChange.Type) == 0)
                                {
                                    bool propagateChange = false;
                                    if (!UpdateRequest.NoPropagation)
                                    {
                                        // If only changes in the activity are related to location or expiration time, the activity update is not propagated to the neighborhood.
                                        propagateChange = (changes & ~(ActivityChange.LocationLatitude | ActivityChange.LocationLongitude | ActivityChange.PrecisionRadius | ActivityChange.ExpirationTime)) != 0;
                                    }

                                    existingActivity.CopyFromSignedActivityInformation(signedActivityInformation);

                                    Update(existingActivity);

                                    if (propagateChange)
                                    {
                                        // The activity has to be propagated to all our followers we create database actions that will be processed by dedicated thread.
                                        signalNeighborhoodAction = await unitOfWork.NeighborhoodActionRepository.AddActivityFollowerActionsAsync(NeighborhoodActionType.ChangeActivity, existingActivity.ActivityId, existingActivity.OwnerIdentityId);
                                    }
                                    else
                                    {
                                        log.Trace("Change of activity ID {0}, owner identity ID '{1}' won't be propagated to neighborhood.", existingActivity.ActivityId, existingActivity.OwnerIdentityId.ToHex());
                                    }

                                    await unitOfWork.SaveThrowAsync();

                                    transaction.Commit();
                                    success = true;
                                }
                                else
                                {
                                    log.Debug("Activity ID {0}, owner identity ID '{1}' attempt to change type.", activityInformation.Id, OwnerIdentityId.ToHex());
                                    res = Status.ErrorInvalidValue;
                                }
                            }
                            // else this is handled below separately, out of locked section
                        }
                        // else Internal error
                    }
                    else
                    {
                        log.Debug("Activity ID {0}, owner identity ID '{1}' does not exist.", activityInformation.Id, OwnerIdentityId.ToHex());
                        res = Status.ErrorNotFound;
                    }
                }
                catch (Exception e)
                {
                    log.Error("Exception occurred: {0}", e.ToString());
                }

                if (!success)
                {
                    log.Warn("Rolling back transaction.");
                    unitOfWork.SafeTransactionRollback(transaction);
                }

                unitOfWork.ReleaseLock(lockObjects);
            }


            if (migrateActivity)
            {
                // If activity should be migrated, we delete it from our database and inform our neighbors.
                StrongBox <bool> notFound = new StrongBox <bool>(false);
                if (await DeleteAndPropagateAsync(activityInformation.Id, OwnerIdentityId, notFound))
                {
                    if (!notFound.Value)
                    {
                        // The activity was deleted from the database and this change will be propagated to the neighborhood.
                        log.Debug("Update rejected, activity ID {0}, owner identity ID '{1}' deleted.", activityInformation.Id, OwnerIdentityId.ToHex());
                        res = Status.ErrorRejected;
                    }
                    else
                    {
                        // Activity of given ID not found among activities created by the client.
                        res = Status.ErrorNotFound;
                    }
                }
            }

            if (success)
            {
                // Send signal to neighborhood action processor to process the new series of actions.
                if (signalNeighborhoodAction)
                {
                    Network.NeighborhoodActionProcessor neighborhoodActionProcessor = (Network.NeighborhoodActionProcessor)Base.ComponentDictionary[Network.NeighborhoodActionProcessor.ComponentName];
                    neighborhoodActionProcessor.Signal();
                }

                res = Status.Ok;
            }


            log.Trace("(-):{0}", res);
            return(res);
        }