erlang解析协议
Table of Contents
erlang虽然是一门相对比较冷门的语言,但是它有一个功能我非常喜欢。它有一种叫bit syntax的语法,可以用声明式的写法来解析二进制报文中的字段,非常简单易用,我平时写一些协议相关的demo,最喜欢用它来做这种协议解析的活。
语法
Value:Size/TypeSpecifierList
Value可以是任何表达式,Size*TypeSpecifierList 就是二进制的位数,实际使用可以省略Size或者TypeSpecifier,也可以两者都不写,就像下面这样。
Value Value:Size Value/TypeSpecifierList
例子
A = 1, B = 17, C = 42, Bin2 = <<A, B, C:16>> <<D:16, E, F/binary>> = Bin2
这是一个bit syntax的二进制模式匹配写法,用<<>>的符号括起来。D:16是Value:Size的写法,表示从二进制数据中获取16位,F/binary则是Value/TypeSpecifierlist的写法,E和F会获取后面两个8位字节。
A = 1, B = 17, C = 42, Bin2 = <<A, B, C:16>>
这部分代码,生成了[1, 17, 00, 42],也就是
1 | 17 | 00 | 42 |
这样的字节流,然后用
<<D:16, E, F/binary>> = Bin2
去匹配这个字节流。D会匹配到1 17这前两个字节,E和F则匹配到了00 和 42,以此来达到协议解析的目的。
这就是erlang的bit syntax, 可以像填写表格一样来解析二进制协议 。
demo1 解析二进制报文
创建一个名为 ip_parser.erl 的文件,用来解析ip头:
-module(ip_parser). -export([parse_ip_datagram/1, demo/0]). -define(IP_VERSION, 4). -define(IP_MIN_HDR_LEN, 5). %% 解析 IP 数据报的函数 parse_ip_datagram(Dgram) -> DgramSize = byte_size(Dgram), case Dgram of <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16, ID:16, Flgs:3, FragOff:13, TTL:8, Proto:8, HdrChkSum:16, SrcIP:32, DestIP:32, RestDgram/binary>> when HLen >= ?IP_MIN_HDR_LEN, 4 * HLen =< DgramSize -> OptsLen = 4 * (HLen - ?IP_MIN_HDR_LEN), <<Opts:OptsLen/binary, Data/binary>> = RestDgram, {ip_header, HLen, SrvcType, TotLen, ID, Flgs, FragOff, TTL, Proto, HdrChkSum, SrcIP, DestIP, Opts, Data}; _ -> {error, invalid_datagram} end. %% 演示函数 demo() -> %% 构造一个示例 IP 数据报 %% IP 版本 4, 头部长度 5, 服务类型 0, 总长度 20, ID 54321, 标志 0, 片偏移 0, %% TTL 64, 协议 TCP, 头部校验和 0, 源地址 127.0.0.1, 目的地址 192.168.0.1 Datagram = <<4:4, 5:4, 0:8, 20:16, 54321:16, 0:3, 0:13, 64:8, 6:8, 0:16, 127, 0, 0, 1, 192, 168, 0, 1, "Hello, world!">>, io:format("Parsing Datagram: ~p~n", [Datagram]), Result = parse_ip_datagram(Datagram), io:format("Result: ~p~n", [Result]), Result.
编译运行
$ erlc ip_parser.erl $ erl 1> ip_parser:demo(). Parsing Datagram: <<69,0,0,20,212,49,0,0,64,6,0,0,127,0,0,1,192,168,0,1,72,101, 108,108,111,44,32,119,111,114,108,100,33>> Result: {ip_header,5,0,20,54321,0,0,64,6,0,2130706433,3232235521,<<>>, <<"Hello, world!">>} {ip_header,5,0,20,54321,0,0,64,6,0,2130706433,3232235521, <<>>,<<"Hello, world!">>}
demo2 从bin文件读取解析
把上面的代码修改一下 ip_parser_from_file.erl
-module(ip_parser_from_file). -export([parse_ip_datagram/1, demo/0]). -define(IP_VERSION, 4). -define(IP_MIN_HDR_LEN, 5). %% 解析 IP 数据报的函数 parse_ip_datagram(Dgram) -> DgramSize = byte_size(Dgram), case Dgram of <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16, ID:16, Flgs:3, FragOff:13, TTL:8, Proto:8, HdrChkSum:16, SrcIP:32, DestIP:32, RestDgram/binary>> when HLen >= ?IP_MIN_HDR_LEN, 4 * HLen =< DgramSize -> OptsLen = 4 * (HLen - ?IP_MIN_HDR_LEN), <<Opts:OptsLen/binary, Data/binary>> = RestDgram, {ip_header, HLen, SrvcType, TotLen, ID, Flgs, FragOff, TTL, Proto, HdrChkSum, SrcIP, DestIP, Opts, Data}; _ -> {error, invalid_datagram} end. %% 演示函数 demo() -> {ok, DatagramBin} = file:read_file("datagram.bin"), io:format("Parsing Datagram: ~p~n", [DatagramBin]), Result = parse_ip_datagram(DatagramBin), io:format("Result: ~p~n", [Result]), Result.
构建一个datagram.bin,用十六进制编辑器编辑下面内容:
45000028000040008006a3fe7f000001c0a80001 48656c6c6f2c20776f726c6421
编译运行
$ erlc ip_parser_from_file.erl $ erl 1> ip_parser_from_file:demo(). Parsing Datagram: <<69,0,0,40,0,0,64,0,128,6,163,254,127,0,0,1,192,168,0,1,72, 101,108,108,111,44,32,119,111,114,108,100,33>> Result: {ip_header,5,0,40,0,2,0,128,6,41982,2130706433,3232235521,<<>>, <<"Hello, world!">>} {ip_header,5,0,40,0,2,0,128,6,41982,2130706433,3232235521, <<>>,<<"Hello, world!">>}