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

        }
Example #2
0
        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();
            }
        }
Example #3
0
        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));
 }