Class: Webtube::Frame
- Inherits:
-
Struct
- Object
- Struct
- Webtube::Frame
- Defined in:
- lib/webtube.rb,
lib/webtube.rb
Overview
Note that [[body]] holds the /raw/ data; that is, if
- [masked?]
-
is true, it will need to be unmasked to get
the payload. Call [[payload]] in order to abstract this away.
Instance Attribute Summary collapse
-
#body ⇒ Object
Returns the value of attribute body.
-
#header ⇒ Object
Returns the value of attribute header.
Class Method Summary collapse
-
.apply_mask(data, mask) ⇒ Object
Apply the given [[mask]], specified as an integer, to the given [[data]].
-
.each_frame_for_message(message: '', opcode: OPCODE_TEXT, masked: false, max_frame_body_size: nil) ⇒ Object
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.
-
.prepare(payload: '', opcode: OPCODE_TEXT, fin: true, masked: false) ⇒ Object
Given a frame’s payload, prepare the header and return a [[Frame]] instance representing such a frame.
-
.read_from_socket(socket) ⇒ Object
Read all the bytes of one WebSocket frame from the given [[socket]] and return them in a [[Frame]] instance.
Instance Method Summary collapse
- #control_frame? ⇒ Boolean
-
#extended_payload_length_field_size ⇒ Object
Determine the size of this frame’s extended payload length field in bytes from the 7-bit short payload length field.
- #fin=(new_value) ⇒ Object
- #fin? ⇒ Boolean
-
#mask ⇒ Object
Extracts the mask as a tetrabyte integer from this frame.
- #masked? ⇒ Boolean
- #opcode ⇒ Object
- #opcode=(new_opcode) ⇒ Object
-
#payload ⇒ Object
Extract the frame’s payload and return it as a [[String]] instance of the [[ASCII-8BIT]] encoding.
-
#payload_length ⇒ Object
Extract the length of this frame’s payload.
-
#rsv ⇒ Object
The three reserved bits of the frame, shifted rightwards to meet the binary point.
- #rsv1 ⇒ Object
- #rsv2 ⇒ Object
- #rsv3 ⇒ Object
Instance Attribute Details
#body ⇒ Object
Returns the value of attribute body
641 642 643 |
# File 'lib/webtube.rb', line 641 def body @body end |
#header ⇒ Object
Returns the value of attribute header
641 642 643 |
# File 'lib/webtube.rb', line 641 def header @header end |
Class Method Details
.apply_mask(data, mask) ⇒ Object
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.
740 741 742 743 744 745 746 747 |
# 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 |
.each_frame_for_message(message: '', opcode: OPCODE_TEXT, masked: false, max_frame_body_size: nil) ⇒ Object
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.
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 |
# File 'lib/webtube.rb', line 838 def self:: message: '', opcode: OPCODE_TEXT, masked: false, max_frame_body_size: nil = .dup.force_encoding Encoding::ASCII_8BIT offset = 0 fin = true begin frame_length = .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: [offset, frame_length], fin: fin, masked: masked) offset += frame_length opcode = 0x00 # for continuation frames end until fin return end |
.prepare(payload: '', opcode: OPCODE_TEXT, fin: true, masked: false) ⇒ Object
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.
800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 |
# 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_from_socket(socket) ⇒ Object
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.
759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 |
# 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 |
Instance Method Details
#control_frame? ⇒ Boolean
681 682 683 |
# File 'lib/webtube.rb', line 681 def control_frame? return opcode >= 0x8 end |
#extended_payload_length_field_size ⇒ Object
Determine the size of this frame’s extended payload length field in bytes from the 7-bit short payload length field.
691 692 693 694 695 696 697 |
# 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 |
#fin=(new_value) ⇒ Object
647 648 649 650 651 |
# 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 |
#fin? ⇒ Boolean
643 644 645 |
# File 'lib/webtube.rb', line 643 def fin? return (header.getbyte(0) & 0x80) != 0 end |
#mask ⇒ Object
Extracts the mask as a tetrabyte integer from this frame. If the frame has the [[masked?]] bit unset, returns
- [nil]
-
instead.
713 714 715 716 717 718 719 720 721 722 723 724 |
# 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 |
#masked? ⇒ Boolean
685 686 687 |
# File 'lib/webtube.rb', line 685 def masked? return (header.getbyte(1) & 0x80) != 0 end |
#opcode ⇒ Object
671 672 673 |
# File 'lib/webtube.rb', line 671 def opcode return header.getbyte(0) & 0x0F end |
#opcode=(new_opcode) ⇒ Object
675 676 677 678 679 |
# File 'lib/webtube.rb', line 675 def opcode= new_opcode header.setbyte 0, (header.getbyte(0) & ~0x0F) | (new_opcode & 0x0F) return new_opcode end |
#payload ⇒ Object
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.
729 730 731 |
# File 'lib/webtube.rb', line 729 def payload return Frame.apply_mask(body, mask) end |
#payload_length ⇒ Object
Extract the length of this frame’s payload. Enough bytes of the header must already have been read; see [[extended_payload_lenth_field_size]].
702 703 704 705 706 707 708 |
# 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 |
#rsv ⇒ Object
The three reserved bits of the frame, shifted rightwards to meet the binary point
667 668 669 |
# File 'lib/webtube.rb', line 667 def rsv return (header.getbyte(0) & 0x70) >> 4 end |
#rsv1 ⇒ Object
653 654 655 |
# File 'lib/webtube.rb', line 653 def rsv1 return (header.getbyte(0) & 0x40) != 0 end |
#rsv2 ⇒ Object
657 658 659 |
# File 'lib/webtube.rb', line 657 def rsv2 return (header.getbyte(0) & 0x20) != 0 end |
#rsv3 ⇒ Object
661 662 663 |
# File 'lib/webtube.rb', line 661 def rsv3 return (header.getbyte(0) & 0x10) != 0 end |