From 24c610c6712eb979bd6425f56e1970126383f1d1 Mon Sep 17 00:00:00 2001 From: James Coglan Date: Fri, 5 Dec 2014 19:32:40 +0000 Subject: [PATCH] Implement ServerSession, passes Autobahn 12.1.*. --- .gitignore | 2 + Gemfile | 2 + README.md | 0 lib/permessage_deflate.rb | 33 +++++++++++ lib/permessage_deflate/server_session.rb | 47 +++++++++++++++ lib/permessage_deflate/session.rb | 75 ++++++++++++++++++++++++ permessage-deflate.gemspec | 17 ++++++ 7 files changed, 176 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 README.md create mode 100644 lib/permessage_deflate.rb create mode 100644 lib/permessage_deflate/server_session.rb create mode 100644 lib/permessage_deflate/session.rb create mode 100644 permessage-deflate.gemspec diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ae6fcf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Gemfile.lock +*.gem diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..851fabc --- /dev/null +++ b/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gemspec diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/lib/permessage_deflate.rb b/lib/permessage_deflate.rb new file mode 100644 index 0000000..f75abb8 --- /dev/null +++ b/lib/permessage_deflate.rb @@ -0,0 +1,33 @@ +require 'zlib' + +class PermessageDeflate + VALID_PARAMS = [ + 'server_no_context_takeover', + 'client_no_context_takeover', + 'server_max_window_bits', + 'client_max_window_bits' + ] + + DEFAULT_MAX_WINDOW_BITS = 15 + VALID_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15] + + root = File.expand_path('..', __FILE__) + require root + '/permessage_deflate/session' + require root + '/permessage_deflate/server_session' + + module Extension + define_method(:name) { 'permessage-deflate' } + define_method(:type) { 'permessage' } + define_method(:rsv1) { true } + define_method(:rsv2) { false } + define_method(:rsv3) { false } + + def create_server_session(offers) + offers.each do |offer| + return ServerSession.new(offer) if ServerSession.valid_params?(offer) + end + end + end + + extend Extension +end diff --git a/lib/permessage_deflate/server_session.rb b/lib/permessage_deflate/server_session.rb new file mode 100644 index 0000000..0146dbd --- /dev/null +++ b/lib/permessage_deflate/server_session.rb @@ -0,0 +1,47 @@ +class PermessageDeflate + class ServerSession < Session + + def self.valid_params?(params) + true # TODO + end + + def initialize(params) + super() + @params = params + end + + def generate_response + params = {} + + # https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.1.1 + if @params['server_no_context_takeover'] + params['server_no_context_takeover'] = true + end + + # https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.1.2 + if @params['client_no_context_takeover'] + params['client_no_context_takeover'] = true + end + + # https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.2.1 + if server_max = @params['server_max_window_bits'] + params['server_max_window_bits'] = [server_max, DEFAULT_MAX_WINDOW_BITS].min + end + + # https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.2.2 + if client_max = @params['client_max_window_bits'] + client_max = DEFAULT_MAX_WINDOW_BITS if client_max == true + params['client_max_window_bits'] = [client_max, DEFAULT_MAX_WINDOW_BITS].min + end + + @own_context_takeover = !params['server_no_context_takeover'] + @own_window_bits = params['server_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS + + @peer_context_takeover = !params['client_no_context_takeover'] + @peer_window_bits = params['client_max_window_bits'] || DEFAULT_MAX_WINDOW_BITS + + params + end + + end +end diff --git a/lib/permessage_deflate/session.rb b/lib/permessage_deflate/session.rb new file mode 100644 index 0000000..248e540 --- /dev/null +++ b/lib/permessage_deflate/session.rb @@ -0,0 +1,75 @@ +class PermessageDeflate + class Session + + MESSAGE_OPCODES = [1, 2] + + def valid_frame_rsv(frame) + if MESSAGE_OPCODES.include?(frame.opcode) + {:rsv1 => true, :rsv2 => false, :rsv3 => false} + else + {:rsv1 => false, :rsv2 => false, :rsv3 => false} + end + end + + def process_incoming_message(message) + compressed = message.frames.first.rsv1 + return message unless compressed + + inflate = get_inflate + + message.data = inflate.inflate(message.data) + + inflate.inflate([0x00, 0x00, 0xff, 0xff].pack('C*')) + + inflate.close unless @inflate + message + end + + def process_outgoing_message(message) + deflate = get_deflate + payload = (deflate.deflate(message.data) + deflate.flush)[0...-4] + frame = message.frames.first + + deflate.close unless @deflate + + frame.final = true + frame.rsv1 = true + frame.length = payload.bytesize + frame.payload = payload + + message.data = payload + message.frames = [frame] + + message + end + + def close + @inflate.close if @inflate + @inflate = nil + + @deflate.close if @deflate + @deflate = nil + end + + private + + def f(string) + bytes = string.bytes.map { |b| b.to_s(16).rjust(2, '0') } + "<#{string.encoding}: #{bytes * ' '}>" + end + + def get_inflate + return @inflate if @inflate + inflate = Zlib::Inflate.new(-@peer_window_bits) + @inflate = inflate if @peer_context_takeover + inflate + end + + def get_deflate + return @deflate if @deflate + deflate = Zlib::Deflate.new(Zlib::DEFAULT_COMPRESSION, -@own_window_bits) + @deflate = deflate if @own_context_takeover + deflate + end + + end +end diff --git a/permessage-deflate.gemspec b/permessage-deflate.gemspec new file mode 100644 index 0000000..33e280f --- /dev/null +++ b/permessage-deflate.gemspec @@ -0,0 +1,17 @@ +Gem::Specification.new do |s| + s.name = 'permessage-deflate' + s.version = '0.1.0' + s.summary = 'Per-message DEFLATE compression extension for WebSocket connections' + s.author = 'James Coglan' + s.email = 'jcoglan@gmail.com' + s.homepage = 'http://github.com/faye/permessage-deflate-ruby' + s.license = 'MIT' + + s.extra_rdoc_files = %w[README.md] + s.rdoc_options = %w[--main README.md --markup markdown] + s.require_paths = %w[lib] + + s.files = %w[README.md] + Dir.glob('lib/**/*.rb') + + s.add_development_dependency 'rspec' +end