zzq 0.1.0 → 0.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/LICENSE-APACHE +202 -0
  4. data/LICENSE-ISC +15 -0
  5. data/README.md +67 -122
  6. data/ext/zzq_native/Cargo.toml +35 -0
  7. data/ext/zzq_native/extconf.rb +8 -0
  8. data/ext/zzq_native/src/broker.rs +163 -0
  9. data/ext/zzq_native/src/client.rs +294 -0
  10. data/ext/zzq_native/src/context.rs +51 -0
  11. data/ext/zzq_native/src/error.rs +117 -0
  12. data/ext/zzq_native/src/lib.rs +52 -0
  13. data/ext/zzq_native/src/op.rs +140 -0
  14. data/ext/zzq_native/src/options.rs +168 -0
  15. data/ext/zzq_native/src/rb.rs +424 -0
  16. data/ext/zzq_native/src/wake.rs +155 -0
  17. data/lib/zzq/broker.rb +90 -21
  18. data/lib/zzq/client.rb +202 -35
  19. data/lib/zzq/errors.rb +37 -0
  20. data/lib/zzq/message.rb +16 -107
  21. data/lib/zzq/options.rb +27 -86
  22. data/lib/zzq/version.rb +2 -1
  23. data/lib/zzq/waiting.rb +65 -0
  24. data/lib/zzq.rb +22 -19
  25. metadata +33 -77
  26. data/LICENSE +0 -13
  27. data/lib/zzq/engine/connection_lifecycle.rb +0 -139
  28. data/lib/zzq/engine/keepalive.rb +0 -75
  29. data/lib/zzq/engine/socket_lifecycle.rb +0 -79
  30. data/lib/zzq/engine.rb +0 -163
  31. data/lib/zzq/error.rb +0 -42
  32. data/lib/zzq/monitor_event.rb +0 -14
  33. data/lib/zzq/persistence/interface.rb +0 -43
  34. data/lib/zzq/persistence/memory.rb +0 -37
  35. data/lib/zzq/persistence/pstore.rb +0 -66
  36. data/lib/zzq/routing/broker.rb +0 -380
  37. data/lib/zzq/routing/client.rb +0 -327
  38. data/lib/zzq/routing/packet_id_allocator.rb +0 -54
  39. data/lib/zzq/routing/qos_tracker.rb +0 -108
  40. data/lib/zzq/routing/retained_store.rb +0 -67
  41. data/lib/zzq/routing/topic_match.rb +0 -49
  42. data/lib/zzq/routing/topic_trie.rb +0 -232
  43. data/lib/zzq/socket.rb +0 -146
  44. data/lib/zzq/subscription.rb +0 -70
  45. data/lib/zzq/transport/ipc.rb +0 -92
  46. data/lib/zzq/transport/mqtt.rb +0 -83
  47. data/lib/zzq/transport/mqtts.rb +0 -173
