/// <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> /// Processes PingRequest message from client. /// <para>Simply copies the payload to a new ping response message.</para> /// </summary> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message.</param> /// <returns>Response message to be sent to the client.</returns> public ProxProtocolMessage ProcessMessagePingRequest(IncomingClient Client, ProxProtocolMessage RequestMessage) { log.Trace("()"); ProxMessageBuilder messageBuilder = Client.MessageBuilder; PingRequest pingRequest = RequestMessage.Request.SingleRequest.Ping; ProxProtocolMessage res = messageBuilder.CreatePingResponse(RequestMessage, pingRequest.Payload.ToByteArray(), ProtocolHelper.GetUnixTimestampMs()); log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); }
/// <summary> /// Sends ERROR_PROTOCOL_VIOLATION to client with message ID set to 0x0BADC0DE. /// </summary> /// <param name="Client">Client to send the error to.</param> public async Task SendProtocolViolation(ClientBase Client) { ProxMessageBuilder mb = new ProxMessageBuilder(0, new List <SemVer>() { SemVer.V100 }, null); ProxProtocolMessage response = mb.CreateErrorProtocolViolationResponse(new ProxProtocolMessage(new Message() { Id = 0x0BADC0DE })); await Client.SendMessageAsync(response); }
/// <summary> /// Creates the instance for a new outgoing TCP client. /// </summary> /// <param name="RemoteEndPoint">Target IP address and port this client will be connected to.</param> /// <param name="UseTls">true if TLS should be used for this TCP client, false otherwise.</param> /// <param name="ShutdownCancellationToken">Cancellation token of the parent component.</param> public OutgoingClient(IPEndPoint RemoteEndPoint, bool UseTls, CancellationToken ShutdownCancellationToken) : base(RemoteEndPoint, UseTls) { string logPrefix = string.Format("[=>{0}] ", RemoteEndPoint); log = new Logger("ProximityServer.Network.OutgoingClient", logPrefix); log.Trace("()"); messageBuilder = new ProxMessageBuilder(0, new List <SemVer>() { SemVer.V100 }, Config.Configuration.Keys); shutdownCancellationToken = ShutdownCancellationToken; log.Trace("(-)"); }
/// <summary> /// Creates the instance for a new TCP server client. /// </summary> /// <param name="Server">Role server that the client connected to.</param> /// <param name="TcpClient">TCP client class that holds the connection and allows communication with the client.</param> /// <param name="Id">Unique identifier of the client's connection.</param> /// <param name="UseTls">true if the client is connected to the TLS port, false otherwise.</param> /// <param name="KeepAliveIntervalMs">Number of seconds for the connection to this client to be without any message until the proximity server can close it for inactivity.</param> /// <param name="LogPrefix">Prefix for log entries created by the client.</param> public IncomingClient(TcpRoleServer <IncomingClient> Server, TcpClient TcpClient, ulong Id, bool UseTls, int KeepAliveIntervalMs, string LogPrefix) : base(TcpClient, new ProxMessageProcessor(Server, LogPrefix), Id, UseTls, KeepAliveIntervalMs, Server.IdBase, Server.ShutdownSignaling, LogPrefix) { this.Id = Id; log = new Logger("ProximityServer.Network.IncomingClient", LogPrefix); log.Trace("(UseTls:{0},KeepAliveIntervalMs:{1})", UseTls, KeepAliveIntervalMs); messageBuilder = new ProxMessageBuilder(Server.IdBase, new List <SemVer>() { SemVer.V100 }, Config.Configuration.Keys); this.KeepAliveIntervalMs = KeepAliveIntervalMs; NextKeepAliveTime = DateTime.UtcNow.AddMilliseconds(this.KeepAliveIntervalMs); ConversationStatus = ClientConversationStatus.NoConversation; log.Trace("(-)"); }
/// <summary> /// Processes VerifyIdentityRequest message from client. /// <para>It verifies the identity's public key against the signature of the challenge provided during the start of the conversation. /// If everything is OK, the status of the conversation is upgraded to Verified.</para> /// </summary> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message.</param> /// <returns>Response message to be sent to the client.</returns> public ProxProtocolMessage ProcessMessageVerifyIdentityRequest(IncomingClient Client, ProxProtocolMessage RequestMessage) { log.Trace("()"); ProxProtocolMessage res = null; if (!CheckSessionConditions(Client, RequestMessage, ServerRole.Client | ServerRole.Neighbor, ClientConversationStatus.ConversationStarted, out res)) { log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); } ProxMessageBuilder messageBuilder = Client.MessageBuilder; VerifyIdentityRequest verifyIdentityRequest = RequestMessage.Request.ConversationRequest.VerifyIdentity; byte[] challenge = verifyIdentityRequest.Challenge.ToByteArray(); if (StructuralEqualityComparer <byte[]> .Default.Equals(challenge, Client.AuthenticationChallenge)) { if (messageBuilder.VerifySignedConversationRequestBody(RequestMessage, verifyIdentityRequest, Client.PublicKey)) { log.Debug("Identity '{0}' successfully verified its public key.", Client.IdentityId.ToHex()); Client.ConversationStatus = ClientConversationStatus.Verified; res = messageBuilder.CreateVerifyIdentityResponse(RequestMessage); } else { log.Warn("Client's challenge signature is invalid."); res = messageBuilder.CreateErrorInvalidSignatureResponse(RequestMessage); } } else { log.Warn("Challenge provided in the request does not match the challenge created by the proximity server."); res = messageBuilder.CreateErrorInvalidValueResponse(RequestMessage, "challenge"); } log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); }
/// <summary> /// Processes ListRolesRequest message from client. /// <para>Obtains a list of role servers and returns it in the response.</para> /// </summary> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message.</param> /// <returns>Response message to be sent to the client.</returns> public ProxProtocolMessage ProcessMessageListRolesRequest(IncomingClient Client, ProxProtocolMessage RequestMessage) { log.Trace("()"); ProxProtocolMessage res = null; if (!CheckSessionConditions(Client, RequestMessage, ServerRole.Primary, null, out res)) { log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); } ProxMessageBuilder messageBuilder = Client.MessageBuilder; ListRolesRequest listRolesRequest = RequestMessage.Request.SingleRequest.ListRoles; List <Iop.Proximityserver.ServerRole> roles = GetRolesFromServerComponent(); res = messageBuilder.CreateListRolesResponse(RequestMessage, roles); log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); }
/// <summary> /// Processes StartConversationRequest message from client. /// <para>Initiates a conversation with the client provided that there is a common version of the protocol supported by both sides.</para> /// </summary> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message.</param> /// <returns>Response message to be sent to the client.</returns> public ProxProtocolMessage ProcessMessageStartConversationRequest(IncomingClient Client, ProxProtocolMessage RequestMessage) { log.Trace("()"); ProxProtocolMessage res = null; if (!CheckSessionConditions(Client, RequestMessage, null, ClientConversationStatus.NoConversation, out res)) { log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); } ProxMessageBuilder messageBuilder = Client.MessageBuilder; StartConversationRequest startConversationRequest = RequestMessage.Request.ConversationRequest.Start; byte[] clientChallenge = startConversationRequest.ClientChallenge.ToByteArray(); byte[] pubKey = startConversationRequest.PublicKey.ToByteArray(); if (clientChallenge.Length == ProxMessageBuilder.ChallengeDataSize) { if ((0 < pubKey.Length) && (pubKey.Length <= ProtocolHelper.MaxPublicKeyLengthBytes)) { SemVer version; if (GetCommonSupportedVersion(startConversationRequest.SupportedVersions, out version)) { Client.PublicKey = pubKey; Client.IdentityId = Crypto.Sha256(Client.PublicKey); if (clientList.AddNetworkPeerWithIdentity(Client)) { Client.MessageBuilder.SetProtocolVersion(version); byte[] challenge = new byte[ProxMessageBuilder.ChallengeDataSize]; Crypto.Rng.GetBytes(challenge); Client.AuthenticationChallenge = challenge; Client.ConversationStatus = ClientConversationStatus.ConversationStarted; log.Debug("Client {0} conversation status updated to {1}, selected version is '{2}', client public key set to '{3}', client identity ID set to '{4}', challenge set to '{5}'.", Client.RemoteEndPoint, Client.ConversationStatus, version, Client.PublicKey.ToHex(), Client.IdentityId.ToHex(), Client.AuthenticationChallenge.ToHex()); res = messageBuilder.CreateStartConversationResponse(RequestMessage, version, Config.Configuration.Keys.PublicKey, Client.AuthenticationChallenge, clientChallenge); } else { res = messageBuilder.CreateErrorInternalResponse(RequestMessage); } } else { log.Warn("Client and server are incompatible in protocol versions."); res = messageBuilder.CreateErrorUnsupportedResponse(RequestMessage); } } else { log.Warn("Client send public key of invalid length of {0} bytes.", pubKey.Length); res = messageBuilder.CreateErrorInvalidValueResponse(RequestMessage, "publicKey"); } } else { log.Warn("Client send clientChallenge, which is {0} bytes long, but it should be {1} bytes long.", clientChallenge.Length, ProxMessageBuilder.ChallengeDataSize); res = messageBuilder.CreateErrorInvalidValueResponse(RequestMessage, "clientChallenge"); } log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); }
/// <summary> /// Validates incoming update item during the neighborhood initialization process with in-memory database of activities from the neighbor. /// </summary> /// <param name="AddItem">Update item to validate.</param> /// <param name="Index">Index of the update item in the message.</param> /// <param name="ActivityDatabase">In-memory temporary database of activities from the neighbor server that were already received and processed.</param> /// <param name="MessageBuilder">Client's network message builder.</param> /// <param name="RequestMessage">Full request message received by the client.</param> /// <param name="ErrorResponse">If the validation fails, this is filled with response message to be sent to the neighbor.</param> /// <returns>true if the validation is successful, false otherwise.</returns> public static bool ValidateInMemorySharedActivityActivityInformation(SharedActivityAddItem AddItem, int Index, Dictionary <string, NeighborActivity> ActivityDatabase, ProxMessageBuilder MessageBuilder, ProxProtocolMessage RequestMessage, out ProxProtocolMessage ErrorResponse) { log.Trace("(Index:{0})", Index); bool res = false; ErrorResponse = null; if (AddItem == null) { AddItem = new SharedActivityAddItem(); } if (AddItem.SignedActivity == null) { AddItem.SignedActivity = new SignedActivityInformation(); } if (AddItem.SignedActivity.Activity == null) { AddItem.SignedActivity.Activity = new ActivityInformation(); } byte[] ownerIdentityPubKey = AddItem.SignedActivity.Activity.OwnerPublicKey.ToByteArray(); if (ValidateSignedActivityInformation(AddItem.SignedActivity, ownerIdentityPubKey, MessageBuilder, RequestMessage, Index.ToString() + ".add.signedActivity.", true, out ErrorResponse)) { string details = null; if (ActivityDatabase.Count >= ActivityBase.MaxPrimaryActivities) { log.Debug("Target server already sent too many activities."); details = "add"; } if (details == null) { byte[] identityId = Crypto.Sha256(ownerIdentityPubKey); NeighborActivity na = new NeighborActivity() { ActivityId = AddItem.SignedActivity.Activity.Id, OwnerIdentityId = identityId }; string activityFullId = na.GetFullId(); if (ActivityDatabase.ContainsKey(activityFullId)) { log.Debug("Activity full ID '{0}' (public key '{1}') already exists.", activityFullId, ownerIdentityPubKey.ToHex()); details = "add.signedActivity.activity.id"; } } if (details == null) { res = true; } else { ErrorResponse = MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, Index.ToString() + "." + details); } } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Validates incoming SharedActivityDeleteItem update item. /// </summary> /// <para>This function does not verify whether the activity exists.</para> /// <param name="DeleteItem">Delete item to validate.</param> /// <param name="Index">Item index in the update message.</param> /// <param name="UsedFullActivityIdsInBatch">List of activity full IDs of already validated items of this batch.</param> /// <param name="MessageBuilder">Client's network message builder.</param> /// <param name="RequestMessage">Full request message received by the client.</param> /// <param name="ErrorResponse">If the validation fails, this is filled with response message to be sent to the neighbor.</param> /// <returns>true if the validation is successful, false otherwise.</returns> public static bool ValidateSharedActivityDeleteItem(SharedActivityDeleteItem DeleteItem, int Index, HashSet <string> UsedFullActivityIdsInBatch, ProxMessageBuilder MessageBuilder, ProxProtocolMessage RequestMessage, out ProxProtocolMessage ErrorResponse) { log.Trace("(Index:{0})", Index); bool res = false; ErrorResponse = null; if (DeleteItem == null) { DeleteItem = new SharedActivityDeleteItem(); } string details = null; uint activityId = DeleteItem.Id; // 0 is not a valid activity identifier. if (activityId == 0) { log.Debug("Invalid activity ID '{0}'.", activityId); details = "delete.id"; } if (details == null) { byte[] identityId = DeleteItem.OwnerNetworkId.ToByteArray(); bool identityIdValid = identityId.Length == ProtocolHelper.NetworkIdentifierLength; if (identityIdValid) { NeighborActivity na = new NeighborActivity() { ActivityId = DeleteItem.Id, OwnerIdentityId = identityId }; string activityFullId = na.GetFullId(); if (!UsedFullActivityIdsInBatch.Contains(activityFullId)) { UsedFullActivityIdsInBatch.Add(activityFullId); } else { log.Debug("Activity full ID '{0}' already processed in this request.", activityFullId); details = "delete.id"; } } else { log.Debug("Invalid owner network ID length '{0}'.", identityId.Length); details = "delete.ownerNetworkId"; } } if (details == null) { res = true; } else { ErrorResponse = MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, Index.ToString() + "." + details); } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Constructs ProtoBuf message from raw data read from the network stream. /// </summary> /// <param name="Data">Raw data to be decoded to the message.</param> /// <returns>ProtoBuf message or null if the data do not represent a valid message.</returns> public override IProtocolMessage CreateMessageFromRawData(byte[] Data) { return(ProxMessageBuilder.CreateMessageFromRawData(Data)); }
/// <summary> /// Validates incoming SharedActivityUpdateItem update item. /// </summary> /// <param name="UpdateItem">Update item to validate.</param> /// <param name="Index">Item index in the update message.</param> /// <param name="SharedActivitiesCount">Number of activities the neighbor already shares with the activity server.</param> /// <param name="UsedFullActivityIdsInBatch">List of activity full IDs of already validated items of this batch.</param> /// <param name="MessageBuilder">Client's network message builder.</param> /// <param name="RequestMessage">Full request message received by the client.</param> /// <param name="ErrorResponse">If the validation fails, this is filled with response message to be sent to the neighbor.</param> /// <returns>true if the validation is successful, false otherwise.</returns> public static bool ValidateSharedActivityUpdateItem(SharedActivityUpdateItem UpdateItem, int Index, int SharedActivitiesCount, HashSet <string> UsedFullActivityIdsInBatch, ProxMessageBuilder MessageBuilder, ProxProtocolMessage RequestMessage, out ProxProtocolMessage ErrorResponse) { log.Trace("(Index:{0},SharedActivitiesCount:{1})", Index, SharedActivitiesCount); bool res = false; ErrorResponse = null; switch (UpdateItem.ActionTypeCase) { case SharedActivityUpdateItem.ActionTypeOneofCase.Add: res = ValidateSharedActivityAddItem(UpdateItem.Add, Index, SharedActivitiesCount, UsedFullActivityIdsInBatch, MessageBuilder, RequestMessage, out ErrorResponse); break; case SharedActivityUpdateItem.ActionTypeOneofCase.Change: res = ValidateSharedActivityChangeItem(UpdateItem.Change, Index, UsedFullActivityIdsInBatch, MessageBuilder, RequestMessage, out ErrorResponse); break; case SharedActivityUpdateItem.ActionTypeOneofCase.Delete: res = ValidateSharedActivityDeleteItem(UpdateItem.Delete, Index, UsedFullActivityIdsInBatch, MessageBuilder, RequestMessage, out ErrorResponse); break; case SharedActivityUpdateItem.ActionTypeOneofCase.Refresh: res = true; break; default: ErrorResponse = MessageBuilder.CreateErrorProtocolViolationResponse(RequestMessage); res = false; break; } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Checks whether the activity search request is valid. /// </summary> /// <param name="ActivitySearchRequest">Activity search request part of the client's request message.</param> /// <param name="MessageBuilder">Client's network message builder.</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 search request is valid, false otherwise.</returns> public static bool ValidateActivitySearchRequest(ActivitySearchRequest ActivitySearchRequest, ProxMessageBuilder MessageBuilder, ProxProtocolMessage RequestMessage, out ProxProtocolMessage ErrorResponse) { log.Trace("()"); bool res = false; ErrorResponse = null; string details = null; int responseResultLimit = ProxMessageProcessor.ActivitySearchMaxResponseRecords; int totalResultLimit = ProxMessageProcessor.ActivitySearchMaxTotalRecords; bool maxResponseRecordCountValid = (1 <= ActivitySearchRequest.MaxResponseRecordCount) && (ActivitySearchRequest.MaxResponseRecordCount <= responseResultLimit); if (!maxResponseRecordCountValid) { log.Debug("Invalid maxResponseRecordCount value '{0}'.", ActivitySearchRequest.MaxResponseRecordCount); details = "maxResponseRecordCount"; } if (details == null) { bool maxTotalRecordCountValid = (1 <= ActivitySearchRequest.MaxTotalRecordCount) && (ActivitySearchRequest.MaxTotalRecordCount <= totalResultLimit) && (ActivitySearchRequest.MaxResponseRecordCount <= ActivitySearchRequest.MaxTotalRecordCount); if (!maxTotalRecordCountValid) { log.Debug("Invalid maxTotalRecordCount value '{0}'.", ActivitySearchRequest.MaxTotalRecordCount); details = "maxTotalRecordCount"; } } if ((details == null) && (ActivitySearchRequest.OwnerNetworkId.Length > 0)) { bool ownerNetworkIdValid = ActivitySearchRequest.OwnerNetworkId.Length == ProtocolHelper.NetworkIdentifierLength; if (!ownerNetworkIdValid) { log.Debug("Invalid owner network ID length '{0}'.", ActivitySearchRequest.OwnerNetworkId.Length); details = "ownerNetworkId"; } } if ((details == null) && (ActivitySearchRequest.Type != null)) { bool typeValid = Encoding.UTF8.GetByteCount(ActivitySearchRequest.Type) <= ProxMessageBuilder.MaxActivitySearchTypeLengthBytes; if (!typeValid) { log.Debug("Invalid type value length '{0}'.", ActivitySearchRequest.Type.Length); details = "type"; } } if ((details == null) && ((ActivitySearchRequest.StartNotAfter != 0) || (ActivitySearchRequest.ExpirationNotBefore != 0))) { DateTime?startNotAfter = null; DateTime?expirationNotBefore = null; if (ActivitySearchRequest.StartNotAfter != 0) { startNotAfter = ProtocolHelper.UnixTimestampMsToDateTime(ActivitySearchRequest.StartNotAfter); bool startNotAfterValid = startNotAfter != null; if (!startNotAfterValid) { log.Debug("Start not after {0} is not a valid timestamp value.", ActivitySearchRequest.StartNotAfter); details = "startNotAfter"; } } if ((details == null) && (ActivitySearchRequest.ExpirationNotBefore != 0)) { expirationNotBefore = ProtocolHelper.UnixTimestampMsToDateTime(ActivitySearchRequest.ExpirationNotBefore); bool expirationNotBeforeValid = expirationNotBefore != null; if (!expirationNotBeforeValid) { log.Debug("Expiration not before {0} is not a valid timestamp value.", ActivitySearchRequest.ExpirationNotBefore); details = "expirationNotBefore"; } else if (ActivitySearchRequest.StartNotAfter != 0) { expirationNotBeforeValid = ActivitySearchRequest.StartNotAfter <= ActivitySearchRequest.ExpirationNotBefore; if (!expirationNotBeforeValid) { log.Debug("Expiration not before {0} is smaller than start not after {1}.", ActivitySearchRequest.StartNotAfter, ActivitySearchRequest.ExpirationNotBefore); details = "expirationNotBefore"; } } } } if ((details == null) && (ActivitySearchRequest.Latitude != GpsLocation.NoLocationLocationType)) { GpsLocation locLat = new GpsLocation(ActivitySearchRequest.Latitude, 0); GpsLocation locLong = new GpsLocation(0, ActivitySearchRequest.Longitude); if (!locLat.IsValid()) { log.Debug("Latitude '{0}' is not a valid GPS latitude value.", ActivitySearchRequest.Latitude); details = "latitude"; } else if (!locLong.IsValid()) { log.Debug("Longitude '{0}' is not a valid GPS longitude value.", ActivitySearchRequest.Longitude); details = "longitude"; } } if ((details == null) && (ActivitySearchRequest.Latitude != GpsLocation.NoLocationLocationType)) { bool radiusValid = ActivitySearchRequest.Radius > 0; if (!radiusValid) { log.Debug("Invalid radius value '{0}'.", ActivitySearchRequest.Radius); details = "radius"; } } if ((details == null) && (ActivitySearchRequest.ExtraData != null)) { bool validLength = (Encoding.UTF8.GetByteCount(ActivitySearchRequest.ExtraData) <= ProxMessageBuilder.MaxActivitySearchExtraDataLengthBytes); bool extraDataValid = RegexTypeValidator.ValidateRegex(ActivitySearchRequest.ExtraData); if (!validLength || !extraDataValid) { log.Debug("Invalid extraData regular expression filter."); details = "extraData"; } } if (details == null) { res = true; } else { ErrorResponse = MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, details); } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Validates ActivityInformation structure by itself. This function does not touch database and does not validate /// anything that depends on the context. /// </summary> /// <param name="Activity">Description of the activity to validate.</param> /// <param name="OwnerIdentityPublicKey">Public key of the activity's owner identity.</param> /// <param name="MessageBuilder">Network message builder of the client who sent this activity description.</param> /// <param name="RequestMessage">Full request message from client.</param> /// <param name="ErrorPrefix">Prefix to add to the validation error details.</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 activity information is valid, false otherwise.</returns> public static bool ValidateActivityInformation(ActivityInformation Activity, byte[] OwnerIdentityPublicKey, ProxMessageBuilder MessageBuilder, ProxProtocolMessage RequestMessage, string ErrorPrefix, out ProxProtocolMessage ErrorResponse) { log.Trace("()"); bool res = false; ErrorResponse = null; string details = null; if (Activity == null) { Activity = new ActivityInformation(); } if (Activity.ProfileServerContact == null) { Activity.ProfileServerContact = new ServerContactInfo(); } SemVer version = new SemVer(Activity.Version); // Currently only supported version is 1.0.0. if (!version.Equals(SemVer.V100)) { log.Debug("Unsupported version '{0}'.", version); details = "version"; } if (details == null) { uint activityId = Activity.Id; // 0 is not a valid activity identifier. if (activityId == 0) { log.Debug("Invalid activity ID '{0}'.", activityId); details = "id"; } } if (details == null) { byte[] pubKey = Activity.OwnerPublicKey.ToByteArray(); bool pubKeyValid = (0 < pubKey.Length) && (pubKey.Length <= ProtocolHelper.MaxPublicKeyLengthBytes) && ByteArrayComparer.Equals(OwnerIdentityPublicKey, pubKey); if (!pubKeyValid) { log.Debug("Invalid public key '{0}' does not match identity public key '{1}'.", pubKey.ToHex(), OwnerIdentityPublicKey.ToHex()); details = "ownerPublicKey"; } } if (details == null) { ServerContactInfo sci = Activity.ProfileServerContact; bool networkIdValid = sci.NetworkId.Length == ProtocolHelper.NetworkIdentifierLength; IPAddress ipAddress = IPAddressExtensions.IpFromBytes(sci.IpAddress.ToByteArray()); bool ipAddressValid = (ipAddress != null) && (Config.Configuration.TestModeEnabled || !ipAddress.IsReservedOrLocal()); bool portValid = (1 <= sci.PrimaryPort) && (sci.PrimaryPort <= 65535); if (!networkIdValid || !ipAddressValid || !portValid) { log.Debug("Profile server contact's network ID is {0}, IP address is {1}, port is {2}.", networkIdValid ? "valid" : "invalid", ipAddressValid ? "valid" : "invalid", portValid ? "valid" : "invalid"); if (!networkIdValid) { details = "profileServerContact.networkId"; } else if (!ipAddressValid) { details = "profileServerContact.ipAddress"; } else if (!portValid) { details = "profileServerContact.primaryPort"; } } } if (details == null) { string activityType = Activity.Type; if (activityType == null) { activityType = ""; } int byteLen = Encoding.UTF8.GetByteCount(activityType); bool activityTypeValid = (0 < byteLen) && (byteLen <= ProxMessageBuilder.MaxActivityTypeLengthBytes); if (!activityTypeValid) { log.Debug("Activity type too long or zero length ({0} bytes, limit is {1}).", byteLen, ProxMessageBuilder.MaxActivityTypeLengthBytes); details = "type"; } } if (details == null) { GpsLocation locLat = new GpsLocation(Activity.Latitude, 0); GpsLocation locLong = new GpsLocation(0, Activity.Longitude); if (!locLat.IsValid()) { log.Debug("Latitude '{0}' is not a valid GPS latitude value.", Activity.Latitude); details = "latitude"; } else if (!locLong.IsValid()) { log.Debug("Longitude '{0}' is not a valid GPS longitude value.", Activity.Longitude); details = "longitude"; } } if (details == null) { uint precision = Activity.Precision; bool precisionValid = (0 <= precision) && (precision <= ProxMessageBuilder.MaxLocationPrecision); if (!precisionValid) { log.Debug("Precision '{0}' is not an integer between 0 and {1}.", precision, ProxMessageBuilder.MaxLocationPrecision); details = "precision"; } } if (details == null) { DateTime?startTime = ProtocolHelper.UnixTimestampMsToDateTime(Activity.StartTime); DateTime?expirationTime = ProtocolHelper.UnixTimestampMsToDateTime(Activity.ExpirationTime); if (startTime == null) { log.Debug("Invalid activity start time timestamp '{0}'.", Activity.StartTime); details = "startTime"; } else if (expirationTime == null) { log.Debug("Invalid activity expiration time timestamp '{0}'.", Activity.ExpirationTime); details = "expirationTime"; } else { if (startTime > expirationTime) { log.Debug("Activity expiration time has to be greater than or equal to its start time."); details = "expirationTime"; } else if (expirationTime.Value > DateTime.UtcNow.AddHours(ActivityBase.MaxActivityLifeTimeHours)) { log.Debug("Activity expiration time {0} is more than {1} hours in the future.", expirationTime.Value.ToString("yyyy-MM-dd HH:mm:ss"), ActivityBase.MaxActivityLifeTimeHours); details = "expirationTime"; } } } if (details == null) { string extraData = Activity.ExtraData; if (extraData == null) { extraData = ""; } // Extra data is semicolon separated 'key=value' list, max ActivityBase.MaxActivityExtraDataLengthBytes bytes long. int byteLen = Encoding.UTF8.GetByteCount(extraData); if (byteLen > ProxMessageBuilder.MaxActivityExtraDataLengthBytes) { log.Debug("Extra data too large ({0} bytes, limit is {1}).", byteLen, ProxMessageBuilder.MaxActivityExtraDataLengthBytes); details = "extraData"; } } if (details == null) { res = true; } else { ErrorResponse = MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, ErrorPrefix + details); } log.Trace("(-):{0}", res); 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> /// Converts an IoP Proximity Server Network protocol message to a binary format. /// </summary> /// <param name="Data">IoP Proximity Server Network protocol message.</param> /// <returns>Binary representation of the message to be sent over the network.</returns> public override byte[] MessageToByteArray(IProtocolMessage Data) { return(ProxMessageBuilder.MessageToByteArray(Data)); }
/// <summary> /// Processing of a message received from a client. /// </summary> /// <param name="Client">TCP client who send the message.</param> /// <param name="IncomingMessage">Full ProtoBuf message to be processed.</param> /// <returns>true if the conversation with the client should continue, false if a protocol violation error occurred and the client should be disconnected.</returns> public async Task <bool> ProcessMessageAsync(ClientBase Client, IProtocolMessage IncomingMessage) { IncomingClient client = (IncomingClient)Client; ProxProtocolMessage incomingMessage = (ProxProtocolMessage)IncomingMessage; ProxMessageBuilder messageBuilder = client.MessageBuilder; bool res = false; log.Debug("()"); try { // Update time until this client's connection is considered inactive. client.NextKeepAliveTime = DateTime.UtcNow.AddMilliseconds(client.KeepAliveIntervalMs); log.Trace("Client ID {0} NextKeepAliveTime updated to {1}.", client.Id.ToHex(), client.NextKeepAliveTime.ToString("yyyy-MM-dd HH:mm:ss")); log.Trace("Received message type is {0}, message ID is {1}.", incomingMessage.MessageTypeCase, incomingMessage.Id); switch (incomingMessage.MessageTypeCase) { case Message.MessageTypeOneofCase.Request: { ProxProtocolMessage responseMessage = messageBuilder.CreateErrorProtocolViolationResponse(incomingMessage); Request request = incomingMessage.Request; log.Trace("Request conversation type is {0}.", request.ConversationTypeCase); switch (request.ConversationTypeCase) { case Request.ConversationTypeOneofCase.SingleRequest: { SingleRequest singleRequest = request.SingleRequest; SemVer version = new SemVer(singleRequest.Version); log.Trace("Single request type is {0}, version is {1}.", singleRequest.RequestTypeCase, version); if (!version.IsValid()) { responseMessage.Response.Details = "version"; break; } switch (singleRequest.RequestTypeCase) { case SingleRequest.RequestTypeOneofCase.Ping: responseMessage = ProcessMessagePingRequest(client, incomingMessage); break; case SingleRequest.RequestTypeOneofCase.ListRoles: responseMessage = ProcessMessageListRolesRequest(client, incomingMessage); break; default: log.Warn("Invalid request type '{0}'.", singleRequest.RequestTypeCase); break; } break; } case Request.ConversationTypeOneofCase.ConversationRequest: { ConversationRequest conversationRequest = request.ConversationRequest; log.Trace("Conversation request type is {0}.", conversationRequest.RequestTypeCase); if (conversationRequest.Signature.Length > 0) { log.Trace("Conversation signature is '{0}'.", conversationRequest.Signature.ToByteArray().ToHex()); } else { log.Trace("No signature provided."); } switch (conversationRequest.RequestTypeCase) { case ConversationRequest.RequestTypeOneofCase.Start: responseMessage = ProcessMessageStartConversationRequest(client, incomingMessage); break; case ConversationRequest.RequestTypeOneofCase.VerifyIdentity: responseMessage = ProcessMessageVerifyIdentityRequest(client, incomingMessage); break; default: log.Warn("Invalid request type '{0}'.", conversationRequest.RequestTypeCase); // Connection will be closed in ReceiveMessageLoop. break; } break; } default: log.Error("Unknown conversation type '{0}'.", request.ConversationTypeCase); // Connection will be closed in ReceiveMessageLoop. break; } if (responseMessage != null) { // Send response to client. res = await client.SendMessageAsync(responseMessage); if (res) { // If the message was sent successfully to the target, we close the connection in case it was a protocol violation error response. if (responseMessage.MessageTypeCase == Message.MessageTypeOneofCase.Response) { res = responseMessage.Response.Status != Status.ErrorProtocolViolation; } } } else { // If there is no response to send immediately to the client, // we want to keep the connection open. res = true; } break; } case Message.MessageTypeOneofCase.Response: { Response response = incomingMessage.Response; log.Trace("Response status is {0}, details are '{1}', conversation type is {2}.", response.Status, response.Details, response.ConversationTypeCase); // Find associated request. If it does not exist, disconnect the client as it // send a response without receiving a request. This is protocol violation, // but as this is a reponse, we have no how to inform the client about it, // so we just disconnect it. UnfinishedRequest unfinishedRequest = client.GetAndRemoveUnfinishedRequest(incomingMessage.Id); if ((unfinishedRequest != null) && (unfinishedRequest.RequestMessage != null)) { ProxProtocolMessage requestMessage = (ProxProtocolMessage)unfinishedRequest.RequestMessage; Request request = requestMessage.Request; // We now check whether the response message type corresponds with the request type. // This is only valid if the status is Ok. If the message types do not match, we disconnect // for the protocol violation again. bool typeMatch = false; bool isErrorResponse = response.Status != Status.Ok; if (!isErrorResponse) { if (response.ConversationTypeCase == Response.ConversationTypeOneofCase.SingleResponse) { typeMatch = (request.ConversationTypeCase == Request.ConversationTypeOneofCase.SingleRequest) && ((int)response.SingleResponse.ResponseTypeCase == (int)request.SingleRequest.RequestTypeCase); } else { typeMatch = (request.ConversationTypeCase == Request.ConversationTypeOneofCase.ConversationRequest) && ((int)response.ConversationResponse.ResponseTypeCase == (int)request.ConversationRequest.RequestTypeCase); } } else { typeMatch = true; } if (typeMatch) { // Now we know the types match, so we can rely on request type even if response is just an error. switch (request.ConversationTypeCase) { case Request.ConversationTypeOneofCase.SingleRequest: { SingleRequest singleRequest = request.SingleRequest; switch (singleRequest.RequestTypeCase) { default: log.Warn("Invalid conversation type '{0}' of the corresponding request.", request.ConversationTypeCase); // Connection will be closed in ReceiveMessageLoop. break; } break; } case Request.ConversationTypeOneofCase.ConversationRequest: { ConversationRequest conversationRequest = request.ConversationRequest; switch (conversationRequest.RequestTypeCase) { default: log.Warn("Invalid type '{0}' of the corresponding request.", conversationRequest.RequestTypeCase); // Connection will be closed in ReceiveMessageLoop. break; } break; } default: log.Error("Unknown conversation type '{0}' of the corresponding request.", request.ConversationTypeCase); // Connection will be closed in ReceiveMessageLoop. break; } } else { log.Warn("Message type of the response ID {0} does not match the message type of the request ID {1}, the connection will be closed.", incomingMessage.Id, unfinishedRequest.RequestMessage.Id); // Connection will be closed in ReceiveMessageLoop. } } else { log.Warn("No unfinished request found for incoming response ID {0}, the connection will be closed.", incomingMessage.Id); // Connection will be closed in ReceiveMessageLoop. } break; } default: log.Error("Unknown message type '{0}', connection to the client will be closed.", incomingMessage.MessageTypeCase); await SendProtocolViolation(client); // Connection will be closed in ReceiveMessageLoop. break; } } catch (Exception e) { log.Error("Exception occurred, connection to the client will be closed: {0}", e.ToString()); await SendProtocolViolation(client); // Connection will be closed in ReceiveMessageLoop. } if (res && client.ForceDisconnect) { log.Debug("Connection to the client will be forcefully closed."); res = false; } log.Debug("(-):{0}", res); return(res); }
/// <summary> /// Validates incoming SharedActivityChangeItem update item. /// <para>This function does not verify whether the activity exists. /// It also does not verify whether the new activity start time and expiration time are correct if they are not both changed.</para> /// </summary> /// <param name="ChangeItem">Change item to validate.</param> /// <param name="Index">Item index in the update message.</param> /// <param name="UsedFullActivityIdsInBatch">List of activity full IDs of already validated items of this batch.</param> /// <param name="MessageBuilder">Client's network message builder.</param> /// <param name="RequestMessage">Full request message received by the client.</param> /// <param name="ErrorResponse">If the validation fails, this is filled with response message to be sent to the neighbor.</param> /// <returns>true if the validation is successful, false otherwise.</returns> public static bool ValidateSharedActivityChangeItem(SharedActivityChangeItem ChangeItem, int Index, HashSet <string> UsedFullActivityIdsInBatch, ProxMessageBuilder MessageBuilder, ProxProtocolMessage RequestMessage, out ProxProtocolMessage ErrorResponse) { log.Trace("(Index:{0})", Index); bool res = false; ErrorResponse = null; if (ChangeItem == null) { ChangeItem = new SharedActivityChangeItem(); } if (ChangeItem.SignedActivity == null) { ChangeItem.SignedActivity = new SignedActivityInformation(); } if (ChangeItem.SignedActivity.Activity == null) { ChangeItem.SignedActivity.Activity = new ActivityInformation(); } byte[] ownerIdentityPubKey = ChangeItem.SignedActivity.Activity.OwnerPublicKey.ToByteArray(); if (ValidateSignedActivityInformation(ChangeItem.SignedActivity, ownerIdentityPubKey, MessageBuilder, RequestMessage, Index.ToString() + ".change.signedActivity.", true, out ErrorResponse)) { string details = null; byte[] identityId = Crypto.Sha256(ownerIdentityPubKey); NeighborActivity na = new NeighborActivity() { ActivityId = ChangeItem.SignedActivity.Activity.Id, OwnerIdentityId = identityId }; string activityFullId = na.GetFullId(); if (!UsedFullActivityIdsInBatch.Contains(activityFullId)) { UsedFullActivityIdsInBatch.Add(activityFullId); } else { log.Debug("Activity full ID '{0}' already processed in this request.", activityFullId); details = "change.signedActivity.activity.id"; } if (details == null) { res = true; } else { ErrorResponse = MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, Index.ToString() + "." + details); } } log.Trace("(-):{0}", res); return(res); }