182 lines
4.7 KiB
Ruby
182 lines
4.7 KiB
Ruby
module WebSocket
|
|
class Extensions
|
|
|
|
autoload :Parser, File.expand_path('../extensions/parser', __FILE__)
|
|
|
|
ExtensionError = Class.new(ArgumentError)
|
|
|
|
MESSAGE_OPCODES = [1, 2]
|
|
|
|
def initialize
|
|
@rsv1 = @rsv2 = @rsv3 = nil
|
|
|
|
@by_name = {}
|
|
@in_order = []
|
|
@sessions = []
|
|
@index = {}
|
|
end
|
|
|
|
def add(ext)
|
|
unless ext.respond_to?(:name) and ext.name.is_a?(String)
|
|
raise TypeError, 'extension.name must be a string'
|
|
end
|
|
|
|
unless ext.respond_to?(:type) and ext.type == 'permessage'
|
|
raise TypeError, 'extension.type must be "permessage"'
|
|
end
|
|
|
|
unless ext.respond_to?(:rsv1) and [true, false].include?(ext.rsv1)
|
|
raise TypeError, 'extension.rsv1 must be true or false'
|
|
end
|
|
|
|
unless ext.respond_to?(:rsv2) and [true, false].include?(ext.rsv2)
|
|
raise TypeError, 'extension.rsv2 must be true or false'
|
|
end
|
|
|
|
unless ext.respond_to?(:rsv3) and [true, false].include?(ext.rsv3)
|
|
raise TypeError, 'extension.rsv3 must be true or false'
|
|
end
|
|
|
|
if @by_name.has_key?(ext.name)
|
|
raise TypeError, %Q{An extension with name "#{ ext.name }" is already registered}
|
|
end
|
|
|
|
@by_name[ext.name] = ext
|
|
@in_order.push(ext)
|
|
end
|
|
|
|
def generate_offer
|
|
sessions = []
|
|
offer = []
|
|
index = {}
|
|
|
|
@in_order.each do |ext|
|
|
session = ext.create_client_session
|
|
next unless session
|
|
|
|
record = [ext, session]
|
|
sessions.push(record)
|
|
index[ext.name] = record
|
|
|
|
offers = session.generate_offer
|
|
offers = offers ? [offers].flatten : []
|
|
|
|
offers.each do |off|
|
|
offer.push(Parser.serialize_params(ext.name, off))
|
|
end
|
|
end
|
|
|
|
@sessions = sessions
|
|
@index = index
|
|
|
|
offer.size > 0 ? offer.join(', ') : nil
|
|
end
|
|
|
|
def activate(header)
|
|
responses = Parser.parse_header(header)
|
|
@sessions = []
|
|
|
|
responses.each_offer do |name, params|
|
|
unless record = @index[name]
|
|
raise ExtensionError, %Q{Server sent am extension response for unknown extension "#{ name } }
|
|
end
|
|
|
|
ext, session = *record
|
|
|
|
if reserved = reserved?(ext)
|
|
raise ExtensionError, %Q{Server sent two extension responses that use the RSV#{ reserved[0] }} +
|
|
%Q{bit: "#{ reserved[1] }" and "#{ ext.name }"}
|
|
end
|
|
|
|
unless session.activate(params) == true
|
|
raise ExtensionError, %Q{Server send unacceptable extension parameters: #{ Parser.serialize_params(name, params) }}
|
|
end
|
|
|
|
reserve(ext)
|
|
@sessions.push(record)
|
|
end
|
|
end
|
|
|
|
def generate_response(header)
|
|
sessions = []
|
|
response = []
|
|
offers = Parser.parse_header(header)
|
|
|
|
@in_order.each do |ext|
|
|
offer = offers.by_name(ext.name)
|
|
next if offer.empty? or reserved?(ext)
|
|
|
|
next unless session = ext.create_server_session(offer)
|
|
|
|
reserve(ext)
|
|
sessions.push([ext, session])
|
|
response.push(Parser.serialize_params(ext.name, session.generate_response))
|
|
end
|
|
|
|
@sessions = sessions
|
|
response.size > 0 ? response.join(', ') : nil
|
|
end
|
|
|
|
def valid_frame_rsv(frame)
|
|
allowed = { :rsv1 => false, :rsv2 => false, :rsv3 => false }
|
|
|
|
if MESSAGE_OPCODES.include?(frame.opcode)
|
|
@sessions.each do |ext, session|
|
|
allowed[:rsv1] ||= ext.rsv1
|
|
allowed[:rsv2] ||= ext.rsv2
|
|
allowed[:rsv3] ||= ext.rsv3
|
|
end
|
|
end
|
|
|
|
(allowed[:rsv1] || !frame.rsv1) &&
|
|
(allowed[:rsv2] || !frame.rsv2) &&
|
|
(allowed[:rsv3] || !frame.rsv3)
|
|
end
|
|
alias :valid_frame_rsv? :valid_frame_rsv
|
|
|
|
def process_incoming_message(message)
|
|
@sessions.reverse.inject(message) do |msg, (ext, session)|
|
|
begin
|
|
session.process_incoming_message(msg)
|
|
rescue => error
|
|
raise ExtensionError, [ext.name, error.message].join(': ')
|
|
end
|
|
end
|
|
end
|
|
|
|
def process_outgoing_message(message)
|
|
@sessions.inject(message) do |msg, (ext, session)|
|
|
begin
|
|
session.process_outgoing_message(msg)
|
|
rescue => error
|
|
raise ExtensionError, [ext.name, error.message].join(': ')
|
|
end
|
|
end
|
|
end
|
|
|
|
def close
|
|
return unless @sessions
|
|
|
|
@sessions.each do |ext, session|
|
|
session.close rescue nil
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def reserve(ext)
|
|
@rsv1 ||= ext.rsv1 && ext.name
|
|
@rsv2 ||= ext.rsv2 && ext.name
|
|
@rsv3 ||= ext.rsv3 && ext.name
|
|
end
|
|
|
|
def reserved?(ext)
|
|
return [1, @rsv1] if @rsv1 and ext.rsv1
|
|
return [2, @rsv2] if @rsv2 and ext.rsv2
|
|
return [3, @rsv3] if @rsv3 and ext.rsv3
|
|
false
|
|
end
|
|
|
|
end
|
|
end
|