websocket 1.2.2 → 1.2.3

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +37 -0
  3. data/.travis.yml +2 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +1 -1
  6. data/Rakefile +3 -3
  7. data/lib/websocket.rb +2 -3
  8. data/lib/websocket/error.rb +57 -25
  9. data/lib/websocket/exception_handler.rb +2 -14
  10. data/lib/websocket/frame.rb +0 -2
  11. data/lib/websocket/frame/base.rb +12 -13
  12. data/lib/websocket/frame/data.rb +1 -3
  13. data/lib/websocket/frame/handler.rb +0 -2
  14. data/lib/websocket/frame/handler/base.rb +2 -4
  15. data/lib/websocket/frame/handler/handler03.rb +149 -112
  16. data/lib/websocket/frame/handler/handler04.rb +3 -3
  17. data/lib/websocket/frame/handler/handler05.rb +3 -3
  18. data/lib/websocket/frame/handler/handler07.rb +8 -10
  19. data/lib/websocket/frame/handler/handler75.rb +10 -12
  20. data/lib/websocket/frame/incoming.rb +0 -2
  21. data/lib/websocket/frame/incoming/client.rb +0 -2
  22. data/lib/websocket/frame/incoming/server.rb +0 -2
  23. data/lib/websocket/frame/outgoing.rb +1 -3
  24. data/lib/websocket/frame/outgoing/client.rb +0 -2
  25. data/lib/websocket/frame/outgoing/server.rb +0 -2
  26. data/lib/websocket/handshake.rb +0 -2
  27. data/lib/websocket/handshake/base.rb +10 -9
  28. data/lib/websocket/handshake/client.rb +22 -35
  29. data/lib/websocket/handshake/handler.rb +0 -2
  30. data/lib/websocket/handshake/handler/base.rb +0 -2
  31. data/lib/websocket/handshake/handler/client.rb +0 -2
  32. data/lib/websocket/handshake/handler/client01.rb +0 -2
  33. data/lib/websocket/handshake/handler/client04.rb +3 -5
  34. data/lib/websocket/handshake/handler/client11.rb +0 -2
  35. data/lib/websocket/handshake/handler/client75.rb +2 -4
  36. data/lib/websocket/handshake/handler/client76.rb +6 -8
  37. data/lib/websocket/handshake/handler/server.rb +0 -1
  38. data/lib/websocket/handshake/handler/server04.rb +3 -5
  39. data/lib/websocket/handshake/handler/server75.rb +2 -4
  40. data/lib/websocket/handshake/handler/server76.rb +7 -9
  41. data/lib/websocket/handshake/server.rb +19 -24
  42. data/lib/websocket/version.rb +1 -1
  43. data/spec/frame/incoming_03_spec.rb +1 -2
  44. data/spec/frame/incoming_04_spec.rb +1 -2
  45. data/spec/frame/incoming_05_spec.rb +1 -2
  46. data/spec/frame/incoming_07_spec.rb +1 -2
  47. data/spec/frame/incoming_75_spec.rb +1 -2
  48. data/spec/support/all_client_drafts.rb +1 -1
  49. data/spec/support/all_server_drafts.rb +7 -10
  50. data/spec/support/frames_base.rb +0 -2
  51. data/spec/support/handshake_requests.rb +4 -4
  52. data/spec/support/outgoing_frames.rb +0 -1
  53. data/spec/support/overwrites.rb +0 -2
  54. data/websocket.gemspec +10 -10
  55. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ca690c7a42466b8cb035271b77010bdc7bf8a19
4
- data.tar.gz: 206ca9afa7b5606cb08b85ef66a8f186342d37b3
3
+ metadata.gz: 48ee0f12aaa5fd6aba76f7c2070a09f600c53204
4
+ data.tar.gz: d011719667dc6227eecf743a764d0f24f7bf17b2
5
5
  SHA512:
