Skip to content

Instantly share code, notes, and snippets.

@rossta
Forked from mloughran/unmasking_benchmark.rb
Created June 2, 2011 10:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rossta/1004242 to your computer and use it in GitHub Desktop.
Save rossta/1004242 to your computer and use it in GitHub Desktop.
Making WebSocket unmasking fast in ruby
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