anytls 0.1.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +11 -0
  3. data/LICENSE +21 -0
  4. data/README.md +143 -0
  5. data/bin/anytls-client +7 -0
  6. data/bin/anytls-server +7 -0
  7. data/docs/ARCHITECTURE.md +105 -0
  8. data/docs/SOURCES.md +15 -0
  9. data/docs/VALIDATION.md +100 -0
  10. data/docs/validation/runtime-ruby34.json +8 -0
  11. data/docs/validation/runtime-ruby40.json +8 -0
  12. data/docs/validation/stress-c1.json +25 -0
  13. data/docs/validation/stress-c128.json +26 -0
  14. data/docs/validation/stress-c32.json +26 -0
  15. data/docs/validation/stress-sequential-10000.json +26 -0
  16. data/lib/anytls/address.rb +53 -0
  17. data/lib/anytls/buffered_io.rb +185 -0
  18. data/lib/anytls/byte_buffer.rb +61 -0
  19. data/lib/anytls/cli/client_listener.rb +91 -0
  20. data/lib/anytls/cli/options.rb +160 -0
  21. data/lib/anytls/cli/runner.rb +89 -0
  22. data/lib/anytls/cli/shutdown.rb +36 -0
  23. data/lib/anytls/client.rb +129 -0
  24. data/lib/anytls/connection_uri.rb +96 -0
  25. data/lib/anytls/deadline.rb +34 -0
  26. data/lib/anytls/dialer.rb +70 -0
  27. data/lib/anytls/errors.rb +23 -0
  28. data/lib/anytls/inbound/http_proxy.rb +224 -0
  29. data/lib/anytls/inbound/mixed.rb +60 -0
  30. data/lib/anytls/inbound/socks4.rb +55 -0
  31. data/lib/anytls/inbound/socks5.rb +138 -0
  32. data/lib/anytls/io_helpers.rb +87 -0
  33. data/lib/anytls/log.rb +15 -0
  34. data/lib/anytls/padding/scheme.rb +121 -0
  35. data/lib/anytls/padding/writer.rb +55 -0
  36. data/lib/anytls/protocol/authentication.rb +44 -0
  37. data/lib/anytls/protocol/decoder.rb +39 -0
  38. data/lib/anytls/protocol/frame.rb +40 -0
  39. data/lib/anytls/protocol/settings.rb +53 -0
  40. data/lib/anytls/protocol/socks_address.rb +59 -0
  41. data/lib/anytls/relay.rb +49 -0
  42. data/lib/anytls/server.rb +220 -0
  43. data/lib/anytls/session.rb +593 -0
  44. data/lib/anytls/session_pool.rb +166 -0
  45. data/lib/anytls/stream.rb +151 -0
  46. data/lib/anytls/tls_config.rb +76 -0
  47. data/lib/anytls/transport/tls_connection.rb +117 -0
  48. data/lib/anytls/uot/connection.rb +122 -0
  49. data/lib/anytls/uot/server.rb +97 -0
  50. data/lib/anytls/version.rb +6 -0
  51. data/lib/anytls.rb +40 -0
  52. metadata +203 -0
