Syntax Question - Nested Switch Case

Hello Bro Members,

I apologize if this is not the right mailing list… I do not wish to spam everyone. I just picked up Bro a few days ago, to still learning the ropes. I have a syntax question that I can’t seem to find anywhere. How do you do a nested switch case inside a record? I have some data 0xAABBCCDD01020304 or 0xAABBCCDD01020405 that I need to verify that the header is 0xAABBCCDD and switch based on the last two bytes, either 0x0304 or 0x0405. Is this a good practice of switch record since data length will change based on the command. The nested case I have below is incorrect and is throwing error “make[3]: *** [test_pac.h] Segmentation fault (core dumped)”

Currently, I have:

enum cmd_codes {
NOP = 0x00000000,
DEVICE_HEADER = 0x AABBCCDD,
DEVICE_CMD2_1 = 0x0304,
DEVICE_CMD2_2 = 0x0405

};

type Header = record {
header: uint32; # header
cmd1: uint16; # 0x0102
cmd2: uint16; # 0x0304 or 0x0405
} &byteorder=bigendian;

type Device_Response = record {
header: Device_Header;
data: case(header.header) of {
DEVICE_HEADER → head: case(header.cmd2) of {
DEVICE_CMD2_1 → info1: Record_A;

DEVICE_CMD2_2 → info2: Record_B;

};

All the rest

default → unknown: bytestring &restofdata;
};
} &byteorder=littleendian;

type Record_A = record {

some data goes here

}

type Record_B = record {

some data goes here

}

Thanks!

It's possible that you've just run into a binpac bug related to nested
records and you can file a bug/issue for that on GitHub, but you may
also be able to organize things differently. For example, instead of
nesting switch/case you can do:

type HeaderCmd(cmd: uint16) = case cmd of {
  DEVICE_CMD2_1 -> info1: Record_A;
  DEVICE_CMD2_2 -> info2: Record_B;
};

type Device_Response = record {
  header: Header;
  data: case(header.header) of {
    DEVICE_HEADER -> head_cmd: HeaderCmd(header.cmd2);
    default -> unknown: bytestring &restofdata;
  };
} &byteorder=littleendian;

Also, if you only ever expect to see that DEVICE_HEADER value and not
any other header values for this protocol, you might just use an
&enforce attribute at the top level instead of a switch/case.

- Jon

Hey Jon,

Thanks for the reply. Works great. However, what’s the best way to continue to keep on switching inside Record_A/B ? With your incorporated suggestions previously:

enum cmd_codes {
NOP = 0x00000000,
DEVICE_HEADER = 0x AABBCCDD,
DEVICE_CMD2_1 = 0x0304,
DEVICE_CMD2_2 = 0x0405

};

type Header = record {
header: uint32; # header
cmd1: uint16; # 0x0102
cmd2: uint16; # 0x0304 or 0x0405
} &byteorder=bigendian;

type Device_Response = record {
header: Device_Header;
data: case(header.header) of {
DEVICE_HEADER → head: HeaderCmd(header.cmd2);

All the rest

default → unknown: bytestring &restofdata;
};
} &byteorder=littleendian;

type HeaderCmd(cmd: uint16) = case cmd of {
DEVICE_CMD2_1 → info1: Record_A;
DEVICE_CMD2_2 → info2: Record_B;
};

type Record_A = record {

title: uint16;
author: uint32;
text: uint64;
ending: uint8;
}

type Record_B = record {

title: uint16;
author: uint32;
text: uint64;

ending: uint8;

}

Based on the ending for each Record, I’d switch to another set. Would I set it up like Device_Response from above if Record_A and Record_B share some data before the switch occurs?

type Record_Response = record {
header: Record_A ;
data: case(header.ending) of {
HAPPY → happy: EndingCmd(header.ending);

SAD → sad: EndingCmd(header.ending);

All the rest

default → unknown: bytestring &restofdata;
};
} &byteorder=littleendian;

type EndingCmd(ending : uint16) = case ending of {
HAPPY_1 → info1: Record_A;
HAPPY_2 → info2: Record_B;
};

type Ending = record {
ending: bytestring;

}

All of the above is under protocol.pac. How would one access information from analyzer.pac? I could send you some files if none of this makes sense.

Thanks!

type HeaderCmd(cmd: uint16) = case cmd of {
  DEVICE_CMD2_1 -> info1: Record_A;
  DEVICE_CMD2_2 -> info2: Record_B;
};

type Record_A = record {
title: uint16;
author: uint32;
text: uint64;
ending: uint8;
}

type Record_B = record {
title: uint16;
author: uint32;
text: uint64;
ending: uint8;
}

Based on the ending for each Record, I'd switch to another set. Would I set it up like Device_Response from above if Record_A and Record_B share some data before the switch occurs?

It's up to you, but if Record_{A,B} share a common format, you might
change HeaderCmd to be a record that contains the common fields plus
an extra switch. Something like:

type HeaderCmd(cmd: uint16) = record {
  title: uint16;
  author: uint32;
  text: uint64;
  ending: uint8;
  endcmd: case ending of {
    ...
  };
};

All of the above is under protocol.pac. How would one access information from analyzer.pac?

You can see most analyzers in the Bro source tree for examples, but a
typical pattern is refining the type to add some post-processing
function. e.g.:

refine connection My_Conn += {
  function proc_header_cmd(rec: HeaderCmd): bool
    %{
    // Add your code here
    return true;
    %}
};

refine typeattr HeaderCmd += &let {
  proc: bool = $context.connection.proc_header_cmd(this);
};

There's nothing special about the filenames -analyzer.pac vs.
-protocol.pac, you can access the same type definitions wherever as
long as the %include setup is correct, but the convention that Bro
tends to use is to put protocol parsing logic in -protocol.pac and
additional protocol analysis/validation logic in -analyzer.pac.

- Jon