permessage_deflate 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5a45dd8e4933a9a41365043f0b770ad2f9df343c
4
+ data.tar.gz: 59b4bbba3b0fc00a970604c1a307bf8b80fc034a
5
+ SHA512:
6
+ metadata.gz: b1c220ecbfe1f83d19c6a59d6045478bc827cb36f72e1f97ad914dc04002f188239bba1adde415f65cb85911a7c423e545e12d6ff1230d57e7bc62626c871af0
7
+ data.tar.gz: ae327691407e387275f0f0e8a5ce54817108a9f4b4dc2fbd9245f23514873ac3585e380ccfe04b38f3a64a5c93d9d8b6a828f87e06550b759cf2ba8f2a29e8f5
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # permessage_deflate [![Build status](https://secure.travis-ci.org/faye/permessage-deflate-ruby.svg)](http://travis-ci.org/faye/permessage-deflate-ruby)
2
+
3
+ Implements the
4
+ [permessage-deflate](https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression)
5
+ WebSocket protocol extension as a plugin for
6
+ [websocket-extensions](https://github.com/faye/websocket-extensions-ruby).
7
+
8
+ ## Installation
9
+
10
+ ```
11
+ $ gem install permessage_deflate
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ Add the plugin to your extensions:
17
+
18
+ ```rb
19
+ require 'websocket/extensions'
20
+ require 'permessage_deflate'
21
+
22
+ exts = WebSocket::Extensions.new
23
+ exts.add(PermessageDeflate)
24
+ ```
25
+
26
+ The extension can be configured, for example:
27
+
28
+ ```rb
29
+ require 'websocket/extensions'
30
+ require 'permessage_deflate'
31
+
32
+ deflate = PermessageDeflate.configure(
33
+ :level => Zlib::BEST_COMPRESSION,
34
+ :max_window_bits => 13
35
+ )
36
+
37
+ exts = WebSocket::Extensions.new
38
+ exts.add(deflate)
39
+ ```
40
+
41
+ The set of available options can be split into two sets: those that control the
42
+ session's compressor for outgoing messages and do not need to be communicated to
43
+ the peer, and those that are negoatiated as part of the protocol. The settings
44
+ only affecting the compressor are described fully in the [Zlib
45
+ documentation](http://ruby-doc.org/stdlib-2.1.0/libdoc/zlib/rdoc/Zlib/Deflate.html#method-c-new):
46
+
47
+ * `:level`: sets the compression level, can be an integer from `0` to `9`, or
48
+ one of the contants `Zlib::NO_COMPRESSION`, `Zlib::BEST_SPEED`,
49
+ `Zlib::BEST_COMPRESSION`, or `Zlib::DEFAULT_COMPRESSION`
50
+ * `:mem_level`: sets how much memory the compressor allocates, can be an integer
51
+ from `1` to `9`, or one of the constants `Zlib::MAX_MEM_LEVEL`, or
52
+ `Zlib::DEF_MEM_LEVEL`
53
+ * `:strategy`: can be one of the constants `Zlib::FILTERED`,
54
+ `Zlib::HUFFMAN_ONLY`, `Zlib::RLE`, `Zlib::FIXED`, or `Zlib::DEFAULT_STRATEGY`
55
+
56
+ The other options relate to settings that are negotiated via the protocol and
57
+ can be used to set the local session's behaviour and control that of the peer:
58
+
59
+ * `:no_context_takeover`: if `true`, stops the session reusing a deflate context
60
+ between messages
61
+ * `:request_no_context_takeover`: if `true`, makes the session tell the other
62
+ peer not to reuse a deflate context between messages
63
+ * `:max_window_bits`: an integer from `8` to `15` inclusive that sets the
64
+ maximum size of the session's sliding window; a lower window size will be used
65
+ if requested by the peer
66
+ * `:request_max_window_bits`: an integer from `8` to `15` inclusive to ask the
67
+ other peer to use to set its maximum sliding window size, if supported
68
+
69
+ ## License
70
+
71
+ (The MIT License)
72
+
73
+ Copyright (c) 2014 James Coglan
74
+
75
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
76
+ this software and associated documentation files (the 'Software'), to deal in
77
+ the Software without restriction, including without limitation the rights to
78
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
79
+ of the Software, and to permit persons to whom the Software is furnished to do
80
+ so, subject to the following conditions:
81
+
82
+ The above copyright notice and this permission notice shall be included in all
83
+ copies or substantial portions of the Software.
84
+
85
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
86
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
87
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
88
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
89
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
90
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
91
+ SOFTWARE.
@@ -0,0 +1,41 @@
1
+ require 'zlib'
2
+
3
+ class PermessageDeflate
4
+ root = File.expand_path('..', __FILE__)
5
+ require root + '/permessage_deflate/session'
6
+ require root + '/permessage_deflate/client_session'
7
+ require root + '/permessage_deflate/server_session'
8
+
9
+ ConfigurationError = Class.new(ArgumentError)
10
+
11
+ module Extension
12
+ define_method(:name) { 'permessage-deflate' }
13
+ define_method(:type) { 'permessage' }
14
+ define_method(:rsv1) { true }
15
+ define_method(:rsv2) { false }
16
+ define_method(:rsv3) { false }
17
+
18
+ def configure(options)
19
+ options = (@options || {}).merge(options)
20
+ PermessageDeflate.new(options)
21
+ end
22
+
23
+ def create_client_session
24
+ ClientSession.new(@options || {})
25
+ end
26
+
27
+ def create_server_session(offers)
28
+ offers.each do |offer|
29
+ return ServerSession.new(@options || {}, offer) if ServerSession.valid_params?(offer)
30
+ end
31
+ nil
32
+ end
33
+ end
34
+
35
+ include Extension
36
+ extend Extension
37
+
38
+ def initialize(options)
39
+ @options = options
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ class PermessageDeflate
2
+ class ClientSession < Session
3
+
4
+ def self.valid_params?(params)
5
+ return false unless super
6
+
7
+ if params.has_key?('client_max_window_bits')
8
+ return false unless VALID_WINDOW_BITS.include?(params['client_max_window_bits'])
9
+ end
10
+
11
+ true
12
+ end
13
+
14
+ def generate_offer
15
+ offer = {}
16
+
17
+ if @accept_no_context_takeover
18
+ offer['client_no_context_takeover'] = true
19
+ end
20
+
21
+ if @accept_max_window_bits
22
+ unless VALID_WINDOW_BITS.include?(@accept_max_window_bits)
23
+ raise ConfigurationError, 'Invalid value for max_window_bits'
24
+ end
25
+ offer['client_max_window_bits'] = @accept_max_window_bits
26
+ else
27
+ offer['client_max_window_bits'] = true
28
+ end
29
+
30
+ if @request_no_context_takeover
31
+ offer['server_no_context_takeover'] = true
32
+ end
33
+
34
+ if @request_max_window_bits
35
+ unless VALID_WINDOW_BITS.include?(@request_max_window_bits)
36
+ raise ConfigurationError, 'Invalid value for request_max_window_bits'
37
+ end
38
+ offer['server_max_window_bits'] = @request_max_window_bits
39
+ end
40
+
41
+ offer
42
+ end
43
+
44
+ def activate(params)
45
+ return false unless ClientSession.valid_params?(params)
46
+
47
+ if @accept_max_window_bits and params['client_max_window_bits']
48
+ return false if params['client_max_window_bits'] > @accept_max_window_bits
49
+ end
50
+
51
+ if @request_no_context_takeover and !params['server_no_context_takeover']
52
+ return false
53
+ end
54
+
55
+ if @request_max_window_bits
56
+ return false unless params['server_max_window_bits']
57
+ return false if params['server_max_window_bits'] > @request_max_window_bits
58
+ end
59
+
60
+ @own_context_takeover = !(@accept_no_context_takeover || params['client_no_context_takeover'])
61
+ @own_window_bits = [
62
+ @accept_max_window_bits || DEFAULT_MAX_WINDOW_BITS,
63
+ params['client_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS
64
+ ].min
65
+
66
+ @peer_context_takeover = !params['server_no_context_takeover']
67
+ @peer_window_bits = params['server_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS
68
+
69
+ true
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,59 @@
1
+ class PermessageDeflate
2
+ class ServerSession < Session
3
+
4
+ def self.valid_params?(params)
5
+ return false unless super
6
+
7
+ if params.has_key?('client_max_window_bits')
8
+ return false unless ([true] + VALID_WINDOW_BITS).include?(params['client_max_window_bits'])
9
+ end
10
+
11
+ true
12
+ end
13
+
14
+ def initialize(options, params)
15
+ super(options)
16
+ @params = params
17
+ end
18
+
19
+ def generate_response
20
+ params = {}
21
+
22
+ # https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.1.1
23
+ if @accept_no_context_takeover or @params['server_no_context_takeover']
24
+ params['server_no_context_takeover'] = true
25
+ end
26
+
27
+ # https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.1.2
28
+ if @request_no_context_takeover or @params['client_no_context_takeover']
29
+ params['client_no_context_takeover'] = true
30
+ end
31
+
32
+ # https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.2.1
33
+ if @accept_max_window_bits or @params['server_max_window_bits']
34
+ accept_max = @accept_max_window_bits || DEFAULT_MAX_WINDOW_BITS
35
+ server_max = @params['server_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS
36
+ params['server_max_window_bits'] = [accept_max, server_max].min
37
+ end
38
+
39
+ # https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.2.2
40
+ if client_max = @params['client_max_window_bits']
41
+ if client_max == true
42
+ params['client_max_window_bits'] = @request_max_window_bits if @request_max_window_bits
43
+ else
44
+ request_max = @request_max_window_bits || DEFAULT_MAX_WINDOW_BITS
45
+ params['client_max_window_bits'] = [request_max, client_max].min
46
+ end
47
+ end
48
+
49
+ @own_context_takeover = !params['server_no_context_takeover']
50
+ @own_window_bits = params['server_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS
51
+
52
+ @peer_context_takeover = !params['client_no_context_takeover']
53
+ @peer_window_bits = params['client_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS
54
+
55
+ params
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,97 @@
1
+ class PermessageDeflate
2
+ class Session
3
+
4
+ VALID_PARAMS = [
5
+ 'server_no_context_takeover',
6
+ 'client_no_context_takeover',
7
+ 'server_max_window_bits',
8
+ 'client_max_window_bits'
9
+ ]
10
+
11
+ DEFAULT_MAX_WINDOW_BITS = 15
12
+ VALID_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]
13
+
14
+ def self.valid_params?(params)
15
+ return false unless params.keys.all? { |k| VALID_PARAMS.include?(k) }
16
+ return false if params.values.grep(Array).any?
17
+
18
+ if params.has_key?('server_no_context_takeover')
19
+ return false unless params['server_no_context_takeover'] == true
20
+ end
21
+
22
+ if params.has_key?('client_no_context_takeover')
23
+ return false unless params['client_no_context_takeover'] == true
24
+ end
25
+
26
+ if params.has_key?('server_max_window_bits')
27
+ return false unless VALID_WINDOW_BITS.include?(params['server_max_window_bits'])
28
+ end
29
+
30
+ true
31
+ end
32
+
33
+ def initialize(options)
34
+ @level = options.fetch(:level, Zlib::DEFAULT_COMPRESSION)
35
+ @mem_level = options.fetch(:mem_level, Zlib::DEF_MEM_LEVEL)
36
+ @strategy = options.fetch(:strategy, Zlib::DEFAULT_STRATEGY)
37
+
38
+ @accept_no_context_takeover = options.fetch(:no_context_takeover, false)
39
+ @accept_max_window_bits = options.fetch(:max_window_bits, nil)
40
+ @request_no_context_takeover = options.fetch(:request_no_context_takeover, false)
41
+ @request_max_window_bits = options.fetch(:request_max_window_bits, nil)
42
+ end
43
+
44
+ def process_incoming_message(message)
45
+ return message unless message.rsv1
46
+
47
+ inflate = get_inflate
48
+
49
+ message.data = inflate.inflate(message.data) +
50
+ inflate.inflate([0x00, 0x00, 0xff, 0xff].pack('C*'))
51
+
52
+ free(inflate) unless @inflate
53
+ message
54
+ end
55
+
56
+ def process_outgoing_message(message)
57
+ deflate = get_deflate
58
+
59
+ message.data = deflate.deflate(message.data, Zlib::SYNC_FLUSH)[0...-4]
60
+ message.rsv1 = true
61
+
62
+ free(deflate) unless @deflate
63
+ message
64
+ end
65
+
66
+ def close
67
+ free(@inflate)
68
+ @inflate = nil
69
+
70
+ free(@deflate)
71
+ @deflate = nil
72
+ end
73
+
74
+ private
75
+
76
+ def free(codec)
77
+ return if codec.nil?
78
+ codec.finish rescue nil
79
+ codec.close
80
+ end
81
+
82
+ def get_inflate
83
+ return @inflate if @inflate
84
+ inflate = Zlib::Inflate.new(-@peer_window_bits)
85
+ @inflate = inflate if @peer_context_takeover
86
+ inflate
87
+ end
88
+
89
+ def get_deflate
90
+ return @deflate if @deflate
91
+ deflate = Zlib::Deflate.new(@level, -@own_window_bits, @mem_level, @strategy)
92
+ @deflate = deflate if @own_context_takeover
93
+ deflate
94
+ end
95
+
96
+ end
97
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: permessage_deflate
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - James Coglan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description:
28
+ email: jcoglan@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files:
32
+ - README.md
33
+ files:
34
+ - README.md
35
+ - lib/permessage_deflate.rb
36
+ - lib/permessage_deflate/client_session.rb
37
+ - lib/permessage_deflate/server_session.rb
38
+ - lib/permessage_deflate/session.rb
39
+ homepage: http://github.com/faye/permessage-deflate-ruby
40
+ licenses:
41
+ - MIT
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options:
45
+ - "--main"
46
+ - README.md
47
+ - "--markup"
48
+ - markdown
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.2.2
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Per-message DEFLATE compression extension for WebSocket connections
67
+ test_files: []