Ejemplo n.º 1
0
        private async void StartSending(UInt64 subscriberId, SubscriberRecord subscriber)
        {
            try {
                var lastRevisions = new Dictionary <UInt32, UInt32>(); // {topic} => {revision}

                // Loop until disposed or closed
                while (!IsDisposed && subscriber.Socket.State == WebSocketState.Open)
                {
                    foreach (var topicRecord in TopicRecords)
                    {
                        // If the client is in need of an update
                        if (subscriber.Channels.Contains(topicRecord.Value.Channel) &&                                                        // Correct channel
                            (!lastRevisions.TryGetValue(topicRecord.Key, out var lastRevision) || lastRevision < topicRecord.Value.Revision)) // There's a new revision
                        {
                            // Send update
                            await subscriber.Socket.SendAsync(topicRecord.Value.Packet, WebSocketMessageType.Binary, true, CancellationToken.None);

                            // Note latest revision sent
                            lastRevisions[topicRecord.Key] = topicRecord.Value.Revision;
                        }
                    }

                    // Wait for next change
                    subscriber.SendLock.WaitOne();
                }
            } catch (ObjectDisposedException) {
            } catch (Exception e) {
                Trace.TraceWarning($"Subscriber {subscriberId} TX: {e.Message}");
            }
        }
Ejemplo n.º 2
0
        private async void StartReceiving(UInt64 subscriberId, SubscriberRecord subscriber)
        {
            var receiveBuffer = new Byte[512]; // TODO

            try {
                while (!IsDisposed && subscriber.Socket.State == WebSocketState.Open)
                {
                    // Receive message into buffer
                    var receiveResult = await subscriber.Socket.ReceiveAsync(new ArraySegment <Byte>(receiveBuffer), CancellationToken.None);

                    // Close connection if requested
                    if (receiveResult.MessageType == WebSocketMessageType.Close)
                    {
                        await subscriber.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);

                        continue;
                    }

                    // Close connection on bad message type
                    if (receiveResult.MessageType == WebSocketMessageType.Text)
                    {
                        await subscriber.Socket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "Cannot accept text frame", CancellationToken.None);

                        continue;
                    }

                    // Close connection if message too long
                    if (!receiveResult.EndOfMessage)
                    {
                        await subscriber.Socket.CloseAsync(WebSocketCloseStatus.MessageTooBig, "Message too big", CancellationToken.None);

                        continue;
                    }

                    //await subscriber.Socket.SendAsync(new ArraySegment<Byte>(receiveBuffer, 0, receiveResult.Count), WebSocketMessageType.Binary, receiveResult.EndOfMessage, CancellationToken.None);
                }
            } catch (ObjectDisposedException) { // TODO: other exceptions
            } catch (Exception e) {
                Trace.TraceWarning($"Subscriber {subscriberId} RX: {e.Message}");
            } finally {
                subscriber.Socket.Dispose();
                SubscriberRecords.TryRemove(subscriberId, out subscriber);
            }
        }
Ejemplo n.º 3
0
        private void ReceieveThread_OnSpin(Object obj)
        {
            var buffer = new Byte[Mtu];

            try {
                while (!IsDisposed)
                {
                    try {
                        // Remove all expired subscriptions  TODO - should be on a timer?
                        var expiry = DateTime.UtcNow.Subtract(KeepAliveInterval).Subtract(KeepAiveGrace);
                        foreach (var ep in SubscriberRecords.Where(a => a.Value.LastAuthorizedAt < expiry).Select(a => a.Key))
                        {
                            SubscriberRecords.TryRemove(ep, out var record);
                            Trace.WriteLine($"{ep} Subscription expired.", "server-receive");
                        }

                        // Wait for packet to arrive
                        var endpoint = (EndPoint) new IPEndPoint(IPAddress.Any, 0);
                        var len      = Socket.ReceiveFrom(buffer, ref endpoint);
                        if (len < 1)
                        {
                            Trace.WriteLine($"Strange byte count {len}.", "server-receive-warning");
                            continue;
                        }

                        // Check packet sanity
                        if (len < Constants.CLIENTTXHEADER_LENGTH)
                        {
                            Trace.WriteLine($"{endpoint} Received packet that is too small to be valid. Discarded.", "server-receive");
                            continue;
                        }
                        if ((len - Constants.CLIENTTXHEADER_LENGTH) % 6 > 0)
                        {
                            Trace.WriteLine($"{endpoint} Received packet is not a valid length. Discarded.", "server-receive");
                            continue;
                        }

                        // Check version
                        var version = buffer[0];
                        if (version != Constants.VERSION)
                        {
                            Trace.WriteLine($"{endpoint} Received packet version does not match or is corrupted. Discarded.", "server-receive");
                            continue;
                        }

                        // Check authorization token
                        var authorizationToken = new Byte[16];
                        Buffer.BlockCopy(buffer, 1, authorizationToken, 0, authorizationToken.Length);
                        if (!AuthorizationFilter(endpoint, authorizationToken))
                        {
                            Trace.WriteLine($"{endpoint} Received packet with rejected authorization token. Discarded.", "server-receive");
                            continue;
                        }

                        // Find subscriber record
                        if (SubscriberRecords.TryGetValue(endpoint, out var subscriberRecord))
                        {
                            // Record exists, update authorizedAt
                            subscriberRecord.LastAuthorizedAt = DateTime.UtcNow;

                            // Process ACKs
                            var pos = Constants.CLIENTTXHEADER_LENGTH;
                            if (pos == len)
                            {
                                Trace.WriteLine($"{endpoint} Sent keep-alive.", "server-receive");
                            }
                            while (pos < len)
                            {
                                // Extract topic
                                var topic = BitConverter.ToUInt32(buffer, pos);

                                // Extract revision
                                var revision = BitConverter.ToUInt16(buffer, pos + 4);

                                Trace.WriteLine($"{endpoint} Acknowledged {topic}#{revision}.", "server-receive");

                                if (TopicRecords.TryGetValue(topic, out var topicRecord))
                                {
                                    if (topicRecord.Revision == revision)
                                    {
                                        topicRecord.PendingSubscribers = topicRecord.PendingSubscribers.Except(new EndPoint[] { endpoint }).ToArray(); // Replace rather than adding so we don't have a sync issue
                                    }
                                }

                                pos += 10;
                            }
                        }
                        else
                        {
                            // Record doesn't exist, created
                            subscriberRecord = SubscriberRecords[endpoint] = new SubscriberRecord()
                            {
                                LastAuthorizedAt = DateTime.UtcNow,
                            };

                            // Queue sending latest value from all topics
                            foreach (var topicRecord in TopicRecords.Select(a => a.Value))
                            {
                                topicRecord.PendingSubscribers = topicRecord.PendingSubscribers.Union(new EndPoint[] { endpoint }).ToArray(); // Replace rather than adding so we don't have a sync issue
                            }
                        }
                    } catch (SocketException ex) {
                        if (ex.SocketErrorCode == SocketError.TimedOut)
                        {
                            continue;
                        }
                        Trace.WriteLine($"Socket error {ex.SocketErrorCode}.", "server-receive-warning");
                    }
                }
            } catch (ObjectDisposedException) { }
        }
