-
-
Save rossta/1004242 to your computer and use it in GitHub Desktop.
Making WebSocket unmasking fast in ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module EventMachine | |
module WebSocket | |
class MaskedString < String | |
def read_mask | |
raise "Too short" if bytesize < 4 # TODO - change | |
@masking_key = String.new(self[0..3]) | |
end | |
def mask_length | |
@masking_key.length | |
end | |
def slice_mask | |
slice!(0, mask_length) | |
end | |
# String#slice substituted for String#getbyte as defined in lib/em-websocket | |
# as they are equivalent for a single parameter | |
def getbyte(index) | |
masked_char = slice(index + mask_length) | |
masked_char ? masked_char ^ @masking_key.slice(index % mask_length) : nil | |
end | |
def getbytes(start_index, count) | |
data = '' | |
count.times do |i| | |
data << getbyte(start_index + i) | |
end | |
data | |
end | |
# slice payload portion of masked string by offsetting | |
# start and finish indices from start of payload (mask_length) | |
def slice_payload(start, finish) | |
slice(mask_length + start, mask_length + finish) | |
end | |
# Extend masking key length to given count starting from offset | |
def full_mask(start, count) | |
(@masking_key * (count / mask_length))[start % mask_length..-1] | |
end | |
def getbytes_fast(start, count) | |
string_to_mask = slice_payload(start, count) | |
masking_string = full_mask(start, count) | |
masking_string.xor!(string_to_mask) | |
end | |
end | |
end | |
end | |
require 'xor' | |
require 'benchmark' | |
require 'digest/md5' | |
require 'test/unit' | |
n = 1000 | |
# Use a 4 byte mask and a 1K string | |
string = rand.to_s[0..3] + 'a' * 1024 | |
Benchmark.bm do |x| | |
x.report("MaskedString getbytes-orig:") { | |
n.times { | |
string = EventMachine::WebSocket::MaskedString.new(string) | |
string.read_mask | |
string.getbytes(0, 1024) | |
} | |
} | |
x.report("MaskedString getbytes-fast:") { | |
n.times { | |
string = EventMachine::WebSocket::MaskedString.new(string) | |
string.read_mask | |
string.getbytes_fast(0, 1024) | |
} | |
} | |
end | |
class TestGetBytesFaster < Test::Unit::TestCase | |
def setup | |
# Use a 4 byte mask and a 1K string | |
string = rand.to_s[0..3] + 'a' * 1024 | |
@string = EventMachine::WebSocket::MaskedString.new(string) | |
@string.read_mask | |
end | |
def test_assert_parity_calculation_is_correct | |
getbytes_orig = @string.getbytes(0, 1024) | |
getbytes_fast = @string.getbytes_fast(0, 1024) | |
assert_equal(Digest::MD5.hexdigest(getbytes_orig), Digest::MD5.hexdigest(getbytes_fast) ) | |
end | |
def test_original_string_remains_unchanged | |
original_string = @string.dup | |
@string.getbytes_fast(0, 1024) | |
assert_equal(original_string, @string) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment