/// <summary> /// Sends ListRolesRequest to the server and reads a response. /// </summary> /// <returns>ListRolesResponse message if the function succeeds, null otherwise.</returns> public async Task <ListRolesResponse> SendListRolesRequest() { log.Trace("()"); ListRolesResponse res = null; PsProtocolMessage requestMessage = MessageBuilder.CreateListRolesRequest(); if (await SendMessageAsync(requestMessage)) { PsProtocolMessage responseMessage = await ReceiveMessageAsync(); if (CheckResponseMessage(requestMessage, responseMessage)) { res = responseMessage.Response.SingleResponse.ListRoles; } else { log.Warn("Received unexpected or invalid message."); } } else { log.Warn("Unable to send message."); } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Establishes a hosting agreement for the client's identity with specific identity type using the already opened connection to the profile server. /// </summary> /// <param name="IdentityType">Identity type of the new identity.</param> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> EstablishProfileHostingAsync(string IdentityType = null) { log.Trace("()"); bool startConversationOk = await StartConversationAsync(); HostingPlanContract contract = null; if (IdentityType != null) { contract = new HostingPlanContract(); contract.IdentityType = IdentityType; } PsProtocolMessage requestMessage = messageBuilder.CreateRegisterHostingRequest(contract); await SendMessageAsync(requestMessage); PsProtocolMessage responseMessage = await ReceiveMessageAsync(); bool idOk = responseMessage.Id == requestMessage.Id; bool statusOk = responseMessage.Response.Status == Status.Ok; bool registerHostingOk = idOk && statusOk; bool res = startConversationOk && registerHostingOk; log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Creates a new relay connection from a caller to a callee using a specific application service. /// </summary> /// <param name="Caller">Network client of the caller.</param> /// <param name="Callee">Network client of the callee.</param> /// <param name="ServiceName">Name of the application service of the callee that is being used for the call.</param> /// <param name="RequestMessage">CallIdentityApplicationServiceRequest message that the caller send in order to initiate the call.</param> public RelayConnection(IncomingClient Caller, IncomingClient Callee, string ServiceName, PsProtocolMessage RequestMessage) { lockObject = new SemaphoreSlim(1); id = Guid.NewGuid(); string logPrefix = string.Format("[{0}:{1}] ", id, ServiceName); string logName = "ProfileServer.Network.ClientList"; log = new Logger(logName, logPrefix); log.Trace("(Caller.Id:{0},Callee.Id:{1},ServiceName:'{2}')", Caller.Id.ToHex(), Callee.Id.ToHex(), ServiceName); serviceName = ServiceName; caller = Caller; callee = Callee; pendingMessage = RequestMessage; callerToken = Guid.NewGuid(); calleeToken = Guid.NewGuid(); log.Trace("Caller token is '{0}'.", callerToken); log.Trace("Callee token is '{0}'.", calleeToken); status = RelayConnectionStatus.WaitingForCalleeResponse; // Relay is created by caller's request, it will expire if the callee does not reply within reasonable time. timeoutTimer = new Timer(TimeoutCallback, status, CalleeResponseCallNotificationDelayMaxSeconds * 1000, Timeout.Infinite); log.Trace("(-)"); }
/// <summary> /// Performs an identity verification process for the client's identity using the already opened connection to the server. /// </summary> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> VerifyIdentityAsync() { log.Trace("()"); bool res = false; if (await StartConversationAsync()) { PsProtocolMessage requestMessage = MessageBuilder.CreateVerifyIdentityRequest(serverChallenge); if (await SendMessageAsync(requestMessage)) { PsProtocolMessage responseMessage = await ReceiveMessageAsync(); if (CheckResponseMessage(requestMessage, responseMessage)) { res = true; } else { log.Warn("Received unexpected or invalid message."); } } else { log.Warn("Unable to send message."); } } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Reads and parses protocol message from the network stream. /// </summary> /// <returns>Parsed protocol message or null if the function fails.</returns> public async Task <PsProtocolMessage> ReceiveMessageAsync() { log.Trace("()"); PsProtocolMessage res = null; byte[] header = new byte[ProtocolHelper.HeaderSize]; int headerBytesRead = 0; int remain = header.Length; bool done = false; log.Trace("Reading message header."); while (!done && (headerBytesRead < header.Length)) { int readAmount = await stream.ReadAsync(header, headerBytesRead, remain); if (readAmount == 0) { log.Trace("Connection to server closed while reading the header."); done = true; break; } headerBytesRead += readAmount; remain -= readAmount; } uint messageSize = BitConverter.ToUInt32(header, 1); log.Trace("Message body size is {0} bytes.", messageSize); byte[] messageBytes = new byte[ProtocolHelper.HeaderSize + messageSize]; Array.Copy(header, messageBytes, header.Length); remain = (int)messageSize; int messageBytesRead = 0; while (!done && (messageBytesRead < messageSize)) { int readAmount = await stream.ReadAsync(messageBytes, ProtocolHelper.HeaderSize + messageBytesRead, remain); if (readAmount == 0) { log.Trace("Connection to server closed while reading the body."); done = true; break; } messageBytesRead += readAmount; remain -= readAmount; } res = new PsProtocolMessage(MessageWithHeader.Parser.ParseFrom(messageBytes).Body); string resStr = res.ToString(); log.Trace("(-):\n{0}", resStr.Substring(0, Math.Min(resStr.Length, 512))); return(res); }
/// <summary> /// Reads and parses protocol message from the network stream. /// </summary> /// <returns>Parsed protocol message or null if the function fails.</returns> public async Task <PsProtocolMessage> ReceiveMessageAsync() { log.Trace("()"); PsProtocolMessage res = null; using (CancellationTokenSource readTimeoutTokenSource = new CancellationTokenSource(60000), timeoutShutdownTokenSource = CancellationTokenSource.CreateLinkedTokenSource(readTimeoutTokenSource.Token, shutdownCancellationToken)) { RawMessageReader messageReader = new RawMessageReader(Stream); RawMessageResult rawMessage = await messageReader.ReceiveMessageAsync(timeoutShutdownTokenSource.Token); if (rawMessage.Data != null) { res = (PsProtocolMessage)CreateMessageFromRawData(rawMessage.Data); if (res.MessageTypeCase == Message.MessageTypeOneofCase.Response) { lastResponseStatus = res.Response.Status; lastResponseDetails = res.Response.Details; } } } ForceDisconnect = res == null; log.Trace("(-):ForceDisconnect={0}", ForceDisconnect, res != null ? "Message" : "null"); return(res); }
/// <summary> /// Starts conversation with the server the client is connected to and checks whether the server response contains expected values. /// </summary> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> StartConversationAsync() { log.Trace("()"); PsProtocolMessage requestMessage = CreateStartConversationRequest(); await SendMessageAsync(requestMessage); PsProtocolMessage responseMessage = await ReceiveMessageAsync(); bool idOk = responseMessage.Id == requestMessage.Id; bool statusOk = responseMessage.Response.Status == Status.Ok; bool challengeVerifyOk = VerifyProfileServerChallengeSignature(responseMessage); SemVer receivedVersion = new SemVer(responseMessage.Response.ConversationResponse.Start.Version); bool versionOk = receivedVersion.Equals(new SemVer(messageBuilder.Version)); bool pubKeyLenOk = responseMessage.Response.ConversationResponse.Start.PublicKey.Length == 32; bool challengeOk = responseMessage.Response.ConversationResponse.Start.Challenge.Length == 32; profileServerKey = responseMessage.Response.ConversationResponse.Start.PublicKey.ToByteArray(); challenge = responseMessage.Response.ConversationResponse.Start.Challenge.ToByteArray(); bool res = idOk && statusOk && challengeVerifyOk && versionOk && pubKeyLenOk && challengeOk; log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Sends StopNeighborhoodUpdatesRequest to the server and reads a response. /// </summary> /// <param name="RequestMessage">Request message to send.</param> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> SendStopNeighborhoodUpdates(PsProtocolMessage RequestMessage) { log.Trace("()"); bool res = false; PsProtocolMessage requestMessage = RequestMessage; if (await SendMessageAsync(requestMessage)) { PsProtocolMessage responseMessage = await ReceiveMessageAsync(); if (CheckResponseMessage(requestMessage, responseMessage)) { res = true; } else { if (lastResponseStatus != Status.ErrorNotFound) { log.Warn("Received unexpected or invalid message."); } } } else { log.Warn("Unable to send message."); } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Sends StartNeighborhoodInitializationRequest to the server and reads a response. /// </summary> /// <param name="PrimaryPort">Primary interface port of the requesting profile server.</param> /// <param name="SrNeighborPort">Neighbors interface port of the requesting profile server.</param> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> StartNeighborhoodInitializationAsync(uint PrimaryPort, uint SrNeighborPort) { log.Trace("()"); bool res = false; Config config = (Config)Base.ComponentDictionary[ConfigBase.ComponentName]; PsProtocolMessage requestMessage = MessageBuilder.CreateStartNeighborhoodInitializationRequest(PrimaryPort, SrNeighborPort, config.ExternalServerAddress); if (await SendMessageAsync(requestMessage)) { PsProtocolMessage responseMessage = await ReceiveMessageAsync(); if (CheckResponseMessage(requestMessage, responseMessage)) { res = true; } else { if (lastResponseStatus != Status.ErrorBusy) { log.Warn("Received unexpected or invalid message."); } } } else { log.Warn("Unable to send message."); } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Generates client's challenge and creates start conversation request with it. /// </summary> /// <returns>StartConversationRequest message that is ready to be sent to the profile server.</returns> public PsProtocolMessage CreateStartConversationRequest() { clientChallenge = new byte[PsMessageBuilder.ChallengeDataSize]; Crypto.Rng.GetBytes(clientChallenge); PsProtocolMessage res = messageBuilder.CreateStartConversationRequest(clientChallenge); return(res); }
/// <summary> /// Sends IoP protocol message over the network stream. /// </summary> /// <param name="Data">Message to send.</param> public async Task SendMessageAsync(PsProtocolMessage Data) { string dataStr = Data.ToString(); log.Trace("()\n{0}", dataStr.Substring(0, Math.Min(dataStr.Length, 512))); byte[] rawData = PsMessageBuilder.MessageToByteArray(Data); await stream.WriteAsync(rawData, 0, rawData.Length); log.Trace("(-)"); }
/// <summary> /// Verifies whether the server successfully signed the correct start conversation challenge. /// </summary> /// <param name="StartConversationResponse">StartConversationResponse received from the server.</param> /// <returns>true if the signature is valid, false otherwise.</returns> public bool VerifyServerChallengeSignature(PsProtocolMessage StartConversationResponse) { log.Trace("()"); byte[] receivedChallenge = StartConversationResponse.Response.ConversationResponse.Start.ClientChallenge.ToByteArray(); bool res = StructuralEqualityComparer <byte[]> .Default.Equals(receivedChallenge, clientChallenge) && MessageBuilder.VerifySignedConversationResponseBodyPart(StartConversationResponse, receivedChallenge, serverKey); log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Verifies whether the profile server successfully signed the correct start conversation challenge. /// </summary> /// <param name="StartConversationResponse">StartConversationResponse received from the profile server.</param> /// <returns>true if the signature is valid, false otherwise.</returns> public bool VerifyProfileServerChallengeSignature(PsProtocolMessage StartConversationResponse) { log.Trace("()"); byte[] receivedChallenge = StartConversationResponse.Response.ConversationResponse.Start.ClientChallenge.ToByteArray(); byte[] profileServerPublicKey = StartConversationResponse.Response.ConversationResponse.Start.PublicKey.ToByteArray(); bool res = (StructuralComparisons.StructuralComparer.Compare(receivedChallenge, clientChallenge) == 0) && messageBuilder.VerifySignedConversationResponseBodyPart(StartConversationResponse, receivedChallenge, profileServerPublicKey); log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Starts conversation with the server the client is connected to and checks whether the server response contains expected values. /// </summary> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> StartConversationAsync() { log.Trace("()"); bool res = false; PsProtocolMessage requestMessage = CreateStartConversationRequest(); if (await SendMessageAsync(requestMessage)) { PsProtocolMessage responseMessage = await ReceiveMessageAsync(); if (CheckResponseMessage(requestMessage, responseMessage)) { try { SemVer receivedVersion = new SemVer(responseMessage.Response.ConversationResponse.Start.Version); bool versionOk = receivedVersion.Equals(new SemVer(MessageBuilder.Version)); bool pubKeyLenOk = responseMessage.Response.ConversationResponse.Start.PublicKey.Length == ProtocolHelper.NetworkIdentifierLength; bool challengeOk = responseMessage.Response.ConversationResponse.Start.Challenge.Length == PsMessageBuilder.ChallengeDataSize; serverKey = responseMessage.Response.ConversationResponse.Start.PublicKey.ToByteArray(); serverId = Crypto.Sha256(serverKey); serverChallenge = responseMessage.Response.ConversationResponse.Start.Challenge.ToByteArray(); bool challengeVerifyOk = VerifyServerChallengeSignature(responseMessage); res = versionOk && pubKeyLenOk && challengeOk && challengeVerifyOk; } catch { log.Warn("Received unexpected or invalid message."); } } else { log.Warn("Received unexpected or invalid message."); } } else { log.Warn("Unable to send message."); } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Cancels a agreement with the profile server, to which there already is an opened connection. /// </summary> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> CancelHostingAgreementAsync() { log.Trace("()"); PsProtocolMessage requestMessage = messageBuilder.CreateCancelHostingAgreementRequest(null); await SendMessageAsync(requestMessage); PsProtocolMessage responseMessage = await ReceiveMessageAsync(); bool idOk = responseMessage.Id == requestMessage.Id; bool statusOk = responseMessage.Response.Status == Status.Ok; bool res = idOk && statusOk; log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Initializes a new identity profile on the profile server. /// </summary> /// <param name="Name">Name of the profile.</param> /// <param name="Image">Optionally, a profile image data.</param> /// <param name="Location">GPS location of the identity.</param> /// <param name="ExtraData">Optionally, identity's extra data.</param> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> InitializeProfileAsync(string Name, byte[] Image, GpsLocation Location, string ExtraData) { log.Trace("()"); PsProtocolMessage requestMessage = messageBuilder.CreateUpdateProfileRequest(SemVer.V100, Name, Image, Location, ExtraData); await SendMessageAsync(requestMessage); PsProtocolMessage responseMessage = await ReceiveMessageAsync(); bool idOk = responseMessage.Id == requestMessage.Id; bool statusOk = responseMessage.Response.Status == Status.Ok; bool res = idOk && statusOk; log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Performs a check-in process for the client's identity using the already opened connection to the profile server. /// </summary> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> CheckInAsync() { log.Trace("()"); bool startConversationOk = await StartConversationAsync(); PsProtocolMessage requestMessage = messageBuilder.CreateCheckInRequest(challenge); await SendMessageAsync(requestMessage); PsProtocolMessage responseMessage = await ReceiveMessageAsync(); bool idOk = responseMessage.Id == requestMessage.Id; bool statusOk = responseMessage.Response.Status == Status.Ok; bool checkInOk = idOk && statusOk; bool res = startConversationOk && checkInOk; log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Obtains its own thumbnail picture from the profile server. /// </summary> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> GetProfileThumbnailImage() { log.Trace("()"); PsProtocolMessage requestMessage = messageBuilder.CreateGetIdentityInformationRequest(identityId, false, true, false); await SendMessageAsync(requestMessage); PsProtocolMessage responseMessage = await ReceiveMessageAsync(); bool idOk = responseMessage.Id == requestMessage.Id; bool statusOk = responseMessage.Response.Status == Status.Ok; thumbnailImage = responseMessage.Response.SingleResponse.GetIdentityInformation.ThumbnailImage.ToByteArray(); if (thumbnailImage.Length == 0) { thumbnailImage = null; } bool res = idOk && statusOk && (thumbnailImage != null); log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Handles situation when the callee replied to the incoming call notification request. /// </summary> /// <param name="ResponseMessage">Full response message from the callee.</param> /// <param name="Request">Unfinished call request message of the caller that corresponds to the response message.</param> /// <returns></returns> public async Task <bool> CalleeRepliedToIncomingCallNotification(PsProtocolMessage ResponseMessage, UnfinishedRequest Request) { log.Trace("()"); bool res = false; bool destroyRelay = false; IncomingClient clientToSendMessage = null; PsProtocolMessage messageToSend = null; await lockObject.WaitAsync(); if (status == RelayConnectionStatus.WaitingForCalleeResponse) { CancelTimeoutTimerLocked(); // The caller is still connected and waiting for an answer to its call request. if (ResponseMessage.Response.Status == Status.Ok) { // The callee is now expected to connect to clAppService with its token. // We need to inform caller that the callee accepted the call. // This is option 4) from ProcessMessageCallIdentityApplicationServiceRequestAsync. messageToSend = caller.MessageBuilder.CreateCallIdentityApplicationServiceResponse(pendingMessage, callerToken.ToByteArray()); clientToSendMessage = caller; pendingMessage = null; caller = null; callee = null; status = RelayConnectionStatus.WaitingForFirstInitMessage; log.Debug("Relay '{0}' status has been changed to {1}.", id, status); /// Install timeoutTimer to expire if the first client does not connect to clAppService port /// and send its initialization message within reasonable time. timeoutTimer = new Timer(TimeoutCallback, RelayConnectionStatus.WaitingForFirstInitMessage, FirstAppServiceInitializationMessageDelayMaxSeconds * 1000, Timeout.Infinite); res = true; } else { // The callee rejected the call or reported other error. // These are options 3) and 2) from ProcessMessageCallIdentityApplicationServiceRequestAsync. if (ResponseMessage.Response.Status == Status.ErrorRejected) { log.Debug("Callee ID '{0}' rejected the call from caller identity ID '{1}', relay '{2}'.", callee.Id.ToHex(), caller.Id.ToHex(), id); messageToSend = caller.MessageBuilder.CreateErrorRejectedResponse(pendingMessage); } else { log.Warn("Callee ID '0} sent error response '{1}' for call request from caller identity ID {2}, relay '{3}'.", callee.Id.ToHex(), ResponseMessage.Response.Status, caller.Id.ToHex(), id); messageToSend = caller.MessageBuilder.CreateErrorNotAvailableResponse(pendingMessage); } clientToSendMessage = caller; destroyRelay = true; } } else { // The relay has probably been destroyed, or something bad happened to it. // We take no action here regardless of what the callee's response is. // If it rejected the call, there is nothing to be done since we do not have // any connection to the caller anymore. log.Debug("Relay status is {0}, nothing to be done.", status); } lockObject.Release(); if (messageToSend != null) { if (await clientToSendMessage.SendMessageAsync(messageToSend)) { log.Debug("Response to call initiation request sent to the caller ID {0}.", clientToSendMessage.Id.ToHex()); } else { log.Debug("Unable to reponse to call initiation request to the caller ID {0}.", clientToSendMessage.Id.ToHex()); } } if (destroyRelay) { Server serverComponent = (Server)Base.ComponentDictionary[Server.ComponentName]; await serverComponent.RelayList.DestroyNetworkRelay(this); } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Checks whether the incoming response is a reponse to a specific request and if the reported status is OK. /// <para>This function sets the last error.</para> /// </summary> /// <param name="RequestMessage">Request message for which the response was received.</param> /// <param name="ResponseMessage">Response message received.</param> /// <returns>true if the response is valid and its status is OK.</returns> public bool CheckResponseMessage(PsProtocolMessage RequestMessage, PsProtocolMessage ResponseMessage) { log.Trace("()"); bool res = false; if (ResponseMessage != null) { if (ResponseMessage.Id == RequestMessage.Id) { if (ResponseMessage.MessageTypeCase == Message.MessageTypeOneofCase.Response) { Response response = ResponseMessage.Response; if (response.Status == Status.Ok) { switch (response.ConversationTypeCase) { case Response.ConversationTypeOneofCase.SingleResponse: if (RequestMessage.Request.ConversationTypeCase == Request.ConversationTypeOneofCase.SingleRequest) { SingleRequest.RequestTypeOneofCase requestType = RequestMessage.Request.SingleRequest.RequestTypeCase; if (response.SingleResponse.ResponseTypeCase.ToString() == requestType.ToString()) { res = true; } else { log.Debug("Single response type {0} does not match single request type {1}.", response.SingleResponse.ResponseTypeCase, requestType); } } else { log.Debug("Response message conversation type {0} does not match request message conversation type {1}.", response.ConversationTypeCase, RequestMessage.Request.ConversationTypeCase); } break; case Response.ConversationTypeOneofCase.ConversationResponse: if (RequestMessage.Request.ConversationTypeCase == Request.ConversationTypeOneofCase.ConversationRequest) { ConversationRequest.RequestTypeOneofCase requestType = RequestMessage.Request.ConversationRequest.RequestTypeCase; if (response.ConversationResponse.ResponseTypeCase.ToString() == requestType.ToString()) { res = true; } else { log.Debug("Conversation response type {0} does not match conversation request type {1}.", response.ConversationResponse.ResponseTypeCase, requestType); } } else { log.Debug("Response message conversation type {0} does not match request message conversation type {1}.", response.ConversationTypeCase, RequestMessage.Request.ConversationTypeCase); } break; default: log.Error("Invalid response conversation type {0}.", ResponseMessage.Response.ConversationTypeCase); break; } } else { log.Debug("Response message status is {0}.", ResponseMessage.Response.Status); } } else { log.Debug("Received message is not response, its message type is {0}.", ResponseMessage.MessageTypeCase); } } else { log.Debug("Response message ID {0} does not match request message ID {1}.", ResponseMessage.Id, RequestMessage.Id); } } else { log.Debug("Response message is null."); } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Checks whether the contract received from user with hosting registration request is valid. /// <para>This function does not verify that the hosting plan exists.</para> /// </summary> /// <param name="IdentityPublicKey">Public key of the identity that wants to register hosting.</param> /// <param name="Contract">Description of the contract.</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 profile update request can be applied, false otherwise.</returns> public static bool ValidateRegisterHostingRequest(byte[] IdentityPublicKey, HostingPlanContract Contract, PsMessageBuilder MessageBuilder, PsProtocolMessage RequestMessage, out PsProtocolMessage ErrorResponse) { log.Trace("(IdentityPublicKey:'{0}')", IdentityPublicKey.ToHex()); bool res = false; ErrorResponse = null; string details = null; if (Contract == null) { Contract = new HostingPlanContract(); } if (!MessageBuilder.VerifySignedConversationRequestBodyPart(RequestMessage, Contract.ToByteArray(), IdentityPublicKey)) { log.Debug("Contract signature is invalid."); ErrorResponse = MessageBuilder.CreateErrorInvalidSignatureResponse(RequestMessage); details = ""; } if (details == null) { byte[] contractPubKey = Contract.IdentityPublicKey.ToByteArray(); bool publicKeyValid = ByteArrayComparer.Equals(contractPubKey, IdentityPublicKey); if (!publicKeyValid) { log.Debug("Contract public key '{0}' does not match client's public key '{1}'.", contractPubKey.ToHex(), IdentityPublicKey.ToHex()); details = "contract.identityPublicKey"; } } if (details == null) { DateTime?startTime = ProtocolHelper.UnixTimestampMsToDateTime(Contract.StartTime); bool startTimeValid = (startTime != null) && ((startTime.Value - DateTime.UtcNow).TotalMinutes >= -60); if (!startTimeValid) { if (startTime == null) { log.Debug("Invalid contract start time timestamp {0}.", Contract.StartTime); } else { log.Debug("Contract start time {0} is more than 1 hour in the past.", startTime.Value.ToString("yyyy-MM-dd HH:mm:ss")); } details = "contract.startTime"; } } if (details == null) { int typeSize = Encoding.UTF8.GetByteCount(Contract.IdentityType); bool typeValid = (0 < typeSize) && (typeSize <= IdentityBase.MaxProfileTypeLengthBytes); if (!typeValid) { log.Debug("Invalid contract identity type size in bytes {0}.", typeSize); details = "contract.identityType"; } } if (details == null) { res = true; } else { if (ErrorResponse == null) { ErrorResponse = MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, details); } } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Callback routine that is called once the timeoutTimer expires. /// <para> /// If relay status is WaitingForCalleeResponse, the callee has to reply to the incoming call notification /// within a reasonable time. If it does the timer is cancelled. If it does not, the timeout occurs. /// </para> /// <para> /// If relay status is WaitingForFirstInitMessage, both clients are expected to connect to clAppService port /// and send an initial message over that service. The timeoutTimer expires when none of the clients /// connects to clAppService port and sends its initialization message within a reasonable time. /// </para> /// <para> /// Then if relay status is WaitingForSecondInitMessage, the profile server receives a message from the first client /// on clAppService port, it starts the timer again, which now expires if the second client does not connect /// and send its initial message within a reasonable time. /// </para> /// </summary> /// <param name="state">Status of the relay when the timer was installed.</param> private async void TimeoutCallback(object State) { LogDiagnosticContext.Start(); RelayConnectionStatus previousStatus = (RelayConnectionStatus)State; log.Trace("(State:{0})", previousStatus); IncomingClient clientToSendMessage = null; PsProtocolMessage messageToSend = null; bool destroyRelay = false; await lockObject.WaitAsync(); if (timeoutTimer != null) { switch (status) { case RelayConnectionStatus.WaitingForCalleeResponse: { // The caller requested the call and the callee was notified. // The callee failed to send us response on time, this is situation 2) // from ProcessMessageCallIdentityApplicationServiceRequestAsync. // We send ERROR_NOT_AVAILABLE to the caller and destroy the relay. log.Debug("Callee failed to reply to the incoming call notification, closing relay."); clientToSendMessage = caller; messageToSend = caller.MessageBuilder.CreateErrorNotAvailableResponse(pendingMessage); break; } case RelayConnectionStatus.WaitingForFirstInitMessage: { // Neither client joined the channel on time, nothing to do, just destroy the relay. log.Debug("None of the clients joined the relay on time, closing relay."); break; } case RelayConnectionStatus.WaitingForSecondInitMessage: { // One client is waiting for the other one to join, but the other client failed to join on time. // We send ERROR_NOT_FOUND to the waiting client and close its connection. log.Debug("{0} failed to join the relay on time, closing relay.", callee != null ? "Caller" : "Callee"); clientToSendMessage = callee != null ? callee : caller; messageToSend = clientToSendMessage.MessageBuilder.CreateErrorNotFoundResponse(pendingMessage); break; } default: log.Debug("Time out triggered while the relay status was {0}.", status); break; } // In case of any timeouts, we just destroy the relay. destroyRelay = true; } else { log.Debug("Timeout timer of relay '{0}' has been destroyed, no action taken.", id); } lockObject.Release(); if (messageToSend != null) { if (!await clientToSendMessage.SendMessageAsync(messageToSend)) { log.Warn("Unable to send message to the client ID {0} in relay '{1}', maybe it is not connected anymore.", clientToSendMessage.Id.ToHex(), id); } } if (destroyRelay) { Server serverComponent = (Server)Base.ComponentDictionary[Server.ComponentName]; await serverComponent.RelayList.DestroyNetworkRelay(this); } log.Trace("(-)"); LogDiagnosticContext.Stop(); }
/// <summary> /// Connects to a profile server and performs a search query on it and downloads all possible results. /// </summary> /// <param name="Server">Profile server to query.</param> /// <param name="NameFilter">Name filter of the search query, or null if name filtering is not required.</param> /// <param name="TypeFilter">Type filter of the search query, or null if type filtering is not required.</param> /// <param name="LocationFilter">Location filter of the search query, or null if location filtering is not required.</param> /// <param name="Radius">If <paramref name="LocationFilter"/> is not null, this is the radius of the target area.</param> /// <param name="IncludeHostedOnly">If set to true, the search results should only include profiles hosted on the queried profile server.</param> /// <param name="IncludeImages">If set to true, the search results should include images.</param> /// <returns>List of results or null if the function fails.</returns> public async Task <SearchQueryInfo> SearchQueryAsync(ProfileServer Server, string NameFilter, string TypeFilter, GpsLocation LocationFilter, int Radius, bool IncludeHostedOnly, bool IncludeImages) { log.Trace("()"); SearchQueryInfo res = null; bool connected = false; try { await ConnectAsync(Server.IpAddress, Server.ClientNonCustomerInterfacePort, true); connected = true; if (await StartConversationAsync()) { uint maxResults = (uint)(IncludeImages ? 1000 : 10000); uint maxResponseResults = (uint)(IncludeImages ? 100 : 1000); PsProtocolMessage requestMessage = messageBuilder.CreateProfileSearchRequest(TypeFilter, NameFilter, null, LocationFilter, (uint)Radius, maxResponseResults, maxResults, IncludeHostedOnly, IncludeImages); await SendMessageAsync(requestMessage); PsProtocolMessage responseMessage = await ReceiveMessageAsync(); bool idOk = responseMessage.Id == requestMessage.Id; bool statusOk = responseMessage.Response.Status == Status.Ok; bool searchRequestOk = idOk && statusOk; if (searchRequestOk) { int totalResultCount = (int)responseMessage.Response.ConversationResponse.ProfileSearch.TotalRecordCount; List <byte[]> coveredServers = new List <byte[]>(); foreach (ByteString coveredServerId in responseMessage.Response.ConversationResponse.ProfileSearch.CoveredServers) { coveredServers.Add(coveredServerId.ToByteArray()); } List <IdentityNetworkProfileInformation> results = responseMessage.Response.ConversationResponse.ProfileSearch.Profiles.ToList(); while (results.Count < totalResultCount) { int remaining = Math.Min((int)maxResponseResults, totalResultCount - results.Count); requestMessage = messageBuilder.CreateProfileSearchPartRequest((uint)results.Count, (uint)remaining); await SendMessageAsync(requestMessage); responseMessage = await ReceiveMessageAsync(); idOk = responseMessage.Id == requestMessage.Id; statusOk = responseMessage.Response.Status == Status.Ok; searchRequestOk = idOk && statusOk; if (!searchRequestOk) { break; } results.AddRange(responseMessage.Response.ConversationResponse.ProfileSearchPart.Profiles.ToList()); } res = new SearchQueryInfo(); res.CoveredServers = coveredServers; res.Results = results; } } } catch (Exception e) { log.Error("Exception occurred: {0}", e.ToString()); } if (connected) { CloseTcpClient(); } if (res != null) { log.Trace("(-):*.Results.Count={0},*.CoveredServers.Count={1}", res.Results.Count, res.CoveredServers.Count); } else { log.Trace("(-):null"); } return(res); }
/// <summary> /// Validates incoming SharedProfileAddItem update received as a part of neighborhood initialization process. /// </summary> /// <param name="AddItem">Update item to validate.</param> /// <param name="Index">Index of the update item in the message.</param> /// <param name="IdentityDatabase">In-memory temporary database of identities hosted on 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 ValidateInMemorySharedProfileAddItem(SharedProfileAddItem AddItem, int Index, Dictionary <byte[], NeighborIdentity> IdentityDatabase, PsMessageBuilder MessageBuilder, PsProtocolMessage RequestMessage, out PsProtocolMessage ErrorResponse) { log.Trace("(Index:{0})", Index); bool res = false; ErrorResponse = null; if (AddItem == null) { AddItem = new SharedProfileAddItem(); } if (AddItem.SignedProfile == null) { AddItem.SignedProfile = new SignedProfileInformation(); } if (AddItem.SignedProfile.Profile == null) { AddItem.SignedProfile.Profile = new ProfileInformation(); } byte[] identityPubKey = AddItem.SignedProfile.Profile.PublicKey.ToByteArray(); if (ValidateSignedProfileInformation(AddItem.SignedProfile, identityPubKey, MessageBuilder, RequestMessage, Index.ToString() + ".add.signedProfile.", true, out ErrorResponse)) { string details = null; if (IdentityDatabase.Count >= IdentityBase.MaxHostedIdentities) { log.Debug("Target server already sent too many profiles."); details = "add"; } if (details == null) { // We do not verify identity duplicity here, that is being done in PsMessageProcessor.ProcessMessageNeighborhoodSharedProfileUpdateRequestAsync. byte[] identityId = Crypto.Sha256(identityPubKey); if (IdentityDatabase.ContainsKey(identityId)) { log.Debug("Identity with public key '{0}' (ID '{1}') already exists.", identityPubKey.ToHex(), identityId.ToHex()); details = "add.signedProfile.profile.publicKey"; } } if (details == null) { byte[] thumbnailImage = AddItem.ThumbnailImage.ToByteArray(); byte[] thumbnailImageHash = AddItem.SignedProfile.Profile.ThumbnailImageHash.ToByteArray(); // Profile image must be PNG or JPEG image, no bigger than Identity.MaxThumbnailImageLengthBytes. bool noImage = thumbnailImageHash.Length == 0; bool thumbnailImageValid = (thumbnailImage.Length <= IdentityBase.MaxThumbnailImageLengthBytes) && (noImage || ImageManager.ValidateImageWithHash(thumbnailImage, thumbnailImageHash)); if (!thumbnailImageValid) { log.Debug("Invalid thumbnail image."); details = "add.thumbnailImage"; } } if (details == null) { res = true; } else { ErrorResponse = MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, Index.ToString() + "." + details); } } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Processes incoming confirmation from the message recipient over the relay. /// </summary> /// <param name="Client">Client that sent the response.</param> /// <param name="ResponseMessage">Full response message.</param> /// <param name="SenderRequest">Sender request message that the recipient confirmed.</param> /// <returns>true if the connection to the client that sent the response should remain open, false if the client should be disconnected.</returns> public async Task <bool> RecipientConfirmedMessage(IncomingClient Client, PsProtocolMessage ResponseMessage, PsProtocolMessage SenderRequest) { log.Trace("()"); bool res = false; bool destroyRelay = false; await lockObject.WaitAsync(); if (status == RelayConnectionStatus.Open) { bool isCaller = Client == caller; IncomingClient otherClient = isCaller ? callee : caller; log.Trace("Over relay '{0}', received confirmation (status code {1}) from client ID {2} of a message sent by client ID {3}.", id, ResponseMessage.Response.Status, Client.Id.ToHex(), otherClient.Id.ToHex()); if (ResponseMessage.Response.Status == Status.Ok) { // We have received a confirmation from the recipient, so we just complete the sender's request to inform it that the message was delivered. PsProtocolMessage otherClientResponse = otherClient.MessageBuilder.CreateApplicationServiceSendMessageResponse(SenderRequest); if (await otherClient.SendMessageAsync(otherClientResponse)) { res = true; } else { log.Warn("Unable to send message to other client ID '0x{0:X16}' on relay '{1}', closing connection to client and destroying the relay.", id, otherClient.Id); destroyRelay = true; } } else { // We have received error from the recipient, so we forward it to the sender and destroy the relay. PsProtocolMessage errorResponse = otherClient.MessageBuilder.CreateErrorNotFoundResponse(SenderRequest); if (!await otherClient.SendMessageAsync(errorResponse)) { log.Warn("In relay '{0}', unable to send error response to the sender client ID {1}, maybe it is disconnected already, destroying the relay.", id, otherClient.Id.ToHex()); } destroyRelay = true; } } else { // This should never happen unless the relay is destroyed already. log.Debug("Relay '{0}' status is {1} instead of Open, destroying relay if it is still active.", id, status); destroyRelay = status != RelayConnectionStatus.Destroyed; } lockObject.Release(); if (destroyRelay) { Server serverComponent = (Server)Base.ComponentDictionary[Server.ComponentName]; await serverComponent.RelayList.DestroyNetworkRelay(this); } log.Trace("(-)"); return(res); }
/// <summary> /// Handles situation when a client connected to a relay disconnected. /// However, the closed connection might be either connection to clCustomer/clNonCustomer port, /// or it might be connection to clAppService port. /// </summary> /// <param name="Client">Client that disconnected.</param> /// <param name="IsRelayConnection">true if the closed connection was to clAppService port, false otherwise.</param> public async Task HandleDisconnectedClient(IncomingClient Client, bool IsRelayConnection) { log.Trace("(Client.Id:{0},IsRelayConnection:{1})", Client.Id.ToHex(), IsRelayConnection); IncomingClient clientToSendMessages = null; List <PsProtocolMessage> messagesToSend = new List <PsProtocolMessage>(); IncomingClient clientToClose = null; await lockObject.WaitAsync(); bool isCallee = Client == callee; if (IsRelayConnection) { log.Trace("Client ({0}) ID {1} disconnected, relay '{2}' status {3}.", isCallee ? "callee" : "caller", Client.Id.ToHex(), id, status); } else { log.Trace("Client (customer) ID {0} disconnected, relay '{1}' status {2}.", Client.Id.ToHex(), id, status); } bool destroyRelay = false; switch (status) { case RelayConnectionStatus.WaitingForCalleeResponse: { if (isCallee) { // The client is callee in a relay that is being initialized. The caller is waiting for callee's response and the callee has just disconnected // from the profile server. This is situation 1) from the comment in ProcessMessageCallIdentityApplicationServiceRequestAsync. // We have to send ERROR_NOT_AVAILABLE to the caller and destroy the relay. log.Trace("Callee disconnected from clCustomer port of relay '{0}', message will be sent to the caller and relay destroyed.", id); clientToSendMessages = caller; messagesToSend.Add(caller.MessageBuilder.CreateErrorNotAvailableResponse(pendingMessage)); destroyRelay = true; } else { // The client is caller in a relay that is being initialized. The caller was waiting for callee's response, but the caller disconnected before // the callee replied. The callee is now expected to reply and either accept or reject the call. If the call is rejected, everything is OK, // and we do not need to take any action. If the call is accepted, the callee will establish a new connection to clAppService port and will // send us initial ApplicationServiceSendMessageRequest message. We will now destroy the relay so that the callee is disconnected // as its token used in the initial message will not be found. log.Trace("Caller disconnected from clCustomer port or clNonCustomer port of relay '{0}', relay will be destroyed.", id); destroyRelay = true; } break; } case RelayConnectionStatus.WaitingForFirstInitMessage: { // In this relay status we do not care about connection to other than clAppService port. if (IsRelayConnection) { // This should never happen because client's Relay is initialized only after // its initialization message is received and that would upgrade the relay to WaitingForSecondInitMessage. } break; } case RelayConnectionStatus.WaitingForSecondInitMessage: { // In this relay status we do not care about connection to other than clAppService port. if (IsRelayConnection) { // One of the clients has sent its initialization message to clAppService port // and is waiting for the other client to do the same. bool isWaitingClient = (callee == Client) || (caller == Client); if (isWaitingClient) { // The client that disconnected was the waiting client. We destroy the relay. // The other client is not connected yet or did not sent its initialization message yet. log.Trace("First client on clAppService port of relay '{0}' closed its connection, destroying the relay.", id); destroyRelay = true; } else { // The client that disconnected was the client that the first client is waiting for. // We do not need to destroy the relay as the client may still connect again // and send its initialization message on time. log.Trace("Second client (that did not sent init message yet) on clAppService port of relay '{0}' closed its connection, no action taken.", id); } } break; } case RelayConnectionStatus.Open: { // In this relay status we do not care about connection to other than clAppService port. if (IsRelayConnection) { // Both clients were connected. We disconnect the other client and destroy the relay. // However, there might be some unfinished ApplicationServiceSendMessageRequest requests // that we have to send responses to. IncomingClient otherClient = isCallee ? caller : callee; log.Trace("{0} disconnected from relay '{1}', closing connection of {2}.", isCallee ? "Callee" : "Caller", id, isCallee ? "caller" : "callee"); clientToSendMessages = otherClient; clientToClose = otherClient; // Find all unfinished requests from this relay. // When a client sends ApplicationServiceSendMessageRequest, the profile server creates ApplicationServiceReceiveMessageNotificationRequest // and adds it as an unfinished request with context set to RelayMessageContext, which contains the sender's ApplicationServiceSendMessageRequest. // This unfinished message is in the list of unfinished message of the recipient. List <UnfinishedRequest> unfinishedRelayRequests = Client.GetAndRemoveUnfinishedRequests(); foreach (UnfinishedRequest unfinishedRequest in unfinishedRelayRequests) { Message unfinishedRequestMessage = (Message)unfinishedRequest.RequestMessage.Message; // Find ApplicationServiceReceiveMessageNotificationRequest request messages sent to the client who closed the connection. if ((unfinishedRequestMessage.MessageTypeCase == Message.MessageTypeOneofCase.Request) && (unfinishedRequestMessage.Request.ConversationTypeCase == Request.ConversationTypeOneofCase.SingleRequest) && (unfinishedRequestMessage.Request.SingleRequest.RequestTypeCase == SingleRequest.RequestTypeOneofCase.ApplicationServiceReceiveMessageNotification)) { // This unfinished request's context holds ApplicationServiceSendMessageRequest message of the client that is still connected. RelayMessageContext ctx = (RelayMessageContext)unfinishedRequest.Context; PsProtocolMessage responseError = clientToSendMessages.MessageBuilder.CreateErrorNotFoundResponse(ctx.SenderRequest); messagesToSend.Add(responseError); } } destroyRelay = true; } break; } case RelayConnectionStatus.Destroyed: // Nothing to be done. break; } lockObject.Release(); if (messagesToSend.Count > 0) { foreach (PsProtocolMessage messageToSend in messagesToSend) { if (!await clientToSendMessages.SendMessageAsync(messageToSend)) { log.Warn("Unable to send message to the client ID {0}, relay '{1}', maybe it is not connected anymore.", clientToSendMessages.Id.ToHex(), id); break; } } } if (clientToClose != null) { await clientToClose.CloseConnectionAsync(); } if (destroyRelay) { Server serverComponent = (Server)Base.ComponentDictionary[Server.ComponentName]; await serverComponent.RelayList.DestroyNetworkRelay(this); } log.Trace("(-)"); }
/// <summary> /// Processes ApplicationServiceSendMessageRequest message from a client. /// <para> /// Relay received message from one client and sends it to the other one. If this is the first request /// a client sends after it connects to clAppService port, the request's message is ignored and the reply is sent /// to the client as the other client is confirmed to join the relay.</para> /// </summary> /// <param name="Client">Client that sent the message.</param> /// <param name="RequestMessage">Full request message.</param> /// <param name="Token">Sender's relay token.</param> /// <returns>Response message to be sent to the client.</returns> public async Task <PsProtocolMessage> ProcessIncomingMessage(IncomingClient Client, PsProtocolMessage RequestMessage, Guid Token) { log.Trace("()"); PsProtocolMessage res = null; bool destroyRelay = false; await lockObject.WaitAsync(); bool isCaller = callerToken.Equals(Token); IncomingClient otherClient = isCaller ? callee : caller; log.Trace("Received message over relay '{0}' in status {1} with client ID {2} being {3} and the other client ID {4} is {5}.", id, status, Client.Id.ToHex(), isCaller ? "caller" : "callee", otherClient != null ? otherClient.Id.ToHex() : "N/A", isCaller ? "callee" : "caller"); switch (status) { case RelayConnectionStatus.WaitingForCalleeResponse: { if (!isCaller) { // We have received a message from callee, but we did not receive its IncomingCallNotificationResponse. // This may be OK if this message has been sent by callee and it just not has been processed before // the callee connected to clAppService port and sent us the initialization message. // In this case we will try to wait a couple of seconds and see if we receive IncomingCallNotificationResponse. // If yes, we continue as if we processed the message in the right order. // In all other cases, this is a fatal error and we have to destroy the relay. lockObject.Release(); bool statusChanged = false; log.Warn("Callee sent initialization message before we received IncomingCallNotificationResponse. We will wait to see if it arrives soon."); for (int i = 0; i < 5; i++) { log.Warn("Attempt #{0}, waiting 1 second.", i + 1); await Task.Delay(1000); await lockObject.WaitAsync(); log.Warn("Attempt #{0}, checking relay status.", i + 1); if (status != RelayConnectionStatus.WaitingForCalleeResponse) { log.Warn("Attempt #{0}, relay status changed to {1}.", i + 1, status); statusChanged = true; } lockObject.Release(); if (statusChanged) { break; } } await lockObject.WaitAsync(); if (statusChanged) { // Status of relay has change, which means either it has been destroyed already, or the IncomingCallNotificationResponse // message we were waiting for arrived. In any case, we call this method recursively, but it can not happen that we would end up here again. lockObject.Release(); log.Trace("Calling ProcessIncomingMessage recursively."); res = await ProcessIncomingMessage(Client, RequestMessage, Token); await lockObject.WaitAsync(); } else { log.Trace("Message received from caller and relay status is WaitingForCalleeResponse and IncomingCallNotificationResponse did not arrive, closing connection to client, destroying relay."); res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage); Client.ForceDisconnect = true; destroyRelay = true; } } else { log.Trace("Message received from caller and relay status is WaitingForCalleeResponse, closing connection to client, destroying relay."); res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage); Client.ForceDisconnect = true; destroyRelay = true; } break; } case RelayConnectionStatus.WaitingForFirstInitMessage: { log.Debug("Received an initialization message from the first client ID '{0}' on relay '{1}', waiting for the second client.", Client.Id.ToHex(), id); CancelTimeoutTimerLocked(); if (Client.Relay == null) { Client.Relay = this; // Other peer is not connected yet, so we put this request on hold and wait for the other client. if (isCaller) { caller = Client; } else { callee = Client; } status = RelayConnectionStatus.WaitingForSecondInitMessage; log.Trace("Relay '{0}' status changed to {1}.", id, status); pendingMessage = RequestMessage; timeoutTimer = new Timer(TimeoutCallback, status, SecondAppServiceInitializationMessageDelayMaxSeconds * 1000, Timeout.Infinite); // res remains null, which is OK as the request is put on hold until the other client joins the channel. } else { // Client already sent us the initialization message, this is protocol violation error, destroy the relay. // Since the relay should be upgraded to WaitingForSecondInitMessage status, this can happen // only if a client does not use a separate connection for each clAppService session, which is forbidden. log.Debug("Client ID {0} on relay '{1}' probably uses a single connection for two relays. Both relays will be destroyed.", Client.Id.ToHex(), id); res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage); destroyRelay = true; } break; } case RelayConnectionStatus.WaitingForSecondInitMessage: { log.Debug("Received an initialization message from the second client on relay '{0}'.", id); CancelTimeoutTimerLocked(); if (Client.Relay == null) { Client.Relay = this; // Other peer is connected already, so we just inform it by sending response to its initial ApplicationServiceSendMessageRequest. if (isCaller) { caller = Client; } else { callee = Client; } status = RelayConnectionStatus.Open; log.Trace("Relay '{0}' status changed to {1}.", id, status); PsProtocolMessage otherClientResponse = otherClient.MessageBuilder.CreateApplicationServiceSendMessageResponse(pendingMessage); pendingMessage = null; if (await otherClient.SendMessageAsync(otherClientResponse)) { // And we also send reply to the second client that the channel is now ready for communication. res = Client.MessageBuilder.CreateApplicationServiceSendMessageResponse(RequestMessage); } else { log.Warn("Unable to send message to other client ID {0}, closing connection to client and destroying the relay.", otherClient.Id.ToHex()); res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage); Client.ForceDisconnect = true; destroyRelay = true; } } else { // Client already sent us the initialization message, this is error, destroy the relay. log.Debug("Client ID {0} on relay '{1}' sent a message before receiving a reply to its initialization message. Relay will be destroyed.", Client.Id.ToHex(), id); res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage); destroyRelay = true; } break; } case RelayConnectionStatus.Open: { if (Client.Relay == this) { // Relay is open, this means that all incoming messages are sent to the other client. byte[] messageForOtherClient = RequestMessage.Request.SingleRequest.ApplicationServiceSendMessage.Message.ToByteArray(); PsProtocolMessage otherClientMessage = otherClient.MessageBuilder.CreateApplicationServiceReceiveMessageNotificationRequest(messageForOtherClient); RelayMessageContext context = new RelayMessageContext(this, RequestMessage); if (await otherClient.SendMessageAndSaveUnfinishedRequestAsync(otherClientMessage, context)) { // res is null, which is fine, the sender is put on hold and we will get back to it once the recipient confirms that it received the message. log.Debug("Message from client ID {0} has been relayed to other client ID {1}.", Client.Id.ToHex(), otherClient.Id.ToHex()); } else { log.Warn("Unable to relay message to other client ID {0}, closing connection to client and destroying the relay.", otherClient.Id.ToHex()); res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage); Client.ForceDisconnect = true; destroyRelay = true; } } else { // This means that the client used a single clAppService port connection for two different relays, which is forbidden. log.Warn("Client ID {0} mixed relay '{1}' with relay '{2}', closing connection to client and destroying both relays.", otherClient.Id.ToHex(), Client.Relay.id, id); res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage); Client.ForceDisconnect = true; destroyRelay = true; } break; } case RelayConnectionStatus.Destroyed: { log.Trace("Relay has been destroyed, closing connection to client."); res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage); Client.ForceDisconnect = true; break; } default: log.Trace("Relay status is '{0}', closing connection to client, destroying relay.", status); res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage); Client.ForceDisconnect = true; destroyRelay = true; break; } lockObject.Release(); if (destroyRelay) { Server serverComponent = (Server)Base.ComponentDictionary[Server.ComponentName]; await serverComponent.RelayList.DestroyNetworkRelay(this); if ((this != Client.Relay) && (Client.Relay != null)) { await serverComponent.RelayList.DestroyNetworkRelay(Client.Relay); } } log.Trace("(-)"); return(res); }
/// <summary> /// Initializes the relay message context. /// </summary> public RelayMessageContext(RelayConnection Relay, PsProtocolMessage SenderRequest) { this.Relay = Relay; this.SenderRequest = SenderRequest; }
/// <summary> /// Creates a new network relay between a caller identity and one of the profile server's customer identities that is online. /// </summary> /// <param name="Caller">Initiator of the call.</param> /// <param name="Callee">Profile server's customer client to be called.</param> /// <param name="ServiceName">Name of the application service to use.</param> /// <param name="RequestMessage">CallIdentityApplicationServiceRequest message that the caller send in order to initiate the call.</param> /// <returns>New relay connection object if the function succeeds, or null otherwise.</returns> public RelayConnection CreateNetworkRelay(IncomingClient Caller, IncomingClient Callee, string ServiceName, PsProtocolMessage RequestMessage) { log.Trace("(Caller.Id:{0},Callee.Id:{1},ServiceName:'{2}')", Caller.Id.ToHex(), Callee.Id.ToHex(), ServiceName); RelayConnection res = null; RelayConnection relay = new RelayConnection(Caller, Callee, ServiceName, RequestMessage); lock (lockObject) { relaysByGuid.Add(relay.GetId(), relay); relaysByGuid.Add(relay.GetCallerToken(), relay); relaysByGuid.Add(relay.GetCalleeToken(), relay); } log.Debug("Relay ID '{0}' added to the relay list.", relay.GetId()); log.Debug("Caller token '{0}' added to the relay list.", relay.GetCallerToken()); log.Debug("Callee token '{0}' added to the relay list.", relay.GetCalleeToken()); res = relay; log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Validates incoming SharedProfileDeleteItem update item. /// </summary> /// <param name="DeleteItem">Delete item to validate.</param> /// <param name="Index">Item index in the update message.</param> /// <param name="UsedProfileIdsInBatch">List of profile network 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 ValidateSharedProfileDeleteItem(SharedProfileDeleteItem DeleteItem, int Index, HashSet <byte[]> UsedProfileIdsInBatch, PsMessageBuilder MessageBuilder, PsProtocolMessage RequestMessage, out PsProtocolMessage ErrorResponse) { log.Trace("(Index:{0})", Index); bool res = false; ErrorResponse = null; if (DeleteItem == null) { DeleteItem = new SharedProfileDeleteItem(); } string details = null; byte[] identityId = DeleteItem.IdentityNetworkId.ToByteArray(); // We do not verify identity existence here, that is being done in ProcessMessageNeighborhoodSharedProfileUpdateRequestAsync. bool identityIdValid = identityId.Length == ProtocolHelper.NetworkIdentifierLength; if (identityIdValid) { if (!UsedProfileIdsInBatch.Contains(identityId)) { UsedProfileIdsInBatch.Add(identityId); } else { log.Debug("ID '{0}' already processed in this request.", identityId.ToHex()); details = "delete.identityNetworkId"; } } else { log.Debug("Invalid identity ID length {0}.", identityId.Length); details = "delete.identityNetworkId"; } if (details == null) { res = true; } else { ErrorResponse = MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, Index.ToString() + "." + details); } log.Trace("(-):{0}", res); return(res); }