omq-zstd 0.4.2 → 0.4.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fcea64306ec7603681d7b3350e7cdf2d22d43487be062e79cc91f51f64aacfb
4
- data.tar.gz: ac5d8d88deb3f90ccf97be757d384612274f4d742b042b79bc949a47ea7d0d3c
3
+ metadata.gz: 22b1f25991e512b445fe5a7202d2671048159b494a7ac5418d1b121f696940ab
4
+ data.tar.gz: 941b1c5f85ec8dd1a9816fbfac241f882be1f2e9cd80696b1cf894465e90d884
5
5
  SHA512:
6
- metadata.gz: 8e5a04f9ffdb46d130af145fa7025c544cc1709e7711cd92180b0408923667e61d70098efc4679c7b0d2617a3acde40b91c9ecaa1b5abb9e56cadc4a34cd8294
7
- data.tar.gz: 25ab87f733a74257a9b2d772d96a76be134b60804ce2796fa0a95710334eb0552f973fab4a7b04442f6d9cce8091da008c5ed17e6868e925b942fdc4dfefd0f6
6
+ metadata.gz: 6525d971daf7707cb46113848b2a70c6e29775f9d7a87c6e9dcbd13cd481eebdd7c6a75c919d629b281e491c3263ec56b2e149bc573e4e0f24df32f042247dab
7
+ data.tar.gz: 99d1a5e3d2ce007036a0712fc7912331bbda35437eca0976d55eb58b8d0eedb52fd5a285c9c025fa658c75231ff4e538fb0f1dcd71d0d38a6d67505a72c4ae1e
data/DESIGN.md CHANGED
@@ -67,7 +67,7 @@ One Codec per Dialer or Listener. Shared across all connections of
67
67
  that endpoint. Owns:
68
68
 
69
69
  - **Compression**: `#compress_parts(parts)` with identity cache
70
- - **Training**: sample collection, `RZstd::Dictionary.train`, dict ID patching
70
+ - **Training**: sample collection, `Zrip::DictTrainer`, dict ID patching
71
71
  - **Send dict**: `#send_dict_bytes` — the trained or user-supplied dict bytes
72
72
 
73
73
  ### ZstdConnection (per-connection)
@@ -124,7 +124,7 @@ end
124
124
  - **Sample size cap**: frames > 1024 bytes are skipped (dictionaries primarily benefit small frames)
125
125
  - **Dict capacity**: 8 KiB (conservative; Zstd recommends ~100:1 sample-to-dict ratio)
126
126
  - **Dict ID patching**: auto-trained dicts get a random ID in the user range (32768..2^31-1) to avoid collisions with Zstd's built-in dict IDs
127
- - **Training failure**: if `RZstd::Dictionary.train` raises, training is disabled permanently for the socket. No retry.
127
+ - **Training failure**: if `Zrip::DictTrainer#train` raises, training is disabled permanently for the socket. No retry.
128
128
 
129
129
  ## Frame dispatch
130
130
 
data/README.md CHANGED
@@ -2,13 +2,19 @@
2
2
 
