From afac3c7136c48d8630bd400c5454e146915e634f Mon Sep 17 00:00:00 2001 From: Serge Aleynikov Date: Mon, 26 Oct 2015 14:46:54 +0100 Subject: erts: Add {line_delimiter, byte()} option to inet:setopts/2 A new {line_delimiter, byte()} option allows line-oriented TCP-based protocols to use a custom line delimiting character. It is to be used in conjunction with {packet, line}. This option also works with erlang:decode_packet/3 when its first argument is 'line'. --- erts/doc/src/erlang.xml | 4 ++++ erts/emulator/beam/atom.names | 1 + erts/emulator/beam/erl_bif_port.c | 10 ++++++++-- erts/emulator/beam/packet_parser.c | 5 +++-- erts/emulator/beam/packet_parser.h | 3 ++- erts/emulator/drivers/common/inet_drv.c | 11 ++++++++++- erts/preloaded/ebin/prim_inet.beam | Bin 72748 -> 72712 bytes erts/preloaded/src/prim_inet.erl | 3 +++ lib/kernel/doc/src/inet.xml | 5 +++++ lib/kernel/src/inet.erl | 4 +++- lib/kernel/src/inet_int.hrl | 1 + lib/kernel/test/bif_SUITE.erl | 10 ++++++++++ lib/kernel/test/gen_tcp_api_SUITE.erl | 17 ++++++++++++++++- 13 files changed, 66 insertions(+), 8 deletions(-) diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index 0492924164..1963b27915 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -933,6 +933,10 @@ if packet_size itself is not set. This use is only intended for backward compatibility.

+ {line_delimiter, 0 ≤ char() ≤ 255} +

For packet type line, sets delimiting character. + Default $\n.

+

Examples:

diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names
index f9a2f3e33e..190e7817dc 100644
--- a/erts/emulator/beam/atom.names
+++ b/erts/emulator/beam/atom.names
@@ -321,6 +321,7 @@ atom ldflags
 atom Le='=<'
 atom lf
 atom line
+atom line_delimiter
 atom line_length
 atom linked_in_driver
 atom links
diff --git a/erts/emulator/beam/erl_bif_port.c b/erts/emulator/beam/erl_bif_port.c
index 3ff54c7a60..e47d7bcbbb 100644
--- a/erts/emulator/beam/erl_bif_port.c
+++ b/erts/emulator/beam/erl_bif_port.c
@@ -1329,7 +1329,8 @@ BIF_RETTYPE decode_packet_3(BIF_ALIST_3)
     ErlSubBin* rest;
     Eterm res;
     Eterm options;
-    int code;
+    int   code;
+    char  delimiter = '\n';
 
     if (!is_binary(BIF_ARG_2) || 
         (!is_list(BIF_ARG_3) && !is_nil(BIF_ARG_3))) {
@@ -1370,6 +1371,11 @@ BIF_RETTYPE decode_packet_3(BIF_ALIST_3)
                 case am_line_length:
                     trunc_len = val;
                     goto next_option;
+                case am_line_delimiter:
+                    if (type == TCP_PB_LINE_LF && val >= 0 && val <= 255) {
+                        delimiter = (char)val;
+                        goto next_option;
+                    }
                 }
             }
         }