6
- metadata.gz: 6b846fa2663e5f85415adc35e2aa39e0e7b21f844b444546c432a6b630c79d4aa4aabf5dbaf996c6334b80e1296b81a35a0935a51a9d0cfa55d6a748fb1762b7
7
- data.tar.gz: cbf090a638401a094b7432f7a634ba7bc2f8fcd77b06e0407a8d2490f3236858368d619a6acf17296a715824c239b1c51beb5577597af91133e6c6472338c659
6
+ metadata.gz: 568b9cd4986500c8c9ddf5ec2d8f90b81b654fd6b10451480209ca505be73e0d8a24b11b10040aa7bd3211c84bf6c25154482655985a117705657e75eb302e0f
7
+ data.tar.gz: 3c3c8d8c324b480654891f7326f1cf7153dbf3f7b4838c1b3ad2bbe670ba25c57dc2485092b049f5d5fd35b1f131a63882a46b989f684fc40a3925fc3d2024bb
@@ -0,0 +1,37 @@
1
+ # Target: 15
2
+ Metrics/AbcSize:
3
+ Max: 24
4
+ Exclude:
5
+ - lib/websocket/frame/handler/handler75.rb
6
+ - spec/**/*
7
+
8
+ Metrics/ClassLength:
9
+ Enabled: false
10
+
11
+ # Target: 6
12
+ Metrics/CyclomaticComplexity:
13
+ Max: 11
14
+ Exclude:
15
+ - spec/support/handshake_requests.rb
16
+
17
+ Metrics/LineLength:
18
+ Enabled: false
19
+
20
+ # Target: 10
21
+ Metrics/MethodLength:
22
+ Max: 18
23
+ Exclude:
24
+ - lib/websocket/frame/handler/handler75.rb
25
+
26
+ # Target: 7
27
+ Metrics/PerceivedComplexity:
28
+ Max: 8
29
+ Exclude:
30
+ - lib/websocket/frame/handler/handler75.rb
31
+ - spec/support/handshake_requests.rb
32
+
33
+ Style/Documentation:
34
+ Enabled: false
35
+
36
+ Style/TrivialAccessors:
37
+ AllowPredicates: true
@@ -8,3 +8,5 @@ rvm:
8
8
  - rbx-2
9
9
  - jruby-19mode
10
10
  - ruby-head
11
+ before_install:
12
+ - gem install bundler
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.3
4
+
5
+ - fix for draft 76 when challenge might sometimes fail
6
+ - multiple small optimizations
7
+
3
8
  ## 1.2.2
4
9
 
5
10
  - fix handshake for draft 11+ sending Sec-WebSocket-Origin instead of Origin
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source 'http://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
data/Rakefile CHANGED
@@ -4,19 +4,19 @@ Bundler::GemHelper.install_tasks
4
4
  require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new do |t|
7
- t.rspec_opts = ["-c", "-f progress"]
7
+ t.rspec_opts = ['-c', '-f progress']
8
8
  t.pattern = 'spec/**/*_spec.rb'
9
9
  end
10
10
 
11
11
  task default: :spec
12
12
 
13
13
  namespace :autobahn do
14
- desc "Run autobahn tests for client"
14
+ desc 'Run autobahn tests for client'
15
15
  task :client do
16
16
  system('wstest --mode=fuzzingserver --spec=autobahn-client.json')
17
17
  end
18
18
 
19
- desc "Run autobahn tests for server"
19
+ desc 'Run autobahn tests for server'
20
20
  task :server do
21
21
  system('wstest --mode=fuzzingclient --spec=autobahn-server.json')
22
22
  end
@@ -4,7 +4,6 @@
4
4
  # @author Bernard "Imanel" Potocki
5
5
  # @see http://github.com/imanel/websocket-ruby main repository
6
6
  module WebSocket
7
-
8
7
  # Default WebSocket version to use
9
8
  DEFAULT_VERSION = 13
10
9
  ROOT = File.expand_path(File.dirname(__FILE__))
@@ -34,11 +33,11 @@ module WebSocket
34
33
  def self.should_raise=(val)
35
34
  @should_raise = val
36
35
  end
37
-
38
36
  end
39
37
 
40
38
  # Try loading websocket-native if available
41
39
  begin
42
40
  require 'websocket-native'
43
- rescue LoadError
41
+ rescue LoadError => e
42
+ raise unless e.message =~ /websocket-native/
44
43
  end
@@ -1,89 +1,121 @@
1
1
  module WebSocket
2
2
  class Error < RuntimeError
3
-
4
3
  class Frame < ::WebSocket::Error
5
-
6
4
  class ControlFramePayloadTooLong < ::WebSocket::Error::Frame