@@ -1,173 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "socket"
4
- require "openssl"
5
- require "uri"
6
- require "io/stream"
7
- require_relative "../engine"
8
-
9
- module ZZQ
10
- module Transport
11
- # MQTT-over-TLS transport — endpoint scheme `mqtts://host:port`
12
- # (default port 8883).
13
- #
14
- # Both bind and connect accept a `tls_context:` option — any
15
- # +OpenSSL::SSL::SSLContext+. On bind the context must carry a
16
- # cert + key; on connect a default client context is used if
17
- # none is given. The transport does not constrain the context;
18
- # see the README for a recommended TLS 1.3 +
19
- # `TLS_CHACHA20_POLY1305_SHA256` configuration.
20
- #
21
- # Per-connection application data is capped at
22
- # {DATA_LIMIT} bytes, after which the socket is torn down and
23
- # the peer must reconnect. This keeps AEAD nonce counters well
24
- # below their unsafe bound (e.g. ChaCha20-Poly1305's ~256 GiB
25
- # per-key integrity bound) without relying on a specific cipher
26
- # choice.
27
- module MQTTS
28
- Engine.transports["mqtts"] = self
29
-
30
- # 128 GiB — conservative for every AEAD in TLS 1.3 (ChaCha20-
31
- # Poly1305 at ~256 GiB, AES-GCM limits are higher under TLS 1.3
32
- # record sizes). Reconnection negotiates fresh keys, so this
33
- # caps nonce reuse exposure regardless of cipher choice.
34
- DATA_LIMIT = 128 * 1024 * 1024 * 1024
35
-
36
-
37
- class << self
38
- def bind(endpoint, engine, tls_context: nil, **)
39
- raise Error, "mqtts:// bind requires tls_context:" unless tls_context
40
-
41
- host, port = parse_endpoint(endpoint)
42
- host = "0.0.0.0" if host == "*"
43
- server = TCPServer.new(host, port)
44
- actual = server.local_address.ip_port
45
- host_part = host.include?(":") ? "[#{host}]" : host
46
- Listener.new("mqtts://#{host_part}:#{actual}", server, tls_context, engine)
47
- end
48
-
49
-
50
- def connect(endpoint, engine, tls_context: nil, **)
51
- host, port = parse_endpoint(endpoint)
52
- ctx = tls_context || OpenSSL::SSL::SSLContext.new
53
- tcp = ::Socket.tcp(host, port, connect_timeout: connect_timeout(engine.options))
54
- ssl = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
55
- ssl.hostname = host # SNI
56
- ssl.sync_close = true
57
- ssl.connect
58
- engine.handle_connected(LimitedStream.wrap(ssl), endpoint: endpoint)
59
- end
60
-
61
-
62
- def connect_timeout(options)
63
- ri = options.reconnect_interval
64
- ri = ri.end if ri.is_a?(Range)
65
- [ri, 0.5].max
66
- end
67
-
68
-
69
- def parse_endpoint(endpoint)
70
- uri = URI.parse(endpoint.sub("mqtts://", "tcp://"))
71
- [uri.hostname, uri.port]
72
- end
73
- end
74
-
75
-
76
- class Listener
77
- attr_reader :endpoint
78
-
79
-
80
- def initialize(endpoint, server, tls_context, engine)
81
- @endpoint = endpoint
82
- @server = server
83
- @ctx = tls_context
84
- @engine = engine
85
- @task = nil
86
- end
87
-
88
-
89
- def start_accept_loop(parent_task, &on_accepted)
90
- @task = parent_task.async(annotation: "zzq mqtts accept #{@endpoint}") do |task|
91
- loop do
92
- client = @server.accept
93
- # TLS handshake per-connection, off the accept loop, so
94
- # one slow handshake can't block the next client.
95
- task.async(annotation: "zzq mqtts handshake") do
96
- ssl = OpenSSL::SSL::SSLSocket.new(client, @ctx)
97
- ssl.sync_close = true
98
- ssl.accept
99
- on_accepted.call(LimitedStream.wrap(ssl))
100
- rescue Async::Stop, IOError, OpenSSL::SSL::SSLError
101
- ssl&.close rescue nil
102
- client.close rescue nil
103
- end
104
- rescue Async::Stop, IOError
105
- break
106
- end
107
- ensure
108
- @server.close rescue nil
109
- end
110
- end
111
-
112
-
113
- def stop
114
- @task&.stop
115
- @server.close rescue nil
116
- end
117
- end
118
-
119
-
120
- # Buffered stream with a per-connection byte cap. When the cap
121
- # is reached on either direction the underlying socket is
122
- # closed and the next I/O raises — forcing the peer to
123
- # reconnect and negotiate fresh keys before ChaCha20-Poly1305's
124
- # nonce counter approaches its unsafe range.
125
- class LimitedStream < IO::Stream::Buffered
126
- def self.wrap(io, limit: DATA_LIMIT)
127
- if io.respond_to?(:buffered=)
128
- io.buffered = false
129
- elsif io.respond_to?(:sync=)
130
- io.sync = true
131
- end
132
- new(io, limit)
133
- end
134
-
135
-
136
- def initialize(io, limit = DATA_LIMIT)
137
- super(io)
138
- @limit = limit
139
- @bytes = 0
140
- end
141
-
142
-
143
- protected
144
-
145
-
146
- def syswrite(buffer)
147
- enforce!(buffer.bytesize)
148
- super
149
- end
150
-
151
-
152
- def sysread(size, buffer)
153
- result = super
154
- enforce!(result.bytesize) if result.is_a?(String)
155
- result
156
- end
157
-
158
-
159
- private
160
-
161
-
162
- def enforce!(n)
163
- @bytes += n
164
- return if @bytes < @limit
165
-
166
- close rescue nil
167
- raise ZZQ::Error,
168
- "mqtts:// data limit reached (#{@limit} B): dropping connection to force fresh keys"
169
- end
170
- end
171
- end
172
- end
173
- end