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. }
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); } } }
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); } } }
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"); }
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); } }