@@ -0,0 +1,593 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyTLS
4
+ # 每条连接由唯一所有者线程推进 TLS、帧处理和流状态。
5
+ # 其他线程提交受容量限制的操作,并等待本地发送完成。
6
+ # 所有者线程中的回调不得同步向同一会话提交任务。
7
+ class Session
8
+ # 与等待者共享的一次性结果;首次完成的结果生效。
9
+ class Completion
10
+ def initialize
11
+ @mutex = Mutex.new
12
+ @changed = ConditionVariable.new
13
+ @done = false
14
+ end
15
+
16
+ def finish(value = nil, error: nil)
17
+ @mutex.synchronize do
18
+ return if @done
19
+
20
+ @value = value
21
+ @error = error
22
+ @done = true
23
+ @changed.broadcast
24
+ end
25
+ end
26
+
27
+ def wait(deadline)
28
+ @mutex.synchronize do
29
+ deadline.wait(@changed, @mutex) until @done
30
+ raise @error if @error
31
+
32
+ @value
33
+ end
34
+ end
35
+ end
36
+
37
+ private_constant :Completion
38
+
39
+ attr_reader :sequence
40
+
41
+ def initialize(socket, tls:, password_digest:, scheme:, server: false, host: nil, sequence: 0,
42
+ handshake_timeout: 5, open_timeout: 5, max_streams: 32, receive_limit: 262_144,
43
+ session_receive_limit: 4_194_304, send_limit: 1_048_576, logger: nil,
44
+ on_stream: nil, on_idle: nil, on_close: nil, on_padding: nil)
45
+ @socket = socket
46
+ @tls = tls
47
+ @host = host
48
+ @server = server
49
+ @password_digest = password_digest
50
+ @scheme = scheme
51
+ @sequence = sequence
52
+ @handshake_timeout = handshake_timeout
53
+ @open_timeout = open_timeout
54
+ @max_streams = max_streams
55
+ @receive_limit = receive_limit
56
+ @session_receive_limit = session_receive_limit
57
+ @send_limit = send_limit
58
+ @logger = logger
59
+ @on_stream = on_stream
60
+ @on_idle = on_idle
61
+ @on_close = on_close
62
+ @on_padding = on_padding
63
+ @mutex = Mutex.new
64
+ @changed = ConditionVariable.new
65
+ # 分配唤醒 socket 前校验填充方案,避免配置错误留下待 GC 回收的 fd。
66
+ @padding_writer = Padding::Writer.new(scheme) unless server
67
+ @wake_reader, @wake_writer = Socket.pair(:UNIX, :STREAM, 0)
68
+ @operations = []
69
+ @transmissions = []
70
+ @streams = {}
71
+ @pending_open = {}
72
+ @queued_bytes = 0
73
+ @control_bytes = 0
74
+ @last_id = 0
75
+ @active_count = 0
76
+ @peer_version = 1
77
+ @closed = false
78
+ @stopping = false
79
+ @settings_received = false
80
+ @first_open = true
81
+ @ready = Completion.new
82
+ @diagnostic_errors = []
83
+ @decoder = Protocol::Decoder.new
84
+ begin
85
+ @thread = Thread.new { run }
86
+ rescue ThreadError
87
+ @wake_reader.close
88
+ @wake_writer.close
89
+ @socket.close unless @socket.closed?
90
+ raise
91
+ end
92
+ @thread.name = "anytls-session-#{sequence}"
93
+ @thread.report_on_exception = false
94
+ end
95
+
96
+ # 就绪仅表示本地 TLS 和认证记录处理已完成,
97
+ # 不证明对端已确认认证或连接出站目标。
98
+ def wait_ready(timeout: (@handshake_timeout * 2) + 1)
99
+ @ready.wait(Deadline.new(timeout))
100
+ self
101
+ end
102
+
103
+ def closed?
104
+ @mutex.synchronize { @closed || @stopping }
105
+ end
106
+
107
+ def peer_version
108
+ @mutex.synchronize { @peer_version }
109
+ end
110
+
111
+ def stream_count
112
+ @mutex.synchronize { @active_count }
113
+ end
114
+
115
+ def open_stream(address, timeout: @open_timeout)
116
+ submit(:open, address, deadline: Deadline.new(timeout), size: 512)
117
+ end
118
+
119
+ def write_stream(id, bytes, deadline:)
120
+ submit(:write, [id, bytes], deadline: deadline, size: bytes.bytesize + Protocol::HEADER_SIZE)
121
+ end
122
+
123
+ def close_stream(id)
124
+ submit(:close, id, deadline: Deadline.new(5), size: Protocol::HEADER_SIZE)
125
+ rescue Error, IOError
126
+ close
127
+ nil
128
+ end
129
+
130
+ def report_connected(id, error: nil)
131
+ submit(:report, [id, error], deadline: Deadline.new(5), size: 1024)
132
+ end
133
+
134
+ def heartbeat(timeout: 5)
135
+ submit(:heartbeat, nil, deadline: Deadline.new(timeout), size: Protocol::HEADER_SIZE)
136
+ end
137
+
138
+ def diagnostic_errors
139
+ @mutex.synchronize { @diagnostic_errors.dup.freeze }
140
+ end
141
+
142
+ def inspect
143
+ "#<#{self.class} sequence=#{sequence} role=#{@server ? 'server' : 'client'} closed=#{closed?}>"
144
+ end
145
+
146
+ def close(wait: true)
147
+ @mutex.synchronize do
148
+ @stopping = true
149
+ @changed.broadcast
150
+ end
151
+ wake
152
+ @thread.join if wait && Thread.current != @thread
153
+ nil
154
+ end
155
+
156
+ private
157
+
158
+ # 接收操作前,按编码字节数及最大填充预留容量。
159
+ # 等待队列容量会消耗调用者原有的单调时间预算。
160
+ def submit(kind, argument, deadline:, size:)
161
+ completion = Completion.new
162
+ cost = size + (@server ? 0 : @scheme.max_padding_bytes)
163
+ @mutex.synchronize do
164
+ raise BufferLimitExceeded, "write exceeds session queue capacity" if cost > @send_limit
165
+
166
+ deadline.wait(@changed, @mutex) while @queued_bytes + cost > @send_limit && !@stopping && !@closed
167
+ raise ClosedError, "session closed" if @stopping || @closed
168
+
169
+ deadline.check!
170
+ @queued_bytes += cost
171
+ @operations << { kind: kind, argument: argument, deadline: deadline, completion: completion, cost: cost }
172
+ end
173
+ wake
174
+ begin
175
+ completion.wait(deadline)
176
+ rescue TimeoutError
177
+ # 已接收的操作可能已影响对端;不得自动重放。
178
+ close(wait: false)
179
+ raise
180
+ end
181
+ end
182
+
183
+ def wake
184
+ @wake_writer.write_nonblock(".", exception: false)
185
+ rescue IOError, SystemCallError
186
+ nil
187
+ end
188
+
189
+ def run
190
+ @transport = Transport::TLSConnection.new(@socket, config: @tls, server: @server, host: @host,
191
+ cancel_io: @wake_reader, cancelled: -> { closed? })
192
+ @transport.handshake(timeout: @handshake_timeout)
193
+ @transport.authenticate(@password_digest, scheme: @scheme, timeout: @handshake_timeout)
194
+ @ready.finish(self)
195
+ event_loop
196
+ rescue StandardError => error
197
+ @failure = translate_error(error)
198
+ @ready.finish(error: @failure)
199
+ log(:debug, "AnyTLS session ended: #{@failure.class}")
200
+ ensure
201
+ finalize
202
+ end
203
+
204
+ def translate_error(error)
205
+ return error if error.is_a?(Error) || error.is_a?(ClosedError)
206
+
207
+ TransportError.new("TLS session failed (#{error.class})")
208
+ end
209
+
210
+ def event_loop
211
+ read_wait = :wait_readable
212
+ until closed?
213
+ process_operations
214
+ expire_opens
215
+ start_transmission unless @current
216
+ @current[:deadline].check! if @current
217
+
218
+ advance_transmission if @current && @transport.advance_write == :complete
219
+ break if @reject_after_flush && !@current && @transmissions.empty?
220
+
221
+ # SSL_write 返回 WANT_WRITE 后,必须先重试原写入,再进行其他 TLS 操作。
222
+ unless @current && @transport.write_wait == :wait_writable
223
+ result = @transport.read_nonblock(16_384)
224
+ if result.nil?
225
+ @decoder.finish!
226
+ raise TransportError, "peer closed TLS session"
227
+ elsif result.is_a?(String)
228
+ @decoder.feed(result).each { |frame| dispatch(frame) }
229
+ next
230
+ else
231
+ read_wait = result
232
+ end
233
+ end
234
+ next if !@current && !@transmissions.empty?
235
+
236
+ wait_for_work(read_wait)
237
+ end
238
+ end
239
+
240
+ def wait_for_work(read_wait)
241
+ readers = [@wake_reader]
242
+ writers = []
243
+ if @current && @transport.write_wait == :wait_writable
244
+ writers << @transport.io
245
+ else
246
+ readers << @transport.io if read_wait == :wait_readable || @transport.write_wait == :wait_readable
247
+ writers << @transport.io if read_wait == :wait_writable || (@current && @transport.write_wait.nil?)
248
+ end
249
+ budgets = [@current&.dig(:deadline)&.remaining, @heartbeat&.dig(:deadline)&.remaining,
250
+ *@pending_open.values.map(&:remaining)].compact
251
+ timeout = budgets.min
252
+ ready = IO.select(readers, writers, nil, timeout)
253
+ if ready&.first&.include?(@wake_reader)
254
+ loop do
255
+ value = @wake_reader.read_nonblock(4096, exception: false)
256
+ break unless value.is_a?(String)
257
+ end
258
+ end
259
+ end
260
+
261
+ def process_operations
262
+ 16.times do
263
+ operation = @mutex.synchronize { @operations.shift }
264
+ break unless operation
265
+
266
+ begin
267
+ operation[:deadline].check!
268
+ case operation[:kind]
269
+ when :open then open_operation(operation)
270
+ when :write then write_operation(operation)
271
+ when :close then close_operation(operation)
272
+ when :report then report_operation(operation)
273
+ when :heartbeat then heartbeat_operation(operation)
274
+ end
275
+ rescue StandardError => error
276
+ release_operation(operation, error: error)
277
+ ensure
278
+ operation.delete(:argument)
279
+ end
280
+ end
281
+ end
282
+
283
+ def open_operation(operation)
284
+ raise ProtocolError, "only a client can open streams" if @server
285
+ raise BufferLimitExceeded, "session stream limit reached" if @streams.size >= @max_streams
286
+ raise ClosedError, "stream identifiers exhausted" if @last_id == Protocol::MAX_STREAM_ID
287
+
288
+ @last_id += 1
289
+ stream = register_stream(@last_id)
290
+ bytes = String.new(encoding: Encoding::BINARY)
291
+ if @first_open
292
+ settings = { "v" => "2", "client" => "anytls-ruby/#{VERSION}", "padding-md5" => @scheme.md5 }
293
+ bytes << encode(Protocol::SETTINGS, 0, Protocol::Settings.encode(settings))
294
+ @first_open = false
295
+ end
296
+ bytes << encode(Protocol::SYN, stream.id)
297
+ bytes << encode(Protocol::PSH, stream.id, Protocol::SocksAddress.encode(operation[:argument]))
298
+ @pending_open[stream.id] = Deadline.new(@open_timeout) if @peer_version >= 2
299
+ queue_transmission(bytes, operation: operation, value: stream)
300
+ end
301
+
302
+ def write_operation(operation)
303
+ id, bytes = operation[:argument]
304
+ raise ClosedError, "stream closed" unless @streams.key?(id)
305
+
306
+ queue_transmission(encode(Protocol::PSH, id, bytes), operation: operation, value: bytes.bytesize)
307
+ end
308
+
309
+ def close_operation(operation)
310
+ id = operation[:argument]
311
+ stream = remove_stream(id)
312
+ if stream
313
+ stream.finish(ClosedError.new("stream closed"), local: true)
314
+ queue_transmission(encode(Protocol::FIN, id), operation: operation, idle: true)
315
+ else
316
+ release_operation(operation)
317
+ end
318
+ end
319
+
320
+ def report_operation(operation)
321
+ id, error = operation[:argument]
322
+ raise ClosedError, "stream closed" unless @streams.key?(id)
323
+
324
+ if @peer_version >= 2
325
+ message = error ? "outbound connection failed (#{error.class})" : "".b
326
+ queue_transmission(encode(Protocol::SYNACK, id, message), operation: operation)
327
+ else
328
+ release_operation(operation)
329
+ end
330
+ end
331
+
332
+ def heartbeat_operation(operation)
333
+ raise ProtocolError, "heartbeat requires negotiated v2" if @peer_version < 2
334
+ raise ProtocolError, "heartbeat already pending" if @heartbeat
335
+
336
+ @heartbeat = operation
337
+ queue_transmission(encode(Protocol::HEART_REQUEST, 0))
338
+ end
339
+
340
+ # 应用操作在完整发送前保留队列计费;对端控制帧响应
341
+ # 使用独立预算,因为没有应用提交者承担其容量。
342
+ def queue_transmission(bytes, operation: nil, value: nil, idle: false)
343
+ unless operation
344
+ @control_bytes += bytes.bytesize
345
+ raise BufferLimitExceeded, "control queue limit exceeded" if @control_bytes > 131_072
346
+ end
347
+ @transmissions << { bytes: bytes, bytesize: bytes.bytesize, operation: operation, value: value, idle: idle,
348
+ deadline: operation ? operation[:deadline] : Deadline.new(5) }
349
+ end
350
+
351
+ def start_transmission
352
+ @current = @transmissions.shift
353
+ return unless @current
354
+
355
+ @chunks = @padding_writer ? @padding_writer.encode(@current[:bytes]) : [@current[:bytes]]
356
+ @current.delete(:bytes)
357
+ @transport.begin_write(@chunks.shift)
358
+ end
359
+
360
+ def advance_transmission
361
+ unless @chunks.empty?
362
+ @transport.begin_write(@chunks.shift)
363
+ return
364
+ end
365
+ transmission = @current
366
+ @current = nil
367
+ if transmission[:operation]
368
+ release_operation(transmission[:operation], value: transmission[:value])
369
+ else
370
+ @control_bytes -= transmission[:bytesize]
371
+ end
372
+ notify_idle if transmission[:idle]
373
+ end
374
+
375
+ def release_operation(operation, value: nil, error: nil)
376
+ @mutex.synchronize do
377
+ @queued_bytes -= operation[:cost]
378
+ @changed.broadcast
379
+ end
380
+ operation[:completion].finish(value, error: error)
381
+ end
382
+
383
+ def dispatch(frame)
384
+ return if @reject_after_flush
385
+
386
+ validate_frame!(frame)
387
+ case frame.command
388
+ when Protocol::WASTE then nil
389
+ when Protocol::SETTINGS then receive_settings(frame.payload)
390
+ when Protocol::SERVER_SETTINGS then receive_server_settings(frame.payload)
391
+ when Protocol::SYN then receive_syn(frame.stream_id)
392
+ when Protocol::PSH then receive_data(frame)
393
+ when Protocol::FIN then finish_remote(frame.stream_id)
394
+ when Protocol::SYNACK then receive_ack(frame)
395
+ when Protocol::UPDATE_PADDING_SCHEME then receive_padding(frame.payload)
396
+ when Protocol::ALERT then raise RemoteError, "server rejected session"
397
+ when Protocol::HEART_REQUEST then queue_transmission(encode(Protocol::HEART_RESPONSE, frame.stream_id))
398
+ when Protocol::HEART_RESPONSE
399
+ if @heartbeat
400
+ release_operation(@heartbeat)
401
+ @heartbeat = nil
402
+ end
403
+ end
404
+ end
405
+
406
+ def validate_frame!(frame)
407
+ command = frame.command
408
+ known = (Protocol::WASTE..Protocol::SERVER_SETTINGS).cover?(command)
409
+ raise ProtocolError, "unknown command has payload" if !known && !frame.payload.empty?
410
+ return unless known
411
+
412
+ empty_commands = [Protocol::SYN, Protocol::FIN, Protocol::HEART_REQUEST, Protocol::HEART_RESPONSE]
413
+ if empty_commands.include?(command) && !frame.payload.empty?
414
+ raise ProtocolError,
415
+ "command must have empty payload"
416
+ end
417
+
418
+ client_only = [Protocol::SETTINGS, Protocol::SYN]
419
+ server_only = [Protocol::SERVER_SETTINGS, Protocol::SYNACK, Protocol::ALERT, Protocol::UPDATE_PADDING_SCHEME]
420
+ if (@server && server_only.include?(command)) || (!@server && client_only.include?(command))
421
+ raise ProtocolError, "command received in invalid direction"
422
+ end
423
+ if [Protocol::SYN, Protocol::PSH, Protocol::FIN, Protocol::SYNACK].include?(command) && frame.stream_id.zero?
424
+ raise ProtocolError, "stream ID must be positive"
425
+ end
426
+ if [Protocol::SYNACK, Protocol::HEART_REQUEST, Protocol::HEART_RESPONSE].include?(command) && @peer_version < 2
427
+ raise ProtocolError, "command requires negotiated v2"
428
+ end
429
+ end
430
+
431
+ def parse_version(settings)
432
+ version = settings["v"]
433
+ raise ProtocolError, "invalid protocol version" unless version&.match?(/\A[1-9]\d{0,8}\z/)
434
+
435
+ [version.to_i, 2].min
436
+ end
437
+
438
+ def receive_settings(payload)
439
+ raise ProtocolError, "duplicate client settings" if @settings_received
440
+
441
+ settings = Protocol::Settings.decode(payload)
442
+ version = parse_version(settings)
443
+ digest = settings["padding-md5"]
444
+ raise ProtocolError, "missing padding digest" unless digest&.match?(/\A[0-9a-f]{32}\z/)
445
+
446
+ @settings_received = true
447
+ @mutex.synchronize { @peer_version = version }
448
+ queue_transmission(encode(Protocol::UPDATE_PADDING_SCHEME, 0, @scheme.raw)) if digest != @scheme.md5
449
+ if version >= 2
450
+ queue_transmission(encode(Protocol::SERVER_SETTINGS, 0,
451
+ Protocol::Settings.encode({ "v" => "2" })))
452
+ end
453
+ end
454
+
455
+ def receive_server_settings(payload)
456
+ raise ProtocolError, "duplicate server settings" if @server_settings_received
457
+
458
+ @server_settings_received = true
459
+ version = parse_version(Protocol::Settings.decode(payload))
460
+ @mutex.synchronize { @peer_version = version }
461
+ return unless version >= 2
462
+
463
+ @streams.each_value do |stream|
464
+ @pending_open[stream.id] = Deadline.new(@open_timeout) if stream.connection_status == :unconfirmed
465
+ end
466
+ end
467
+
468
+ def receive_syn(id)
469
+ unless @settings_received
470
+ queue_transmission(encode(Protocol::ALERT, 0, "client settings required"))
471
+ @reject_after_flush = true
472
+ return
473
+ end
474
+ raise ProtocolError, "non-monotonic stream ID" unless id > @last_id
475
+
476
+ @last_id = id
477
+ if @streams.size >= @max_streams
478
+ queue_transmission(encode(Protocol::SYNACK, id, "stream limit reached")) if @peer_version >= 2
479
+ queue_transmission(encode(Protocol::FIN, id))
480
+ return
481
+ end
482
+ stream = register_stream(id)
483
+ if @on_stream&.call(stream, self) == false
484
+ remove_stream(id)&.finish(BufferLimitExceeded.new("server stream limit reached"))
485
+ queue_transmission(encode(Protocol::SYNACK, id, "stream limit reached")) if @peer_version >= 2
486
+ queue_transmission(encode(Protocol::FIN, id))
487
+ end
488
+ end
489
+
490
+ # 所有者线程不得等待消费者;超出预算时拒绝受影响的流,
491
+ # 保留其他流及其已缓冲字节。
492
+ def receive_data(frame)
493
+ stream = @streams[frame.stream_id]
494
+ return unless stream
495
+
496
+ if @streams.values.sum(&:buffered_bytes) + frame.payload.bytesize > @session_receive_limit
497
+ raise BufferLimitExceeded, "session receive buffer limit exceeded"
498
+ end
499
+
500
+ stream.receive(frame.payload)
501
+ rescue BufferLimitExceeded => error
502
+ remove_stream(frame.stream_id)&.finish(error)
503
+ queue_transmission(encode(Protocol::FIN, frame.stream_id), idle: true)
504
+ end
505
+
506
+ def receive_ack(frame)
507
+ @pending_open.delete(frame.stream_id)
508
+ stream = @streams[frame.stream_id]
509
+ return unless stream
510
+
511
+ if frame.payload.empty?
512
+ stream.confirm
513
+ else
514
+ remove_stream(frame.stream_id)&.finish(RemoteError.new("remote outbound connection failed"))
515
+ queue_transmission(encode(Protocol::FIN, frame.stream_id), idle: true)
516
+ end
517
+ end
518
+
519
+ # 发布校验后的快照,供客户端未来的会话使用;当前会话的写入器
520
+ # 继续使用原方案和包计数。
521
+ def receive_padding(payload)
522
+ scheme = Padding::Scheme.new(payload)
523
+ @on_padding&.call(scheme)
524
+ rescue ConfigurationError => error
525
+ log(:warn, "ignored invalid remote padding scheme (#{error.class})")
526
+ end
527
+
528
+ def finish_remote(id)
529
+ stream = remove_stream(id)
530
+ stream&.finish
531
+ notify_idle if stream
532
+ end
533
+
534
+ def register_stream(id)
535
+ stream = Stream.new(self, id, receive_limit: @receive_limit)
536
+ @streams[id] = stream
537
+ @mutex.synchronize { @active_count = @streams.size }
538
+ stream
539
+ end
540
+
541
+ def remove_stream(id)
542
+ @pending_open.delete(id)
543
+ stream = @streams.delete(id)
544
+ @mutex.synchronize { @active_count = @streams.size }
545
+ stream
546
+ end
547
+
548
+ def notify_idle
549
+ @on_idle&.call(self) if @streams.empty? && !closed?
550
+ end
551
+
552
+ def expire_opens
553
+ @pending_open.each_value(&:check!)
554
+ @heartbeat&.fetch(:deadline)&.check!
555
+ @stopping = true if @reject_after_flush && @transmissions.empty? && !@current
556
+ end
557
+
558
+ def encode(command, id, payload = "".b)
559
+ Protocol::Frame.new(command: command, stream_id: id, payload: payload).encode
560
+ end
561
+
562
+ def log(level, message)
563
+ error = Log.emit(@logger, level, message)
564
+ return unless error
565
+
566
+ @mutex.synchronize { @diagnostic_errors << error if @diagnostic_errors.size < 16 }
567
+ end
568
+
569
+ def finalize
570
+ failure = @failure || ClosedError.new("session closed")
571
+ @transport&.close
572
+ @socket.close unless @socket.closed?
573
+ @streams.each_value { |stream| stream.finish(failure) }
574
+ @streams.clear
575
+ operations = @mutex.synchronize do
576
+ @closed = @stopping = true
577
+ @active_count = 0
578
+ queued = @operations
579
+ @operations = []
580
+ @changed.broadcast
581
+ queued
582
+ end
583
+ operations += @transmissions.filter_map { |entry| entry[:operation] }
584
+ operations << @current[:operation] if @current && @current[:operation]
585
+ operations << @heartbeat if @heartbeat
586
+ operations.each { |operation| operation[:completion].finish(error: failure) }
587
+ @ready.finish(error: failure)
588
+ @wake_reader.close
589
+ @wake_writer.close
590
+ @on_close&.call(self)
591
+ end
592
+ end
593
+ end