Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matching on part of a binary in a method signature

I'm reading a binary file, and within it there is a structure where the first byte of data indicates the type of data following it. I'm trying to handle this via pattern matching, and am having trouble.

I've tried a few ways I figured may work, none of which do. You can see my feeble attempts below:

defmodule Test do

  def build(<< 0x11, rest >>) do
    "11!"
  end

  def build(<< 0x12, rest :: size(4) >>) do
    "12!"
  end

  def build(<< type, rest >>)
  when type == 0x13 do
    "13!"
  end

  def build(bytes) do
    "Unknown!"
  end

end

[ << 0x11, 0x01, 0x02, 0x03, 0x04 >>,
  << 0x12, 0x01, 0x02, 0x03, 0x04 >>,
  << 0x13, 0x01, 0x02, 0x03, 0x04 >> ]
|> Enum.map(&Test.build/1)
|> IO.inspect
# => ["Unknown!", "Unknown!", "Unknown!"]

I'd like to get: ["11!", "12!", "13!"] instead.

The data matched by these is all a fixed size (in this case 5 total bytes). This SO question seems to suggest I need to specify the total size as well? Not sure how to do that.

Ultimately I don't care about the value of the first byte if the methods are dispatched via matching, so rest is the only thing I really need within each method body. What am I missing?

like image 725
Nick Veys Avatar asked May 08 '15 17:05

Nick Veys


1 Answers

Each unbound variable in a binary pattern matches one byte by default. If you want to match an arbitrary length rest, you need to use the binary modifier.

defmodule Test do
  def build(<<0x11, rest :: binary>>) do
    "11!"
  end

  def build(<<0x12, rest :: binary>>) do
    "12!"
  end

  def build(<<0x13, rest :: binary>>) do
    "13!"
  end

  def build(bytes) do
    "Unknown!"
  end
end
like image 160
Patrick Oscity Avatar answered Nov 16 '22 13:11

Patrick Oscity