Class: LibWebSocket::Frame

Inherits:
Object
  • Object
show all
Defined in:
lib/libwebsocket/frame.rb,
lib/libwebsocket/frame/error.rb

Overview

Construct or parse a WebSocket frame.

SYNOPSIS

# Create frame
frame = LibWebSocket::Frame.new('123')
frame.to_bytes

# Parse frames
frame = LibWebSocket::Frame.new
frame.append(...)
frame.next # get next message
frame.next # get another next message

Defined Under Namespace

Classes: Error

Constant Summary collapse

MAX_RAND_INT =
2 ** 32
TYPES =
{
  :text   => 0x01,
  :binary => 0x02,
  :ping   => 0x09,
  :pong   => 0x0a,
  :close  => 0x08
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = '') ⇒ Frame

Create a new Frame instance. Automatically detect if the passed data is a string or bytes. Options can be buffer or hash with options:

:buffer - content of buffer
:type - frame type(allowed values: text, binary, ping, pong, close)
:version - protocol version(see readme for supported versions)
:max_fragments_amount - max number of message parts per single frame
:max_payload_size - max bytesize of single message

Examples:

LibWebSocket::Frame->new('data')
LibWebSocket::Frame->new(:buffer => 'data', :type => 'close')


42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/libwebsocket/frame.rb', line 42

def initialize(options = '')
  if options.is_a?(Hash)
    options.each {|k,v| instance_variable_set("@#{k}",v) }
  else
    @buffer = options
  end

  @buffer ||= ''
  @version ||= 'draft-ietf-hybi-10'
  @fragments = []
  @max_fragments_amount ||= 128
  @max_payload_size ||= 65536
end

Instance Attribute Details

#bufferObject

Returns the value of attribute buffer.



30
31
32
# File 'lib/libwebsocket/frame.rb', line 30

def buffer
  @buffer
end

#finObject

Returns the value of attribute fin.



30
31
32
# File 'lib/libwebsocket/frame.rb', line 30

def fin
  @fin
end

#maskedObject

Returns the value of attribute masked.



30
31
32
# File 'lib/libwebsocket/frame.rb', line 30

def masked
  @masked
end

#max_fragments_amountObject

Returns the value of attribute max_fragments_amount.



30
31
32
# File 'lib/libwebsocket/frame.rb', line 30

def max_fragments_amount
  @max_fragments_amount
end

#max_payload_sizeObject

Returns the value of attribute max_payload_size.



30
31
32
# File 'lib/libwebsocket/frame.rb', line 30

def max_payload_size
  @max_payload_size
end

#opcodeObject

Returns the value of attribute opcode.



30
31
32
# File 'lib/libwebsocket/frame.rb', line 30

def opcode
  @opcode
end

#rsvObject

Returns the value of attribute rsv.



30
31
32
# File 'lib/libwebsocket/frame.rb', line 30

def rsv
  @rsv
end

#versionObject

Returns the value of attribute version.



30
31
32
# File 'lib/libwebsocket/frame.rb', line 30

def version
  @version
end

Instance Method Details

#append(string = nil) ⇒ Object

Append a frame chunk.

Examples:

frame.append("\x00foo")
frame.append("bar\xff")


60
61
62
63
64
65
66
67
# File 'lib/libwebsocket/frame.rb', line 60

def append(string = nil)
  return unless string.is_a?(String)

  string.force_encoding("ASCII-8BIT") if string.respond_to?(:force_encoding)

  self.buffer += string
  return self
end

#binary?Boolean

Check if frame is of binary type.

Returns:

  • (Boolean)


89
# File 'lib/libwebsocket/frame.rb', line 89

def binary?; opcode == TYPES[:binary]; end

#close?Boolean

Check if frame is of close type.

Returns:

  • (Boolean)


87
# File 'lib/libwebsocket/frame.rb', line 87

def close?;  opcode == TYPES[:close];  end

#nextObject

Return the next frame.

Examples:

frame.append(...)
frame.next; # next message


73
74
75
76
77
78
79
# File 'lib/libwebsocket/frame.rb', line 73

def next
  bytes = self.next_bytes
  return unless bytes

  bytes.force_encoding('UTF-8') if bytes.respond_to?(:force_encoding)
  return bytes
end

#next_bytesObject

Return the next message as a UTF-8 encoded string.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/libwebsocket/frame.rb', line 92

def next_bytes
  if ['draft-hixie-75', 'draft-ietf-hybi-00'].include? self.version
    if self.buffer.slice!(/\A\xff\x00/m)
      self.opcode = TYPES[:close]
      return ''
    end

    return unless self.buffer.slice!(/^[^\x00]*\x00(.*?)\xff/m)
    return $1
  end

  return unless self.buffer.length >= 2

  while self.buffer.length > 0
    hdr = self.buffer[0..0]
    bits = hdr.unpack("B*").first.split(//)

    self.fin = bits[0]
    self.rsv = bits[1..3]
    opcode = hdr.unpack('C').first & 0b00001111
    offset = 1 # FIN,RSV[1-3],OPCODE

    payload_len = buffer[1..1].unpack('C').first
    self.masked = (payload_len & 0b10000000) >> 7
    offset += 1 # + MASKED,PAYLOAD_LEN

    payload_len = payload_len & 0b01111111
    if payload_len == 126
      return unless self.buffer.length >= offset + 2

      payload_len = self.buffer[offset..offset+2].unpack('n').first
      offset += 2
    elsif payload_len > 126
      return unless self.buffer.length >= offset + 4
      bits = self.buffer[offset..offset+7].unpack('B*').first
      bits.gsub!(/^./,'0') # Most significant bit must be 0.
      bits = bits[32..-1] # No idea how to unpack 64-bit unsigned integer with big-endian byte order
      payload_len = Array(bits).pack("B*").unpack("N").first
      offset += 8
    end

    if payload_len > self.max_payload_size
      self.buffer = ''
      raise Error::MessageTooBig.new("Payload is too big. Deny big message (#{payload_len}) or increase max_payload_size (#{self.max_payload_size})")
    end

    mask = ''
    if self.masked == 1
      return unless self.buffer.length >= offset + 4

      mask = self.buffer[offset..offset+4]
      offset += 4
    end

    return if self.buffer.length < offset + payload_len

    payload = self.buffer[offset..offset+payload_len-1]
    payload = self.mask(payload, mask) if self.masked == 1

    self.buffer[0..offset+payload_len-1] = ''

    # Inject control frame
    if !@fragments.empty? && (opcode & 0b1000 != 0)
      self.opcode = opcode
      return payload
    end

    if self.fin != '0'
      if @fragments.empty?
        self.opcode = opcode
      else
        self.opcode = @fragments.shift
      end

      payload = (@fragments + Array(payload)).join
      @fragments = []
      return payload
    else
      # Remember first fragment opcode
      @fragments.push(opcode) if @fragments.empty?

      @fragments.push(payload)
      raise Error::PolicyViolation.new("Too many fragments") if @fragments.size > self.max_fragments_amount
    end
  end

  return
end

#ping?Boolean

Check if frame is a ping request.

Returns:

  • (Boolean)


85
# File 'lib/libwebsocket/frame.rb', line 85

def ping?;   opcode == TYPES[:ping];   end

#pong?Boolean

Check if frame is a pong response.

Returns:

  • (Boolean)


86
# File 'lib/libwebsocket/frame.rb', line 86

def pong?;   opcode == TYPES[:pong];   end

#text?Boolean

Check if frame is of text type.

Returns:

  • (Boolean)


88
# File 'lib/libwebsocket/frame.rb', line 88

def text?;   opcode == TYPES[:text];   end