@@ -1390,7 +1396,7 @@ BIF_RETTYPE decode_packet_3(BIF_ALIST_3)
         pca.aligned_ptr = bin_ptr;
     }
     packet_sz = packet_get_length(type, (char*)pca.aligned_ptr, pca.bin_sz,
-                                  max_plen, trunc_len, &http_state);
+                                  max_plen, trunc_len, delimiter, &http_state);
     if (!(packet_sz > 0 && packet_sz <= pca.bin_sz)) {
         if (packet_sz < 0) {
 	    goto error;
diff --git a/erts/emulator/beam/packet_parser.c b/erts/emulator/beam/packet_parser.c
index 2dd421a9e9..a737a86f14 100644
--- a/erts/emulator/beam/packet_parser.c
+++ b/erts/emulator/beam/packet_parser.c
@@ -256,6 +256,7 @@ int packet_get_length(enum PacketParseType htype,
                       const char* ptr, unsigned n, /* Bytes read so far */
                       unsigned max_plen,     /* Max packet length, 0=no limit */
                       unsigned trunc_len,    /* Truncate (lines) if longer, 0=no limit */
+                      char     delimiter,    /* Line delimiting character */
                       int*     statep)       /* Protocol specific state */
 {
     unsigned hlen, plen;
@@ -299,9 +300,9 @@ int packet_get_length(enum PacketParseType htype,
         goto remain;
 
     case TCP_PB_LINE_LF: {
-        /* TCP_PB_LINE_LF:  [Data ... \n]  */
+        /* TCP_PB_LINE_LF:  [Data ... Delimiter]  */
         const char* ptr2;
-        if ((ptr2 = memchr(ptr, '\n', n)) == NULL) {
+        if ((ptr2 = memchr(ptr, delimiter, n)) == NULL) {
             if (n > max_plen && max_plen != 0) { /* packet full */
                 DEBUGF((" => packet full (no NL)=%d\r\n", n));
                 goto error;
diff --git a/erts/emulator/beam/packet_parser.h b/erts/emulator/beam/packet_parser.h
index ff158ff8b8..717d905fad 100644
--- a/erts/emulator/beam/packet_parser.h
+++ b/erts/emulator/beam/packet_parser.h
@@ -105,7 +105,8 @@ int packet_get_length(enum PacketParseType htype,
 		      const char* ptr, unsigned n,  /* Bytes read so far */
 		      unsigned max_plen,      /* Packet max length, 0=no limit */
 		      unsigned trunc_len,     /* Truncate (lines) if longer, 0=no limit */
-		      int* statep);           /* Internal protocol state */
+		      char     delimiter,     /* Line delimiting character */
+		      int*     statep);       /* Internal protocol state */
 
 ERTS_GLB_INLINE
 void packet_get_body(enum PacketParseType htype,
diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c
index 89b71aa66a..a829599fe5 100644
--- a/erts/emulator/drivers/common/inet_drv.c
+++ b/erts/emulator/drivers/common/inet_drv.c
@@ -885,6 +885,7 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n)
 #define INET_LOPT_MSGQ_LOWTRMRK     37  /* set local msgq low watermark */
 #define INET_LOPT_NETNS             38  /* Network namespace pathname */
 #define INET_LOPT_TCP_SHOW_ECONNRESET 39  /* tell user about incoming RST */
+#define INET_LOPT_LINE_DELIM        40  /* Line delimiting char */
 /* SCTP options: a separate range, from 100: */
 #define SCTP_OPT_RTOINFO		100
 #define SCTP_OPT_ASSOCINFO		101
@@ -1154,6 +1155,7 @@ typedef struct {
 #else
     Uint32        send_oct[2];  /* number of octets sent, 64 bits */
 #endif
+    char          delimiter;    /* Line delimiting character (def: '\n')  */
     unsigned long send_cnt;     /* number of packets sent */
     unsigned long send_max;     /* maximum packet send */
     double send_avg;            /* average packet size sent */
@@ -6276,6 +6278,12 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
 	    }
 	    continue;
 
+	case INET_LOPT_LINE_DELIM:
+	    DEBUGF(("inet_set_opts(%ld): s=%d, LINE_DELIM=%d\r\n",
+		    (long)desc->port, desc->s, ival));
+	    desc->delimiter = (char)ival;
+	    continue;
+
 	case INET_OPT_REUSEADDR: 
 #ifdef __WIN32__
 	    continue;  /* Bjorn says */
@@ -8371,6 +8379,7 @@ static ErlDrvData inet_start(ErlDrvPort port, int size, int protocol)
     desc->deliver = INET_DELIVER_TERM; /* standard term format */
     desc->active  = INET_PASSIVE;      /* start passive */
     desc->active_count = 0;
+    desc->delimiter    = '\n';         /* line delimiting char */
     desc->oph = NULL;
     desc->opt = NULL;
 
@@ -9882,7 +9891,7 @@ static int tcp_remain(tcp_descriptor* desc, int* len)
 
     tlen = packet_get_length(desc->inet.htype, ptr, n, 
                              desc->inet.psize, desc->i_bufsz,
-                             &desc->http_state);
+                             desc->inet.delimiter, &desc->http_state);
 
     DEBUGF(("tcp_remain(%ld): s=%d, n=%d, nfill=%d nsz=%d, tlen %d\r\n",
 	    (long)desc->inet.port, desc->inet.s, n, nfill, nsz, tlen));
diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam
index 5a188be3ba..8b87d1ae26 100644
Binary files a/erts/preloaded/ebin/prim_inet.beam and b/erts/preloaded/ebin/prim_inet.beam differ
diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl
index 4d04e1dacb..d5c8fd4268 100644
--- a/erts/preloaded/src/prim_inet.erl
+++ b/erts/preloaded/src/prim_inet.erl
@@ -1147,6 +1147,7 @@ enc_opt(packet_size)     -> ?INET_LOPT_PACKET_SIZE;
 enc_opt(read_packets)    -> ?INET_LOPT_READ_PACKETS;
 enc_opt(netns)           -> ?INET_LOPT_NETNS;
 enc_opt(show_econnreset) -> ?INET_LOPT_TCP_SHOW_ECONNRESET;
+enc_opt(line_delimiter)  -> ?INET_LOPT_LINE_DELIM;
 enc_opt(raw)             -> ?INET_OPT_RAW;
 % Names of SCTP opts:
 enc_opt(sctp_rtoinfo)	 	   -> ?SCTP_OPT_RTOINFO;
@@ -1205,6 +1206,7 @@ dec_opt(?INET_LOPT_PACKET_SIZE)      -> packet_size;
 dec_opt(?INET_LOPT_READ_PACKETS)     -> read_packets;
 dec_opt(?INET_LOPT_NETNS)           -> netns;
 dec_opt(?INET_LOPT_TCP_SHOW_ECONNRESET) -> show_econnreset;
+dec_opt(?INET_LOPT_LINE_DELIM)      -> line_delimiter;
 dec_opt(?INET_OPT_RAW)              -> raw;
 dec_opt(I) when is_integer(I)     -> undefined.
 
@@ -1287,6 +1289,7 @@ type_opt_1(packet) ->
 	   {httph_bin,?TCP_PB_HTTPH_BIN},
 	   {ssl, ?TCP_PB_SSL_TLS}, % obsolete
 	   {ssl_tls, ?TCP_PB_SSL_TLS}]};
+type_opt_1(line_delimiter)  -> int;
 type_opt_1(mode) ->
     {enum,[{list, ?INET_MODE_LIST},
 	   {binary, ?INET_MODE_BINARY}]};
diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml
index e5d7ce048a..e6d418dc58 100644
--- a/lib/kernel/doc/src/inet.xml
+++ b/lib/kernel/doc/src/inet.xml
@@ -981,6 +981,11 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp
 	    indicated length are accepted and not considered invalid due
 	    to internal buffer limitations.

+ {line_delimiter, Char}(TCP/IP sockets) + +

Sets the line delimiting character for line oriented protocols + (line). Default value is $\n.

+
{priority, Priority}

Set the protocol-defined priority for all packets to be sent diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index da7f04089d..855c6377a3 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -671,7 +671,7 @@ stats() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% connect_options() -> [tos, priority, reuseaddr, keepalive, linger, sndbuf, recbuf, nodelay, - header, active, packet, packet_size, buffer, mode, deliver, + header, active, packet, packet_size, buffer, mode, deliver, line_delimiter, exit_on_close, high_watermark, low_watermark, high_msgq_watermark, low_msgq_watermark, send_timeout, send_timeout_close, delay_send, raw, show_econnreset]. @@ -721,6 +721,8 @@ con_opt([Opt | Opts], #connect_opts{} = R, As) -> {active,N} when is_integer(N), N < 32768, N >= -32768 -> NOpts = lists:keydelete(active, 1, R#connect_opts.opts), con_opt(Opts, R#connect_opts { opts = [{active,N}|NOpts] }, As); + {line_delimiter,C} when is_integer(C), C >= 0, C =< 255 -> + con_add(line_delimiter, C, R, Opts, As); {Name,Val} when is_atom(Name) -> con_add(Name, Val, R, Opts, As); _ -> {error, badarg} end; diff --git a/lib/kernel/src/inet_int.hrl b/lib/kernel/src/inet_int.hrl index bfe4c9ec8c..e7c6cf8ae2 100644 --- a/lib/kernel/src/inet_int.hrl +++ b/lib/kernel/src/inet_int.hrl @@ -149,6 +149,7 @@ -define(INET_LOPT_MSGQ_LOWTRMRK, 37). -define(INET_LOPT_NETNS, 38). -define(INET_LOPT_TCP_SHOW_ECONNRESET, 39). +-define(INET_LOPT_LINE_DELIM, 40). % Specific SCTP options: separate range: -define(SCTP_OPT_RTOINFO, 100). -define(SCTP_OPT_ASSOCINFO, 101). diff --git a/lib/kernel/test/bif_SUITE.erl b/lib/kernel/test/bif_SUITE.erl index c3840f3d16..dd3010567a 100644 --- a/lib/kernel/test/bif_SUITE.erl +++ b/lib/kernel/test/bif_SUITE.erl @@ -33,6 +33,7 @@ spawn_failures/1, run_fun/1, + decode_packet_delim/1, wilderness/1]). -export([init_per_testcase/2, end_per_testcase/2]). @@ -516,6 +517,15 @@ fetch_proc_vals(Pid) -> {value,{heap_size,HS}} = lists:keysearch(heap_size, 1, PI), ?line {Ls, P, FA, HS}. +decode_packet_delim(doc) -> + ["Test erlang:packet_delim/3 with {line_delimiter,0} option"]; +decode_packet_delim(suite) -> + []; +decode_packet_delim(Config) when is_list(Config) -> + {ok,<<"abc",0>>,<<"efg",0>>} = + erlang:decode_packet(line, <<"abc",0,"efg",0>>, [{line_delimiter, 0}]), + {more, undefined} = erlang:decode_packet(line, <<"abc",0,"efg",0>>, []). + % This testcase should probably be moved somewhere else wilderness(doc) -> ["Test that memory allocation command line options affecting the" diff --git a/lib/kernel/test/gen_tcp_api_SUITE.erl b/lib/kernel/test/gen_tcp_api_SUITE.erl index a051d504b2..2febb1bd68 100644 --- a/lib/kernel/test/gen_tcp_api_SUITE.erl +++ b/lib/kernel/test/gen_tcp_api_SUITE.erl @@ -31,7 +31,7 @@ init_per_testcase/2, end_per_testcase/2, t_connect_timeout/1, t_accept_timeout/1, t_connect_bad/1, - t_recv_timeout/1, t_recv_eof/1, + t_recv_timeout/1, t_recv_eof/1, t_recv_delim/1, t_shutdown_write/1, t_shutdown_both/1, t_shutdown_error/1, t_shutdown_async/1, t_fdopen/1, t_fdconnect/1, t_implicit_inet6/1]). @@ -131,6 +131,21 @@ t_recv_eof(Config) when is_list(Config) -> ?line {error, closed} = gen_tcp:recv(Client, 0), ok. +t_recv_delim(doc) -> "Test using message delimiter $X"; +t_recv_delim(suite) -> []; +t_recv_delim(Config) when is_list(Config) -> + {ok, L} = gen_tcp:listen(0, []), + {ok, Port} = inet:port(L), + Opts = [{active,false},{packet,line},{line_delimiter,$X}], + {ok, Client} = gen_tcp:connect(localhost, Port, Opts), + {ok, A} = gen_tcp:accept(L), + ok = gen_tcp:send(A, "abcXefgX"), + {ok, "abcX"} = gen_tcp:recv(Client, 0, 0), + {ok, "efgX"} = gen_tcp:recv(Client, 0, 0), + ok = gen_tcp:close(Client), + ok = gen_tcp:close(A), + ok. + %%% gen_tcp:shutdown/2 t_shutdown_write(Config) when is_list(Config) -> -- cgit v1.2.3