7
- def message; :control_frame_payload_too_long; end
5
+ def message
6
+ :control_frame_payload_too_long
7
+ end
8
8
  end
9
9
 
10
10
  class DataFrameInsteadContinuation < ::WebSocket::Error::Frame
11
- def message; :data_frame_instead_continuation; end
11
+ def message
12
+ :data_frame_instead_continuation
13
+ end
12
14
  end
13
15
 
14
16
  class FragmentedControlFrame < ::WebSocket::Error::Frame
15
- def message; :fragmented_control_frame; end
17
+ def message
18
+ :fragmented_control_frame
19
+ end
16
20
  end
17
21
 
18
22
  class Invalid < ::WebSocket::Error::Frame
19
- def message; :invalid_frame; end
23
+ def message
24
+ :invalid_frame
25
+ end
20
26
  end
21
27
 
22
28
  class InvalidPayloadEncoding < ::WebSocket::Error::Frame
23
- def message; :invalid_payload_encoding; end
29
+ def message
30
+ :invalid_payload_encoding
31
+ end
24
32
  end
25
33
 
26
34
  class MaskTooShort < ::WebSocket::Error::Frame
27
- def message; :mask_is_too_short; end
35
+ def message
36
+ :mask_is_too_short
37
+ end
28
38
  end
29
39
 
30
40
  class ReservedBitUsed < ::WebSocket::Error::Frame
31
- def message; :reserved_bit_used; end
41
+ def message
42
+ :reserved_bit_used
43
+ end
32
44
  end
33
45
 
34
46
  class TooLong < ::WebSocket::Error::Frame
35
- def message; :frame_too_long; end
47
+ def message
48
+ :frame_too_long
49
+ end
36
50
  end
37
51
 
38
52
  class UnexpectedContinuationFrame < ::WebSocket::Error::Frame
39
- def message; :unexpected_continuation_frame; end
53
+ def message
54
+ :unexpected_continuation_frame
55
+ end
40
56
  end
41
57
 
42
58
  class UnknownFrameType < ::WebSocket::Error::Frame
43
- def message; :unknown_frame_type; end
59
+ def message
60
+ :unknown_frame_type
61
+ end
44
62
  end
45
63
 
46
64
  class UnknownOpcode < ::WebSocket::Error::Frame
47
- def message; :unknown_opcode; end
65
+ def message
66
+ :unknown_opcode
67
+ end
48
68
  end
49
69
 
50
70
  class UnknownCloseCode < ::WebSocket::Error::Frame
51
- def message; :unknown_close_code; end
71
+ def message
72
+ :unknown_close_code
73
+ end
52
74
  end
53
75
 
54
76
  class UnknownVersion < ::WebSocket::Error::Frame
55
- def message; :unknown_protocol_version; end
77
+ def message
78
+ :unknown_protocol_version
79
+ end
56
80
  end
57
-
58
81
  end
59
82
 
60
83
  class Handshake < ::WebSocket::Error
61
-
62
84
  class GetRequestRequired < ::WebSocket::Error::Handshake
63
- def message; :get_request_required; end
85
+ def message
86
+ :get_request_required
87
+ end
64
88
  end
65
89
 
66
90
  class InvalidAuthentication < ::WebSocket::Error::Handshake
67
- def message; :invalid_handshake_authentication; end
91
+ def message
92
+ :invalid_handshake_authentication
93
+ end
68
94
  end
69
95
 
70
96
  class InvalidHeader < ::WebSocket::Error::Handshake
71
- def message; :invalid_header; end
97
+ def message
98
+ :invalid_header
99
+ end
72
100
  end
73
101
 
74
102
  class InvalidStatusCode < ::WebSocket::Error::Handshake
75
- def message; :invalid_status_code; end
103
+ def message
104
+ :invalid_status_code
105
+ end
76
106
  end
77
107
 
78
108
  class NoHostProvided < ::WebSocket::Error::Handshake
79
- def message; :no_host_provided; end
109
+ def message
110
+ :no_host_provided
111
+ end
80
112
  end
81
113
 
82
114
  class UnknownVersion < ::WebSocket::Error::Handshake
83
- def message; :unknown_protocol_version; end
115
+ def message
116
+ :unknown_protocol_version
117
+ end
84
118
  end