3
3
  [![Gem Version](https://img.shields.io/gem/v/omq-zstd?color=e9573f)](https://rubygems.org/gems/omq-zstd)
4
4
  [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)
5
- [![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%203.3-CC342D?logo=ruby&logoColor=white)](https://www.ruby-lang.org)
5
+ [![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%204.0-CC342D?logo=ruby&logoColor=white)](https://www.ruby-lang.org)
6
+
7
+ Experimental Zstandard-compressed TCP transport for
8
+ [OMQ](https://github.com/zeromq/omq.rb).
9
+
10
+ Use [`omq-lz4`](../omq-lz4) for CPU- or memory-sensitive compressed TCP
11
+ work. This gem remains for research, comparison, OMQ.rs parity, and cases
12
+ where zstd ratio matters more than transport simplicity.
6
13
 
7
- Zstandard-compressed TCP transport for [OMQ](https://github.com/paddor/omq).
8
14
  Pick `zstd+tcp://` instead of `tcp://` and every message part on the wire is
9
15
  compressed per-part with [Zstandard](https://github.com/facebook/zstd).
10
16
  Compression is intrinsic to the transport: no negotiation, no socket option,
11
- no payload changes. The ZMTP handshake runs over plain TCP; only
17
+ no payload changes. The ZMTP handshake runs over plain TCP. Only
12
18
  post-handshake message parts are compressed.
13
19
 
14
20
  See [RFC.md](RFC.md) for the wire-format specification and
@@ -73,11 +79,17 @@ The sender ships the dictionary to the receiver in-band as a one-shot
73
79
  single-part message prefixed with the dictionary sentinel
74
80
  (`37 A4 30 EC`), so the receiver does not need a copy on disk.
75
81
 
76
- **Auto-trained dictionary** (zero config, the default when no `dict:` is
77
- passed): the sender collects up to 1000 samples or 100 KiB (whichever hits
78
- first), skipping samples larger than 2048 bytes. It trains a 2 KiB dictionary,
79
- ships it inline, and switches to dictionary mode. Until then, payloads are
80
- compressed without a dictionary or sent plaintext when below the threshold.
82
+ **Auto-trained dictionary** (opt-in):
83
+
84
+ ```ruby
85
+ push.connect("zstd+tcp://127.0.0.1:5555", auto_dict: true)
86
+ ```
87
+
88
+ The sender collects up to 1000 samples or 100 KiB (whichever hits first),
89
+ skipping samples larger than 2048 bytes. It trains a 2 KiB dictionary, ships
90
+ it inline, and switches to dictionary mode. Until then, payloads are compressed
91
+ without a dictionary or sent plaintext when below the threshold. Automatic
92
+ dictionary training is off by default.
81
93
 
82
94
  ### Compression thresholds
83
95
 
@@ -145,6 +157,7 @@ transport layer. They are not delivered to the application.
145
157
  | Train max bytes | 100 KiB |
146
158
  | Train max sample length | 2048 B |
147
159
  | Dictionary capacity | 2 KiB |
160
+ | Auto dictionary training | Off by default |
148
161
 
149
162
  ## When to use it
150
163
 
@@ -170,7 +183,7 @@ It is **not** worth it for:
170
183
  `require "omq/zstd"` registers the `zstd+tcp` scheme on
171
184
  `OMQ::Engine.transports`. A `zstd+tcp` socket builds a per-engine
172
185
  `Codec` (one Zstd dictionary instance shared across all the socket's
173
- connections; fan-out compresses each part exactly once). Each accepted
186
+ connections. Fan-out compresses each part exactly once). Each accepted
174
187
  or dialed TCP connection is wrapped in `ZstdConnection`, a
175
188
  `SimpleDelegator` over the underlying ZMTP connection that intercepts
176
189
  `#send_message` / `#write_message` / `#receive_message`. Message parts
data/RFC.md CHANGED
@@ -265,21 +265,26 @@ exceeds the limit MUST be rejected before decoder invocation.
265
265
  ### 7.1 Dictionary message format
266
266
 
267
267
  A dictionary is shipped as a **single-part ZMTP message** (no MORE flag)
268
- whose body begins with the dictionary sentinel:
268
+ whose body is exactly a Zstandard dictionary blob in ZDICT format. ZDICT
269
+ blobs begin with the dictionary magic `37 A4 30 EC`; this transport uses
270
+ those first 4 bytes as the dictionary sentinel:
269
271
 
270
272
  ```
271
273
  +------------------+------------------------+
272
- | 37 A4 30 EC | dictionary bytes |
273
- | (4 bytes) | (D bytes) |
274
+ | 37 A4 30 EC | rest of ZDICT blob |
275
+ | (4 bytes) | (D - 4 bytes) |
274
276
  +------------------+------------------------+
275
277
  ```
276
278
 
277
- The sentinel `37 A4 30 EC` is specific to this specification and has no
278
- relationship to Zstandard's internals. It was chosen to avoid collision
279
- with the Zstandard frame magic and the uncompressed sentinel.
279
+ The sentinel `37 A4 30 EC` is the standard Zstandard dictionary magic
280
+ (`DICT_MAGIC`, little-endian). It is intentionally reused as this
281
+ transport's dictionary discriminator because a ZDICT blob already carries
282
+ it and it does not collide with the Zstandard frame magic or the
283
+ uncompressed sentinel.
280
284
 
281
- The remaining `D` bytes are the raw dictionary as it should be passed
282
- to the Zstandard decoder's dictionary-load operation.
285
+ The complete `D` bytes, including the leading `37 A4 30 EC` magic, are
286
+ the raw dictionary as it should be passed to the Zstandard decoder's
287
+ dictionary-load operation.
283
288
 
284
289
  ### 7.2 Constraints
285
290
 
@@ -287,9 +292,10 @@ to the Zstandard decoder's dictionary-load operation.
287
292
  not set on the frame header). A dictionary sentinel in a multipart
288
293
  message's non-final or non-only part is a protocol error.
289
294
 
290
- - A dictionary message MUST NOT exceed **8 KiB** total (sentinel +
291
- dictionary bytes). A receiver that receives a dictionary message
292
- larger than 8 KiB MUST close the connection.
295
+ - A dictionary message MUST NOT exceed **8 KiB** total. Since the
296
+ message body is exactly the ZDICT blob, this is also the maximum
297
+ dictionary size. A receiver that receives a dictionary message larger
298
+ than 8 KiB MUST close the connection.
293
299
 
294
300
  - A sender MUST send at most **one** dictionary message per direction
295
301
  per connection. A receiver that receives a second dictionary message
@@ -305,9 +311,9 @@ to the Zstandard decoder's dictionary-load operation.
305
311
  When the receiver encounters a dictionary part:
306
312
 
307
313
  1. Validate the constraints in Sec. 7.2.
308
- 2. Strip the 4-byte sentinel.
309
- 3. Install the remaining bytes as the decompression dictionary for this
310
- connection.
314
+ 2. Install the complete message body as the decompression dictionary for
315
+ this connection. The leading `37 A4 30 EC` bytes are part of the ZDICT
316
+ blob and MUST NOT be stripped.
311
317
  4. Discard the message. It is not delivered to the application.
312
318
 
313
319
  If all parts of a ZMTP message are dictionary parts (which is always
@@ -338,23 +344,31 @@ in shared training; they use their own dictionary independently.
338
344
 
339
345
  ### 7.5 Automatic dictionary training
340
346
 
341
- A sender MAY train a dictionary automatically from early traffic:
342
-
343
- 1. Buffer plaintext samples from the first messages. Samples larger
344
- than **2048 bytes** SHOULD be skipped; dictionaries primarily
345
- benefit small frames.
346
- 2. When the buffer reaches **1000 samples** OR **100 KiB** of
347
- plaintext (whichever comes first), train a Zstandard dictionary from
348
- the buffered samples and discard the buffer.
349
- 3. The recommended dictionary capacity (training target size) is
350
- **2 KiB**.
351
- 4. Ship the trained dictionary via a dictionary message (Sec. 7.1) on
352
- every connection, before any compressed part that uses it.
353
- 5. Switch to dictionary-bound compression for all subsequent parts.
354
-
355
- If training fails (the sample set was too small or too uniform), the
356
- sender MUST stay in no-dictionary mode for the rest of the socket's
357
- lifetime. It MUST NOT retry training.
347
+ A sender MAY train a dictionary automatically from early traffic.
348
+ The training algorithm, trigger condition, and parameters are
349
+ implementation choices. The following wire-level constraints MUST be
350
+ respected:
351
+
352
+ - A trained dictionary MUST NOT exceed the maximum dictionary size
353
+ (Sec. 7.2: 8 KiB).
354
+ - A sender MUST ship at most one dictionary per direction per
355
+ connection (Sec. 7.2).
356
+ - If training fails (the sample set was too small or too uniform),
357
+ the sender MUST stay in no-dictionary mode for the rest of the
358
+ socket's lifetime. It MUST NOT retry training.
359
+
360
+ The following parameters are RECOMMENDED for implementations that
361
+ support auto-training:
362
+
363
+ | Parameter | Recommended value |
364
+ |------------------------|------------------------------------------|
365
+ | Dictionary capacity | 2 KiB |
366
+ | Training trigger | 1000 samples OR 100 KiB (first reached) |
367
+ | Max sample length | 2048 bytes |
368
+ | Training algorithm | FastCOVER |
369
+
370
+ Auto-training SHOULD be disabled by default. Applications MAY enable it
371
+ explicitly or supply an out-of-band dictionary instead.
358
372
 
359
373
  ### 7.6 Dictionary ID
360
374
 
@@ -22,19 +22,24 @@ module OMQ
22
22
  attr_reader :send_dict_bytes, :max_message_size
23
23
 
24
24
 
25
- def initialize(level:, dict: nil, max_message_size: nil)
25
+ def initialize(level:, dict: nil, max_message_size: nil, auto_dict: nil)
26
26
  @level = level
27
27
  @max_message_size = max_message_size
28
+ @dict_capacity = if auto_dict.is_a?(Hash) && auto_dict[:capacity]
29
+ auto_dict[:capacity]
30
+ else
31
+ DICT_CAPACITY
32
+ end
28
33
 
29
34
  # Start with a no-dict FrameCodec. Once a dict is configured
30
35
  # (either via the `dict:` kwarg or via auto-training), this is
31
- # replaced with a fresh dict-bound FrameCodec per rzstd 0.4,
36
+ # replaced with a fresh dict-bound FrameCodec. In Zrip, the
32
37
  # dict is a permanent property of the codec, so swapping dicts
33
38
  # means constructing a new one.
34
- @send_codec = RZstd::FrameCodec.new(level: @level)
39
+ @send_codec = Zrip::FrameCodec.new(level: @level)
35
40
  @send_dict_bytes = nil
36
41
 
37
- @training = dict.nil?
42
+ @training = !!auto_dict && dict.nil?
38
43
  @train_samples = []
39
44
  @train_bytes = 0
40
45
 
@@ -102,18 +107,16 @@ module OMQ
102
107
  return unless @train_samples.size >= TRAIN_MAX_SAMPLES ||
103
108
  @train_bytes >= TRAIN_MAX_BYTES
104
109
 
105
- begin
106
- trained = RZstd::Dictionary.train(@train_samples, capacity: DICT_CAPACITY)
107
- rescue RuntimeError
108
- @training = false
109
- @train_samples = nil
110
- return
111
- end
110
+ trainer = Zrip::DictTrainer.new(@dict_capacity)
111
+ @train_samples.each { |s| trainer.add_sample(s) }
112
+ trained_bytes = trainer.train
112
113
 
113
114
  @training = false
114
115
  @train_samples = nil
115
116
 
116
- patched = patch_auto_dict_id(trained.bytes)
117
+ return if trained_bytes.empty?
118
+
119
+ patched = patch_auto_dict_id(trained_bytes)
117
120
  install_send_dict(patched)
118
121
  end
119
122
 
@@ -136,9 +139,9 @@ module OMQ
136
139
  end
137
140
 
138
141
  # Replace the no-dict send codec with a fresh dict-bound one;
139
- # the old codec is GC'd. rzstd 0.4 treats dict as a permanent
140
- # codec property, so install-time is always a fresh build.
141
- @send_codec = RZstd::FrameCodec.new(dict: bytes, level: @level)
142
+ # the old codec is GC'd. Zrip treats dict as a permanent codec
143
+ # property, so install-time is always a fresh build.
144
+ @send_codec = Zrip::FrameCodec.new(dict: bytes, level: @level)
142
145
  @send_dict_bytes = bytes
143
146
  end
144
147
 
@@ -15,12 +15,11 @@ module OMQ
15
15
  super(conn)
16
16
  @codec = codec
17
17
  @dict_shipped = false
18
- # rzstd 0.4: FrameCodec is the decoder. Starts no-dict; when a
19
- # dict shipment arrives on this direction, we build a fresh
20
- # dict-bound FrameCodec and replace this one. Level is a
21
- # compression parameter only — not consulted on decompress —
22
- # so we don't carry the send-side level through to recv.
23
- @recv_codec = RZstd::FrameCodec.new
18
+ # FrameCodec is the decoder. Starts no-dict; when a dict
19
+ # shipment arrives on this direction, we build a fresh
20
+ # dict-bound FrameCodec and replace this one.
21
+ @recv_codec = Zrip::FrameCodec.new
22
+ @recv_no_dict_codec = @recv_codec
24
23
  @recv_dict_bytes = nil
25
24
  @last_wire_size_in = nil
26
25
  end
@@ -128,16 +127,24 @@ module OMQ
128
127
 
129
128
  decompress_opts = budget ? { max_output_size: budget } : {}
130
129
 
131
- @recv_codec.decompress(wire, **decompress_opts)
132
- rescue RZstd::DecompressError => e
130
+ codec = frame_has_dict_id?(wire) ? @recv_codec : @recv_no_dict_codec
131
+ codec.decompress(wire, **decompress_opts)
132
+ rescue Zrip::DecompressError => e
133
133
  raise ProtocolError, "decompression failed: #{e.message}"
134
- rescue RZstd::MissingContentSizeError => e
134
+ rescue Zrip::MissingContentSizeError => e
135
135
  raise ProtocolError, "Zstd frame missing Frame_Content_Size (#{e.message})"
136
- rescue RZstd::OutputSizeLimitError => e
136
+ rescue Zrip::OutputSizeLimitError => e
137
137
  raise ProtocolError, "declared FCS exceeds limit (#{e.message})"
138
138
  end
139
139
 
140
140
 
141
+ def frame_has_dict_id?(wire)
142
+ return false if wire.bytesize < 5
143
+
144
+ (wire.getbyte(4) & 0x03) != 0
145
+ end
146
+
147
+
141
148
  def install_recv_dict(wire)
142
149
  if wire.bytesize < 8
143
150
  raise ProtocolError, "dict frame too short"
@@ -147,10 +154,7 @@ module OMQ
147
154
  raise ProtocolError, "dict exceeds #{Codec::MAX_DICT_SIZE} bytes"
148
155
  end
149
156
 
150
- # Replace the no-dict recv codec with a fresh dict-bound one;
151
- # the old codec is GC'd (rzstd 0.4: dict is a permanent codec
152
- # property).
153
- @recv_codec = RZstd::FrameCodec.new(dict: wire.b)
157
+ @recv_codec = Zrip::FrameCodec.new(dict: wire.b)
154
158
  @recv_dict_bytes = wire.b
155
159
  end
156
160
 
@@ -24,10 +24,18 @@ module OMQ
24
24
  # @param engine [Engine]
25
25
  # @param level [Integer] Zstd compression level
26
26
  # @param dict [String, nil] user-supplied dictionary bytes
27
+ # @param auto_dict [true, Hash, nil] enable automatic dictionary
28
+ # training. Pass `true` for defaults or `{ capacity: N }`.
27
29
  # @return [Listener]
28
30
  #
29
- def listener(endpoint, engine, level: -3, dict: nil, **)
30
- codec = codec_for(engine, level: level, dict: dict)
31
+ def listener(endpoint, engine, level: -3, dict: nil, auto_dict: nil, **)
32
+ validate_auto_dict!(auto_dict, dict)
33
+ codec = codec_for(
34
+ engine,
35
+ level: level,
36
+ dict: dict,
37
+ auto_dict: normalize_auto_dict(auto_dict),
38
+ )
31
39
 
32
40
  host, port = parse_endpoint(endpoint)
33
41
  host = normalize_bind_host(host)
@@ -52,10 +60,18 @@ module OMQ
52
60
  # @param engine [Engine]
53
61
  # @param level [Integer] Zstd compression level
54
62
  # @param dict [String, nil] user-supplied dictionary bytes
63
+ # @param auto_dict [true, Hash, nil] enable automatic dictionary
64
+ # training. Pass `true` for defaults or `{ capacity: N }`.
55
65
  # @return [Dialer]
56
66
  #
57
- def dialer(endpoint, engine, level: -3, dict: nil, **)
58
- codec = codec_for(engine, level: level, dict: dict)
67
+ def dialer(endpoint, engine, level: -3, dict: nil, auto_dict: nil, **)
68
+ validate_auto_dict!(auto_dict, dict)
69
+ codec = codec_for(
70
+ engine,
71
+ level: level,
72
+ dict: dict,
73
+ auto_dict: normalize_auto_dict(auto_dict),
74
+ )
59
75
  Dialer.new(endpoint, engine, codec)
60
76
  end
61
77
 
@@ -107,12 +123,41 @@ module OMQ
107
123
  # @param dict [String, nil]
108
124
  # @return [Codec]
109
125
  #
110
- def codec_for(engine, level:, dict:)
111
- @codecs[engine] ||= Codec.new level: level, dict: dict,
126
+ def validate_auto_dict!(auto_dict, dict)
127
+ return unless auto_dict
128
+
129
+ if dict
130
+ raise ArgumentError, "cannot combine auto_dict: and dict:"
131
+ end
132
+
133
+ return if auto_dict == true
134
+
135
+ unless auto_dict.is_a?(Hash)
136
+ raise TypeError, "auto_dict: must be true or a Hash; got #{auto_dict.class}"
137
+ end
138
+
139
+ cap = auto_dict[:capacity]
140
+ if cap && (cap < 1 || cap > Codec::MAX_DICT_SIZE)
141
+ raise ArgumentError,
142
+ "auto_dict capacity #{cap} out of range [1, #{Codec::MAX_DICT_SIZE}]"
143
+ end
144
+ end
145
+
146
+
147
+ def normalize_auto_dict(auto_dict)
148
+ return unless auto_dict
149
+ return { capacity: Codec::DICT_CAPACITY }.freeze if auto_dict == true
150
+
151
+ { capacity: auto_dict[:capacity] || Codec::DICT_CAPACITY }.freeze
152
+ end
153
+
154
+
155
+ def codec_for(engine, level:, dict:, auto_dict:)
156
+ @codecs[engine] ||= Codec.new level: level, dict: dict, auto_dict: auto_dict,
112
157
  max_message_size: engine.options.max_message_size
113
158
 
114
159
  rescue Ractor::IsolationError
115
- Codec.new level: level, dict: dict,
160
+ Codec.new level: level, dict: dict, auto_dict: auto_dict,
116
161
  max_message_size: engine.options.max_message_size
117
162
  end
118
163
  end
@@ -9,7 +9,7 @@
9
9
  # push.connect("zstd+tcp://127.0.0.1:5555", level: -3)
10
10
 
11
11
  require "omq"
12
- require "rzstd"
12
+ require "zrip"
13
13
 
14
14
  require_relative "zstd_tcp/codec"
15
15
  require_relative "zstd_tcp/connection"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OMQ
4
4
  module Zstd
5
- VERSION = "0.4.2"
5
+ VERSION = "0.4.4"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq-zstd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
@@ -15,30 +15,31 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.23'
18
+ version: '0.28'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0.23'
25
+ version: '0.28'
26
26
  - !ruby/object:Gem::Dependency
27
- name: rzstd
27
+ name: zrip
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '0.4'
32
+ version: 0.1.1
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '0.4'
40
- description: Adds zstd+tcp:// endpoint support to OMQ with per-frame Zstd compression,
41
- bounded decompression, in-band dictionary shipping, and sender-side dictionary training.
39
+ version: 0.1.1
40
+ description: Experimental zstd+tcp:// endpoint support for OMQ with per-frame Zstd
41
+ compression, bounded decompression, in-band dictionary shipping, and sender-side
42
+ dictionary training.
42
43
  email:
43
44
  - paddor@gmail.com
44
45
  executables: []
@@ -55,7 +56,7 @@ files:
55
56
  - lib/omq/transport/zstd_tcp/transport.rb
56
57
  - lib/omq/zstd.rb
57
58
  - lib/omq/zstd/version.rb
58
- homepage: https://github.com/paddor/omq-zstd
59
+ homepage: https://github.com/zeromq/omq.rb/tree/main/gems/omq-zstd
59
60
  licenses:
60
61
  - ISC
61
62
  metadata: {}
@@ -66,14 +67,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
66
67
  requirements:
67
68
  - - ">="
68
69
  - !ruby/object:Gem::Version
69
- version: '3.3'
70
+ version: '4.0'
70
71
  required_rubygems_version: !ruby/object:Gem::Requirement
71
72
  requirements:
72
73
  - - ">="
73
74
  - !ruby/object:Gem::Version
74
75
  version: '0'
75
76
  requirements: []
76
- rubygems_version: 4.0.10
77
+ rubygems_version: 4.0.16
77
78
  specification_version: 4
78
- summary: Zstd+TCP transport for OMQ
79
+ summary: Experimental Zstd+TCP transport for OMQ
79
80
  test_files: []