Recieve packet from socket in Erlang active mode with ASCII packet size header

zxq9 zxq9@REDACTED
Tue Jan 5 15:51:59 CET 2021


Hi, George.

Reply is below.

On 2021/01/04 22:40, George Hope wrote:
> I have Erlang SSL TCP socket which has a permanent TCP connection to 
> other party,
> we use a protocol similar to ISO8583 protocol four first byte is a 
> packet size which is ASCII encoded.
> Based on Erlang inet documentation 
> (https://erlang.org/doc/man/inet.html) It only supports unsigned integer 
> for packet size.
> 
> ``` The header length can be one, two, or four bytes, and containing an 
> unsigned integer in big-endian byte order.```
> 
> right now I use gen_server handle_info, as soon as I receive a packet I 
> read first four byte then compare it to Binary size and if the binary 
> size is small I do nothing and put recieved binary to LastBin and wait 
> for rest of packet and If more than one msg is in packet I call read_iso 
> packet several times, short sample if what I do is like this:

This sounds ISO-8583 -ish. Note that 8583 allows several ways to encode 
a message's length, not just as ASCII text in the first 4 bytes -- so 
just make sure you know what kind of data your receiving.

Anyway, your solution is headed in the right direction, but it can be 
expressed a bit more concisely and also needs to account for cases where 
you have two messages joined up (so the tail of one message arrives in 
the same message as the beginning of the next message), as well as 
handling cases where you receive less than 4 bytes of the beginning of a 
message (which usually occurs when the network has joined up the border 
of two messages).

Here is one way of approaching that (note the following code is untested 
and might have typos or whatever, but hopefully it expresses the idea):


handle_info({ssl, Socket, Bin}, State = #s{socket = Socket}) ->
     NewState = rx(Bin, State),
     {noreply, NewState};
handle_info({tcp, Socket, Bin}, State = #s{socket = Socket}) ->
     NewState = rx(Bin, State),
     {noreply, NewState}.


rx(Bin = <<ASCII:4/binary, _/binary>>, State = #s{left = none, acc = 
none}) ->
     Size = binary_to_integer(ASCII),
     Received = byte_size(Bin),
     if
         Received == Size ->
             ok = do_something(Bin),
             State;
         Received >  Size ->
             <<Message:Size/binary, Rest/binary>> = Bin,
             ok = do_something(Message),
             rx(Rest, State);
         Received  < Size ->
             Left = Size - Received,
             State#s{left = Left, acc = Bin}
     end;
rx(Bin, State = #s{left = none, acc = none}) ->
     State#s{left = none, acc = Bin};
rx(Bin, State = #s{left = none, acc = Acc}) ->
     NewBin = <<Acc/binary, Bin/binary>>,
     rx(NewBin, State#s{acc = none});
rx(Bin, State = #s{left = Left, acc = Acc}) when Left == byte_size(Bin) ->
     Message = <<Acc/binary, Bin/binary>>,
     ok = do_something(Message),
     State#s{left = none, acc = none};
rx(Bin, State = #s{left = Left, acc = Acc}) ->
     Received = byte_size(Bin),
     case Left - Received of
         NewLeft when NewLeft > 0 ->
             NewAcc = <<Acc/binary, Bin/binary>>,
             State#s{left = NewLeft, acc = NewAcc};
         NewLeft when NewLeft < 0 ->
             <<Tail:Left/binary, Rest/binary>> = Bin,
             Message = <<Acc/binary, Tail/binary>>,
             ok = do_something(Message),
             rx(Rest, State#s{left = none, acc = none})
     end.


The only reason for the mess is handling the border case between 
messages and the annoying reality of it being possible to receive less 
than the first 4 bytes of a message.

In the above example the leading 4 bytes is included in the message 
passed to do_something/1 and the message size value in the header 
includes the header itself. Changing details like that around is a 
fairly trivial matter.

Hopefully this explains more than it confuses.

-Craig


More information about the erlang-questions mailing list