85
-
86
119
  end
87
-
88
120
  end
89
121
  end
@@ -1,22 +1,12 @@
1
1
  module WebSocket
2
2
  module ExceptionHandler
3
-
4
- attr_reader :error
3
+ attr_accessor :error
5
4
 
6
5
  def self.included(base)
7
6
  base.extend(ClassMethods)
8
7
  end
9
8
 
10
- private
11
-
12
- # Changes state to error and sets error message
13
- # @param [String] message Error message to set
14
- def set_error(message)
15
- @error = message
16
- end
17
-
18
9
  module ClassMethods
19
-
20
10
  # Rescue from WebSocket::Error errors.
21
11
  #
22
12
  # @param [String] method_name Name of method that should be wrapped and rescued
@@ -28,15 +18,13 @@ module WebSocket
28
18
  begin
29
19
  send("#{method_name}_without_rescue", *args)
30
20
  rescue WebSocket::Error => e
31
- set_error(e.message.to_sym)
21
+ self.error = e.message.to_sym
32
22
  WebSocket.should_raise ? raise : options[:return]
33
23
  end
34
24
  end
35
25
  alias_method "#{method_name}_without_rescue", method_name
36
26
  alias_method method_name, "#{method_name}_with_rescue"
37
27
  end
38
-
39
28
  end
40
-
41
29
  end
42
30
  end
@@ -1,11 +1,9 @@
1
1
  module WebSocket
2
2
  module Frame
3
-
4
3
  autoload :Base, "#{::WebSocket::ROOT}/websocket/frame/base"
5
4
  autoload :Data, "#{::WebSocket::ROOT}/websocket/frame/data"
6
5
  autoload :Handler, "#{::WebSocket::ROOT}/websocket/frame/handler"
7
6
  autoload :Incoming, "#{::WebSocket::ROOT}/websocket/frame/incoming"
8
7
  autoload :Outgoing, "#{::WebSocket::ROOT}/websocket/frame/outgoing"
9
-
10
8
  end
11
9
  end
@@ -26,7 +26,7 @@ module WebSocket
26
26
  # Check if some errors occured
27
27
  # @return [Boolean] True if error is set
28
28
  def error?
29
- !!@error
29
+ !@error.nil?
30
30
  end
31
31
 
32
32
  # Is selected type supported for selected handler?
@@ -36,13 +36,13 @@ module WebSocket
36
36
 
37
37
  # Implement in submodules
38
38
  def supported_frames
39
- raise NotImplementedError
39
+ fail NotImplementedError
40
40
  end
41
41
 
42
42
  # Recreate inspect as #to_s was overwritten
43
43
  def inspect
44
- vars = self.instance_variables.map { |v| "#{v}=#{instance_variable_get(v).inspect}" }.join(', ')
45
- insp = "#{self.class}:0x%08x" % (self.__id__ * 2)
44
+ vars = instance_variables.map { |v| "#{v}=#{instance_variable_get(v).inspect}" }.join(', ')
45
+ insp = Kernel.format("#{self.class}:0x%08x", __id__)
46
46
  "<#{insp} #{vars}>"
47
47
  end
48
48
 
@@ -52,16 +52,15 @@ module WebSocket
52
52
  # @return [Boolean] false if protocol number is unknown, otherwise true
53
53
  def include_version
54
54
  @handler = case @version
55
- when 75..76 then Handler::Handler75.new(self)
56
- when 0..2 then Handler::Handler75.new(self)
57
- when 3 then Handler::Handler03.new(self)
58
- when 4 then Handler::Handler04.new(self)
59
- when 5..6 then Handler::Handler05.new(self)
60
- when 7..13 then Handler::Handler07.new(self)
61
- else raise WebSocket::Error::Frame::UnknownVersion
62
- end
55
+ when 75..76 then Handler::Handler75.new(self)
56
+ when 0..2 then Handler::Handler75.new(self)
57
+ when 3 then Handler::Handler03.new(self)
58
+ when 4 then Handler::Handler04.new(self)
59
+ when 5..6 then Handler::Handler05.new(self)
60
+ when 7..13 then Handler::Handler07.new(self)
61
+ else fail WebSocket::Error::Frame::UnknownVersion
62
+ end
63
63
  end
