/// <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); }
/// <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); }