librats 1.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -331
- package/binding.gyp +16 -3
- package/lib/index.d.ts +288 -696
- package/lib/index.js +407 -44
- package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
- package/native-src/CMakeLists.txt +404 -179
- package/native-src/LICENSE +1 -1
- package/native-src/src/librats/bindings/rats.cpp +762 -0
- package/native-src/src/librats/bindings/rats.h +380 -0
- package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
- package/native-src/src/librats/bittorrent/bencode.h +176 -0
- package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
- package/native-src/src/librats/bittorrent/bitfield.h +76 -0
- package/native-src/src/librats/bittorrent/byte_io.h +58 -0
- package/native-src/src/librats/bittorrent/choker.cpp +25 -0
- package/native-src/src/librats/bittorrent/choker.h +46 -0
- package/native-src/src/librats/bittorrent/client.cpp +413 -0
- package/native-src/src/librats/bittorrent/client.h +227 -0
- package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
- package/native-src/src/librats/bittorrent/disk_io.h +150 -0
- package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
- package/native-src/src/librats/bittorrent/extensions.h +94 -0
- package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
- package/native-src/src/librats/bittorrent/file_storage.h +79 -0
- package/native-src/src/librats/bittorrent/log.h +42 -0
- package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
- package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
- package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
- package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
- package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
- package/native-src/src/librats/bittorrent/peer_list.h +75 -0
- package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
- package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
- package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
- package/native-src/src/librats/bittorrent/reactor.h +89 -0
- package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
- package/native-src/src/librats/bittorrent/resume_data.h +41 -0
- package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
- package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
- package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
- package/native-src/src/librats/bittorrent/torrent.h +260 -0
- package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
- package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
- package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
- package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
- package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
- package/native-src/src/librats/bittorrent/tracker.h +108 -0
- package/native-src/src/librats/bittorrent/types.cpp +206 -0
- package/native-src/src/librats/bittorrent/types.h +86 -0
- package/native-src/src/librats/core/address.cpp +35 -0
- package/native-src/src/librats/core/address.h +78 -0
- package/native-src/src/librats/core/bytes.h +69 -0
- package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
- package/native-src/src/librats/core/chained_send_buffer.h +183 -0
- package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
- package/native-src/src/librats/core/endpoint_parse.h +31 -0
- package/native-src/src/librats/core/event_bus.h +70 -0
- package/native-src/src/librats/core/host_endpoint.h +56 -0
- package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
- package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
- package/native-src/src/librats/core/ip_address.cpp +120 -0
- package/native-src/src/librats/core/ip_address.h +109 -0
- package/native-src/src/librats/core/mpsc_queue.h +47 -0
- package/native-src/src/librats/core/notifier.h +74 -0
- package/native-src/src/librats/core/receive_buffer.cpp +219 -0
- package/native-src/src/librats/core/receive_buffer.h +171 -0
- package/native-src/src/librats/core/service_registry.h +58 -0
- package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
- package/native-src/src/librats/core/socket.h +496 -0
- package/native-src/src/librats/core/timer_queue.h +105 -0
- package/native-src/src/librats/core/types.cpp +43 -0
- package/native-src/src/librats/core/types.h +103 -0
- package/native-src/src/librats/core/wakeup_pipe.h +83 -0
- package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
- package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
- package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
- package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
- package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
- package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
- package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
- package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
- package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
- package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
- package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
- package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
- package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
- package/native-src/src/librats/crypto/hkdf.c +266 -0
- package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
- package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
- package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
- package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
- package/native-src/src/librats/crypto/poly1305.h +37 -0
- package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
- package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
- package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
- package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
- package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
- package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
- package/native-src/src/librats/dht/announce.cpp +37 -0
- package/native-src/src/librats/dht/announce.h +41 -0
- package/native-src/src/librats/dht/bep42.cpp +109 -0
- package/native-src/src/librats/dht/bep42.h +48 -0
- package/native-src/src/librats/dht/dht.cpp +501 -0
- package/native-src/src/librats/dht/dht.h +119 -0
- package/native-src/src/librats/dht/dht_runner.cpp +103 -0
- package/native-src/src/librats/dht/dht_runner.h +71 -0
- package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
- package/native-src/src/librats/dht/dos_blocker.h +47 -0
- package/native-src/src/librats/dht/find_peers.cpp +52 -0
- package/native-src/src/librats/dht/find_peers.h +73 -0
- package/native-src/src/librats/dht/id.h +167 -0
- package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
- package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
- package/native-src/src/librats/dht/log.h +38 -0
- package/native-src/src/librats/dht/node.cpp +473 -0
- package/native-src/src/librats/dht/node.h +164 -0
- package/native-src/src/librats/dht/node_entry.h +81 -0
- package/native-src/src/librats/dht/observer.h +72 -0
- package/native-src/src/librats/dht/persistence.cpp +90 -0
- package/native-src/src/librats/dht/persistence.h +32 -0
- package/native-src/src/librats/dht/routing_table.cpp +559 -0
- package/native-src/src/librats/dht/routing_table.h +185 -0
- package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
- package/native-src/src/librats/dht/rpc_manager.h +77 -0
- package/native-src/src/librats/dht/storage.cpp +92 -0
- package/native-src/src/librats/dht/storage.h +74 -0
- package/native-src/src/librats/dht/transport.h +27 -0
- package/native-src/src/librats/dht/traversal.cpp +326 -0
- package/native-src/src/librats/dht/traversal.h +120 -0
- package/native-src/src/librats/dht/udp_transport.cpp +49 -0
- package/native-src/src/librats/dht/udp_transport.h +51 -0
- package/native-src/src/librats/mdns/log.h +22 -0
- package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
- package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
- package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
- package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
- package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
- package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
- package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
- package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
- package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
- package/native-src/src/librats/node/circuit_service.h +84 -0
- package/native-src/src/librats/node/config.h +110 -0
- package/native-src/src/librats/node/dial_service.h +54 -0
- package/native-src/src/librats/node/dialer.cpp +264 -0
- package/native-src/src/librats/node/dialer.h +188 -0
- package/native-src/src/librats/node/host_events.h +26 -0
- package/native-src/src/librats/node/identify.cpp +130 -0
- package/native-src/src/librats/node/identify.h +71 -0
- package/native-src/src/librats/node/nat_status.cpp +103 -0
- package/native-src/src/librats/node/nat_status.h +118 -0
- package/native-src/src/librats/node/node.cpp +865 -0
- package/native-src/src/librats/node/node.h +344 -0
- package/native-src/src/librats/node/node_context.h +33 -0
- package/native-src/src/librats/node/peer_network.h +91 -0
- package/native-src/src/librats/peer/peer.h +49 -0
- package/native-src/src/librats/peer/peer_book.cpp +181 -0
- package/native-src/src/librats/peer/peer_book.h +88 -0
- package/native-src/src/librats/peer/peer_id.cpp +72 -0
- package/native-src/src/librats/peer/peer_id.h +62 -0
- package/native-src/src/librats/peer/peer_info.h +37 -0
- package/native-src/src/librats/peer/peer_table.cpp +170 -0
- package/native-src/src/librats/peer/peer_table.h +148 -0
- package/native-src/src/librats/security/handshaker.h +66 -0
- package/native-src/src/librats/security/identity.h +43 -0
- package/native-src/src/librats/security/noise_security.cpp +122 -0
- package/native-src/src/librats/security/noise_security.h +37 -0
- package/native-src/src/librats/security/plaintext_security.h +106 -0
- package/native-src/src/librats/security/session.h +37 -0
- package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
- package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
- package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
- package/native-src/src/librats/subsystems/bittorrent.h +136 -0
- package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
- package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
- package/native-src/src/librats/subsystems/dht_service.h +36 -0
- package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
- package/native-src/src/librats/subsystems/file_transfer.h +367 -0
- package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
- package/native-src/src/librats/subsystems/hole_punch.h +290 -0
- package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
- package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
- package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
- package/native-src/src/librats/subsystems/message_json.cpp +112 -0
- package/native-src/src/librats/subsystems/message_json.h +88 -0
- package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
- package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
- package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
- package/native-src/src/librats/subsystems/ping_service.h +66 -0
- package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
- package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
- package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
- package/native-src/src/librats/subsystems/pubsub.h +175 -0
- package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
- package/native-src/src/librats/subsystems/reconnection.h +126 -0
- package/native-src/src/librats/subsystems/relay.cpp +1142 -0
- package/native-src/src/librats/subsystems/relay.h +211 -0
- package/native-src/src/librats/subsystems/relay_service.h +46 -0
- package/native-src/src/librats/transport/connection.cpp +343 -0
- package/native-src/src/librats/transport/connection.h +283 -0
- package/native-src/src/librats/transport/link.h +96 -0
- package/native-src/src/librats/transport/reactor.cpp +588 -0
- package/native-src/src/librats/transport/reactor.h +262 -0
- package/native-src/src/librats/transport/reactor_pool.h +81 -0
- package/native-src/src/librats/transport/relay_link.cpp +208 -0
- package/native-src/src/librats/transport/relay_link.h +303 -0
- package/native-src/src/librats/transport/tcp_link.cpp +49 -0
- package/native-src/src/librats/transport/tcp_link.h +43 -0
- package/native-src/src/librats/transport/udp_mux.cpp +617 -0
- package/native-src/src/librats/transport/udp_mux.h +363 -0
- package/native-src/src/librats/transport/udp_packet.cpp +121 -0
- package/native-src/src/librats/transport/udp_packet.h +190 -0
- package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
- package/native-src/src/librats/transport/udp_stream.h +614 -0
- package/native-src/src/librats/util/features.h.in +51 -0
- package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
- package/native-src/src/librats/util/fs.h +136 -0
- package/native-src/src/librats/util/json.cpp +1002 -0
- package/native-src/src/librats/util/json.h +444 -0
- package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
- package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
- package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
- package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
- package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
- package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
- package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
- package/native-src/src/librats/util/rats_export.h +69 -0
- package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
- package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
- package/native-src/src/librats/wire/frame.cpp +76 -0
- package/native-src/src/librats/wire/frame.h +111 -0
- package/native-src/src/librats/wire/message_router.cpp +45 -0
- package/native-src/src/librats/wire/message_router.h +47 -0
- package/package.json +5 -4
- package/scripts/build-librats.js +1 -0
- package/scripts/postinstall.js +3 -3
- package/scripts/prepare-package.js +4 -4
- package/scripts/verify-installation.js +63 -105
- package/src/librats_node.cpp +1067 -1323
- package/native-src/src/bencode.cpp +0 -485
- package/native-src/src/bencode.h +0 -145
- package/native-src/src/bittorrent.cpp +0 -14
- package/native-src/src/bittorrent.h +0 -74
- package/native-src/src/bt_bitfield.cpp +0 -372
- package/native-src/src/bt_bitfield.h +0 -316
- package/native-src/src/bt_choker.cpp +0 -228
- package/native-src/src/bt_choker.h +0 -147
- package/native-src/src/bt_client.cpp +0 -1047
- package/native-src/src/bt_client.h +0 -445
- package/native-src/src/bt_create_torrent.cpp +0 -677
- package/native-src/src/bt_create_torrent.h +0 -473
- package/native-src/src/bt_extension.cpp +0 -469
- package/native-src/src/bt_extension.h +0 -309
- package/native-src/src/bt_file_storage.cpp +0 -261
- package/native-src/src/bt_file_storage.h +0 -298
- package/native-src/src/bt_handshake.cpp +0 -134
- package/native-src/src/bt_handshake.h +0 -157
- package/native-src/src/bt_messages.cpp +0 -364
- package/native-src/src/bt_messages.h +0 -324
- package/native-src/src/bt_network.cpp +0 -1007
- package/native-src/src/bt_network.h +0 -417
- package/native-src/src/bt_peer_connection.cpp +0 -742
- package/native-src/src/bt_peer_connection.h +0 -592
- package/native-src/src/bt_piece_picker.cpp +0 -786
- package/native-src/src/bt_piece_picker.h +0 -473
- package/native-src/src/bt_resume_data.cpp +0 -410
- package/native-src/src/bt_resume_data.h +0 -249
- package/native-src/src/bt_torrent.cpp +0 -2120
- package/native-src/src/bt_torrent.h +0 -641
- package/native-src/src/bt_torrent_info.cpp +0 -659
- package/native-src/src/bt_torrent_info.h +0 -418
- package/native-src/src/bt_types.h +0 -621
- package/native-src/src/chained_send_buffer.cpp +0 -75
- package/native-src/src/chained_send_buffer.h +0 -137
- package/native-src/src/crypto/hkdf.c +0 -266
- package/native-src/src/crypto/poly1305.h +0 -36
- package/native-src/src/dht.cpp +0 -3311
- package/native-src/src/dht.h +0 -717
- package/native-src/src/disk_io.cpp +0 -632
- package/native-src/src/disk_io.h +0 -315
- package/native-src/src/file_transfer.cpp +0 -1415
- package/native-src/src/file_transfer.h +0 -286
- package/native-src/src/fs.h +0 -108
- package/native-src/src/gossipsub.cpp +0 -1139
- package/native-src/src/gossipsub.h +0 -403
- package/native-src/src/ice.cpp +0 -893
- package/native-src/src/ice.h +0 -559
- package/native-src/src/json.hpp +0 -25526
- package/native-src/src/librats.cpp +0 -2378
- package/native-src/src/librats.h +0 -2324
- package/native-src/src/librats_bittorrent.cpp +0 -601
- package/native-src/src/librats_c.cpp +0 -1557
- package/native-src/src/librats_c.h +0 -323
- package/native-src/src/librats_discovery.cpp +0 -402
- package/native-src/src/librats_encryption.cpp +0 -275
- package/native-src/src/librats_file_transfer.cpp +0 -144
- package/native-src/src/librats_gossipsub.cpp +0 -289
- package/native-src/src/librats_ice.cpp +0 -213
- package/native-src/src/librats_log_macros.h +0 -36
- package/native-src/src/librats_logging.cpp +0 -173
- package/native-src/src/librats_mdns.cpp +0 -166
- package/native-src/src/librats_persistence.cpp +0 -796
- package/native-src/src/librats_portmap.cpp +0 -419
- package/native-src/src/librats_reconnection.cpp +0 -218
- package/native-src/src/librats_statistic.cpp +0 -105
- package/native-src/src/librats_storage.cpp +0 -189
- package/native-src/src/rats_export.h +0 -17
- package/native-src/src/receive_buffer.cpp +0 -82
- package/native-src/src/receive_buffer.h +0 -127
- package/native-src/src/socket.h +0 -228
- package/native-src/src/threadmanager.cpp +0 -105
- package/native-src/src/threadmanager.h +0 -53
- package/native-src/src/tracker.cpp +0 -1264
- package/native-src/src/tracker.h +0 -319
- package/native-src/src/turn.cpp +0 -762
- package/native-src/src/turn.h +0 -460
- package/native-src/src/wakeup_pipe.h +0 -60
- /package/native-src/src/{os.h → librats/util/os.h} +0 -0
|
@@ -0,0 +1,1194 @@
|
|
|
1
|
+
#include "librats/transport/udp_stream.h"
|
|
2
|
+
#include "librats/core/io_poller.h" // PollIn / PollOut / PollErr
|
|
3
|
+
#include "librats/util/logger.h"
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
|
|
8
|
+
namespace librats {
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
using Clock = UdpStream::Clock;
|
|
13
|
+
|
|
14
|
+
/// Sentinel for "no delayed ack is armed".
|
|
15
|
+
constexpr Clock::time_point kNoDeadline{};
|
|
16
|
+
|
|
17
|
+
template <typename A, typename B>
|
|
18
|
+
Clock::duration clamp_duration(Clock::duration v, A lo, B hi) {
|
|
19
|
+
const auto low = std::chrono::duration_cast<Clock::duration>(lo);
|
|
20
|
+
const auto high = std::chrono::duration_cast<Clock::duration>(hi);
|
|
21
|
+
return v < low ? low : (v > high ? high : v);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
} // namespace
|
|
25
|
+
|
|
26
|
+
UdpStream::UdpStream(UdpStreamHost& host, const Address& remote, uint32_t recv_id,
|
|
27
|
+
uint32_t send_id, ConnRole role, Clock::time_point now,
|
|
28
|
+
DialProfile profile)
|
|
29
|
+
: host_(host), remote_(remote), recv_id_(recv_id), send_id_(send_id), role_(role),
|
|
30
|
+
state_(role == ConnRole::Outbound ? State::SynSent : State::Connected),
|
|
31
|
+
last_recv_(now), last_send_(now) {
|
|
32
|
+
pace_last_ = now;
|
|
33
|
+
pace_tokens_ = kPaceMinBurst; // nothing to pace against until the first RTT sample
|
|
34
|
+
// Seeded rather than left at the epoch: a sentinel there would collide with a
|
|
35
|
+
// clock whose zero is a real instant, which is exactly what a test driving
|
|
36
|
+
// virtual time hands us.
|
|
37
|
+
last_data_send_ = now;
|
|
38
|
+
hystart_reset();
|
|
39
|
+
|
|
40
|
+
if (role_ != ConnRole::Outbound) return;
|
|
41
|
+
|
|
42
|
+
// The dial's own retry shape (see DialProfile). Clamped rather than trusted:
|
|
43
|
+
// zero attempts would be a stream that dies on its first timeout with nothing
|
|
44
|
+
// ever sent twice, and an interval outside the RTO bounds would either spin the
|
|
45
|
+
// timer or stall the dial past the establish deadline above it.
|
|
46
|
+
syn_attempts_ = (std::max)(1, profile.syn_attempts);
|
|
47
|
+
syn_backoff_ = profile.syn_backoff;
|
|
48
|
+
rto_ = clamp_duration(std::chrono::milliseconds(profile.syn_rto_ms), kMinRto, kMaxRto);
|
|
49
|
+
|
|
50
|
+
// The dial itself is just the first packet of the stream: a Syn occupies a
|
|
51
|
+
// sequence number like any other, so the ordinary retransmission machinery
|
|
52
|
+
// covers a lost dial with no special case — only a tighter attempt cap (see
|
|
53
|
+
// kSynMaxAttempts), so a UDP-blocked path gives up quickly enough for the
|
|
54
|
+
// dialer to fall back to TCP.
|
|
55
|
+
OutPacket syn = new_packet(rudp::PacketType::Syn);
|
|
56
|
+
syn.seq = next_seq_++;
|
|
57
|
+
sent_.push_back(std::move(syn));
|
|
58
|
+
transmit(sent_.back(), now);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Outbound ────────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
UdpStream::OutPacket UdpStream::new_packet(rudp::PacketType type) {
|
|
64
|
+
OutPacket pkt;
|
|
65
|
+
pkt.type = type;
|
|
66
|
+
if (!spare_.empty()) {
|
|
67
|
+
pkt.buf = std::move(spare_.back());
|
|
68
|
+
spare_.pop_back();
|
|
69
|
+
pkt.buf.clear(); // capacity survives; the bytes do not
|
|
70
|
+
} else {
|
|
71
|
+
pkt.buf.reserve(rudp::kMaxDatagram); // headroom + a full payload, allocated once
|
|
72
|
+
}
|
|
73
|
+
pkt.buf.resize(rudp::kMaxHeaderSize); // reserve the headroom transmit() writes into
|
|
74
|
+
return pkt;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
void UdpStream::recycle(OutPacket& pkt) {
|
|
78
|
+
if (spare_.size() >= kMaxSpareBuffers) return;
|
|
79
|
+
pkt.buf.clear();
|
|
80
|
+
spare_.push_back(std::move(pkt.buf));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
uint16_t UdpStream::advertised_window() const noexcept {
|
|
84
|
+
// What we are still willing to buffer: the reorder slots a gap is holding,
|
|
85
|
+
// plus whatever the connection has not read out of the in-order buffer yet.
|
|
86
|
+
const size_t used = reorder_.size() + inbox_.size() / rudp::kMaxPayload;
|
|
87
|
+
if (used >= rudp::kMaxWindowPackets) return 0;
|
|
88
|
+
return static_cast<uint16_t>(rudp::kMaxWindowPackets - used);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
void UdpStream::fill_common(rudp::Packet& p) const {
|
|
92
|
+
p.conn_id = send_id_;
|
|
93
|
+
p.window = advertised_window();
|
|
94
|
+
// Cumulative: the highest sequence number received with no gap before it.
|
|
95
|
+
// recv_next_ is the first one still missing, so the ack is the one before it
|
|
96
|
+
// (0 while nothing has arrived — sequence numbers start at 1).
|
|
97
|
+
p.ack = recv_next_ - 1;
|
|
98
|
+
|
|
99
|
+
if (reorder_.empty()) return;
|
|
100
|
+
|
|
101
|
+
const uint32_t bits = sack_bitmap();
|
|
102
|
+
if (bits != 0) {
|
|
103
|
+
p.flags |= rudp::FlagSack;
|
|
104
|
+
p.sack = bits;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
uint32_t UdpStream::sack_bitmap() const noexcept {
|
|
109
|
+
if (!sack_dirty_) return sack_bits_;
|
|
110
|
+
|
|
111
|
+
// Selective ack: bit i covers ack+2+i, i.e. the 32 packets that follow the
|
|
112
|
+
// hole at recv_next_. Derived from the reorder buffer and recv_next_ alone, so
|
|
113
|
+
// it is rebuilt only when one of those moves — not on every packet sent.
|
|
114
|
+
uint32_t bits = 0;
|
|
115
|
+
for (uint32_t i = 0; i < rudp::kSackBits; ++i)
|
|
116
|
+
if (reorder_.count(recv_next_ + 1 + i)) bits |= (1u << i);
|
|
117
|
+
|
|
118
|
+
sack_bits_ = bits;
|
|
119
|
+
sack_dirty_ = false;
|
|
120
|
+
return bits;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
void UdpStream::transmit(OutPacket& pkt, Clock::time_point now) {
|
|
124
|
+
rudp::Packet p;
|
|
125
|
+
p.type = pkt.type;
|
|
126
|
+
fill_common(p); // may raise FlagSack, which is what decides the header length
|
|
127
|
+
p.seq = pkt.seq;
|
|
128
|
+
|
|
129
|
+
// The payload is already sitting in pkt.buf behind kMaxHeaderSize bytes of
|
|
130
|
+
// headroom, so the header goes in immediately ahead of it and the datagram
|
|
131
|
+
// leaves as one contiguous range. Nothing is copied here — not on the first
|
|
132
|
+
// transmission, and not on any retransmission.
|
|
133
|
+
const size_t hdr = rudp::header_size(p);
|
|
134
|
+
uint8_t* const start = pkt.buf.data() + (rudp::kMaxHeaderSize - hdr);
|
|
135
|
+
rudp::encode_header(p, start);
|
|
136
|
+
|
|
137
|
+
host_.send_datagram(remote_, start, hdr + pkt.size());
|
|
138
|
+
|
|
139
|
+
// Everything that reaches the wire is metered, retransmissions included: a
|
|
140
|
+
// repair loads the bottleneck exactly as new data does, and a pacer that
|
|
141
|
+
// ignored repairs would let a recovering stream burst precisely when the path
|
|
142
|
+
// has just proved it cannot take one. Only *new* data is gated by the pacer
|
|
143
|
+
// though (see can_transmit) — holding a retransmission back would stall the
|
|
144
|
+
// recovery the timeout just started.
|
|
145
|
+
const uint64_t on_wire = hdr + pkt.size();
|
|
146
|
+
pace_tokens_ = pace_tokens_ > on_wire ? pace_tokens_ - on_wire : 0;
|
|
147
|
+
|
|
148
|
+
// These bytes are on the path now. A retransmission of a packet that was never
|
|
149
|
+
// given up on adds nothing — it is the same bytes travelling again, not more of
|
|
150
|
+
// them — which is why this is a transition rather than an addition.
|
|
151
|
+
if (!pkt.in_flight) {
|
|
152
|
+
pkt.in_flight = true;
|
|
153
|
+
flight_bytes_ += pkt.size();
|
|
154
|
+
}
|
|
155
|
+
// RFC 6298 (5.1): something is outstanding, so the timer has to be running —
|
|
156
|
+
// set to whichever of the probe and the timeout comes first (see loss_timeout).
|
|
157
|
+
if (rto_deadline_ == kNoDeadline) rto_deadline_ = now + loss_timeout();
|
|
158
|
+
|
|
159
|
+
pkt.sends++;
|
|
160
|
+
pkt.sent_at = now;
|
|
161
|
+
last_send_ = now;
|
|
162
|
+
// Data, not a bare acknowledgement — this is the clock the idle-restart rule
|
|
163
|
+
// reads, and the keep-alive must not be allowed to keep resetting it.
|
|
164
|
+
last_data_send_ = now;
|
|
165
|
+
|
|
166
|
+
// Every packet carries the ack field, so sending one settles whatever
|
|
167
|
+
// acknowledgement was owed — the delayed-ack timer exists precisely to give
|
|
168
|
+
// this a chance to happen.
|
|
169
|
+
need_ack_ = false;
|
|
170
|
+
unacked_packets_ = 0;
|
|
171
|
+
ack_due_ = kNoDeadline;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
void UdpStream::send_control(rudp::PacketType type, Clock::time_point now) {
|
|
175
|
+
uint8_t buf[rudp::kMaxDatagram];
|
|
176
|
+
|
|
177
|
+
rudp::Packet p;
|
|
178
|
+
p.type = type;
|
|
179
|
+
fill_common(p);
|
|
180
|
+
// A control packet consumes no sequence number: it carries the next one we
|
|
181
|
+
// *will* use, purely so a peer can see where the stream stands. Nothing
|
|
182
|
+
// retransmits it — a lost ack is repaired by the next one.
|
|
183
|
+
p.seq = next_seq_;
|
|
184
|
+
|
|
185
|
+
host_.send_datagram(remote_, buf, rudp::encode(p, buf));
|
|
186
|
+
|
|
187
|
+
last_send_ = now;
|
|
188
|
+
need_ack_ = false;
|
|
189
|
+
unacked_packets_ = 0;
|
|
190
|
+
ack_due_ = kNoDeadline;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
bool UdpStream::cwnd_allows(size_t bytes) const noexcept {
|
|
194
|
+
// Always allow one packet out when the path is idle. This keeps a stream from
|
|
195
|
+
// deadlocking when the congestion window shrinks below a single packet — a
|
|
196
|
+
// window is an estimate of what the path will carry, and an estimate that has
|
|
197
|
+
// collapsed to nothing still has to be able to take a sample. It says nothing
|
|
198
|
+
// about the *receiver's* window, which is not an estimate at all and is
|
|
199
|
+
// enforced ahead of this, in window_allows().
|
|
200
|
+
if (flight_bytes_ == 0) return true;
|
|
201
|
+
return flight_bytes_ + bytes <= cwnd_;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
bool UdpStream::window_allows() const noexcept {
|
|
205
|
+
if (state_ != State::Connected) return false; // nothing may overtake the Syn
|
|
206
|
+
if (unsent_.empty()) return false;
|
|
207
|
+
|
|
208
|
+
// A receiver that advertises zero has said it will buffer nothing more, and
|
|
209
|
+
// that is absolute: it is the only bound on the memory one peer can make
|
|
210
|
+
// another spend on it, so an empty pipe is no licence to send anyway. It would
|
|
211
|
+
// not stay a single probe if it were — the packet is ordinary in-order data,
|
|
212
|
+
// the peer acknowledges it and holds it, the pipe empties, and the next one
|
|
213
|
+
// follows on its heels. What looks like a persist probe is then a steady
|
|
214
|
+
// trickle that fills the receiver far past the window it advertised, bounded
|
|
215
|
+
// in the end by the sender's own queue rather than by anything the receiver
|
|
216
|
+
// said.
|
|
217
|
+
//
|
|
218
|
+
// Nothing is needed from this side to get going again: a receiver whose reader
|
|
219
|
+
// drains a full buffer announces the re-opened window at once and unprompted
|
|
220
|
+
// (see read(), and UdpStreamLink::read for the wake-up that carries it), and
|
|
221
|
+
// its keep-alive carries the window if that announcement is lost.
|
|
222
|
+
if (peer_window_ == 0) return false;
|
|
223
|
+
|
|
224
|
+
if (sent_.empty()) return true;
|
|
225
|
+
if (sent_.size() >= peer_window_) return false;
|
|
226
|
+
if (sent_.size() >= rudp::kMaxWindowPackets) return false;
|
|
227
|
+
return cwnd_allows(unsent_.front().size());
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
bool UdpStream::can_transmit() const noexcept {
|
|
231
|
+
if (!window_allows()) return false;
|
|
232
|
+
return pace_allows(unsent_.front().size());
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ── Pacing ──────────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
uint64_t UdpStream::pace_window() const noexcept {
|
|
238
|
+
// No round-trip estimate, no rate: there is nothing to derive a pace from,
|
|
239
|
+
// and the window at that point is four packets, which cannot hurt anyone.
|
|
240
|
+
if (!have_rtt_ || srtt_ <= Clock::duration::zero()) return 0;
|
|
241
|
+
|
|
242
|
+
// The cautious phase grows at a quarter of slow start, so it does not need
|
|
243
|
+
// slow start's doubling headroom.
|
|
244
|
+
const bool doubling = in_slow_start() && !css_;
|
|
245
|
+
const uint32_t num = doubling ? kPaceGainSlowStartNum : kPaceGainSteadyNum;
|
|
246
|
+
const uint32_t den = doubling ? kPaceGainSlowStartDen : kPaceGainSteadyDen;
|
|
247
|
+
return static_cast<uint64_t>(cwnd_) * num / den;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
uint64_t UdpStream::pace_bytes_over(Clock::duration dt) const noexcept {
|
|
251
|
+
const uint64_t window = pace_window();
|
|
252
|
+
if (window == 0 || dt <= Clock::duration::zero()) return 0;
|
|
253
|
+
|
|
254
|
+
// window * dt / srtt. The caller clamps dt to a second and a window is at
|
|
255
|
+
// most ~1.2 MB, so the product stays four orders of magnitude inside 64 bits.
|
|
256
|
+
const auto dt_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(dt).count();
|
|
257
|
+
const auto srtt_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(srtt_).count();
|
|
258
|
+
if (srtt_ns <= 0) return 0;
|
|
259
|
+
return window * static_cast<uint64_t>(dt_ns) / static_cast<uint64_t>(srtt_ns);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
void UdpStream::pace_accrue(Clock::time_point now) {
|
|
263
|
+
auto dt = now - pace_last_;
|
|
264
|
+
if (dt < Clock::duration::zero()) dt = Clock::duration::zero();
|
|
265
|
+
// The bucket is capped a few lines below, so integrating over a long silence
|
|
266
|
+
// buys nothing — and the clamp is what keeps the multiplication finite.
|
|
267
|
+
const auto cap = std::chrono::duration_cast<Clock::duration>(std::chrono::seconds(1));
|
|
268
|
+
if (dt > cap) dt = cap;
|
|
269
|
+
pace_last_ = now;
|
|
270
|
+
|
|
271
|
+
if (pace_window() == 0) {
|
|
272
|
+
// Not pacing yet. Keep the bucket at the floor rather than at zero, so
|
|
273
|
+
// the first packet after the first round-trip sample is not held back by
|
|
274
|
+
// an empty bucket the stream never had a chance to fill.
|
|
275
|
+
pace_tokens_ = kPaceMinBurst;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
pace_tokens_ += pace_bytes_over(dt);
|
|
280
|
+
const uint64_t burst = (std::max)(static_cast<uint64_t>(kPaceMinBurst),
|
|
281
|
+
pace_bytes_over(kPaceQuantum));
|
|
282
|
+
if (pace_tokens_ > burst) pace_tokens_ = burst;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
bool UdpStream::pace_allows(size_t bytes) const noexcept {
|
|
286
|
+
if (pace_window() == 0) return true; // no estimate to pace against
|
|
287
|
+
// Never hold back a stream with an empty pipe. That covers the lone packet a
|
|
288
|
+
// recovering stream is allowed and the first packet after an idle period —
|
|
289
|
+
// neither of which can congest anything, and both of which would deadlock if a
|
|
290
|
+
// rate derived from an empty pipe were allowed to refuse them.
|
|
291
|
+
if (flight_bytes_ == 0) return true;
|
|
292
|
+
return pace_tokens_ >= bytes;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
UdpStream::Clock::duration UdpStream::pace_wait(size_t bytes) const noexcept {
|
|
296
|
+
const uint64_t window = pace_window();
|
|
297
|
+
if (window == 0 || pace_tokens_ >= bytes) return Clock::duration::zero();
|
|
298
|
+
|
|
299
|
+
const uint64_t deficit = static_cast<uint64_t>(bytes) - pace_tokens_;
|
|
300
|
+
const auto srtt_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(srtt_).count();
|
|
301
|
+
if (srtt_ns <= 0) return Clock::duration::zero();
|
|
302
|
+
|
|
303
|
+
// The inverse of pace_bytes_over(): how long this many bytes take to accrue.
|
|
304
|
+
const uint64_t wait_ns = deficit * static_cast<uint64_t>(srtt_ns) / window;
|
|
305
|
+
return std::chrono::duration_cast<Clock::duration>(std::chrono::nanoseconds(wait_ns));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
void UdpStream::restart_after_idle(Clock::time_point now) {
|
|
309
|
+
// Only with the pipe genuinely empty. While anything is outstanding the
|
|
310
|
+
// window describes a path we are actively measuring, and is not stale.
|
|
311
|
+
if (flight_bytes_ != 0) return;
|
|
312
|
+
if (cwnd_ <= kInitialCwnd) return; // nothing to give back
|
|
313
|
+
|
|
314
|
+
const auto idle = now - last_data_send_;
|
|
315
|
+
if (idle < rto_) return;
|
|
316
|
+
|
|
317
|
+
// RFC 2861. A congestion window is a measurement, and one nobody has
|
|
318
|
+
// validated for a round trip is a guess about a path we stopped watching. The
|
|
319
|
+
// shape of P2P traffic makes this the common case rather than the corner one
|
|
320
|
+
// — silence, a burst of gossip, silence — and without this the burst goes out
|
|
321
|
+
// at a rate justified by what the path looked like ten seconds ago.
|
|
322
|
+
//
|
|
323
|
+
// Halve once per timeout of silence, down to the window a fresh stream would
|
|
324
|
+
// start with. ssthresh is deliberately untouched: it records where this path
|
|
325
|
+
// congested, and going quiet does not make that wrong — keeping it is what
|
|
326
|
+
// lets the stream climb back in slow start instead of re-running the whole
|
|
327
|
+
// discovery from scratch.
|
|
328
|
+
const auto rto_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(rto_).count();
|
|
329
|
+
const auto idle_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(idle).count();
|
|
330
|
+
const uint64_t halvings = rto_ns > 0 ? static_cast<uint64_t>(idle_ns / rto_ns) : 32;
|
|
331
|
+
|
|
332
|
+
cwnd_ = halvings >= 32 ? kInitialCwnd
|
|
333
|
+
: (std::max)(cwnd_ >> halvings, kInitialCwnd);
|
|
334
|
+
|
|
335
|
+
// Consume the silence that was just paid for. Without this the *same* elapsed
|
|
336
|
+
// time is re-read on every tick and halves an already-halved window again, so
|
|
337
|
+
// a window would decay per tick rather than per timeout — far faster than
|
|
338
|
+
// intended, and enough to undo a healthy window during an ordinary lull.
|
|
339
|
+
last_data_send_ += Clock::duration(rto_ * static_cast<Clock::rep>(halvings));
|
|
340
|
+
|
|
341
|
+
// The bucket describes a rate that no longer applies either. Zeroing it (and
|
|
342
|
+
// the clock behind it) means the stream releases the one packet an empty pipe
|
|
343
|
+
// always allows and then paces the rest at the restarted rate, instead of
|
|
344
|
+
// spending a burst allowance earned while it was silent.
|
|
345
|
+
pace_tokens_ = 0;
|
|
346
|
+
pace_last_ = now;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
void UdpStream::retransmit_lost(Clock::time_point now) {
|
|
350
|
+
// The flag keeps this off the hot path entirely: without it the scan below
|
|
351
|
+
// would walk the whole retransmission queue on every acknowledgement of a
|
|
352
|
+
// healthy transfer — a window's worth of packets, every time — to discover
|
|
353
|
+
// that there is nothing to repair.
|
|
354
|
+
if (!have_lost_) return;
|
|
355
|
+
|
|
356
|
+
// Packets a timeout has given up on: still owed to the peer, no longer counted
|
|
357
|
+
// against the window. They are re-sent from the front, under whatever the
|
|
358
|
+
// congestion window currently allows — so the pipe refills at the rate the
|
|
359
|
+
// recovering window dictates instead of all at once, and a hole is always
|
|
360
|
+
// repaired before anything queued behind it is sent.
|
|
361
|
+
for (OutPacket& pkt : sent_) {
|
|
362
|
+
if (pkt.acked || pkt.in_flight) continue; // the peer has it, or it is already back out
|
|
363
|
+
if (pkt.sends == 0) continue; // never sent; pump() owns it
|
|
364
|
+
if (!cwnd_allows(pkt.size())) return; // more still owed: come back with a bigger window
|
|
365
|
+
transmit(pkt, now);
|
|
366
|
+
++retransmits_;
|
|
367
|
+
}
|
|
368
|
+
have_lost_ = false; // the scan reached the end, so nothing is waiting to go back out
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
void UdpStream::pump(Clock::time_point now) {
|
|
372
|
+
// A window that has gone unused for a round trip is stale before anything
|
|
373
|
+
// else here reads it, so this comes first.
|
|
374
|
+
restart_after_idle(now);
|
|
375
|
+
pace_accrue(now);
|
|
376
|
+
|
|
377
|
+
// Repairs first: what the peer is missing blocks everything queued behind it,
|
|
378
|
+
// so spending the window on new data before the hole is filled would only grow
|
|
379
|
+
// the peer's reorder buffer.
|
|
380
|
+
retransmit_lost(now);
|
|
381
|
+
|
|
382
|
+
while (can_transmit()) {
|
|
383
|
+
OutPacket pkt = std::move(unsent_.front());
|
|
384
|
+
unsent_.pop_front();
|
|
385
|
+
pkt.seq = next_seq_++;
|
|
386
|
+
|
|
387
|
+
sent_.push_back(std::move(pkt));
|
|
388
|
+
transmit(sent_.back(), now); // this is what puts it in flight
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// If the loop stopped and every window would still have allowed the next
|
|
392
|
+
// packet, the pacer is the only thing holding it — and a pacer is released by
|
|
393
|
+
// time, not by an acknowledgement, so it needs a deadline of its own. When a
|
|
394
|
+
// window is what stopped us there is deliberately no timer: the ack that
|
|
395
|
+
// opens it is what wakes the stream, and arming one here would spin.
|
|
396
|
+
pace_due_ = Clock::time_point{};
|
|
397
|
+
if (window_allows()) {
|
|
398
|
+
const auto wait = pace_wait(unsent_.front().size());
|
|
399
|
+
if (wait > Clock::duration::zero()) pace_due_ = now + wait;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
size_t UdpStream::write(const ByteView* slices, size_t count, Clock::time_point now) {
|
|
404
|
+
if (state_ != State::Connected || fin_queued_) return 0;
|
|
405
|
+
|
|
406
|
+
size_t budget = queued_bytes_ >= kSendQueueLimit ? 0 : kSendQueueLimit - queued_bytes_;
|
|
407
|
+
if (budget == 0) return 0;
|
|
408
|
+
|
|
409
|
+
size_t taken = 0;
|
|
410
|
+
for (size_t i = 0; i < count && budget > 0; ++i) {
|
|
411
|
+
const uint8_t* src = slices[i].data();
|
|
412
|
+
size_t left = slices[i].size();
|
|
413
|
+
while (left > 0 && budget > 0) {
|
|
414
|
+
// Pack into the tail packet while it has room, so a burst of small
|
|
415
|
+
// frames leaves as one datagram instead of one datagram each. Only a
|
|
416
|
+
// Data packet can be topped up — a queued Fin closes the stream and
|
|
417
|
+
// must stay the last thing in the queue.
|
|
418
|
+
if (unsent_.empty() || unsent_.back().type != rudp::PacketType::Data ||
|
|
419
|
+
unsent_.back().space() == 0) {
|
|
420
|
+
unsent_.push_back(new_packet(rudp::PacketType::Data));
|
|
421
|
+
}
|
|
422
|
+
OutPacket& tail = unsent_.back();
|
|
423
|
+
|
|
424
|
+
const size_t n = (std::min)({left, tail.space(), budget});
|
|
425
|
+
tail.buf.insert(tail.buf.end(), src, src + n);
|
|
426
|
+
src += n;
|
|
427
|
+
left -= n;
|
|
428
|
+
budget -= n;
|
|
429
|
+
taken += n;
|
|
430
|
+
queued_bytes_ += n;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
pump(now);
|
|
435
|
+
return taken;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
void UdpStream::begin_close(Clock::time_point now) {
|
|
439
|
+
if (state_ != State::Connected || fin_queued_) return;
|
|
440
|
+
fin_queued_ = true;
|
|
441
|
+
|
|
442
|
+
unsent_.push_back(new_packet(rudp::PacketType::Fin));
|
|
443
|
+
pump(now);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
void UdpStream::abort(Clock::time_point now) {
|
|
447
|
+
if (state_ != State::Dead) send_control(rudp::PacketType::Reset, now);
|
|
448
|
+
die(CloseReason::LocalClose);
|
|
449
|
+
events_ = 0; // the connection asked for this; it does not need telling
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ── Inbound ─────────────────────────────────────────────────────────────────
|
|
453
|
+
|
|
454
|
+
void UdpStream::on_packet(const rudp::Packet& p, Clock::time_point now) {
|
|
455
|
+
if (state_ == State::Dead) return;
|
|
456
|
+
|
|
457
|
+
last_recv_ = now;
|
|
458
|
+
|
|
459
|
+
if (p.type == rudp::PacketType::Reset) {
|
|
460
|
+
LOG_DEBUG("udp", "Stream " << recv_id_ << " reset by " << remote_.to_string());
|
|
461
|
+
die(CloseReason::PeerReset);
|
|
462
|
+
flush_events();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Handled before anything else reads the header: a Retry comes from a responder
|
|
467
|
+
// that is holding no state for us at all, so its window and sequence number
|
|
468
|
+
// describe nothing and must not be folded into what we believe about the peer.
|
|
469
|
+
if (p.type == rudp::PacketType::Retry) {
|
|
470
|
+
handle_retry(p, now);
|
|
471
|
+
flush_events();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
bool ack_now = (p.type == rudp::PacketType::Syn || p.type == rudp::PacketType::Fin);
|
|
476
|
+
|
|
477
|
+
handle_ack(p, now);
|
|
478
|
+
|
|
479
|
+
// The Syn is the first entry in the retransmission queue, so the moment it is
|
|
480
|
+
// no longer there the dial has been answered and the stream is up.
|
|
481
|
+
if (state_ == State::SynSent &&
|
|
482
|
+
(sent_.empty() || sent_.front().type != rudp::PacketType::Syn)) {
|
|
483
|
+
state_ = State::Connected;
|
|
484
|
+
raise(PollOut);
|
|
485
|
+
LOG_DEBUG("udp", "Stream " << recv_id_ << " connected to " << remote_.to_string());
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (p.type == rudp::PacketType::Syn || p.type == rudp::PacketType::Data ||
|
|
489
|
+
p.type == rudp::PacketType::Fin) {
|
|
490
|
+
// The peer is sending again, so it is not stopped on a window we re-opened
|
|
491
|
+
// and there is nothing left to announce. A bare acknowledgement deliberately
|
|
492
|
+
// does not count: a sender stuck on a stale zero window still keep-alives,
|
|
493
|
+
// and taking that as proof would call off the very repeats meant for it.
|
|
494
|
+
window_announces_ = 0;
|
|
495
|
+
|
|
496
|
+
const uint32_t before = recv_next_;
|
|
497
|
+
handle_sequenced(p);
|
|
498
|
+
// A packet that did not fill the gap it was expected to means the peer is
|
|
499
|
+
// missing something: say so at once rather than waiting out the delayed
|
|
500
|
+
// ack, since that ack is what triggers its fast retransmit.
|
|
501
|
+
if (recv_next_ == before) ack_now = true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
pump(now);
|
|
505
|
+
|
|
506
|
+
// The connection asked to be told when it could write again, and an ack just
|
|
507
|
+
// freed queue space.
|
|
508
|
+
if (want_write_ && state_ == State::Connected && queued_bytes_ < kSendQueueLimit)
|
|
509
|
+
raise(PollOut);
|
|
510
|
+
|
|
511
|
+
if (need_ack_) {
|
|
512
|
+
// Acknowledge every second packet even without a hole (the classic
|
|
513
|
+
// ack-every-other-segment rule), so a bulk sender's window keeps opening
|
|
514
|
+
// without a round trip's worth of delay per packet.
|
|
515
|
+
if (ack_now || unacked_packets_ >= 2) send_control(rudp::PacketType::Ack, now);
|
|
516
|
+
else if (ack_due_ == kNoDeadline) ack_due_ = now + kDelayedAck;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
flush_events();
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
void UdpStream::handle_ack(const rudp::Packet& p, Clock::time_point now) {
|
|
523
|
+
// An ack past the highest sequence number we have ever assigned is nonsense;
|
|
524
|
+
// honouring it would retire packets that were never sent.
|
|
525
|
+
if (rudp::seq_less(next_seq_ - 1, p.ack)) return;
|
|
526
|
+
|
|
527
|
+
// What the receiver will still buffer — taken only from a packet that is not
|
|
528
|
+
// from the past. A path that duplicates or reorders a datagram hands back a
|
|
529
|
+
// window from before the one we are already acting on, and latching that would
|
|
530
|
+
// stop a sender the receiver has since made room for. With no probe left to
|
|
531
|
+
// discover the mistake (see window_allows) the stream would then sit until the
|
|
532
|
+
// peer's next keep-alive, ten seconds of silence bought by one stale packet.
|
|
533
|
+
//
|
|
534
|
+
// The cumulative acknowledgement is what dates them: it never moves backwards
|
|
535
|
+
// at the peer, so one that has moved backwards arrived out of order. Only
|
|
536
|
+
// *strictly* older is refused — a retransmission carries a header built when it
|
|
537
|
+
// was sent, so its window is current even though the packet is not, and TCP's
|
|
538
|
+
// stricter reading of this (SND.WL1) would throw that away. Two packets sharing
|
|
539
|
+
// an ack are genuinely indistinguishable here, which is what the receiver's
|
|
540
|
+
// repeated announcement covers from the other end (see read()).
|
|
541
|
+
//
|
|
542
|
+
// Deliberately below the check above rather than in on_packet(): a forged or
|
|
543
|
+
// corrupt ack from the future must not be allowed to set the mark, or every
|
|
544
|
+
// legitimate update after it would look stale and the window would freeze for
|
|
545
|
+
// the life of the stream.
|
|
546
|
+
if (!rudp::seq_less(p.ack, window_ack_)) {
|
|
547
|
+
peer_window_ = p.window;
|
|
548
|
+
window_ack_ = p.ack;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
size_t newly_acked = 0;
|
|
552
|
+
size_t retired = 0; ///< packets the cumulative ack removed from the queue
|
|
553
|
+
while (!sent_.empty() && rudp::seq_le(sent_.front().seq, p.ack)) {
|
|
554
|
+
OutPacket& front = sent_.front();
|
|
555
|
+
// Karn's rule: a packet that was retransmitted cannot say which copy this
|
|
556
|
+
// ack answers, so it contributes no round-trip sample. Nor does one that a
|
|
557
|
+
// selective ack already retired — its real round trip was over when that
|
|
558
|
+
// SACK arrived, and measuring it against this later cumulative ack would
|
|
559
|
+
// inflate the estimate by however long the hole in front of it took to
|
|
560
|
+
// fill, which is exactly when a *tight* RTO matters most.
|
|
561
|
+
if (front.sends == 1 && !front.acked) sample_rtt(now - front.sent_at);
|
|
562
|
+
|
|
563
|
+
if (front.in_flight) {
|
|
564
|
+
flight_bytes_ -= front.size();
|
|
565
|
+
front.in_flight = false;
|
|
566
|
+
}
|
|
567
|
+
// A packet a selective ack already retired left the queue's accounting then;
|
|
568
|
+
// one a timeout gave up on left only the *flight* accounting, and still owes
|
|
569
|
+
// its bytes to queued_bytes_ — which is why the two are settled separately.
|
|
570
|
+
if (!front.acked) {
|
|
571
|
+
queued_bytes_ -= front.size();
|
|
572
|
+
newly_acked += front.size();
|
|
573
|
+
}
|
|
574
|
+
recycle(front);
|
|
575
|
+
sent_.pop_front();
|
|
576
|
+
++retired;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// The episode ends once everything that was outstanding when the loss was
|
|
580
|
+
// detected has been acknowledged (the NewReno recovery point). Until then the
|
|
581
|
+
// window has already been reduced for it and must not be reduced again.
|
|
582
|
+
if (in_recovery_ && rudp::seq_le(recover_seq_, p.ack)) in_recovery_ = false;
|
|
583
|
+
|
|
584
|
+
if (p.has_sack() && !sent_.empty()) {
|
|
585
|
+
// Every packet in the queue occupies exactly one sequence number, so the
|
|
586
|
+
// packet a bit refers to is found by subtraction rather than by search.
|
|
587
|
+
const uint32_t base = p.ack + 2;
|
|
588
|
+
for (uint32_t i = 0; i < rudp::kSackBits; ++i) {
|
|
589
|
+
if ((p.sack & (1u << i)) == 0) continue;
|
|
590
|
+
const int32_t idx = rudp::seq_diff(base + i, sent_.front().seq);
|
|
591
|
+
if (idx < 0 || static_cast<size_t>(idx) >= sent_.size()) continue;
|
|
592
|
+
OutPacket& pkt = sent_[static_cast<size_t>(idx)];
|
|
593
|
+
if (pkt.acked) continue;
|
|
594
|
+
pkt.acked = true;
|
|
595
|
+
if (pkt.in_flight) {
|
|
596
|
+
flight_bytes_ -= pkt.size();
|
|
597
|
+
pkt.in_flight = false;
|
|
598
|
+
}
|
|
599
|
+
queued_bytes_ -= pkt.size();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Selective acks name what got through, which by elimination names what did
|
|
604
|
+
// not. Repairing those now is the difference between recovering a lossy
|
|
605
|
+
// window in one round trip and unpicking it one duplicate-ack at a time.
|
|
606
|
+
if (p.has_sack()) repair_sacked_holes(now);
|
|
607
|
+
|
|
608
|
+
if (newly_acked > 0) {
|
|
609
|
+
dup_acks_ = 0;
|
|
610
|
+
tail_probes_ = 0; // the peer answered; this silence is over
|
|
611
|
+
last_ack_recv_ = p.ack;
|
|
612
|
+
grow_window(newly_acked);
|
|
613
|
+
// After the window moves, not before: the round-trip rise HyStart++ acts
|
|
614
|
+
// on is only meaningful against the window that produced it.
|
|
615
|
+
hystart_on_ack(p.ack);
|
|
616
|
+
// Progress means the path is alive: drop back to the estimated RTO,
|
|
617
|
+
// undoing any doubling a previous timeout applied.
|
|
618
|
+
if (have_rtt_) rto_ = clamp_duration(srtt_ + 4 * rttvar_, kMinRto, kMaxRto);
|
|
619
|
+
} else if (p.type == rudp::PacketType::Ack && p.ack == last_ack_recv_ && p.ack != 0 &&
|
|
620
|
+
!sent_.empty()) {
|
|
621
|
+
// A *pure* ack whose cumulative number stood still: the peer is receiving
|
|
622
|
+
// packets past a hole and re-reporting the same edge. Three of those is the
|
|
623
|
+
// classic loss signal, and repairing it now saves a whole retransmission
|
|
624
|
+
// timeout.
|
|
625
|
+
//
|
|
626
|
+
// The type check is what makes this a loss signal rather than a coincidence,
|
|
627
|
+
// and RFC 5681 defines a duplicate ack that way for exactly this reason. Every
|
|
628
|
+
// packet here carries the ack field, so on a two-way stream the peer's own
|
|
629
|
+
// Data rides over the same number until our next packet reaches it — three of
|
|
630
|
+
// *those* say nothing about loss, they only say the peer had something of its
|
|
631
|
+
// own to send. Counting them halved the window and re-sent a packet that was
|
|
632
|
+
// merely still in flight, on a stream where nothing had been dropped at all —
|
|
633
|
+
// which on a peer-to-peer link (gossip during a transfer, any request while a
|
|
634
|
+
// response streams back) is the normal case rather than the corner one.
|
|
635
|
+
//
|
|
636
|
+
// Nothing is lost by being strict: a hole makes the receiver acknowledge at
|
|
637
|
+
// once (see on_packet), so a one-way flow still produces the pure acks this
|
|
638
|
+
// counts, and a two-way one is covered by repair_sacked_holes() above, which
|
|
639
|
+
// names the missing packets outright instead of inferring them.
|
|
640
|
+
if (++dup_acks_ == 3) {
|
|
641
|
+
enter_recovery();
|
|
642
|
+
transmit(sent_.front(), now);
|
|
643
|
+
++retransmits_;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// RFC 6298 (5.2/5.3): the timer restarts whenever the cumulative ack retires
|
|
648
|
+
// something, and stops once nothing is outstanding. Counted in packets rather
|
|
649
|
+
// than bytes, because a Syn and a Fin each occupy a sequence number while
|
|
650
|
+
// carrying no payload — a byte count would leave the timer running on the
|
|
651
|
+
// deadline the *handshake* set, which is neither the one the first data packet
|
|
652
|
+
// deserves nor one anything else will correct.
|
|
653
|
+
if (retired > 0) rto_deadline_ = sent_.empty() ? kNoDeadline : now + loss_timeout();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
void UdpStream::handle_retry(const rudp::Packet& p, Clock::time_point now) {
|
|
657
|
+
// "Not until you prove you are really at that address." A responder under load
|
|
658
|
+
// answers a dial with a cookie instead of a stream, and will not spend a byte of
|
|
659
|
+
// memory on us until it comes back. Only a dial that has not been answered yet
|
|
660
|
+
// can be retried, and only once — see retried_.
|
|
661
|
+
if (state_ != State::SynSent || retried_) return;
|
|
662
|
+
if (p.payload.size() != rudp::kCookieSize) return;
|
|
663
|
+
if (sent_.empty() || sent_.front().type != rudp::PacketType::Syn) return;
|
|
664
|
+
|
|
665
|
+
retried_ = true;
|
|
666
|
+
OutPacket& syn = sent_.front();
|
|
667
|
+
|
|
668
|
+
// The same Syn, same sequence number, now carrying the cookie. Its payload was
|
|
669
|
+
// empty until now, and the send accounting has to learn about the bytes: the
|
|
670
|
+
// cumulative ack that eventually retires this packet subtracts size() from both
|
|
671
|
+
// counters, so anything that grows a queued packet must add to them first.
|
|
672
|
+
syn.buf.resize(rudp::kMaxHeaderSize);
|
|
673
|
+
syn.buf.insert(syn.buf.end(), p.payload.begin(), p.payload.end());
|
|
674
|
+
queued_bytes_ += rudp::kCookieSize;
|
|
675
|
+
// Only if the packet is currently counted as in flight: if a timeout has just
|
|
676
|
+
// given up on it, transmit() below will count the whole grown packet afresh,
|
|
677
|
+
// and adding the difference here as well would count the cookie twice.
|
|
678
|
+
if (syn.in_flight) flight_bytes_ += rudp::kCookieSize;
|
|
679
|
+
|
|
680
|
+
// The round trip we just spent proving our address is not a lost packet, so it
|
|
681
|
+
// does not count against the dial's attempt budget — and the dial gets a fresh
|
|
682
|
+
// timeout to answer in, rather than what was left of the first one.
|
|
683
|
+
syn.sends = 0;
|
|
684
|
+
rto_deadline_ = now + rto_;
|
|
685
|
+
transmit(syn, now);
|
|
686
|
+
|
|
687
|
+
LOG_DEBUG("udp", "Stream " << recv_id_ << " re-dialing " << remote_.to_string()
|
|
688
|
+
<< " with an address-validation cookie");
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
void UdpStream::repair_sacked_holes(Clock::time_point now) {
|
|
692
|
+
// Everything before the highest selectively acknowledged packet has had its
|
|
693
|
+
// chance: the peer received something sent *after* it, so it is not merely
|
|
694
|
+
// late. Three packets of margin is the usual allowance for reordering — the
|
|
695
|
+
// same threshold the duplicate-ack rule uses, for the same reason.
|
|
696
|
+
//
|
|
697
|
+
// The search is bounded by the reach of a selective ack rather than by the
|
|
698
|
+
// length of the queue. A bit can only ever mark the kSackBits packets after
|
|
699
|
+
// the hole — handle_ack() resolves bit i to index (p.ack + 2 + i) - front,
|
|
700
|
+
// and the cumulative retire above leaves front at exactly p.ack + 1, so the
|
|
701
|
+
// highest index it can set is kSackBits — and an index only ever moves *down*
|
|
702
|
+
// as the queue drains from the front. So nothing past that window can carry
|
|
703
|
+
// the flag, and walking the whole queue to discover it (a full window of
|
|
704
|
+
// packets, on every acknowledgement of a loss episode — which is when they
|
|
705
|
+
// are at their most frequent) was searching where the answer cannot be.
|
|
706
|
+
// Backwards inside that window, so the common case returns on the first hit.
|
|
707
|
+
const size_t limit = (std::min)(sent_.size(), size_t{rudp::kSackBits} + 1);
|
|
708
|
+
int highest_acked = -1;
|
|
709
|
+
for (size_t i = limit; i-- > 0;) {
|
|
710
|
+
if (sent_[i].acked) { highest_acked = static_cast<int>(i); break; }
|
|
711
|
+
}
|
|
712
|
+
if (highest_acked < 3) return;
|
|
713
|
+
|
|
714
|
+
const auto spacing = (std::max)(std::chrono::duration_cast<Clock::duration>(kMinRepairSpacing),
|
|
715
|
+
srtt_);
|
|
716
|
+
|
|
717
|
+
int repaired = 0;
|
|
718
|
+
for (int i = 0; i + 3 <= highest_acked && repaired < kMaxRepairsPerAck; ++i) {
|
|
719
|
+
OutPacket& pkt = sent_[static_cast<size_t>(i)];
|
|
720
|
+
if (pkt.acked) continue;
|
|
721
|
+
if (now - pkt.sent_at < spacing) continue; // already re-sent very recently
|
|
722
|
+
transmit(pkt, now);
|
|
723
|
+
++retransmits_;
|
|
724
|
+
++repaired;
|
|
725
|
+
}
|
|
726
|
+
// One window reduction for the whole episode — not one per packet repaired,
|
|
727
|
+
// and not one per ack that repairs something. Several selective acks arrive
|
|
728
|
+
// per round trip and recovery spans several round trips, so halving on each
|
|
729
|
+
// of them would drive the window to the floor over a loss TCP would have
|
|
730
|
+
// ridden out with a single halving. enter_recovery() enforces that.
|
|
731
|
+
if (repaired > 0) enter_recovery();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
void UdpStream::handle_sequenced(const rudp::Packet& p) {
|
|
735
|
+
need_ack_ = true;
|
|
736
|
+
|
|
737
|
+
if (rudp::seq_less(p.seq, recv_next_)) return; // already delivered; just re-ack
|
|
738
|
+
|
|
739
|
+
// Only Data carries stream content. A Syn occupies a sequence number like any
|
|
740
|
+
// other packet, but what it carries is the address-validation cookie the mux
|
|
741
|
+
// has already checked — delivering that as stream bytes would splice four bytes
|
|
742
|
+
// of nonsense into the front of the peer's handshake.
|
|
743
|
+
const ByteView body = (p.type == rudp::PacketType::Data) ? p.payload : ByteView{};
|
|
744
|
+
|
|
745
|
+
if (p.seq == recv_next_) {
|
|
746
|
+
++unacked_packets_;
|
|
747
|
+
deliver(body, p.type == rudp::PacketType::Fin);
|
|
748
|
+
++recv_next_;
|
|
749
|
+
sack_dirty_ = true; // the bitmap is relative to recv_next_, which just moved
|
|
750
|
+
drain_reorder();
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Past the gap: hold it, but only within the window we advertised — anything
|
|
755
|
+
// beyond that is a peer ignoring flow control, and buffering it would let one
|
|
756
|
+
// peer decide how much memory we spend.
|
|
757
|
+
const int32_t ahead = rudp::seq_diff(p.seq, recv_next_);
|
|
758
|
+
if (ahead <= 0 || ahead >= rudp::kMaxWindowPackets) return;
|
|
759
|
+
if (reorder_.count(p.seq)) return;
|
|
760
|
+
|
|
761
|
+
InPacket held;
|
|
762
|
+
held.payload = body.to_bytes();
|
|
763
|
+
held.fin = (p.type == rudp::PacketType::Fin);
|
|
764
|
+
reorder_.emplace(p.seq, std::move(held));
|
|
765
|
+
sack_dirty_ = true;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
void UdpStream::deliver(ByteView payload, bool fin) {
|
|
769
|
+
if (peer_fin_) return; // nothing follows a Fin
|
|
770
|
+
|
|
771
|
+
if (!payload.empty()) {
|
|
772
|
+
const ByteSpan into = inbox_.prepare(payload.size());
|
|
773
|
+
std::memcpy(into.data(), payload.data(), payload.size());
|
|
774
|
+
inbox_.commit(payload.size());
|
|
775
|
+
raise(PollIn);
|
|
776
|
+
}
|
|
777
|
+
if (fin) {
|
|
778
|
+
peer_fin_ = true;
|
|
779
|
+
raise(PollIn); // the reader has to see the end of stream
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
void UdpStream::drain_reorder() {
|
|
784
|
+
for (;;) {
|
|
785
|
+
auto it = reorder_.find(recv_next_);
|
|
786
|
+
if (it == reorder_.end()) break;
|
|
787
|
+
deliver(ByteView(it->second.payload), it->second.fin);
|
|
788
|
+
reorder_.erase(it);
|
|
789
|
+
++recv_next_;
|
|
790
|
+
sack_dirty_ = true;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
size_t UdpStream::read(uint8_t* into, size_t len) {
|
|
795
|
+
const size_t n = (std::min)(len, inbox_.size());
|
|
796
|
+
if (n == 0) return 0;
|
|
797
|
+
|
|
798
|
+
std::memcpy(into, inbox_.data(), n);
|
|
799
|
+
const uint16_t before = advertised_window();
|
|
800
|
+
inbox_.consume(n);
|
|
801
|
+
// Draining the buffer may have re-opened a window we had advertised as full.
|
|
802
|
+
// The peer is waiting on that number, so it has to be told without waiting for
|
|
803
|
+
// traffic that will never come while it is stopped — hence an owed ack with no
|
|
804
|
+
// deadline attached, which the next tick() sends outright rather than holding
|
|
805
|
+
// for company (see there). Leaving ack_due_ unset is the *signal*, not an
|
|
806
|
+
// omission: everything else that owes an ack has a packet of its own to wait for.
|
|
807
|
+
//
|
|
808
|
+
// And again after that, a few times, if the peer stays quiet. Nothing
|
|
809
|
+
// retransmits a bare acknowledgement, and the sender this one is for is stopped
|
|
810
|
+
// — so were it dropped, the only thing left to restart the transfer would be
|
|
811
|
+
// the keep-alive ten seconds out. The repeats also cover the one case the
|
|
812
|
+
// sender cannot: two packets carrying the same acknowledgement and different
|
|
813
|
+
// windows, which it has no way to tell apart (see handle_ack). Cleared as soon
|
|
814
|
+
// as the peer sends anything sequenced, which is proof it heard us.
|
|
815
|
+
if (before == 0 && advertised_window() > 0) {
|
|
816
|
+
need_ack_ = true;
|
|
817
|
+
window_announces_ = kMaxWindowAnnounces;
|
|
818
|
+
window_due_ = kNoDeadline; // the first one goes at once
|
|
819
|
+
}
|
|
820
|
+
return n;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// ── Timing, congestion control, lifecycle ───────────────────────────────────
|
|
824
|
+
|
|
825
|
+
void UdpStream::sample_rtt(Clock::duration rtt) {
|
|
826
|
+
if (rtt < Clock::duration::zero()) return;
|
|
827
|
+
|
|
828
|
+
hystart_sample(rtt);
|
|
829
|
+
|
|
830
|
+
if (!have_rtt_) {
|
|
831
|
+
srtt_ = rtt;
|
|
832
|
+
rttvar_ = rtt / 2;
|
|
833
|
+
have_rtt_ = true;
|
|
834
|
+
} else {
|
|
835
|
+
// RFC 6298: rttvar = 3/4 rttvar + 1/4 |srtt - r| ; srtt = 7/8 srtt + 1/8 r.
|
|
836
|
+
const auto err = srtt_ > rtt ? srtt_ - rtt : rtt - srtt_;
|
|
837
|
+
rttvar_ = (rttvar_ * 3 + err) / 4;
|
|
838
|
+
srtt_ = (srtt_ * 7 + rtt) / 8;
|
|
839
|
+
}
|
|
840
|
+
rto_ = clamp_duration(srtt_ + 4 * rttvar_, kMinRto, kMaxRto);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
void UdpStream::enter_recovery() {
|
|
844
|
+
if (in_recovery_) return; // already paid for this episode
|
|
845
|
+
in_recovery_ = true;
|
|
846
|
+
// Everything assigned a sequence number so far is what has to be acknowledged
|
|
847
|
+
// before the episode is over. next_seq_ is the number the *next* packet will
|
|
848
|
+
// take, so the highest one outstanding is one below it.
|
|
849
|
+
recover_seq_ = next_seq_ - 1;
|
|
850
|
+
on_loss(false);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
void UdpStream::on_loss(bool timeout) {
|
|
854
|
+
++window_reductions_;
|
|
855
|
+
// A loss settles the question the cautious phase was asking, and settles it
|
|
856
|
+
// the expensive way. Whatever slow start is entered from here starts its
|
|
857
|
+
// round-trip comparison afresh rather than against a queue that has since
|
|
858
|
+
// drained.
|
|
859
|
+
css_ = false;
|
|
860
|
+
if (timeout) {
|
|
861
|
+
// A timeout says the path is congested enough to have dropped everything
|
|
862
|
+
// in flight: back down to one packet and re-probe from there.
|
|
863
|
+
ssthresh_ = (std::max)(static_cast<uint32_t>(flight_bytes_ / 2), kMinCwnd);
|
|
864
|
+
cwnd_ = rudp::kMaxPayload;
|
|
865
|
+
} else {
|
|
866
|
+
// A fast retransmit means packets are still flowing, so halve rather than
|
|
867
|
+
// collapse.
|
|
868
|
+
ssthresh_ = (std::max)(cwnd_ / 2, kMinCwnd);
|
|
869
|
+
cwnd_ = ssthresh_;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
UdpStream::Clock::duration UdpStream::probe_timeout() const noexcept {
|
|
874
|
+
if (!have_rtt_) return kInitialRto;
|
|
875
|
+
|
|
876
|
+
// RFC 9002's PTO: a round trip, the variance allowance, and the time the peer
|
|
877
|
+
// is entitled to hold an acknowledgement back for. The last term is what stops
|
|
878
|
+
// the probe racing our own delayed-ack rule and calling a slow peer a lost one.
|
|
879
|
+
const auto granularity = std::chrono::duration_cast<Clock::duration>(kMinProbeTimeout) / 8;
|
|
880
|
+
auto pto = srtt_ + (std::max)(4 * rttvar_, granularity)
|
|
881
|
+
+ std::chrono::duration_cast<Clock::duration>(kDelayedAck);
|
|
882
|
+
|
|
883
|
+
// Doubled per consecutive probe: if the first went unanswered the path is
|
|
884
|
+
// worse than the estimate said, and asking again at the same spacing would
|
|
885
|
+
// just be asking twice.
|
|
886
|
+
for (int i = 0; i < tail_probes_ && pto < std::chrono::duration_cast<Clock::duration>(kMaxRto); ++i)
|
|
887
|
+
pto *= 2;
|
|
888
|
+
|
|
889
|
+
return clamp_duration(pto, kMinProbeTimeout, kMaxRto);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
UdpStream::Clock::duration UdpStream::loss_timeout() const noexcept {
|
|
893
|
+
if (state_ != State::Connected || tail_probes_ >= kMaxTailProbes) return rto_;
|
|
894
|
+
|
|
895
|
+
// Never later than the timeout it stands in front of. A probe exists to ask
|
|
896
|
+
// the question *sooner* and more cheaply than the retransmission timeout would
|
|
897
|
+
// — on a path whose round trip is long enough that the probe interval exceeds
|
|
898
|
+
// the timeout, waiting for the probe would be pure added delay. Capped, the
|
|
899
|
+
// worst case is that it fires exactly when the timeout would have, and the
|
|
900
|
+
// stream still keeps its window instead of collapsing it.
|
|
901
|
+
return (std::min)(probe_timeout(), rto_);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
void UdpStream::on_rto(Clock::time_point now) {
|
|
905
|
+
// Find the oldest packet the peer has not confirmed. A selectively acknowledged
|
|
906
|
+
// one at the front would mean the peer already has it, so it is not what the
|
|
907
|
+
// timeout is about.
|
|
908
|
+
OutPacket* oldest = nullptr;
|
|
909
|
+
for (OutPacket& pkt : sent_) {
|
|
910
|
+
if (!pkt.acked) { oldest = &pkt; break; }
|
|
911
|
+
}
|
|
912
|
+
if (!oldest || oldest->sends == 0) {
|
|
913
|
+
rto_deadline_ = kNoDeadline; // nothing outstanding; the timer has no work
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const int cap = (state_ == State::SynSent) ? syn_attempts_ : kMaxRetransmits;
|
|
918
|
+
if (oldest->sends >= cap) {
|
|
919
|
+
die(state_ == State::SynSent ? CloseReason::ConnectFailed : CloseReason::PeerReset);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Tail loss probe, before any of the collapse below is believed. The peer has
|
|
924
|
+
// gone quiet, which on a stream whose last packet was lost is indistinguishable
|
|
925
|
+
// from a peer that is merely slow — and the two call for opposite responses.
|
|
926
|
+
// So ask first: re-send something it has not acknowledged, change nothing else,
|
|
927
|
+
// and let the answer decide. A probe that was unnecessary costs one packet;
|
|
928
|
+
// the collapse below, taken wrongly, costs the whole window.
|
|
929
|
+
//
|
|
930
|
+
// Deliberately not for a dial: a Syn has its own, tighter attempt budget that
|
|
931
|
+
// the transport race depends on (see kSynMaxAttempts).
|
|
932
|
+
if (state_ == State::Connected && tail_probes_ < kMaxTailProbes) {
|
|
933
|
+
// The LAST unacknowledged packet, not the first (RFC 8985 §7.2). This is
|
|
934
|
+
// the whole mechanism, not a detail of it: the probe's acknowledgement has
|
|
935
|
+
// to land *past* every hole in front of it, so the receiver holds it out of
|
|
936
|
+
// order and the selective ack that comes back names all of them at once —
|
|
937
|
+
// which is what lets one round trip repair a whole lost burst.
|
|
938
|
+
//
|
|
939
|
+
// Probing the front instead produces an acknowledgement that advances the
|
|
940
|
+
// cumulative number by exactly one and names nothing. Each probe would then
|
|
941
|
+
// recover a single packet, and because that acknowledgement is new data it
|
|
942
|
+
// resets tail_probes_ — so the escalation below is never reached, the rest
|
|
943
|
+
// of the burst stays counted in flight with the window shut behind it, and
|
|
944
|
+
// grow_window() walks the window *up* through what is in fact a total loss.
|
|
945
|
+
// For a single lost packet the two are the same packet and the difference
|
|
946
|
+
// does not show; for a lost burst it is the difference between repairing in
|
|
947
|
+
// one round trip and crawling out one packet per probe.
|
|
948
|
+
OutPacket* probe = oldest;
|
|
949
|
+
for (auto it = sent_.rbegin(); it != sent_.rend(); ++it) {
|
|
950
|
+
if (!it->acked) { probe = &*it; break; }
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
++tail_probes_;
|
|
954
|
+
transmit(*probe, now);
|
|
955
|
+
++retransmits_;
|
|
956
|
+
rto_deadline_ = now + loss_timeout(); // backed off, still capped by the RTO
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// A timeout is the stronger signal and always collapses the window, even
|
|
961
|
+
// mid-recovery — but it also restarts the episode, so the selective acks that
|
|
962
|
+
// come back as the pipe refills do not each take another halving out of a
|
|
963
|
+
// window that is already down to one packet. (on_loss reads flight_bytes_, so
|
|
964
|
+
// it has to run before the accounting below is undone.)
|
|
965
|
+
on_loss(true);
|
|
966
|
+
in_recovery_ = true;
|
|
967
|
+
recover_seq_ = next_seq_ - 1;
|
|
968
|
+
|
|
969
|
+
// Everything outstanding has had a full retransmission timeout to arrive and
|
|
970
|
+
// nothing acknowledged it, so it is presumed lost and stops occupying the path.
|
|
971
|
+
//
|
|
972
|
+
// This step is what makes recovery possible at all. Leaving the bytes counted
|
|
973
|
+
// would leave flight_bytes_ holding a whole window while cwnd_ is back down to
|
|
974
|
+
// one packet, and cwnd_allows() — the gate every transmission goes through —
|
|
975
|
+
// would refuse for as long as those packets sat in the queue. The sender would
|
|
976
|
+
// then crawl forward one packet per timeout, unable to grow the window or
|
|
977
|
+
// repair the rest, until the transfer effectively stopped.
|
|
978
|
+
for (OutPacket& pkt : sent_) {
|
|
979
|
+
if (!pkt.in_flight) continue;
|
|
980
|
+
pkt.in_flight = false;
|
|
981
|
+
flight_bytes_ -= pkt.size();
|
|
982
|
+
have_lost_ = true;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Exponential backoff, so a path that is down is probed ever more cheaply
|
|
986
|
+
// instead of being hammered. The deadline is set from it before anything goes
|
|
987
|
+
// out, so the retransmissions below do not each restart the timer.
|
|
988
|
+
//
|
|
989
|
+
// A punching dial is the one case that opts out (see DialProfile): there the
|
|
990
|
+
// peer's NAT is expected to swallow the early Syns, so backing off would spread
|
|
991
|
+
// the few attempts we get across seconds and leave the window in which both
|
|
992
|
+
// sides are actually probing barely covered. Only the dial can ask for this —
|
|
993
|
+
// an established stream always backs off.
|
|
994
|
+
if (syn_backoff_ || state_ != State::SynSent)
|
|
995
|
+
rto_ = clamp_duration(rto_ * 2, kMinRto, kMaxRto);
|
|
996
|
+
rto_deadline_ = now + rto_;
|
|
997
|
+
|
|
998
|
+
retransmit_lost(now);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
void UdpStream::hystart_reset() {
|
|
1002
|
+
css_ = false;
|
|
1003
|
+
css_rounds_ = 0;
|
|
1004
|
+
css_baseline_rtt_ = (Clock::duration::max)();
|
|
1005
|
+
round_min_rtt_ = (Clock::duration::max)();
|
|
1006
|
+
prev_round_min_rtt_ = (Clock::duration::max)();
|
|
1007
|
+
round_samples_ = 0;
|
|
1008
|
+
round_end_ = next_seq_ - 1;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
void UdpStream::hystart_sample(Clock::duration rtt) {
|
|
1012
|
+
// The *minimum* of the round, not the average: a queue building in front of
|
|
1013
|
+
// the bottleneck lifts even the luckiest packet's round trip, where an
|
|
1014
|
+
// average moves just as readily for one straggler that had nothing to do
|
|
1015
|
+
// with congestion.
|
|
1016
|
+
if (rtt < round_min_rtt_) round_min_rtt_ = rtt;
|
|
1017
|
+
++round_samples_;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
void UdpStream::hystart_on_ack(uint32_t ack) {
|
|
1021
|
+
// Only slow start is in question. Congestion avoidance has already found its
|
|
1022
|
+
// ceiling and climbs a packet per round trip, which no queue signal improves.
|
|
1023
|
+
if (!in_slow_start()) { css_ = false; return; }
|
|
1024
|
+
|
|
1025
|
+
// Entry: this round's best round trip has risen clear of the previous round's
|
|
1026
|
+
// by more than the threshold, which means a queue is forming ahead of us.
|
|
1027
|
+
// Leave doubling for the cautious phase rather than exiting outright — one
|
|
1028
|
+
// round's rise may be noise, and CSS is how that gets settled without
|
|
1029
|
+
// throwing the remaining growth away.
|
|
1030
|
+
if (!css_ && round_samples_ >= kHyRttSamples &&
|
|
1031
|
+
prev_round_min_rtt_ != (Clock::duration::max)() &&
|
|
1032
|
+
round_min_rtt_ != (Clock::duration::max)()) {
|
|
1033
|
+
const auto thresh = clamp_duration(prev_round_min_rtt_ / 8,
|
|
1034
|
+
kHyMinRttThresh, kHyMaxRttThresh);
|
|
1035
|
+
if (round_min_rtt_ >= prev_round_min_rtt_ + thresh) {
|
|
1036
|
+
css_ = true;
|
|
1037
|
+
css_rounds_ = 0;
|
|
1038
|
+
css_baseline_rtt_ = round_min_rtt_;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
if (!rudp::seq_le(round_end_, ack)) return; // the round is still running
|
|
1043
|
+
|
|
1044
|
+
const auto ended_min = round_min_rtt_;
|
|
1045
|
+
prev_round_min_rtt_ = ended_min;
|
|
1046
|
+
round_min_rtt_ = (Clock::duration::max)();
|
|
1047
|
+
round_samples_ = 0;
|
|
1048
|
+
round_end_ = next_seq_ - 1;
|
|
1049
|
+
|
|
1050
|
+
if (!css_) return;
|
|
1051
|
+
|
|
1052
|
+
// The rise did not hold — it was one round's noise. Take the caution back and
|
|
1053
|
+
// let slow start carry on, which is the whole reason CSS exists.
|
|
1054
|
+
if (ended_min != (Clock::duration::max)() && ended_min < css_baseline_rtt_) {
|
|
1055
|
+
css_ = false;
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// It held long enough to believe. Stop doubling *here*, at a window the path
|
|
1060
|
+
// demonstrably carries — this is the point of the exercise: the ceiling is
|
|
1061
|
+
// found without having had to lose a window to find it.
|
|
1062
|
+
if (++css_rounds_ >= kHyCssRounds) {
|
|
1063
|
+
ssthresh_ = cwnd_;
|
|
1064
|
+
css_ = false;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
void UdpStream::grow_window(size_t acked_bytes) {
|
|
1069
|
+
if (cwnd_ < ssthresh_) {
|
|
1070
|
+
uint32_t inc = static_cast<uint32_t>((std::min)(acked_bytes, size_t{rudp::kMaxPayload} * 2));
|
|
1071
|
+
// The cautious phase probes at a quarter of slow start: still upward, so a
|
|
1072
|
+
// spurious signal costs almost nothing, but no longer doubling while it is
|
|
1073
|
+
// being decided whether the round-trip rise was a real queue.
|
|
1074
|
+
if (css_) inc /= kHyCssGrowthDivisor;
|
|
1075
|
+
cwnd_ += inc;
|
|
1076
|
+
} else {
|
|
1077
|
+
// Additive increase: one packet per window, spread over the acks that
|
|
1078
|
+
// make up that window.
|
|
1079
|
+
const uint64_t inc = static_cast<uint64_t>(rudp::kMaxPayload) * acked_bytes / cwnd_;
|
|
1080
|
+
cwnd_ += static_cast<uint32_t>(inc > 0 ? inc : 1);
|
|
1081
|
+
}
|
|
1082
|
+
cwnd_ = (std::min)(cwnd_, kMaxCwnd);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
std::optional<Clock::time_point> UdpStream::next_deadline() const noexcept {
|
|
1086
|
+
// A dead stream has no timers left to run; the mux collects it instead of
|
|
1087
|
+
// servicing it, so it asks for no wake-up at all.
|
|
1088
|
+
if (state_ == State::Dead) return std::nullopt;
|
|
1089
|
+
|
|
1090
|
+
// The idle deadline is the one thing always armed: silence from the peer ends
|
|
1091
|
+
// the stream whatever else is or is not outstanding.
|
|
1092
|
+
Clock::time_point due = last_recv_ + kIdleTimeout;
|
|
1093
|
+
|
|
1094
|
+
// An owed acknowledgement. The epoch here is not "no deadline" but "at once" —
|
|
1095
|
+
// read() leaves it that way to ask for a window update, and a peer stopped on a
|
|
1096
|
+
// zero window sends nothing for the ack to ride on. Returning the epoch makes
|
|
1097
|
+
// that the earliest possible deadline, which is exactly what it means.
|
|
1098
|
+
if (need_ack_) due = (std::min)(due, ack_due_);
|
|
1099
|
+
|
|
1100
|
+
// A re-opened window still waiting to be heard back on. Same convention as the
|
|
1101
|
+
// owed ack above — the count is what arms this, so the epoch means "at once".
|
|
1102
|
+
if (window_announces_ > 0) due = (std::min)(due, window_due_);
|
|
1103
|
+
|
|
1104
|
+
if (rto_deadline_ != kNoDeadline) due = (std::min)(due, rto_deadline_);
|
|
1105
|
+
|
|
1106
|
+
// A packet the pacer is holding back. Unlike everything else here this one is
|
|
1107
|
+
// released by the clock alone — no acknowledgement is coming to free it — so
|
|
1108
|
+
// without this deadline a paced stream would sit until the keep-alive.
|
|
1109
|
+
if (pace_due_ != kNoDeadline) due = (std::min)(due, pace_due_);
|
|
1110
|
+
|
|
1111
|
+
// Keep-alive: says we are still here, and keeps a NAT's mapping for this port
|
|
1112
|
+
// open. Only meaningful once the stream is up — a Syn in flight is covered by
|
|
1113
|
+
// the retransmission timeout above.
|
|
1114
|
+
if (state_ == State::Connected) due = (std::min)(due, last_send_ + kKeepAlive);
|
|
1115
|
+
|
|
1116
|
+
return due;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
void UdpStream::tick(Clock::time_point now) {
|
|
1120
|
+
if (state_ == State::Dead) return;
|
|
1121
|
+
|
|
1122
|
+
if (now - last_recv_ >= kIdleTimeout) {
|
|
1123
|
+
LOG_DEBUG("udp", "Stream " << recv_id_ << " to " << remote_.to_string()
|
|
1124
|
+
<< " idle for " << kIdleTimeout.count() << "s; closing");
|
|
1125
|
+
die(CloseReason::IdleTimeout);
|
|
1126
|
+
flush_events();
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
if (rto_deadline_ != kNoDeadline && now >= rto_deadline_) {
|
|
1131
|
+
on_rto(now);
|
|
1132
|
+
if (state_ == State::Dead) { flush_events(); return; }
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// A delayed acknowledgement that has come due — or one carrying no deadline at
|
|
1136
|
+
// all, which is how read() asks for a window update. That case deliberately has
|
|
1137
|
+
// no deadline to wait out: the peer is stopped on a window of zero and will send
|
|
1138
|
+
// nothing for an acknowledgement to ride on, so an ack sent from here is the
|
|
1139
|
+
// only thing that can tell it to start again.
|
|
1140
|
+
// A window we re-opened that the peer has not answered. It is stopped, so it
|
|
1141
|
+
// will not produce a packet for the update to ride on however long we wait —
|
|
1142
|
+
// the announcement has to be volunteered, and volunteered again if it is lost.
|
|
1143
|
+
if (window_announces_ > 0 && now >= window_due_) {
|
|
1144
|
+
need_ack_ = true;
|
|
1145
|
+
ack_due_ = kNoDeadline; // at once, for the same reason read() leaves it so
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
if (need_ack_ && (ack_due_ == kNoDeadline || now >= ack_due_)) {
|
|
1149
|
+
send_control(rudp::PacketType::Ack, now);
|
|
1150
|
+
// That one carried the window. Space the rest by the retransmission
|
|
1151
|
+
// timeout: it is this side's own estimate of how long an answer would take
|
|
1152
|
+
// to come back, and asking again sooner would only ask twice.
|
|
1153
|
+
if (window_announces_ > 0) {
|
|
1154
|
+
--window_announces_;
|
|
1155
|
+
window_due_ = now + rto_;
|
|
1156
|
+
}
|
|
1157
|
+
} else if (state_ == State::Connected && now - last_send_ >= kKeepAlive) {
|
|
1158
|
+
// Say something now and then: it keeps the peer's idle timer from firing
|
|
1159
|
+
// and, just as importantly, keeps a NAT's mapping for this port alive.
|
|
1160
|
+
send_control(rudp::PacketType::Ack, now);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
pump(now);
|
|
1164
|
+
flush_events();
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
void UdpStream::die(CloseReason reason) {
|
|
1168
|
+
if (state_ == State::Dead) return;
|
|
1169
|
+
state_ = State::Dead;
|
|
1170
|
+
close_reason_ = reason;
|
|
1171
|
+
sent_.clear();
|
|
1172
|
+
unsent_.clear();
|
|
1173
|
+
reorder_.clear();
|
|
1174
|
+
spare_.clear();
|
|
1175
|
+
sack_bits_ = 0;
|
|
1176
|
+
sack_dirty_ = false;
|
|
1177
|
+
flight_bytes_ = 0;
|
|
1178
|
+
queued_bytes_ = 0;
|
|
1179
|
+
rto_deadline_ = kNoDeadline;
|
|
1180
|
+
pace_due_ = kNoDeadline;
|
|
1181
|
+
pace_tokens_ = 0;
|
|
1182
|
+
have_lost_ = false;
|
|
1183
|
+
window_announces_ = 0; // nobody is waiting on a window this stream will never serve
|
|
1184
|
+
raise(PollErr);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
void UdpStream::flush_events() {
|
|
1188
|
+
if (events_ == 0) return;
|
|
1189
|
+
const uint32_t events = events_;
|
|
1190
|
+
events_ = 0;
|
|
1191
|
+
host_.stream_events(*this, events);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
} // namespace librats
|