64
-
65
64
  end
66
65
  end
67
66
  end
@@ -1,7 +1,6 @@
1
1
  module WebSocket
2
2
  module Frame
3
3
  class Data < String
4
-
5
4
  def initialize(*args)
6
5
  super(*convert_args(args))
7
6
  end
@@ -17,7 +16,7 @@ module WebSocket
17
16
 
18
17
  # Extract mask from 4 first bytes according to spec
19
18
  def set_mask
20
- raise WebSocket::Error::Frame::MaskTooShort if bytesize < 4
19
+ fail WebSocket::Error::Frame::MaskTooShort if bytesize < 4
21
20
  @masking_key = self[0..3].bytes.to_a
22
21
  end
23
22
 
@@ -42,7 +41,6 @@ module WebSocket
42
41
  end
43
42
  result
44
43
  end
45
-
46
44
  end
47
45
  end
48
46
  end
@@ -1,7 +1,6 @@
1
1
  module WebSocket
2
2
  module Frame
3
3
  module Handler
4
-
5
4
  autoload :Base, "#{::WebSocket::ROOT}/websocket/frame/handler/base"
6
5
 
7
6
  autoload :Handler03, "#{::WebSocket::ROOT}/websocket/frame/handler/handler03"
@@ -9,7 +8,6 @@ module WebSocket
9
8
  autoload :Handler05, "#{::WebSocket::ROOT}/websocket/frame/handler/handler05"
10
9
  autoload :Handler07, "#{::WebSocket::ROOT}/websocket/frame/handler/handler07"
11
10
  autoload :Handler75, "#{::WebSocket::ROOT}/websocket/frame/handler/handler75"
12
-
13
11
  end
14
12
  end
15
13
  end
@@ -2,7 +2,6 @@ module WebSocket
2
2
  module Frame
3
3
  module Handler
4
4
  class Base
5
-
6
5
  def initialize(frame)
7
6
  @frame = frame
8
7
  end
@@ -10,13 +9,13 @@ module WebSocket
10
9
  # Convert data to raw frame ready to send to client
11
10
  # @return [String] Encoded frame
12
11
  def encode_frame
13
- raise NotImplementedError
12
+ fail NotImplementedError
14
13
  end
15
14
 
16
15
  # Convert raw data to decoded frame
17
16
  # @return [WebSocket::Frame::Incoming] Frame if found, nil otherwise
18
17
  def decode_frame
19
- raise NotImplementedError
18
+ fail NotImplementedError
20
19
  end
21
20
 
22
21
  private
@@ -34,7 +33,6 @@ module WebSocket
34
33
  def data_frame?(frame_type)
35
34
  [:text, :binary].include?(frame_type)
36
35
  end
37
-
38
36
  end
39
37
  end
40
38
  end
@@ -1,10 +1,10 @@
1
1
  # encoding: binary
2
+ require 'securerandom'
2
3
 
3
4
  module WebSocket
4
5
  module Frame
5
6
  module Handler
6
7
  class Handler03 < Base
7
-
8
8
  # Hash of frame names and it's opcodes
