class Webtube::Frame
Public Class Methods
Apply the given [[mask]], specified as an integer, to the given [[data]]. Note that since the underlying operation is [[XOR]], the operation can be repeated to reverse itself.
- [nil]
-
can be supplied instead of [[mask]] to indicate
that no processing is needed.
# File lib/webtube.rb, line 740 def self::apply_mask data, mask return data if mask.nil? return (data + "\0\0\0"). # pad to full tetras unpack('L>*'). # extract tetras map!{|i| i ^ mask}. # XOR each with the mask pack('L>*'). # pack back into a string byteslice(0, data.bytesize) # remove padding end
Given a message and attributes, break it up into frames, and yields each such [[Frame]] separately for processing by the caller – usually, delivery to the other end via the socket. Takes care to not fragment control messages. If masking is required, uses [[SecureRandom]] to generate masks for each frame.
# File lib/webtube.rb, line 838 def self::each_frame_for_message message: '', opcode: OPCODE_TEXT, masked: false, max_frame_body_size: nil message = message.dup.force_encoding Encoding::ASCII_8BIT offset = 0 fin = true begin frame_length = message.bytesize - offset fin = !(opcode <= 0x07 and max_frame_body_size and frame_length > max_frame_body_size) frame_length = max_frame_body_size unless fin yield Webtube::Frame.prepare( opcode: opcode, payload: message[offset, frame_length], fin: fin, masked: masked) offset += frame_length opcode = 0x00 # for continuation frames end until fin return end
Given a frame's payload, prepare the header and return a
- [Frame]
-
instance representing such a frame. Optionally,
some header fields can also be set.
It's OK for the caller to modify some header fields, such as [[fin]] or [[opcode]], on the returned [[Frame]] by calling the appropriate methods. Its body should not be modified after construction, however, because its length and possibly its mask is already encoded in the header.
# File lib/webtube.rb, line 800 def self::prepare( payload: '', opcode: OPCODE_TEXT, fin: true, masked: false) header = [0].pack 'C' # we'll fill in the first byte later mask_flag = masked ? 0x80 : 0x00 header << if payload.bytesize <= 125 then [mask_flag | payload.bytesize].pack 'C' elsif payload.bytesize <= 0xFFFF then [mask_flag | 126, payload.bytesize].pack 'C S>' elsif payload.bytesize <= 0x7FFF_FFFF_FFFF_FFFF then [mask_flag | 127, payload.bytesize].pack 'C Q>' else raise 'payload too big for a WebSocket frame' end frame = Frame.new(header) unless masked then frame.body = payload else mask = SecureRandom.random_bytes(4) frame.header << mask frame.body = apply_mask(payload, mask.unpack('L>')[0]) end # now, it's time to fill out the first byte frame.fin = fin frame.opcode = opcode return frame end
Read all the bytes of one WebSocket frame from the given
- [socket]
-
and return them in a [[Frame]] instance. In
case traffic ends before the frame is complete, raise [[BrokenFrame]].
Note that this will call [[socket.read]] twice or thrice, and assumes no other thread will consume bytes from the socket inbetween. In a multithreaded environment, it may be necessary to apply external locking.
# File lib/webtube.rb, line 759 def self::read_from_socket socket header = socket.read(2) unless header and header.bytesize == 2 then header ||= String.new encoding: Encoding::ASCII_8BIT raise BrokenFrame.new(header) end frame = Frame.new header header_tail_size = frame.extended_payload_length_field_size + (frame.masked? ? 4 : 0) unless header_tail_size.zero? then header_tail = socket.read(header_tail_size) frame.header << header_tail if header_tail unless header_tail and header_tail.bytesize == header_tail_size then raise BrokenFrame.new(frame.header) end end data_size = frame.payload_length frame.body = socket.read(data_size) unless frame.body and frame.body.bytesize == data_size then raise BrokenFrame.new(frame.body ? frame.header + frame.body : frame.header) end return frame end
Public Instance Methods
# File lib/webtube.rb, line 681 def control_frame? return opcode >= 0x8 end
Determine the size of this frame's extended payload length field in bytes from the 7-bit short payload length field.
# File lib/webtube.rb, line 691 def extended_payload_length_field_size return case header.getbyte(1) & 0x7F when 126 then 2 when 127 then 8 else 0 end end
# File lib/webtube.rb, line 647 def fin= new_value header.setbyte 0, header.getbyte(0) & 0x7F | (new_value ? 0x80 : 0x00) return new_value end
# File lib/webtube.rb, line 643 def fin? return (header.getbyte(0) & 0x80) != 0 end
Extracts the mask as a tetrabyte integer from this frame. If the frame has the [[masked?]] bit unset, returns
- [nil]
-
instead.
# File lib/webtube.rb, line 713 def mask if masked? then mask_offset = 2 + case header.getbyte(1) & 0x7F when 126 then 2 when 127 then 8 else 0 end return header.unpack('@%i L>' % mask_offset)[0] else return nil end end
# File lib/webtube.rb, line 685 def masked? return (header.getbyte(1) & 0x80) != 0 end
# File lib/webtube.rb, line 671 def opcode return header.getbyte(0) & 0x0F end
# File lib/webtube.rb, line 675 def opcode= new_opcode header.setbyte 0, (header.getbyte(0) & ~0x0F) | (new_opcode & 0x0F) return new_opcode end
Extract the frame's payload and return it as a [[String]] instance of the [[ASCII-8BIT]] encoding. If the frame has the [[masked?]] bit set, this also involves demasking.
# File lib/webtube.rb, line 729 def payload return Frame.apply_mask(body, mask) end
Extract the length of this frame's payload. Enough bytes of the header must already have been read; see [[extended_payload_lenth_field_size]].
# File lib/webtube.rb, line 702 def payload_length return case base = header.getbyte(1) & 0x7F when 126 then header.unpack('@2 S>')[0] when 127 then header.unpack('@2 Q>')[0] else base end end
The three reserved bits of the frame, shifted rightwards to meet the binary point
# File lib/webtube.rb, line 667 def rsv return (header.getbyte(0) & 0x70) >> 4 end
# File lib/webtube.rb, line 653 def rsv1 return (header.getbyte(0) & 0x40) != 0 end
# File lib/webtube.rb, line 657 def rsv2 return (header.getbyte(0) & 0x20) != 0 end
# File lib/webtube.rb, line 661 def rsv3 return (header.getbyte(0) & 0x10) != 0 end