예제 #1
0
        private async Task FlushOutgoingAsync()
        {
            while (await _sendSignal.Reader.WaitToReadAsync())
            {
                // The actual value doesn't matter.
                if (!_sendSignal.Reader.TryRead(out _))
                {
                    continue;
                }

                // Ask quiche for a datagram.
                var buf     = new byte[Constants.MAX_DATAGRAM_SIZE];
                var written = GetOutgoingDatagram(buf);

                if (written == (int)QuicheErrorCode.Done)
                {
                    Log("Finished sending.");
                    continue;
                }
                else if (written < 0)
                {
                    // Some kind of error
                    var ex = QuicheException.FromErrorCode((QuicheErrorCode)written);
                    Log($"Error preparing outgoing datagram: {ex.Message}");
                    throw ex;
                }

                // Send the datagram
                Log($"Sending datagram of {written} bytes.");
                var sent = await _client.SendAsync(buf, written);

                if (sent != written)
                {
                    throw new InvalidOperationException("Unable to send a complete datagram!");
                }

                // TODO: Handle timeout.

                // Update TCS state
                UpdateTaskState();
            }

            // The channel is closed.
        }
예제 #2
0
        internal int StreamRecv(long streamId, Memory <byte> buf, out bool fin)
        {
            unsafe
            {
                fixed(byte *pinned = buf.Span)
                {
                    var received = (int)NativeMethods.quiche_conn_stream_recv(
                        _connection, (ulong)streamId, pinned, (UIntPtr)buf.Length, out fin);

                    if (received == (int)QuicheErrorCode.Done)
                    {
                        received = 0;
                    }
                    else if (received < 0)
                    {
                        throw QuicheException.FromErrorCode((QuicheErrorCode)received);
                    }
                    return(received);
                }
            }
        }
예제 #3
0
        internal int StreamSend(long streamId, ReadOnlyMemory <byte> buf, bool fin)
        {
            unsafe
            {
                fixed(byte *pinned = buf.Span)
                {
                    var written = (int)NativeMethods.quiche_conn_stream_send(
                        _connection, (ulong)streamId, pinned, (UIntPtr)buf.Length, fin);

                    if (written < 0)
                    {
                        throw QuicheException.FromErrorCode((QuicheErrorCode)written);
                    }

                    // Signal the send loop that there's data to flush
                    SignalSend();

                    return(written);
                }
            }
        }
예제 #4
0
        private async Task ProcessIncomingAsync()
        {
            // Keep looping until we're told we're closed
            // If we're not closed, we expect at least one more UDP datagram, so we keep looping.
            while (!_closedTcs.Task.IsCompleted)
            {
                // Recieve from the socket
                Log("Waiting for incoming datagram");
                var recv = await _client.ReceiveAsync();

                Log($"Received incoming datagram of {recv.Buffer.Length} bytes.");

                // Send it to quiche
                var done = ProcessIncomingDatagram(recv.Buffer);
                if (done == (int)QuicheErrorCode.Done)
                {
                    Log("Finished recieving.");
                }
                else if (done < 0)
                {
                    // Some kind of error
                    var ex = QuicheException.FromErrorCode((QuicheErrorCode)done);
                    Log($"Error processing incoming datagram: {ex.Message}");
                    throw ex;
                }
                else
                {
                    Log($"Received {done} bytes.");
                }

                // Signal we may have more data to send
                SignalSend();

                // Update TCS state
                UpdateTaskState();
            }
            Log("Ending incoming loop");
        }
예제 #5
0
        private void ApplyConfiguration(IntPtr config)
        {
            if (ApplicationProtocols.Count > 0)
            {
                // Build a single buffer that is a set of non-empty 8-bit length prefixed strings:
                // For example "\x08http/1.1\x08http/0.9"
                var totalLength = ApplicationProtocols.Count + ApplicationProtocols.Sum(p => p.Value.Length);

                // PERF: Could probably stackalloc this if it's small enough
                // I believe quiche copies the data out as soon as the function is called, so it's safe to do so.
                var buf    = new byte[totalLength];
                var offset = 0;
                foreach (var protocol in ApplicationProtocols)
                {
                    if (protocol.Value.Length > byte.MaxValue)
                    {
                        var protocolName = Encoding.UTF8.GetString(protocol.Value.ToArray());
                        var message      = $"Application Protocol value is too long: {protocolName}";
                        throw new InvalidOperationException(message);
                    }
                    buf[offset] = (byte)protocol.Value.Length;
                    protocol.Value.CopyTo(buf.AsMemory(offset + 1));
                    offset += protocol.Value.Length + 1;
                }

                // Set the value on the internal config struct
                var err = NativeMethods.quiche_config_set_application_protos(config, buf, (UIntPtr)buf.Length);
                if (err != 0)
                {
                    throw QuicheException.FromErrorCode((QuicheErrorCode)err);
                }
            }

            NativeMethods.quiche_config_verify_peer(config, VerifyPeerCertificate);
            NativeMethods.quiche_config_grease(config, EnableTlsGrease);
            NativeMethods.quiche_config_set_disable_active_migration(config, !AllowActiveMigration);

            if (EnableSslKeyLogging)
            {
                NativeMethods.quiche_config_log_keys(config);
            }

            if (EnableEarlyData)
            {
                NativeMethods.quiche_config_enable_early_data(config);
            }

            if (IdleTimeout != TimeSpan.Zero)
            {
                NativeMethods.quiche_config_set_idle_timeout(config, (long)IdleTimeout.TotalMilliseconds);
            }

            if (MaxPacketSize != null)
            {
                NativeMethods.quiche_config_set_max_packet_size(config, MaxPacketSize.Value);
            }

            if (InitialMaxData != null)
            {
                NativeMethods.quiche_config_set_initial_max_data(config, InitialMaxData.Value);
            }

            if (InitialMaxStreamDataBiDiLocal != null)
            {
                NativeMethods.quiche_config_set_initial_max_stream_data_bidi_local(config, InitialMaxStreamDataBiDiLocal.Value);
            }

            if (InitialMaxStreamDataBiDiRemote != null)
            {
                NativeMethods.quiche_config_set_initial_max_stream_data_bidi_remote(config, InitialMaxStreamDataBiDiRemote.Value);
            }

            if (InitialMaxStreamDataUni != null)
            {
                NativeMethods.quiche_config_set_initial_max_stream_data_uni(config, InitialMaxStreamDataUni.Value);
            }

            if (InitialMaxStreamsBidi != null)
            {
                NativeMethods.quiche_config_set_initial_max_streams_bidi(config, InitialMaxStreamsBidi.Value);
            }

            if (InitialMaxStreamsUni != null)
            {
                NativeMethods.quiche_config_set_initial_max_streams_uni(config, InitialMaxStreamsUni.Value);
            }

            if (AckDelayExponent != null)
            {
                NativeMethods.quiche_config_set_ack_delay_exponent(config, AckDelayExponent.Value);
            }

            if (MaxAckDelay != null)
            {
                NativeMethods.quiche_config_set_max_ack_delay(config, MaxAckDelay.Value);
            }
        }