Skip to content

Use IO::Buffer for masking. #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- macos

ruby:
- "3.1"
- "3.2"

steps:
Expand Down
53 changes: 53 additions & 0 deletions benchmark/mask_xor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
def mask_xor_buffer(data, mask)
buffer = data.dup
mask_buffer = IO::Buffer.for(mask)

IO::Buffer.for(buffer) do |buffer|
buffer.xor!(mask_buffer)
end

return buffer
end

def mask_xor_string(data, mask)
result = String.new(encoding: Encoding::BINARY)

for i in 0...data.bytesize do
result << (data.getbyte(i) ^ mask.getbyte(i % 4))
end

return result
end

require 'benchmark/ips'
require 'securerandom'

MASK = SecureRandom.bytes(4)
DATA = SecureRandom.bytes(1024 * 1024)

Benchmark.ips do |x|
# Configure the number of seconds used during
# the warmup phase (default 2) and calculation phase (default 5)
x.config(:time => 5, :warmup => 2)

# These parameters can also be configured this way
x.time = 5
x.warmup = 2

x.report("IO::Buffer") {mask_xor_buffer(DATA, MASK)}
x.report("String") {mask_xor_string(DATA, MASK)}

# Compare the iterations per second of the various reports!
x.compare!
end

# Warming up --------------------------------------
# IO::Buffer 152.000 i/100ms
# String 1.000 i/100ms
# Calculating -------------------------------------
# IO::Buffer 1.534k (± 0.1%) i/s - 7.752k in 5.051915s
# String 11.929 (± 0.0%) i/s - 60.000 in 5.029975s

# Comparison:
# IO::Buffer: 1534.5 i/s
# String: 11.9 i/s - 128.63x slower
39 changes: 26 additions & 13 deletions lib/protocol/websocket/frame.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,30 @@ def continued?
attr_accessor :length
attr_accessor :payload

if IO.const_defined?(:Buffer) && IO::Buffer.respond_to?(:for) && IO::Buffer.method_defined?(:xor!)
private def mask_xor(data, mask)
buffer = data.dup
mask_buffer = IO::Buffer.for(mask)

IO::Buffer.for(buffer) do |buffer|
buffer.xor!(mask_buffer)
end

return buffer
end
else
warn "IO::Buffer not available, falling back to slow implementation of mask_xor!"
private def mask_xor(data, mask)
result = String.new(encoding: Encoding::BINARY)

for i in 0...data.bytesize do
result << (data.getbyte(i) ^ mask.getbyte(i % 4))
end

return result
end
end

def pack(data = "")
length = data.bytesize

Expand All @@ -94,12 +118,7 @@ def pack(data = "")
end

if @mask
@payload = String.new(encoding: Encoding::BINARY)

for i in 0...data.bytesize do
@payload << (data.getbyte(i) ^ mask.getbyte(i % 4))
end

@payload = mask_xor(data, mask)
@length = length
else
@payload = data
Expand All @@ -111,13 +130,7 @@ def pack(data = "")

def unpack
if @mask and !@payload.empty?
data = String.new(encoding: Encoding::BINARY)

for i in 0...@payload.bytesize do
data << (@payload.getbyte(i) ^ @mask.getbyte(i % 4))
end

return data
return mask_xor(@payload, @mask)
else
return @payload
end
Expand Down