9
9
  FRAME_TYPES = {
10
10
  continuation: 0,
@@ -25,155 +25,192 @@ module WebSocket
25
25
 
26
26
  # @see WebSocket::Frame::Handler::Base#encode_frame
27
27
  def encode_frame
28
- frame = ''
28
+ frame = if @frame.outgoing_masking?
29
+ masking_key = SecureRandom.random_bytes(4)
30
+ tmp_data = Data.new(masking_key + @frame.data)
31
+ tmp_data.set_mask
32
+ masking_key + tmp_data.getbytes(4, tmp_data.size)
33
+ else
34
+ @frame.data
35
+ end
36
+
37
+ encode_header + frame
38
+ end
39
+
40
+ # @see WebSocket::Frame::Handler::Base#decode_frame
41
+ def decode_frame
42
+ while @frame.data.size > 1
43
+ valid_header, more, frame_type, mask, payload_length = decode_header
44
+ return unless valid_header
45
+
46
+ application_data = decode_payload(payload_length, mask)
47
+
48
+ if more
49
+ decode_continuation_frame(application_data, frame_type)
50
+ elsif frame_type == :continuation
51
+ return decode_finish_continuation_frame(application_data)
52
+ else
53
+ fail(WebSocket::Error::Frame::InvalidPayloadEncoding) if frame_type == :text && !application_data.valid_encoding?
54
+ return @frame.class.new(version: @frame.version, type: frame_type, data: application_data, decoded: true)
55
+ end
56
+ end
57
+ nil
58
+ end
59
+
60
+ # Allow turning on or off masking
61
+ def masking?
62
+ false
63
+ end
64
+
65
+ private
29
66
 
30
- opcode = type_to_opcode(@frame.type)
31
- byte1 = opcode | (fin ? 0b10000000 : 0b00000000) # since more, rsv1-3 are 0 and 0x80 for Draft 4
32
- frame << byte1
67
+ # This allows flipping the more bit to fin for draft 04
68
+ def fin
69
+ false
70
+ end
71
+
72
+ # Convert frame type name to opcode
73
+ # @param [Symbol] frame_type Frame type name
74
+ # @return [Integer] opcode or nil
75
+ # @raise [WebSocket::Error] if frame opcode is not known
76
+ def type_to_opcode(frame_type)
77
+ FRAME_TYPES[frame_type] || fail(WebSocket::Error::Frame::UnknownFrameType)
78
+ end
33
79
 
80
+ # Convert frame opcode to type name
81
+ # @param [Integer] opcode Opcode
82
+ # @return [Symbol] Frame type name or nil
83
+ # @raise [WebSocket::Error] if frame type name is not known
84
+ def opcode_to_type(opcode)
85
+ FRAME_TYPES_INVERSE[opcode] || fail(WebSocket::Error::Frame::UnknownOpcode)
86
+ end
87
+
88
+ def encode_header
34
89
  mask = @frame.outgoing_masking? ? 0b10000000 : 0b00000000
35
90
 
36
- length = @frame.data.size
91
+ output = ''
92
+ output << (type_to_opcode(@frame.type) | (fin ? 0b10000000 : 0b00000000)) # since more, rsv1-3 are 0 and 0x80 for Draft 4
93
+ output << encode_payload_length(@frame.data.size, mask)
94
+ output
95
+ end
96
+
97
+ def encode_payload_length(length, mask)
98
+ output = ''
37
99
  if length <= 125
38
- byte2 = length # since rsv4 is 0
39
- frame << (byte2 | mask)
100
+ output << (length | mask) # since rsv4 is 0
40
101
  elsif length < 65_536 # write 2 byte length
41
- frame << (126 | mask)
42
- frame << [length].pack('n')
102
+ output << (126 | mask)
103
+ output << [length].pack('n')
43
104
  else # write 8 byte length
44
- frame << (127 | mask)
45
- frame << [length >> 32, length & 0xFFFFFFFF].pack('NN')
105
+ output << (127 | mask)
106
+ output << [length >> 32, length & 0xFFFFFFFF].pack('NN')
46
107
  end
47
-
48
- if @frame.outgoing_masking?
49
- masking_key = [rand(256).chr, rand(256).chr, rand(256).chr, rand(256).chr].join
50
- tmp_data = Data.new([masking_key, @frame.data.to_s].join)
51
- tmp_data.set_mask
52
- frame << masking_key + tmp_data.getbytes(4, tmp_data.size)
53
- else
54
- frame << @frame.data
55
- end
56
-
57
- frame
108
+ output
58
109
  end
59
110
 
60
- # @see WebSocket::Frame::Handler::Base#decode_frame
61
- def decode_frame
62
- while @frame.data.size > 1
63
- pointer = 0
111
+ def decode_header
112
+ more, frame_type = decode_first_byte
113
+ header_length, payload_length, mask = decode_second_byte(frame_type)
114
+ return unless header_length
64
115
 
65
- more = ((@frame.data.getbyte(pointer) & 0b10000000) == 0b10000000) ^ fin
116
+ # Compute the expected frame length
117
+ frame_length = header_length + payload_length
118
+ frame_length += 4 if mask
66
119
 
67
- raise(WebSocket::Error::Frame::ReservedBitUsed) if @frame.data.getbyte(pointer) & 0b01110000 != 0b00000000
120
+ fail(WebSocket::Error::Frame::TooLong) if frame_length > WebSocket.max_frame_size
68
121
 
69
- opcode = @frame.data.getbyte(pointer) & 0b00001111
70
- frame_type = opcode_to_type(opcode)
71
- pointer += 1
122
+ # Check buffer size
123
+ return unless buffer_exists?(frame_length) # Buffer incomplete
72
124
 
73
- raise(WebSocket::Error::Frame::FragmentedControlFrame) if more && control_frame?(frame_type)
74
- raise(WebSocket::Error::Frame::DataFrameInsteadContinuation) if data_frame?(frame_type) && !@application_data_buffer.nil?
125
+ # Remove frame header
126
+ @frame.data.slice!(0...header_length)
75
127
 
76
- mask = @frame.incoming_masking? && (@frame.data.getbyte(pointer) & 0b10000000) == 0b10000000
77
- length = @frame.data.getbyte(pointer) & 0b01111111
128
+ [true, more, frame_type, mask, payload_length]
129
+ end
78
130
 
79
- raise(WebSocket::Error::Frame::ControlFramePayloadTooLong) if length > 125 && control_frame?(frame_type)
131
+ def buffer_exists?(buffer_number)
132
+ !@frame.data.getbyte(buffer_number - 1).nil?
133
+ end
80
134
 
81
- pointer += 1
135
+ def decode_first_byte
136
+ first_byte = @frame.data.getbyte(0)
82
137
 
83
- payload_length = case length
84
- when 127 # Length defined by 8 bytes
85
- # Check buffer size
86
- return if @frame.data.getbyte(pointer + 8 - 1) == nil # Buffer incomplete
138
+ fail(WebSocket::Error::Frame::ReservedBitUsed) if first_byte & 0b01110000 != 0b00000000
87
139
 
88
- # Only using the last 4 bytes for now, till I work out how to
89
- # unpack 8 bytes. I'm sure 4GB frames will do for now :)
90
- l = @frame.data.getbytes(pointer + 4, 4).unpack('N').first
91
- pointer += 8
92
- l
93
- when 126 # Length defined by 2 bytes
94
- # Check buffer size
95
- return if @frame.data.getbyte(pointer + 2 - 1) == nil # Buffer incomplete
140
+ more = ((first_byte & 0b10000000) == 0b10000000) ^ fin
141
+ frame_type = opcode_to_type first_byte & 0b00001111
96
142
 
97
- l = @frame.data.getbytes(pointer, 2).unpack('n').first
98
- pointer += 2
99
- l
100
- else
101
- length
102
- end
143
+ fail(WebSocket::Error::Frame::FragmentedControlFrame) if more && control_frame?(frame_type)
144
+ fail(WebSocket::Error::Frame::DataFrameInsteadContinuation) if data_frame?(frame_type) && !@application_data_buffer.nil?
103
145
 
104
- # Compute the expected frame length
105
- frame_length = pointer + payload_length
106
- frame_length += 4 if mask
146
+ [more, frame_type]
147
+ end
107
148
 
108
- raise(WebSocket::Error::Frame::TooLong) if frame_length > WebSocket.max_frame_size
149
+ def decode_second_byte(frame_type)
150
+ second_byte = @frame.data.getbyte(1)
109
151
 
110
- # Check buffer size
111
- return if @frame.data.getbyte(frame_length - 1) == nil # Buffer incomplete
152
+ mask = @frame.incoming_masking? && (second_byte & 0b10000000) == 0b10000000
153
+ length = second_byte & 0b01111111
112
154
 
113
- # Remove frame header
114
- @frame.data.slice!(0...pointer)
115
- pointer = 0
155
+ fail(WebSocket::Error::Frame::ControlFramePayloadTooLong) if length > 125 && control_frame?(frame_type)
116
156
 
117
- # Read application data (unmasked if required)
118
- @frame.data.set_mask if mask
119
- pointer += 4 if mask
120
- application_data = @frame.data.getbytes(pointer, payload_length)
121
- application_data.force_encoding('UTF-8')
122
- pointer += payload_length
123
- @frame.data.unset_mask if mask
157
+ header_length, payload_length = decode_payload_length(length)
124
158
 
125
- # Throw away data up to pointer
126
- @frame.data.slice!(0...pointer)
159
+ [header_length, payload_length, mask]
160
+ end
127
161
 
128
- raise(WebSocket::Error::Frame::UnexpectedContinuationFrame) if frame_type == :continuation && !@frame_type
162
+ def decode_payload_length(length)
163
+ case length
164
+ when 127 # Length defined by 8 bytes
165
+ # Check buffer size
166
+ return unless buffer_exists?(10) # Buffer incomplete
129
167
 
130
- if more
131
- @application_data_buffer ||= ''
132
- @application_data_buffer << application_data
133
- @frame_type ||= frame_type
134
- else
135
- # Message is complete
136
- if frame_type == :continuation
137
- @application_data_buffer << application_data
138
- # Test valid UTF-8 encoding
139
- raise(WebSocket::Error::Frame::InvalidPayloadEncoding) if @frame_type == :text && !@application_data_buffer.valid_encoding?
140
- message = @frame.class.new(version: @frame.version, type: @frame_type, data: @application_data_buffer, decoded: true)
141
- @application_data_buffer = nil
142
- @frame_type = nil
143
- return message
144
- else
145
- raise(WebSocket::Error::Frame::InvalidPayloadEncoding) if frame_type == :text && !application_data.valid_encoding?
146
- return @frame.class.new(version: @frame.version, type: frame_type, data: application_data, decoded: true)
147
- end
148
- end
168
+ # Only using the last 4 bytes for now, till I work out how to
169
+ # unpack 8 bytes. I'm sure 4GB frames will do for now :)
170
+ [10, @frame.data.getbytes(6, 4).unpack('N').first]
171
+ when 126 # Length defined by 2 bytes
172
+ # Check buffer size
173
+ return unless buffer_exists?(4) # Buffer incomplete
174
+
175
+ [4, @frame.data.getbytes(2, 2).unpack('n').first]
176
+ else
177
+ [2, length]
149
178
  end
150
- return nil
151
179
  end
152
180
 
153
- # Allow turning on or off masking
154
- def masking?; false; end
181
+ def decode_payload(payload_length, mask)
182
+ pointer = 0
155
183
 
156
- private
184
+ # Read application data (unmasked if required)
185
+ @frame.data.set_mask if mask
186
+ pointer += 4 if mask
187
+ payload = @frame.data.getbytes(pointer, payload_length)
188
+ payload.force_encoding('UTF-8')
189
+ pointer += payload_length
190
+ @frame.data.unset_mask if mask
157
191
 
158
- # This allows flipping the more bit to fin for draft 04
159
- def fin; false; end
192
+ # Throw away data up to pointer
193
+ @frame.data.slice!(0...pointer)
160
194
 
161
- # Convert frame type name to opcode
162
- # @param [Symbol] frame_type Frame type name
163
- # @return [Integer] opcode or nil
164
- # @raise [WebSocket::Error] if frame opcode is not known
165
- def type_to_opcode(frame_type)
166
- FRAME_TYPES[frame_type] || raise(WebSocket::Error::Frame::UnknownFrameType)
195
+ payload
167
196
  end
168
197
 
169
- # Convert frame opcode to type name
170
- # @param [Integer] opcode Opcode
171
- # @return [Symbol] Frame type name or nil
172
- # @raise [WebSocket::Error] if frame type name is not known
173
- def opcode_to_type(opcode)
174
- FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error::Frame::UnknownOpcode)
198
+ def decode_continuation_frame(application_data, frame_type)
199
+ @application_data_buffer ||= ''
200
+ @application_data_buffer << application_data
201
+ @frame_type ||= frame_type
175
202
  end
176
203
 
204
+ def decode_finish_continuation_frame(application_data)
205
+ fail(WebSocket::Error::Frame::UnexpectedContinuationFrame) unless @frame_type
206
+ @application_data_buffer << application_data
207
+ # Test valid UTF-8 encoding
208
+ fail(WebSocket::Error::Frame::InvalidPayloadEncoding) if @frame_type == :text && !@application_data_buffer.valid_encoding?
209
+ message = @frame.class.new(version: @frame.version, type: @frame_type, data: @application_data_buffer, decoded: true)
210
+ @application_data_buffer = nil
211
+ @frame_type = nil
212
+ message
213
+ end
177
214
  end
178
215
  end
179
216
  end