-module(emqx_tcp_frame).
-include("/emqx_tcp/include/emqx_tcp.hrl").
-export([initial_parse_state/1, parse/2, serialize/2]).
-export([format/1]).
%% 初始化解析状态
initial_parse_state(Options) when is_map(Options) ->{none, merge_opts(Options)}.
%% 合并解析配置
merge_opts(Options) ->maps:merge(#{max_size => 65535, version => 1}, Options).
%% 解析空数据
parse(<<>>, {none, Options}) ->
{ok,{more, fun (Bin) -> parse(Bin, {none, Options}) end}};
%% 读取数据包的类型和标志
parse(<<Type:4, Flags:4, Rest/binary>>, {none, Options}) ->
parse_frame_type(Type, Flags, Rest, Options);
parse(Bin, {more, Cont})when is_binary(Bin), is_function(Cont) -> Cont(Bin).
%% 解析类型为Type = 1(FRAME_TYPE_CONN 连接类型),Flags = 1的数据包
parse_frame_type(1, 1, Rest, Options) ->
case run_read_funs([fun read_length_binary/1], Rest) of
{ok, Rest1, [ConnPayload]} ->
%% 解析连接负载
case parse_conn_payload(ConnPayload) of
{error, Reason} -> {error, Reason};
%% 获取用户名和密码
{Keepalive, ClientId, Username, Password} ->
Pkt = #tcp_packet_conn{client_id = ClientId,
keepalive = Keepalive,
username = Username,
password = Password, version = 1},
{ok, Pkt, Rest1, {none, Options#{version => 1}}}
end;
{more, Rest} ->
{ok,
{more,fun (Bin) ->
parse_frame_type(1,1,<<Rest/binary, Bin/binary>>,Options)
end}}
end;
parse_frame_type(1, Version, _Rest, _Options) ->
{error, {not_supported_version, Version}};
%% FRAME_TYPE_DATATRANS 业务数据报文
parse_frame_type(3, Flags, Rest,Options = #{max_size := MaxSize}) ->
case run_read_funs([fun read_length_binary/1], Rest) of
{ok, Rest1, [Data]} ->
%% 计算数据报文
case byte_size(Data) of
%% 数据长度大于最大长度
Len when Len > MaxSize ->
{error, {max_size_limit, Len, MaxSize}};
Len ->
%% 构造数据报文
Pkt = #tcp_packet_datatrans{length = Len, data = Data},
%% 返回ok
{ok, Pkt, Rest1, {none, Options}}
end;
%% 有更多数据包仍需处理
{more, Rest} ->
{ok,
{more,
fun (Bin) ->
parse_frame_type(3,Flags,<<Rest/binary, Bin/binary>>,Options)
end}}
end;
%% 解析报文类型为4的数据包
parse_frame_type(4, _Flags, Rest, Options) ->
{ok, #tcp_packet_ping{}, Rest, {none, Options}};
%% 解析报文类型为6的数据包
parse_frame_type(6, _Flags, Rest, Options) ->
{ok, #tcp_packet_disconn{}, Rest, {none, Options}};
%% 非法报文
parse_frame_type(Type, Flags, Rest, Options) ->
{error, {illegal_frame, {Type, Flags, Rest, Options}}}.
%% 解析连接报文
parse_conn_payload(<<K:8, L1:16, ClientId:L1/binary>>) ->
{K, ClientId, undefined, undefined};
parse_conn_payload(<<K:8, L1:16, ClientId:L1/binary, L2:16, Username:L2/binary>>) ->
{K, ClientId, Username, undefined};
parse_conn_payload(<<K:8, L1:16, ClientId:L1/binary,L2:16, Username:L2/binary, L3:16,
Password:L3/binary>>) ->
{K, ClientId, Username, Password};
parse_conn_payload(_) ->{error, invalid_conn_payload_format}.
%% 读取二进制里面的数据
-spec run_read_funs(list(), binary()) -> {ok,binary(),
ReadResult :: list()} |{more, binary()}.
run_read_funs(Funs, Bin) when is_list(Funs) and is_binary(Bin) ->
case run_read_funs(Funs, Bin, []) of
{ok, Remaining, Results} -> {ok, Remaining, Results};
{pause, _, _} -> {more, Bin}
end.
run_read_funs([], Bin, Acc) ->{ok, Bin, lists:reverse(Acc)};
run_read_funs([Fun | RFuns], Bin, Acc) ->
case Fun(Bin) of
{more, Bin} -> {pause, Bin, lists:reverse(Acc)};
{Content, RestBin} -> run_read_funs(RFuns, RestBin, [Content | Acc])
end.
%% 读数据长度
-spec read_length_binary(binary()) -> {more, binary()} |
{Content :: binary(), Rest :: binary()}.
%% 小于2,说明数据包未读完
read_length_binary(Bin) when byte_size(Bin) < 2 ->
{more, Bin};
%% 匹配两个字节数据,取出长度值
read_length_binary(<<Len:16, Rest/binary>> = Bin) ->
%% 如果数据包Rest的长度大于提取的长度,说明还有更多数据
case byte_size(Rest) >= Len of
false -> {more, Bin};
true ->
%% 提取Rest里面的数据{数据长度,数据内容}
<<Content:Len/binary, Rest1/binary>> = Rest,
%% 返回内容和剩余的数据Rest1
{Content, Rest1}
end.
%% 序列化连接数据包
serialize(#tcp_packet_conn{client_id = ClientId,keepalive = Keepalive,
username = Username,
password = Password, version = Version}, _Opts) ->
%% 负载数据二进制化
Payload = <<Keepalive:8, (lbin(ClientId))/binary,
(encode_username_and_passowrd(Username, Password))/binary>>,
%% 计算负载部分数据长度
LenOfPaylaod = byte_size(Payload),
%% 组成数据包返回
<<1:4, Version:4, LenOfPaylaod:16, Payload/binary>>;
%% 连接确认数据包序列化
serialize(#tcp_packet_connack{code = Code, msg = Msg},_Opts) ->
<<2:4, Code:4, (lbin(Msg))/binary>>;
%% 业务数据包
serialize(#tcp_packet_datatrans{data = Data}, _Opts) ->
<<3:4, 0:4, (lbin(Data))/binary>>;
serialize(#tcp_packet_ping{}, _Opts) -> <<4:4, 0:4>>;
serialize(#tcp_packet_pong{}, _Opts) -> <<5:4, 0:4>>;
serialize(#tcp_packet_disconn{}, _Opts) -> <<6:4, 0:4>>.
%% 用户和密码内容编码
encode_username_and_passowrd(undefined, undefined) -><<>>;
encode_username_and_passowrd(Username, undefined) -><<(lbin(Username))/binary>>;
encode_username_and_passowrd(Username, Password)when is_binary(Username),
is_binary(Password) ->
<<(lbin(Username))/binary, (lbin(Password))/binary>>;
encode_username_and_passowrd(Username, Password) ->
error({not_supported_username_password,Username,Password}).
lbin(B) when is_binary(B) -><<(byte_size(B)):16, B/binary>>.
format(#tcp_packet_conn{client_id = ClientId,username = Username}) ->
io_lib:format("CONNECT(client_id=~s, username=~p)",[ClientId, Username]);
format(#tcp_packet_connack{code = Code, msg = Msg}) ->
io_lib:format("CONNACK(code=~p, msg=~s)", [Code, Msg]);
format(#tcp_packet_datatrans{length = Len, data = Data}) ->
io_lib:format("DATATRANS(length=~p, data=~p)",[Len, Data]);
format(#tcp_packet_ping{}) -> io_lib:format("PING", []);
format(#tcp_packet_pong{}) -> io_lib:format("PONG", []);
format(#tcp_packet_disconn{}) ->io_lib:format("DISCONN", []).
emqx私有tcp协议服务器开发---emqx_tcp_frame模块
猜你喜欢
转载自blog.csdn.net/qq513036862/article/details/110312013
今日推荐
周排行