rewrite masking function to be tail-recursive and use binary matches#17
Merged
the-mikedavis merged 2 commits intomainfrom Jun 29, 2021
Merged
rewrite masking function to be tail-recursive and use binary matches#17the-mikedavis merged 2 commits intomainfrom
the-mikedavis merged 2 commits intomainfrom
Conversation
Collaborator
Author
|
💥 compiling inline makes it even better dang bench fileMix.install([:benchee])
defmodule Foo do
def stream_mask(payload, <<a, b, c, d>>) do
[a, b, c, d]
|> Stream.cycle()
|> Enum.reduce_while({payload, _acc = <<>>}, fn
_mask_key, {<<>>, acc} ->
{:halt, acc}
mask_key, {<<part_key::integer, payload_rest::binary>>, acc} ->
{:cont, {payload_rest, <<acc::binary, Bitwise.bxor(mask_key, part_key)::integer>>}}
end)
end
def match_mask(payload, mask, acc \\ <<>>)
def match_mask(payload, nil, _acc), do: payload
# n=4 is the happy path
# n=3..1 catches cases where the remaining byte_size/1 of the payload is shorter
# than the mask
for n <- 4..1 do
def match_mask(
<<part_key::integer-size(8)-unit(unquote(n)), payload_rest::binary>>,
<<mask_key::integer-size(8)-unit(unquote(n)), _::binary>> = mask,
acc
) do
match_mask(
payload_rest,
mask,
<<acc::binary, Bitwise.bxor(mask_key, part_key)::integer-size(8)-unit(unquote(n))>>
)
end
end
def match_mask(<<>>, _mask, acc), do: acc
@compile {:inline, compile_mask: 2, compile_mask: 3}
def compile_mask(payload, mask, acc \\ <<>>)
def compile_mask(payload, nil, _acc), do: payload
# n=4 is the happy path
# n=3..1 catches cases where the remaining byte_size/1 of the payload is shorter
# than the mask
for n <- 4..1 do
def compile_mask(
<<part_key::integer-size(8)-unit(unquote(n)), payload_rest::binary>>,
<<mask_key::integer-size(8)-unit(unquote(n)), _::binary>> = mask,
acc
) do
compile_mask(
payload_rest,
mask,
<<acc::binary, Bitwise.bxor(mask_key, part_key)::integer-size(8)-unit(unquote(n))>>
)
end
end
def compile_mask(<<>>, _mask, acc), do: acc
end
payload = :crypto.strong_rand_bytes(10_000)
mask = :crypto.strong_rand_bytes(4)
Benchee.run(
%{
"stream" => fn payload -> Foo.stream_mask(payload, mask) end,
"match" => fn payload -> Foo.match_mask(payload, mask) end,
"compile" => fn payload -> Foo.compile_mask(payload, mask) end
},
time: 10,
memory_time: 2,
inputs: %{
"Small" => :crypto.strong_rand_bytes(100),
"Medium" => :crypto.strong_rand_bytes(10_000),
"Large" => :crypto.strong_rand_bytes(1_000_000)
}
)and the results: |
Collaborator
Author
|
looks like gun does this in just about the same way but without the metaprogramming: https://github.com/ninenines/cowlib/blob/0f5c2f8922c89c58f51696cce690245cbdc5f327/src/cow_ws.erl#L526-L542 mask(<<>>, _, Unmasked) ->
Unmasked;
mask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
T = O bxor MaskKey,
mask(Rest, MaskKey, << Acc/binary, T:32 >>);
mask(<< O:24 >>, MaskKey, Acc) ->
<< MaskKey2:24, _:8 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:24 >>;
mask(<< O:16 >>, MaskKey, Acc) ->
<< MaskKey2:16, _:16 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:16 >>;
mask(<< O:8 >>, MaskKey, Acc) ->
<< MaskKey2:8, _:24 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:8 >>. |
tonyvanriet
approved these changes
Jun 29, 2021
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
closes #16
saw these results with benchee:
the bench file
woof that was a lot of memory consumption beforehand!
what's this masking function?
When the client sends frames to the server, it "mask"s the payloads of the frames using 4 random bytes. The "mask" operation is that you take each byte of the mask and XOR it with a byte of the payload. When you reach the end of the mask bytes, you repeat the mask. When you reach the end of the payload, you're done!
From RFC6455 section 5.3 on masking:
I don't quite remember why we do this (cache busting, some outdated idea of a security mechanism, etc.), but we do this often, so might as well make it efficient.