public async Task LoginAsync(UserStatus initialStatus = UserStatus.Online) { await @lock.WriterLockAsync(); try { if (IsLoggedIn) return; IConnection connection = null; ConnectionStream stream = null; CommandReader reader = null; CommandWriter writer = null; ResponseTracker responseTracker = null; IConnectableObservable<Command> commands = null; IDisposable commandsDisposable = null; int transferCount = 0; SocketEndPoint endPoint = SocketEndPoint.Parse("messenger.hotmail.com:1863"); string authTicket = null; while (authTicket == null) { connection = new SocketConnection(); await connection.ConnectAsync(endPoint); stream = new ConnectionStream(connection); writer = new CommandWriter(stream); reader = new CommandReader(stream, new Dictionary<string, Type> { { "VER", typeof(VersionCommand) }, { "CVR", typeof(ClientVersionCommand) }, { "USR", typeof(AuthenticateCommand) }, { "XFR", typeof(TransferCommand) }, { "SYN", typeof(SynchronizeCommand) }, { "SBS", typeof(SbsCommand) }, { "MSG", typeof(MessageCommand) }, { "LST", typeof(UserCommand) }, { "LSG", typeof(GroupCommand) }, { "BPR", typeof(UserPropertyCommand) }, { "BLP", typeof(PrivacySettingCommand) }, { "GTC", typeof(PrivacySettingCommand) }, { "CHG", typeof(ChangeStatusCommand) }, { "UBX", typeof(BroadcastCommand) }, { "PRP", typeof(LocalPropertyCommand) }, { "NLN", typeof(UserOnlineCommand) }, { "ILN", typeof(InitialUserOnlineCommand) }, { "FLN", typeof(UserOfflineCommand) }, { "UUX", typeof(SendBroadcastCommand) }, { "NOT", typeof(NotificationCommand) }, { "QNG", typeof(PingCommand) }, { "CHL", typeof(ChallengeCommand) }, { "ADC", typeof(AddContactCommand) }, { "REM", typeof(RemoveContactCommand) }, { "ADG", typeof(AddGroupCommand) }, { "RMG", typeof(RemoveGroupCommand) }, { "REG", typeof(RenameGroupCommand) }, { "QRY", typeof(AcceptChallengeCommand) }, { "RNG", typeof(RingCommand) }, { "SBP", typeof(ChangeUserPropertyCommand) }, { "IMS", typeof(EnableIMCommand) }, }); commands = reader.GetReadObservable().Publish(); responseTracker = new ResponseTracker(writer, commands); commandsDisposable = commands.Connect(); var versionCommand = new VersionCommand("MSNP12"); var versionResponse = await responseTracker.GetResponseAsync<VersionCommand>(versionCommand, defaultTimeout); if (versionResponse.Versions.Length == 0) throw new ProtocolNotAcceptedException(); var clientVersionCommand = new ClientVersionCommand { LocaleId = "0x0409", OsType = "winnt", OsVersion = "5.0", Architecture = "1386", LibraryName = "MSMSGS", ClientVersion = "5.0.0482", ClientName = "WindowsMessenger", LoginName = credentials.LoginName, }; await responseTracker.GetResponseAsync<ClientVersionCommand>(clientVersionCommand, defaultTimeout); var userCommand = new AuthenticateCommand("TWN", "I", credentials.LoginName); var userResponse = await responseTracker.GetResponseAsync(userCommand, new Type[] { typeof(AuthenticateCommand), typeof(TransferCommand) }, defaultTimeout); if (userResponse is AuthenticateCommand) { authTicket = (userResponse as AuthenticateCommand).Argument; } else if (userResponse is TransferCommand) { TransferCommand transferResponse = userResponse as TransferCommand; if (transferCount > 3) throw new InvalidOperationException("The maximum number of redirects has been reached."); transferCount++; endPoint = SocketEndPoint.Parse(transferResponse.Host); commandsDisposable.Dispose(); reader.Close(); writer.Close(); connection.Dispose(); } } PassportAuthentication auth = new PassportAuthentication(); string authToken = await auth.GetToken(credentials.LoginName, credentials.Password, authTicket); var authCommand = new AuthenticateCommand("TWN", "S", authToken); var authResponse = await responseTracker.GetResponseAsync<AuthenticateCommand>(authCommand, defaultTimeout); var synCommand = new SynchronizeCommand(syncTimeStamp1 ?? "0", syncTimeStamp2 ?? "0"); var synResponse = await responseTracker.GetResponseAsync<SynchronizeCommand>(synCommand, defaultTimeout); IDisposable syncCommandsSubscription = null; List<Command> syncCommands = null; if (synResponse.TimeStamp1 != syncTimeStamp1 || synResponse.TimeStamp2 != syncTimeStamp2) { syncCommands = new List<Command>(); Type[] syncTypes = new Type[] { typeof(MessageCommand), typeof(UserCommand), typeof(GroupCommand), typeof(LocalPropertyCommand), typeof(PrivacySettingCommand), }; syncCommandsSubscription = commands .Where(c => syncTypes.Contains(c.GetType())) .Catch(Observable.Empty<Command>()) .Subscribe(c => syncCommands.Add(c)); //if we're expecting users/groups, wait for them before we proceed if (synResponse.UserCount + synResponse.GroupCount > 0) { await commands .Where(c => c is UserCommand || c is GroupCommand) .Take(synResponse.UserCount + synResponse.GroupCount) .Timeout(defaultTimeout); } } UserCapabilities capabilities = 0; MSNObject displayPicture = MSNObject.Empty; if (LocalUser != null) { capabilities = LocalUser.Capabilities; displayPicture = LocalUser.DisplayPicture; } Command changeStatusCommand = new ChangeStatusCommand(User.StatusToString(UserStatus.Online), (uint)capabilities, displayPicture != MSNObject.Empty ? displayPicture.ToString() : "0"); await responseTracker.GetResponseAsync(changeStatusCommand, defaultTimeout); if (syncCommandsSubscription != null) syncCommandsSubscription.Dispose(); this.writer = writer; this.reader = reader; this.stream = stream; this.connection = connection; this.responseTracker = responseTracker; this.commands = commands; this.commandsDisposable = commandsDisposable; if (LocalUser == null) { LocalUser = new LocalUser(this, credentials.LoginName); userCache.Add(credentials.LoginName, new WeakReference(LocalUser)); } LocalUser.Status = initialStatus; SyncEvents syncEvents = null; if (syncCommands != null) { syncTimeStamp1 = synResponse.TimeStamp1; syncTimeStamp2 = synResponse.TimeStamp2; syncEvents = ProcessSyncCommands(syncCommands); } var commandsSafe = commands .Catch<Command, ConnectionErrorException>(tx => Observable.Empty<Command>()); commandsSafe.OfType<MessageCommand>().Subscribe(cmd => HandleMessages(cmd, connection)); commandsSafe.OfType<RingCommand>().Subscribe(cmd => HandleRings(cmd, connection)); commandsSafe.OfType<BroadcastCommand>().Subscribe(cmd => HandleBroadcasts(cmd, connection)); commandsSafe.OfType<NotificationCommand>().Subscribe(cmd => HandleNotifications(cmd, connection)); commandsSafe.OfType<AddContactCommand>().Subscribe(cmd => HandleNewUsers(cmd, connection)); commandsSafe.OfType<OutCommand>().Subscribe(cmd => HandleOut(cmd, connection)); commandsSafe.OfType<ChallengeCommand>().Subscribe(cmd => HandleChallenges(cmd, connection)); commandsSafe.OfType<UserOnlineCommand>().Subscribe(cmd => HandleOnlineUsers(cmd, connection)); commandsSafe.OfType<InitialUserOnlineCommand>().Subscribe(cmd => HandleOnlineUsers(cmd, connection)); commandsSafe.OfType<UserOfflineCommand>().Subscribe(cmd => HandleOfflineUsers(cmd, connection)); connection.Error += connection_Error; IsLoggedIn = true; OnLoggedIn(); OnUserStatusChanged(new UserStatusEventArgs(LocalUser, initialStatus, UserStatus.Offline, true)); if (syncEvents != null) RaiseSyncEvents(syncEvents); } finally { @lock.WriterRelease(); } }
public async Task Send(RequestMessage message, CancellationToken cancellationToken = default) { if (message == null) { throw new ArgumentNullException(nameof(message)); } // INS12350-Serial-API-Host-Appl.-Prg.-Guide | 6.5.2 Request/Response frame flow // Note that due to the simple nature of the simple acknowledge mechanism, only one REQ->RES session is allowed. await _sendLock.WaitAsync(cancellationToken); try { // number of retransmissions var retransmissions = 0; // return only on ACK or Exception while (true) { var completion = new TaskCompletionSource <Frame>(TaskCreationOptions.RunContinuationsAsynchronously); // INS12350-Serial-API-Host-Appl.-Prg.-Guide | 5.1 ACK frame // The host MUST wait for a period of 1500ms before timing out waiting for the ACK frame // timeoutCancellation.CancelAfter(ProtocolSettings.ACKWaitTime); var chain = _observable .Where(frame => frame == Frame.ACK || frame == Frame.NAK || frame == Frame.CAN) .Timeout(ProtocolSettings.ACKWaitTime); // set completion cancelled when token gets cancelled using (cancellationToken.Register(() => completion.TrySetCanceled())) { // start listening for received frames, call onVerifyResponse for every received frame using (var subscription = chain.Subscribe ( (element) => completion.TrySetResult(element), (error) => completion.TrySetException(error)) ) { // encode the message to a dataframe var frame = Encode(message); if (retransmissions == 0) { _logger.LogDebug($"Sending: {frame}"); } else { _logger.LogWarning($"Resending: {frame}, attempt: {retransmissions}"); } // INS12350-Serial-API-Host-Appl.-Prg.-Guide | 6.3 Retransmission // Twaiting = 100ms + n*1000ms // where n is incremented at each retransmission. n = 0 is used for the first waiting period. var waitTime = ProtocolSettings.RetryDelayWaitTime.TotalMilliseconds + (retransmissions++ *ProtocolSettings.RetryAttemptWaitTime.TotalMilliseconds); // INS12350-Serial-API-Host-Appl.-Prg.-Guide | 6.2.2 Data frame delivery timeout // The transmitter MAY compensate for the 1600ms already elapsed when calculating the retransmission waiting period waitTime -= _stopwatch.ElapsedMilliseconds; if (waitTime > 0) { await Task.Delay((int)waitTime, cancellationToken); } // send the request await _writer.Write(frame, cancellationToken); // restart timing _stopwatch.Restart(); try { // wait for validated response var response = await completion.Task; // ACK received, so where done if (response == Frame.ACK) { break; } // INS12350-Serial-API-Host-Appl.-Prg.-Guide | 6.3 Retransmission // A host or Z-Wave chip MUST NOT carry out more than 3 retransmissions if (retransmissions >= ProtocolSettings.MaxRetryAttempts) { if (response == Frame.CAN) { throw new CanResponseException(); } if (response == Frame.NAK) { throw new NakResponseException(); } } } catch (TaskCanceledException) when(cancellationToken.IsCancellationRequested) { // operation was externally canceled, so rethrow throw; } catch (TimeoutException) { // operation timed-out _logger.LogWarning($"Timeout while waiting for an ACK"); // INS12350-Serial-API-Host-Appl.-Prg.-Guide | 6.3 Retransmission // A host or Z-Wave chip MUST NOT carry out more than 3 retransmissions if (retransmissions >= ProtocolSettings.MaxRetryAttempts) { throw new TimeoutException("Timeout while waiting for an ACK"); } } } } } } finally { // done, so release lock, other send operations allowed _sendLock.Release(); } }
private async Task ConnectAsync(CancellationToken token) { try { await BackOff(); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connecting); Debug.WriteLine($"opening websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})"); await _clientWebSocket.ConnectAsync(_webSocketUri, token); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connected); Debug.WriteLine($"connection established on websocket {_clientWebSocket.GetHashCode()}, invoking Options.OnWebsocketConnected()"); await(Options.OnWebsocketConnected?.Invoke(_client) ?? Task.CompletedTask); Debug.WriteLine($"invoking Options.OnWebsocketConnected() on websocket {_clientWebSocket.GetHashCode()}"); _connectionAttempt = 1; // create receiving observable _incomingMessages = Observable .Defer(() => GetReceiveTask().ToObservable()) .Repeat() // complete sequence on OperationCanceledException, this is triggered by the cancellation token on disposal .Catch <WebsocketMessageWrapper, OperationCanceledException>(exception => Observable.Empty <WebsocketMessageWrapper>()) .Publish(); // subscribe maintenance var maintenanceSubscription = _incomingMessages.Subscribe(_ => { }, ex => { Debug.WriteLine($"incoming message stream {_incomingMessages.GetHashCode()} received an error: {ex}"); _exceptionSubject.OnNext(ex); _incomingMessagesConnection?.Dispose(); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); }, () => { Debug.WriteLine($"incoming message stream {_incomingMessages.GetHashCode()} completed"); _incomingMessagesConnection?.Dispose(); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); }); // connect observable var connection = _incomingMessages.Connect(); Debug.WriteLine($"new incoming message stream {_incomingMessages.GetHashCode()} created"); _incomingMessagesConnection = new CompositeDisposable(maintenanceSubscription, connection); var initRequest = new GraphQLWebSocketRequest { Type = GraphQLWebSocketMessageType.GQL_CONNECTION_INIT, Payload = Options.ConfigureWebSocketConnectionInitPayload(Options) }; // setup task to await connection_ack message var ackTask = _incomingMessages .Where(response => response != null) .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK || response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ERROR) .FirstAsync() .ToTask(); // send connection init Debug.WriteLine($"sending connection init message"); await SendWebSocketMessageAsync(initRequest); var response = await ackTask; if (response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK) { Debug.WriteLine($"connection acknowledged: {Encoding.UTF8.GetString(response.MessageBytes)}"); } else { var errorPayload = Encoding.UTF8.GetString(response.MessageBytes); Debug.WriteLine($"connection error received: {errorPayload}"); throw new GraphQLWebsocketConnectionException(errorPayload); } } catch (Exception e) { Debug.WriteLine($"failed to establish websocket connection"); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); _exceptionSubject.OnNext(e); throw; } }
protected IObservable <TOutput> GetOutputStream() { return(GetOutputStreamImpl().SubscribeOn(_eventLoopScheduler) .TakeUntil(_connectionChanged.Where(x => x.IsConnected)) .Repeat()); }
private IObservable <Unit> RegisterPriceTrigger(string symbol) { return(_timer .Where(_ => _priceGenerators[Random.Next(_priceGenerators.Count)].Symbol == symbol) .Select(_ => Unit.Default)); }