Ejemplo n.º 4
0
        private async void AcceptThread_OnSpin(Object obj)
        {
            try {
                // Loop until disposed
                while (!IsDisposed)
                {
                    // Wait for inbound request
                    var listenerContext = await Listener.GetContextAsync();

                    // Respond to options requests
                    if (listenerContext.Request.HttpMethod == "OPTIONS")
                    {
                        listenerContext.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
                        listenerContext.Response.AddHeader("Access-Control-Allow-Methods", "GET");
                        listenerContext.Response.AddHeader("Access-Control-Max-Age", "86400");
                        listenerContext.Response.Close();
                        continue;
                    }
                    listenerContext.Response.AppendHeader("Access-Control-Allow-Origin", "*");

                    // Handle requests for client
                    if (listenerContext.Request.Url.PathAndQuery == "/client.ts")
                    {
                        listenerContext.Response.StatusCode        = 200;
                        listenerContext.Response.StatusDescription = "OK";
                        listenerContext.Response.ContentType       = "application/typescript";
                        listenerContext.Response.Close(ClientTypeScript, false);
                        continue;
                    }
                    if (listenerContext.Request.Url.PathAndQuery == "/client.js")
                    {
                        listenerContext.Response.StatusCode        = 200;
                        listenerContext.Response.StatusDescription = "OK";
                        listenerContext.Response.ContentType       = "application/javascript";
                        listenerContext.Response.Close(ClientJavaScript, false);
                        continue;
                    }

                    // Favicon
                    if (listenerContext.Request.Url.PathAndQuery == "/favicon.ico")
                    {
                        listenerContext.Response.StatusCode        = 404;
                        listenerContext.Response.StatusDescription = "Not found";
                        listenerContext.Response.Close(ClientJavaScript, false);
                        continue;
                    }

                    // Reject if not a websocket request
                    if (!listenerContext.Request.IsWebSocketRequest)
                    {
                        listenerContext.Response.StatusCode        = 426;
                        listenerContext.Response.StatusDescription = "WebSocket required";
                        listenerContext.Response.Close();
                        continue;
                    }

                    // Get channel list
                    var channelsStrings = listenerContext.Request.QueryString["channels"];
                    if (String.IsNullOrWhiteSpace(channelsStrings))
                    {
                        listenerContext.Response.StatusCode        = 400;
                        listenerContext.Response.StatusDescription = "Missing channels";
                        listenerContext.Response.Close();
                        continue;
                    }
                    var channels = new List <UInt32>();
                    foreach (var channelString in channelsStrings.Split(','))
                    {
                        if (!UInt32.TryParse(channelString, out var channel))
                        {
                            listenerContext.Response.StatusCode        = 400;
                            listenerContext.Response.StatusDescription = $"Bad channel {channelString}";
                            listenerContext.Response.Close();
                            continue;
                        }
                        channels.Add(channel);
                    }

                    // TODO: reject if not secure?
                    // TODO: authentication

                    // Upgrade to web sockets
                    WebSocketContext webSocketContext = null;
                    try {
                        webSocketContext = await listenerContext.AcceptWebSocketAsync(SubProtocol, KeepAliveInterval);
                    } catch (Exception e) {
                        listenerContext.Response.StatusCode        = 400;
                        listenerContext.Response.StatusDescription = e.Message; // TODO: bad idea?
                        listenerContext.Response.Close();
                        return;
                    }

                    // Create subscriber record
                    var subscriberId = (UInt64)(Interlocked.Increment(ref NextSubscriberId) - Int64.MinValue);
                    var subscriber   = SubscriberRecords[subscriberId] = new SubscriberRecord()
                    {
                        Socket   = webSocketContext.WebSocket,
                        Channels = channels.ToArray()
                    };

                    Task.Run(async() => StartReceiving(subscriberId, subscriber));  // TODO: Clean this up
                    Task.Run(async() => StartSending(subscriberId, subscriber));
                }
            } catch (ObjectDisposedException) { }
        }