Skip to main content

src/vpn_frame.erl

%%%-------------------------------------------------------------------
%% @doc VPN packet framing.
%%%-------------------------------------------------------------------
-module(vpn_frame).

-export([encode/3, decode/1]).

-define(VERSION, 1).
-define(TYPE_DATA, 1).
-define(HEADER_SIZE, 12).

encode(PeerId, Seq, Payload) when is_integer(Seq),
                                  Seq >= 0,
                                  is_binary(Payload) ->
    PeerIdBin = peer_id_to_binary(PeerId),
    PeerLen = byte_size(PeerIdBin),
    case PeerLen =< 16#FFFF of
        true ->
            <<?VERSION:8,
              ?TYPE_DATA:8,
              Seq:64/unsigned,
              PeerLen:16/unsigned,
              PeerIdBin:PeerLen/binary,
              Payload/binary>>;
        false ->
            erlang:error({peer_id_too_large, PeerLen})
    end.

decode(Binary) when is_binary(Binary), byte_size(Binary) < ?HEADER_SIZE ->
    {error, truncated_frame};
decode(<<Version:8, _/binary>>) when Version =/= ?VERSION ->
    {error, {unsupported_version, Version}};
decode(<<?VERSION:8, ?TYPE_DATA:8, Seq:64/unsigned, PeerLen:16/unsigned, Rest/binary>>) ->
    case Rest of
        <<PeerIdBin:PeerLen/binary, Payload/binary>> ->
            {ok, #{version => ?VERSION,
                   type => data,
                   seq => Seq,
                   peer_id => PeerIdBin,
                   payload => Payload}};
        _ ->
            {error, truncated_peer_id}
    end;
decode(<<_Version:8, Type:8, _/binary>>) ->
    {error, {unsupported_type, Type}}.

peer_id_to_binary(PeerId) when is_atom(PeerId) ->
    atom_to_binary(PeerId, utf8);
peer_id_to_binary(PeerId) when is_binary(PeerId) ->
    PeerId;
peer_id_to_binary(PeerId) ->
    erlang:error({invalid_peer_id, PeerId}).