diff --git a/erts/emulator/beam/erl_bif_port.c b/erts/emulator/beam/erl_bif_port.c index cfa351a7550e..668308a97e5c 100644 --- a/erts/emulator/beam/erl_bif_port.c +++ b/erts/emulator/beam/erl_bif_port.c @@ -811,6 +811,8 @@ open_port(Process* p, Eterm name, Eterm settings, int *err_typep, int *err_nump) /* These are the defaults */ opts.packet_bytes = 0; + opts.packet_header_endianness = + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_BIG; opts.use_stdio = 1; opts.redir_stderr = 0; opts.read_write = 0; @@ -847,18 +849,49 @@ open_port(Process* p, Eterm name, Eterm settings, int *err_typep, int *err_nump) arity = *tp++; option = *tp++; if (option == am_packet) { - if (is_not_small(*tp)) { - goto bad_settings; - } - opts.packet_bytes = signed_val(*tp); - switch (opts.packet_bytes) { - case 1: - case 2: - case 4: - break; - default: - goto bad_settings; - } + if (is_tuple_arity(*tp, 2)) { + Eterm *packet = tuple_val(*tp); + Sint packet_bytes; + + if (is_not_small(packet[1])) + goto bad_settings; + + packet_bytes = signed_val(packet[1]); + if (packet_bytes < 2 || packet_bytes > 4) + goto bad_settings; + opts.packet_bytes = (int) packet_bytes; + + if (packet[2] == am_big) { + opts.packet_header_endianness = + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_BIG; + } else if (packet[2] == am_little) { + opts.packet_header_endianness = + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE; + } else if (packet[2] == am_native) { +#if defined(WORDS_BIGENDIAN) + opts.packet_header_endianness = + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_BIG; +#else + opts.packet_header_endianness = + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE; +#endif + } else { + goto bad_settings; + } + } else { + Sint packet_bytes; + + if (is_not_small(*tp)) + goto bad_settings; + + packet_bytes = signed_val(*tp); + if (packet_bytes < 1 || packet_bytes > 4) + goto bad_settings; + opts.packet_bytes = (int) packet_bytes; + + opts.packet_header_endianness = + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_BIG; + } } else if (option == am_line) { if (is_not_small(*tp)) { goto bad_settings; @@ -978,6 +1011,8 @@ open_port(Process* p, Eterm name, Eterm settings, int *err_typep, int *err_nump) } } else if (*nargs == am_stream) { opts.packet_bytes = 0; + opts.packet_header_endianness = + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_BIG; } else if (*nargs == am_use_stdio) { opts.use_stdio = 1; } else if (*nargs == am_stderr_to_stdout) { @@ -1489,25 +1524,73 @@ BIF_RETTYPE decode_packet_3(BIF_ALIST_3) int code; char delimiter = '\n'; - switch (BIF_ARG_1) { - case make_small(0): case am_raw: type = TCP_PB_RAW; break; - case make_small(1): type = TCP_PB_1; break; - case make_small(2): type = TCP_PB_2; break; - case make_small(4): type = TCP_PB_4; break; - case am_asn1: type = TCP_PB_ASN1; break; - case am_sunrm: type = TCP_PB_RM; break; - case am_cdr: type = TCP_PB_CDR; break; - case am_fcgi: type = TCP_PB_FCGI; break; - case am_line: type = TCP_PB_LINE_LF; break; - case am_tpkt: type = TCP_PB_TPKT; break; - case am_http: type = TCP_PB_HTTP; break; - case am_httph: type = TCP_PB_HTTPH; break; - case am_http_bin: type = TCP_PB_HTTP_BIN; break; - case am_httph_bin: type = TCP_PB_HTTPH_BIN; break; - case am_ssl_tls: type = TCP_PB_SSL_TLS; break; - default: - BIF_P->fvalue = am_badopt; - BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO); + if (is_tuple_arity(BIF_ARG_1, 2)) { + Eterm *packet; + Sint packet_bytes; + + packet = tuple_val(BIF_ARG_1); + if (is_not_small(packet[1])) { + BIF_P->fvalue = am_badopt; + BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO); + } + + packet_bytes = signed_val(packet[1]); + if (packet_bytes < 2 || packet_bytes > 4) { + BIF_P->fvalue = am_badopt; + BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO); + } + + switch (packet[2]) { +#if defined(WORDS_BIGENDIAN) + case am_native: +#endif + case am_big: + switch (packet_bytes) { + case 2: type = TCP_PB_2_BIG; break; + case 3: type = TCP_PB_3_BIG; break; + case 4: type = TCP_PB_4_BIG; break; + default: + ERTS_UNREACHABLE; + } + break; +#if !defined(WORDS_BIGENDIAN) + case am_native: +#endif + case am_little: + switch (packet_bytes) { + case 2: type = TCP_PB_2_LITTLE; break; + case 3: type = TCP_PB_3_LITTLE; break; + case 4: type = TCP_PB_4_LITTLE; break; + default: + ERTS_UNREACHABLE; + } + break; + default: + BIF_P->fvalue = am_badopt; + BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO); + } + } else { + switch (BIF_ARG_1) { + case make_small(0): case am_raw: type = TCP_PB_RAW; break; + case make_small(1): type = TCP_PB_1; break; + case make_small(2): type = TCP_PB_2_BIG; break; + case make_small(3): type = TCP_PB_3_BIG; break; + case make_small(4): type = TCP_PB_4_BIG; break; + case am_asn1: type = TCP_PB_ASN1; break; + case am_sunrm: type = TCP_PB_RM; break; + case am_cdr: type = TCP_PB_CDR; break; + case am_fcgi: type = TCP_PB_FCGI; break; + case am_line: type = TCP_PB_LINE_LF; break; + case am_tpkt: type = TCP_PB_TPKT; break; + case am_http: type = TCP_PB_HTTP; break; + case am_httph: type = TCP_PB_HTTPH; break; + case am_http_bin: type = TCP_PB_HTTP_BIN; break; + case am_httph_bin: type = TCP_PB_HTTPH_BIN; break; + case am_ssl_tls: type = TCP_PB_SSL_TLS; break; + default: + BIF_P->fvalue = am_badopt; + BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO); + } } if (!is_bitstring(BIF_ARG_2) || diff --git a/erts/emulator/beam/erl_sys_driver.h b/erts/emulator/beam/erl_sys_driver.h index 6d84d77d0c95..5c4c880febaf 100644 --- a/erts/emulator/beam/erl_sys_driver.h +++ b/erts/emulator/beam/erl_sys_driver.h @@ -37,6 +37,11 @@ typedef SWord ErlDrvEvent; /* An event to be selected on. */ typedef struct _SysDriverOpts SysDriverOpts; +typedef enum { + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_BIG = 0, + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE +} ErtsSysDriverPacketHeaderEndianness; + #include "erl_driver.h" /* @@ -48,6 +53,7 @@ struct _SysDriverOpts { Uint ifd; /* Input file descriptor (fd driver). */ Uint ofd; /* Outputfile descriptor (fd driver). */ int packet_bytes; /* Number of bytes in packet header. */ + ErtsSysDriverPacketHeaderEndianness packet_header_endianness; int read_write; /* Read and write bits. */ int use_stdio; /* Use standard I/O: TRUE or FALSE. */ int redir_stderr; /* Redirect stderr to stdout: TRUE/FALSE. */ @@ -69,7 +75,3 @@ struct _SysDriverOpts { }; #endif - - - - diff --git a/erts/emulator/beam/packet_parser.c b/erts/emulator/beam/packet_parser.c index 9dd243e21912..e42f90b22885 100644 --- a/erts/emulator/beam/packet_parser.c +++ b/erts/emulator/beam/packet_parser.c @@ -269,20 +269,48 @@ int packet_get_length(enum PacketParseType htype, plen = get_int8(ptr); goto remain; - case TCP_PB_2: - /* TCP_PB_2: [L1,L0 | Data] */ + case TCP_PB_2_BIG: + /* TCP_PB_2_BIG: [L1,L0 | Data] */ hlen = 2; if (n < hlen) goto more; plen = get_int16(ptr); goto remain; - case TCP_PB_4: - /* TCP_PB_4: [L3,L2,L1,L0 | Data] */ + case TCP_PB_2_LITTLE: + /* TCP_PB_2_LITTLE: [L0,L1 | Data] */ + hlen = 2; + if (n < hlen) goto more; + plen = get_little_int16(ptr); + goto remain; + + case TCP_PB_3_BIG: + /* TCP_PB_3_BIG: [L2,L1,L0 | Data] */ + hlen = 3; + if (n < hlen) goto more; + plen = get_int24(ptr); + goto remain; + + case TCP_PB_3_LITTLE: + /* TCP_PB_3_LITTLE: [L0,L1,L2 | Data] */ + hlen = 3; + if (n < hlen) goto more; + plen = get_little_int24(ptr); + goto remain; + + case TCP_PB_4_BIG: + /* TCP_PB_4_BIG: [L3,L2,L1,L0 | Data] */ hlen = 4; if (n < hlen) goto more; plen = get_int32(ptr); goto remain; + case TCP_PB_4_LITTLE: + /* TCP_PB_4_LITTLE: [L0,L1,L2,L3 | Data] */ + hlen = 4; + if (n < hlen) goto more; + plen = get_little_uint32(ptr); + goto remain; + case TCP_PB_RM: /* TCP_PB_RM: [L3,L2,L1,L0 | Data] ** where MSB (bit) is used to signal end of record @@ -882,4 +910,3 @@ int packet_parse_ssl(const char* buf, int len, return pcb->ssl_tls(arg, type, major, minor, buf+5, len-5, NULL, 0); } } - diff --git a/erts/emulator/beam/packet_parser.h b/erts/emulator/beam/packet_parser.h index e2231fcc414f..0dc6336ef2d4 100644 --- a/erts/emulator/beam/packet_parser.h +++ b/erts/emulator/beam/packet_parser.h @@ -30,12 +30,11 @@ #include "sys.h" -/* INET_LOPT_PACKET options */ enum PacketParseType { TCP_PB_RAW = 0, TCP_PB_1 = 1, - TCP_PB_2 = 2, - TCP_PB_4 = 3, + TCP_PB_2_BIG = 2, + TCP_PB_4_BIG = 3, TCP_PB_ASN1 = 4, TCP_PB_RM = 5, TCP_PB_CDR = 6, @@ -46,7 +45,11 @@ enum PacketParseType { TCP_PB_HTTPH = 11, TCP_PB_SSL_TLS = 12, TCP_PB_HTTP_BIN = 13, - TCP_PB_HTTPH_BIN = 14 + TCP_PB_HTTPH_BIN = 14, + TCP_PB_2_LITTLE = 15, + TCP_PB_3_BIG = 16, + TCP_PB_3_LITTLE = 17, + TCP_PB_4_LITTLE = 18 }; typedef struct http_atom { @@ -153,8 +156,12 @@ void packet_get_body(enum PacketParseType htype, const char** bufp, int* lenp) { switch (htype) { case TCP_PB_1: *bufp += 1; *lenp -= 1; break; - case TCP_PB_2: *bufp += 2; *lenp -= 2; break; - case TCP_PB_4: *bufp += 4; *lenp -= 4; break; + case TCP_PB_2_BIG: *bufp += 2; *lenp -= 2; break; + case TCP_PB_2_LITTLE: *bufp += 2; *lenp -= 2; break; + case TCP_PB_3_BIG: *bufp += 3; *lenp -= 3; break; + case TCP_PB_3_LITTLE: *bufp += 3; *lenp -= 3; break; + case TCP_PB_4_BIG: *bufp += 4; *lenp -= 4; break; + case TCP_PB_4_LITTLE: *bufp += 4; *lenp -= 4; break; case TCP_PB_FCGI: *lenp -= ((struct fcgi_head*)*bufp)->paddingLength; break; @@ -184,4 +191,3 @@ int packet_parse(enum PacketParseType htype, const char* buf, int len, #endif /* ERTS_GLB_INLINE_INCL_FUNC_DEF */ #endif /* !__PACKET_PARSER_H__ */ - diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h index 0d6ae8faca4e..5f6bbb443cef 100644 --- a/erts/emulator/beam/sys.h +++ b/erts/emulator/beam/sys.h @@ -1281,7 +1281,15 @@ ERTS_GLB_INLINE size_t sys_strlen(const char *s) (((byte*) (s))[1] << 8) | \ (((byte*) (s))[0])) -#define get_uint32(s) ((Uint32)get_int32(s)) +#define get_uint32(s) ((((Uint32)((byte*) (s))[0]) << 24) | \ + (((Uint32)((byte*) (s))[1]) << 16) | \ + (((Uint32)((byte*) (s))[2]) << 8) | \ + (((Uint32)((byte*) (s))[3]))) + +#define get_little_uint32(s) ((((Uint32)((byte*) (s))[3]) << 24) | \ + (((Uint32)((byte*) (s))[2]) << 16) | \ + (((Uint32)((byte*) (s))[1]) << 8) | \ + (((Uint32)((byte*) (s))[0]))) #define put_int32(i, s) do {((byte*)(s))[0] = (byte)((i) >> 24) & 0xff; \ ((byte*)(s))[1] = (byte)((i) >> 16) & 0xff; \ @@ -1301,20 +1309,36 @@ ERTS_GLB_INLINE size_t sys_strlen(const char *s) (((byte*) (s))[1] << 8) | \ (((byte*) (s))[2])) +#define get_little_int24(s) ((((byte*) (s))[2] << 16) | \ + (((byte*) (s))[1] << 8) | \ + (((byte*) (s))[0])) + #define put_int24(i, s) do {((byte*)(s))[0] = (byte)((i) >> 16) & 0xff; \ ((byte*)(s))[1] = (byte)((i) >> 8) & 0xff; \ ((byte*)(s))[2] = (byte)(i) & 0xff;} \ while (0) +#define put_little_int24(i, s) do {((byte*)(s))[2] = (byte)((i) >> 16) & 0xff; \ + ((byte*)(s))[1] = (byte)((i) >> 8) & 0xff; \ + ((byte*)(s))[0] = (byte)(i) & 0xff;} \ + while (0) + #define get_int16(s) ((((byte*) (s))[0] << 8) | \ (((byte*) (s))[1])) +#define get_little_int16(s) ((((byte*) (s))[1] << 8) | \ + (((byte*) (s))[0])) + #define put_int16(i, s) do {((byte*)(s))[0] = (byte)((i) >> 8) & 0xff; \ ((byte*)(s))[1] = (byte)(i) & 0xff;} \ while (0) +#define put_little_int16(i, s) do {((byte*)(s))[1] = (byte)((i) >> 8) & 0xff; \ + ((byte*)(s))[0] = (byte)(i) & 0xff;} \ + while (0) + #define get_int8(s) ((((byte*) (s))[0] )) diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c index e0a02e2ab796..91ac94b3d73e 100644 --- a/erts/emulator/drivers/common/inet_drv.c +++ b/erts/emulator/drivers/common/inet_drv.c @@ -3872,13 +3872,13 @@ inet_async_binary_data ErlDrvBinary * bin, int offs, int len, void *mp) { unsigned int hsz = desc->hsz + phsz; - const int spec_size = PACKET_ERL_DRV_TERM_DATA_LEN; ErlDrvTermData spec [PACKET_ERL_DRV_TERM_DATA_LEN]; ErlDrvTermData caller; int aid; int req; int i = 0; #ifdef HAVE_SCTP + const int spec_size = PACKET_ERL_DRV_TERM_DATA_LEN; int ok_pos; #endif @@ -4108,11 +4108,13 @@ static int packet_binary_message(inet_descriptor* desc, void *mp) { unsigned int hsz = desc->hsz; - const int spec_size = PACKET_ERL_DRV_TERM_DATA_LEN; ErlDrvTermData spec [PACKET_ERL_DRV_TERM_DATA_LEN]; int i = 0; int alen; char* data = bin->orig_bytes+offs; +#ifdef HAVE_SCTP + const int spec_size = PACKET_ERL_DRV_TERM_DATA_LEN; +#endif DEBUGF(("packet_binary_message(%p): len = %d\r\n", desc->port, len)); @@ -6863,6 +6865,7 @@ int inet_setopt(int fd, ** return -1 on error ** 0 if ok ** 1 if ok force deliver of queued data +** 2 if ok continue receive processing with updated options */ #ifdef HAVE_SCTP static int sctp_set_opts(inet_descriptor* desc, char* ptr, int len); @@ -7945,6 +7948,14 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len) if ( ((desc->stype == SOCK_STREAM) && IS_CONNECTED(desc)) || ((desc->stype == SOCK_DGRAM) && IS_OPEN(desc))) { int trigger_recv; + int htype_changed; + + htype_changed = + (desc->stype == SOCK_STREAM) && (desc->htype != old_htype); + if (htype_changed) { + tcp_descriptor *tdesc = (tcp_descriptor *) desc; + tdesc->i_remain = 0; + } /* XXX: UDP sockets could also trigger immediate read here NIY */ trigger_recv = @@ -7972,17 +7983,16 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len) /* passive => active change */ return 1; } - if (desc->htype != old_htype) { - tcp_descriptor *tdesc = (tcp_descriptor *) desc; - /* Header type change in active mode. - * Invalidate the calculated packet remaining length. - */ - tdesc->i_remain = 0; + if (htype_changed) { return 1; } return 0; } + + if (htype_changed && desc->opt != NULL && !INET_IGNORED(desc)) { + return 2; + } } return 0; } @@ -11136,16 +11146,13 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf, /* fprintf(stderr,"Triggered tcp_deliver by setopt.\r\n"); */ tcp_deliver((tcp_descriptor *) desc, 0); return ctl_reply_ok(rbuf, rsize); - default: - /* fprintf(stderr,"Triggered tcp_recv by setopt.\r\n"); */ - /* - * Same as above, but active changed to once w/o header type - * change, so try a read instead of just deliver. - */ + case 2: if ((tcp_recv((tcp_descriptor *) desc, 0) >= 0) && desc->active) { sock_select(desc, (FD_READ|FD_CLOSE), 1); } return ctl_reply_ok(rbuf, rsize); + default: + ERTS_UNREACHABLE; } } @@ -12900,8 +12907,12 @@ static int packet_header_length(tcp_descriptor *desc) { * Set hlen to the minimal header bytes, for starters. */ case TCP_PB_1: hlen = 1; break; - case TCP_PB_2: hlen = 2; break; - case TCP_PB_4: hlen = 4; break; + case TCP_PB_2_BIG: hlen = 2; break; + case TCP_PB_2_LITTLE: hlen = 2; break; + case TCP_PB_3_BIG: hlen = 3; break; + case TCP_PB_3_LITTLE: hlen = 3; break; + case TCP_PB_4_BIG: hlen = 4; break; + case TCP_PB_4_LITTLE: hlen = 4; break; case TCP_PB_RM: hlen = 4; break; case TCP_PB_ASN1: hlen = 2; break; case TCP_PB_SSL_TLS: hlen = 5; break; @@ -13738,19 +13749,40 @@ static int tcp_sendv(tcp_descriptor* desc, ErlIOVec* ev) put_int8(len, buf); h_len = 1; break; - case TCP_PB_2: + case TCP_PB_2_BIG: put_int16(len, buf); h_len = 2; break; - case TCP_PB_4: + case TCP_PB_2_LITTLE: + put_little_int16(len, buf); + h_len = 2; + break; + case TCP_PB_3_BIG: + put_int24(len, buf); + h_len = 3; + break; + case TCP_PB_3_LITTLE: + put_little_int24(len, buf); + h_len = 3; + break; + case TCP_PB_4_BIG: put_int32(len, buf); h_len = 4; break; + case TCP_PB_4_LITTLE: + put_little_int32(len, buf); + h_len = 4; + break; default: h_len = 0; break; } + if (h_len > 0 && (Uint64) len >= ((Uint64) 1 << (h_len * 8))) { + inet_reply_error(INETP(desc), EMSGSIZE); + return 1; + } + inet_output_count(INETP(desc), len+h_len); if (h_len > 0) { @@ -13854,19 +13886,40 @@ static int tcp_send(tcp_descriptor* desc, char* ptr, ErlDrvSizeT len) put_int8(len, buf); h_len = 1; break; - case TCP_PB_2: + case TCP_PB_2_BIG: put_int16(len, buf); h_len = 2; break; - case TCP_PB_4: + case TCP_PB_2_LITTLE: + put_little_int16(len, buf); + h_len = 2; + break; + case TCP_PB_3_BIG: + put_int24(len, buf); + h_len = 3; + break; + case TCP_PB_3_LITTLE: + put_little_int24(len, buf); + h_len = 3; + break; + case TCP_PB_4_BIG: put_int32(len, buf); h_len = 4; break; + case TCP_PB_4_LITTLE: + put_little_int32(len, buf); + h_len = 4; + break; default: h_len = 0; break; } + if (h_len > 0 && (Uint64) len >= ((Uint64) 1 << (h_len * 8))) { + inet_reply_error(INETP(desc), EMSGSIZE); + return 1; + } + inet_output_count(INETP(desc), len+h_len); sz = driver_sizeq(ix); diff --git a/erts/emulator/sys/unix/sys_drivers.c b/erts/emulator/sys/unix/sys_drivers.c index 8dedc8d82a9f..7a568816e2d3 100644 --- a/erts/emulator/sys/unix/sys_drivers.c +++ b/erts/emulator/sys/unix/sys_drivers.c @@ -110,6 +110,7 @@ typedef struct driver_data { ErtsSysFdData *ofd; ErtsSysFdData *ifd; int packet_bytes; + ErtsSysDriverPacketHeaderEndianness packet_header_endianness; int pid; int alive; int status; @@ -366,6 +367,8 @@ create_driver_data(ErlDrvPort port_num, prt->os_pid = pid; driver_data->packet_bytes = packet_bytes; + driver_data->packet_header_endianness = + opts->packet_header_endianness; driver_data->port_num = port_num; driver_data->pid = pid; driver_data->alive = exit_status ? 1 : 0; @@ -1069,13 +1072,33 @@ static void outputv(ErlDrvData e, ErlIOVec* ev) /* (len > ((unsigned long)-1 >> (4-pb)*8)) */ /* if (pb >= 0 && (len & (((ErlDrvSizeT)1 << (pb*8))) - 1) != len) {*/ - if (((pb == 2) && (len > 0xffff)) || (pb == 1 && len > 0xff)) { + if (((pb == 4) && (len > (ErlDrvSizeT) ERTS_UINT32_MAX)) + || ((pb == 3) && (len > 0xffffff)) + || ((pb == 2) && (len > 0xffff)) + || (pb == 1 && len > 0xff)) { driver_failure_posix(ix, EINVAL); return; /* -1; */ } - /* Handles 0 <= pb <= 4 only */ - put_int32((Uint32) len, lb); - lbp = lb + (4-pb); + if (pb != 0 && dd->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + lbp = lb; + switch (pb) { + case 2: + put_little_int16(len, lbp); + break; + case 3: + put_little_int24(len, lbp); + break; + case 4: + put_little_int32((Uint32) len, lbp); + break; + default: + ERTS_UNREACHABLE; + } + } else { + put_int32((Uint32) len, lb); + lbp = lb + (4 - pb); + } ev->iov[0].iov_base = lbp; ev->iov[0].iov_len = pb; @@ -1150,14 +1173,34 @@ static void output(ErlDrvData e, char* buf, ErlDrvSizeT len) struct iovec iv[2]; /* (len > ((unsigned long)-1 >> (4-pb)*8)) */ - if (((pb == 2) && (len > 0xffff)) + if (((pb == 4) && (len > (ErlDrvSizeT) ERTS_UINT32_MAX)) + || ((pb == 3) && (len > 0xffffff)) + || ((pb == 2) && (len > 0xffff)) || (pb == 1 && len > 0xff) || dd->pid == 0 /* Attempt at output before port is ready */) { driver_failure_posix(ix, EINVAL); return; /* -1; */ } - put_int32(len, lb); - lbp = lb + (4-pb); + if (pb != 0 && dd->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + lbp = lb; + switch (pb) { + case 2: + put_little_int16(len, lbp); + break; + case 3: + put_little_int24(len, lbp); + break; + case 4: + put_little_int32((Uint32) len, lbp); + break; + default: + ERTS_UNREACHABLE; + } + } else { + put_int32(len, lb); + lbp = lb + (4 - pb); + } qsz = driver_sizeq(ix); if (qsz) { @@ -1405,12 +1448,37 @@ static void ready_input(ErlDrvData e, ErlDrvEvent ready_fd) } dd->ifd->psz = 0; - switch (packet_bytes) { - case 1: h = get_int8(dd->ifd->pbuf); break; - case 2: h = get_int16(dd->ifd->pbuf); break; - case 4: h = get_uint32(dd->ifd->pbuf); break; - default: ASSERT(0); return; /* -1; */ - } + switch (packet_bytes) { + case 1: + h = get_int8(dd->ifd->pbuf); + break; + case 2: + if (dd->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + h = get_little_int16(dd->ifd->pbuf); + } else { + h = get_int16(dd->ifd->pbuf); + } + break; + case 3: + if (dd->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + h = get_little_int24(dd->ifd->pbuf); + } else { + h = get_int24(dd->ifd->pbuf); + } + break; + case 4: + if (dd->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + h = get_little_uint32(dd->ifd->pbuf); + } else { + h = get_uint32(dd->ifd->pbuf); + } + break; + default: + ERTS_UNREACHABLE; + } if (h <= (bytes_left)) { driver_output(port_num, (char*) cpos, h); diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c index 25c49b5c685f..da4b34a6f81d 100644 --- a/erts/emulator/sys/win32/sys.c +++ b/erts/emulator/sys/win32/sys.c @@ -507,6 +507,7 @@ struct driver_data { int packet_bytes; /* 0: continuous stream, 1, 2, or 4: the number * of bytes in the packet header. */ + ErtsSysDriverPacketHeaderEndianness packet_header_endianness; HANDLE port_pid; /* PID of the port process. */ AsyncIo in; /* Control block for overlapped reading. */ AsyncIo out; /* Control block for overlapped writing. */ @@ -621,7 +622,9 @@ unrefer_driver_data(DriverData *dp) */ static DriverData* -new_driver_data(ErlDrvPort port_num, int packet_bytes, int wait_objs_required, int use_threads) +new_driver_data(ErlDrvPort port_num, int packet_bytes, + ErtsSysDriverPacketHeaderEndianness packet_header_endianness, + int wait_objs_required, int use_threads) { DriverData* dp; @@ -648,6 +651,7 @@ new_driver_data(ErlDrvPort port_num, int packet_bytes, int wait_objs_required, i dp->outbuf = NULL; dp->port_num = port_num; dp->packet_bytes = packet_bytes; + dp->packet_header_endianness = packet_header_endianness; dp->port_pid = INVALID_HANDLE_VALUE; if (init_async_io(dp, &dp->in, use_threads) == -1) goto async_io_error1; @@ -1191,8 +1195,10 @@ spawn_start(ErlDrvPort port_num, char* utf8_name, SysDriverOpts* opts) if (opts->read_write & DO_WRITE) neededSelects++; - if ((dp = new_driver_data(port_num, opts->packet_bytes, neededSelects, - !use_named_pipes)) == NULL) + if ((dp = new_driver_data( + port_num, opts->packet_bytes, + opts->packet_header_endianness, + neededSelects, !use_named_pipes)) == NULL) return ERL_DRV_ERROR_GENERAL; /* @@ -2135,7 +2141,9 @@ fd_start(ErlDrvPort port_num, char* name, SysDriverOpts* opts) dp = save_22_port; return reuse_driver_data(dp, (HANDLE) opts->ifd, (HANDLE) opts->ofd, opts->read_write, port_num); } else { - if ((dp = new_driver_data(port_num, opts->packet_bytes, 2, TRUE)) == NULL) + if ((dp = new_driver_data(port_num, opts->packet_bytes, + opts->packet_header_endianness, + 2, TRUE)) == NULL) return ERL_DRV_ERROR_GENERAL; /** @@ -2409,7 +2417,8 @@ output(ErlDrvData drv_data, char* buf, ErlDrvSizeT len) * Check that the message can be sent with given header length. */ - if ((pb == 2 && len > 65535) || (pb == 1 && len > 255)) { + if ((pb == 3 && len > 0xffffff) + || (pb == 2 && len > 65535) || (pb == 1 && len > 255)) { driver_failure_posix(dp->port_num, EINVAL); return ; /* -1; */ } @@ -2432,13 +2441,41 @@ output(ErlDrvData drv_data, char* buf, ErlDrvSizeT len) current = bin->orig_bytes; switch (pb) { - case 4: - *current++ = (len >> 24) & 255; - *current++ = (len >> 16) & 255; - case 2: - *current++ = (len >> 8) & 255; + case 0: + break; case 1: - *current++ = len & 255; + put_int8(len, current); + current += 1; + break; + case 2: + if (dp->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + put_little_int16(len, current); + } else { + put_int16(len, current); + } + current += 2; + break; + case 3: + if (dp->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + put_little_int24(len, current); + } else { + put_int24(len, current); + } + current += 3; + break; + case 4: + if (dp->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + put_little_int32((Uint32) len, current); + } else { + put_int32((Uint32) len, current); + } + current += 4; + break; + default: + ERTS_UNREACHABLE; } /* @@ -2588,20 +2625,41 @@ ready_input(ErlDrvData drv_data, ErlDrvEvent ready_event) * the packet size. */ - int packet_size = 0; - unsigned char *header = (unsigned char *) dp->inbuf; - - switch (pb) { - case 4: - packet_size = (packet_size << 8) | *header++; - packet_size = (packet_size << 8) | *header++; - case 2: - packet_size = (packet_size << 8) | *header++; - case 1: - packet_size = (packet_size << 8) | *header++; - } - - dp->totalNeeded += packet_size; + Uint32 packet_size; + + switch (pb) { + case 1: + packet_size = get_int8(dp->inbuf); + break; + case 2: + if (dp->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + packet_size = get_little_int16(dp->inbuf); + } else { + packet_size = get_int16(dp->inbuf); + } + break; + case 3: + if (dp->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + packet_size = get_little_int24(dp->inbuf); + } else { + packet_size = get_int24(dp->inbuf); + } + break; + case 4: + if (dp->packet_header_endianness == + ERTS_SYS_DRIVER_PACKET_HEADER_ENDIAN_LITTLE) { + packet_size = get_little_uint32(dp->inbuf); + } else { + packet_size = get_uint32(dp->inbuf); + } + break; + default: + ERTS_UNREACHABLE; + } + + dp->totalNeeded += (int) packet_size; /* * Make sure that the receive buffer is big enough. diff --git a/erts/emulator/test/decode_packet_SUITE.erl b/erts/emulator/test/decode_packet_SUITE.erl index dfe0a1d723c6..99b5311cd4d9 100644 --- a/erts/emulator/test/decode_packet_SUITE.erl +++ b/erts/emulator/test/decode_packet_SUITE.erl @@ -58,13 +58,33 @@ basic(Config) when is_list(Config) -> {more, 5+1} = decode_pkt(1,<<5,1,2,3,4>>), {more, 5+2} = decode_pkt(2,<<0,5,1,2,3,4>>), + {more, 5+3} = decode_pkt(3,<<0,0,5,1,2,3,4>>), + {more, 5+3} = decode_pkt({3,big},<<0,0,5,1,2,3,4>>), + {more, 5+3} = decode_pkt({3,little},<<5,0,0,1,2,3,4>>), {more, 5+4} = decode_pkt(4,<<0,0,0,5,1,2,3,4>>), {more, undefined} = decode_pkt(1,<<>>), {more, undefined} = decode_pkt(2,<<0>>), + {more, undefined} = decode_pkt(3,<<0,0>>), {more, undefined} = decode_pkt(4,<<0,0,0>>), - - Types = [1,2,4,asn1,sunrm,cdr,fcgi,tpkt,ssl_tls], + {more, 16#0102 + 2} = decode_pkt({2,big}, <<16#0102:16/big>>), + {more, 16#0102 + 2} = + decode_pkt({2,little}, <<16#0102:16/little>>), + {more, 16#0102 + 2} = + decode_pkt({2,native}, <<16#0102:16/native>>), + {more, 16#010203 + 3} = decode_pkt(3, <<16#010203:24>>), + {more, 16#010203 + 3} = + decode_pkt({3,big}, <<16#010203:24/big>>), + {more, 16#010203 + 3} = + decode_pkt({3,little}, <<16#010203:24/little>>), + {more, 16#010203 + 3} = + decode_pkt({3,native}, <<16#010203:24/native>>), + + Types = [1,2,3,4, + {2,big},{2,little},{2,native}, + {3,big},{3,little},{3,native}, + {4,big},{4,little},{4,native}, + asn1,sunrm,cdr,fcgi,tpkt,ssl_tls], %% Run tests for different header types and bit offsets. @@ -132,9 +152,32 @@ pack(1,Bin) -> pack(2,Bin) -> Psz = byte_size(Bin), {<>, Bin}; +pack(3,Bin) -> + Psz = byte_size(Bin), + {<>, Bin}; pack(4,Bin) -> Psz = byte_size(Bin), {<>, Bin}; +pack({N,big},Bin) -> + pack(N,Bin); +pack({2,little},Bin) -> + Psz = byte_size(Bin), + {<>, Bin}; +pack({3,little},Bin) -> + Psz = byte_size(Bin), + {<>, Bin}; +pack({4,little},Bin) -> + Psz = byte_size(Bin), + {<>, Bin}; +pack({2,native},Bin) -> + Psz = byte_size(Bin), + {<>, Bin}; +pack({3,native},Bin) -> + Psz = byte_size(Bin), + {<>, Bin}; +pack({4,native},Bin) -> + Psz = byte_size(Bin), + {<>, Bin}; pack(asn1,Bin) -> Ident = case rand:uniform(3) of 1 -> <<17>>; @@ -244,12 +287,23 @@ packet_size(Config) when is_list(Config) -> ok end end, - lists:foreach(F, [{T,D} || T<-[1,2,4,asn1,sunrm,cdr,fcgi,tpkt,ssl_tls], + Types = [1,2,3,4, + {2,big},{2,little},{2,native}, + {3,big},{3,little},{3,native}, + {4,big},{4,little},{4,native}, + asn1,sunrm,cdr,fcgi,tpkt,ssl_tls], + lists:foreach(F, [{T,D} || T<-Types, D<-lists:seq(0, byte_size(Packet)*2)]), %% Test OTP-8102, "negative" 4-byte sizes. lists:foreach(fun(Size) -> - {error,_} = decode_pkt(4,<>) + {error,_} = decode_pkt(4,<>), + {error,_} = decode_pkt({4,big}, + <>), + {error,_} = decode_pkt({4,little}, + <>), + {error,_} = decode_pkt({4,native}, + <>) end, lists:seq(-10,-1)), @@ -285,7 +339,10 @@ neg(Config) when is_list(Config) -> %% Invalid Type args lists:foreach(fun(T)-> BadargF(T,Bin,[]) end, - [3,-1,5,2.0,{2},unknown,[],"line",Bin,Fun,self()]), + [-1,5,2.0, + {0,big},{1,big},{5,big},{2},{2,middle},{3,middle},{4,middle}, + {2,big,extra},{2.0,big}, + unknown,[],"line",Bin,Fun,self()]), %% Invalid Bin args lists:foreach(fun(B)-> BadargF(0,B,[]) end, diff --git a/erts/emulator/test/port_SUITE.erl b/erts/emulator/test/port_SUITE.erl index f19e23d433e6..fc33402de1fb 100644 --- a/erts/emulator/test/port_SUITE.erl +++ b/erts/emulator/test/port_SUITE.erl @@ -39,12 +39,15 @@ %% [Spawn of external program is tested.] %% %% {fd, In, Out} -%% Open file descriptors In and Out. [Not tested] +%% Open file descriptors In and Out. Packet framing is tested. %% %% PortSettings can be %% %% {packet, N} -%% N is 1, 2 or 4. +%% N is 1, 2, 3 or 4. +%% +%% {packet, {N, Endian}} +%% N is 2, 3 or 4. Endian is big, little, or native. %% %% stream (default) %% Without packet length. @@ -92,6 +95,7 @@ eof/1, exit_status/1, exit_status_multi_scheduling_block/1, + fd_packet_endian/1, huge_env/1, pipe_limit_env/1, input_only/1, @@ -119,6 +123,10 @@ otp_5119/1, otp_6224/1, output_only/1, + packet_options/1, + packet_endian_spawn/1, + packet_option_replacement/1, + packet_stream_replacement/1, parallelism_option/1, parallell/1, port_program_with_path/1, @@ -159,7 +167,8 @@ %% Internal exports. -export([tps/3]). -export([otp_3906_forker/5, otp_3906_start_forker_starter/4]). --export([env_slave_main/1]). +-export([env_slave_main/1, fd_packet_endian_child/0, + fd_stream_replacement_child/0]). -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/file.hrl"). @@ -170,7 +179,10 @@ suite() -> {timetrap, {minutes, 1}}]. all() -> - [otp_6224, {group, stream}, basic_ping, slow_writes, + [otp_6224, {group, stream}, basic_ping, packet_options, + packet_option_replacement, + packet_endian_spawn, packet_stream_replacement, + fd_packet_endian, slow_writes, bad_packet, bad_port_messages, {group, options}, {group, multiple_packets}, parallell, dying_port, dropped_commands, port_program_with_path, name1, env, huge_env, bad_env, cd, @@ -305,16 +317,164 @@ stream_big(Config) when is_list(Config) -> stream_ping(Config, 77777, " -s40000", []), ok. -%% Sends packet with header size of 1, 2, and 4, with packets of various +%% Sends packet with header size of 1, 2, 3, and 4, with packets of various %% sizes. basic_ping(Config) when is_list(Config) -> ct:timetrap({minutes, 2}), ping(Config, sizes(1), 1, "", []), ping(Config, sizes(2), 2, "", []), + ping(Config, sizes(3), 3, "", []), ping(Config, sizes(4), 4, "", []), ok. +%% Check the public open_port/2 packet-option parser without relying on a +%% system driver's framing implementation. +packet_options(Config) when is_list(Config) -> + PortTest = port_test(Config), + lists:foreach( + fun(Packet) -> + Port = open_port({spawn, PortTest ++ " -n"}, + [exit_status, {packet, Packet}]), + receive + {Port, {exit_status, 0}} -> ok + after 5000 -> + ct:fail({packet_option_start_timeout, Packet}) + end + end, + [2, 3, 4, + {2, big}, {2, little}, {2, native}, + {3, big}, {3, little}, {3, native}, + {4, big}, {4, little}, {4, native}]), + lists:foreach( + fun(Packet) -> bad_argument(Config, [{packet, Packet}]) end, + [{1, big}, {5, big}, {2, middle}, {2}, + {2, little, extra}, {2.0, big}]), + ok. + +%% Test explicit packet byte order against an independent external program in +%% both wire directions. +packet_endian_spawn(Config) when is_list(Config) -> + packet_endian_spawn_test(Config). + +packet_endian_spawn_test(Config) -> + ct:timetrap({minutes, 3}), + lists:foreach( + fun(Endian) -> + ping(Config, sizes(2), {2, Endian}, "", []), + ping(Config, sizes(3), {3, Endian}, "", []), + ping(Config, sizes(4), {4, Endian}, "", []) + end, + [big, little, native]), + + expect_input(Config, [258], {2, little}, "", []), + expect_input(Config, [258], {2, native}, "", []), + ok. + +%% A later packet setting must replace both the width and byte order used by +%% the system driver, not merely the parser's stored option. +packet_option_replacement(Config) when is_list(Config) -> + ping(Config, [258], {2, big}, "", + [{packet, {4, little}}, {packet, {2, big}}]), + HostEndian = erlang:system_info(endian), + OppositeEndian = case HostEndian of + big -> little; + little -> big + end, + ping(Config, [258], {2, native}, "", + [{packet, {4, OppositeEndian}}, {packet, {2, native}}]), + ok. + +%% `stream` must replace an earlier endian-aware packet option without making +%% the retained byte-order field observable in a port-driver callback. +packet_stream_replacement(Config) when is_list(Config) -> + packet_stream_replacement_test(Config). + +packet_stream_replacement_test(Config) -> + PortTest = port_test(Config), + StreamOptions = [binary, {packet, {3, little}}, stream], + Spawn = open_port({spawn, PortTest ++ " -h0"}, StreamOptions), + try + true = port_command(Spawn, <<"spawn">>), + stream_receive_all1(Spawn, <<"spawn">>) + after + true = port_close(Spawn) + end, + + Fd = open_fd_child(fd_stream_replacement_child, []), + try + stream_receive_all1(Fd, <<"fd">>), + true = port_command(Fd, <<"peer">>), + fd_packet_result(Fd, <<"peer">>, false) + after + try port_close(Fd) of + true -> ok + catch + error:badarg -> ok + end + end. +%% Test endian-aware framing through the Unix fd driver's outputv callback. +fd_packet_endian(Config) when is_list(Config) -> + Port = open_fd_child(fd_packet_endian_child, []), + try + stream_receive_all1(Port, <<1, 0, 0, $A>>), + true = port_command(Port, <<1, 0, 0, $B>>), + fd_packet_result(Port, <<1, 0, 0, $B>>, false) + after + try port_close(Port) of + true -> ok + catch + error:badarg -> ok + end + end. + +fd_packet_endian_child() -> + Port = open_port({fd, 0, 1}, [binary, {packet, {3, little}}]), + true = port_command(Port, <<$A>>), + receive + {Port, {data, <<$B>>}} -> + true = port_command(Port, <<$B>>) + after 5000 -> + halt(1) + end, + true = port_close(Port). + +fd_stream_replacement_child() -> + Port = open_port({fd, 0, 1}, + [binary, {packet, {3, little}}, stream]), + true = port_command(Port, <<"fd">>), + receive + {Port, {data, <<"peer">>}} -> + true = port_command(Port, <<"peer">>) + after 5000 -> + halt(1) + end, + true = port_close(Port). + +open_fd_child(Function, Args0) -> + [Exec0 | ExecArgs] = string:split(ct:get_progname(), " ", all), + Exec = os:find_executable(Exec0), + TestDir = filename:dirname(code:which(?MODULE)), + RunArgs = [atom_to_list(Function) | Args0], + Args = ExecArgs ++ ["-pa", TestDir, "-nouser", + "-run", ?MODULE_STRING] ++ RunArgs ++ + ["-run", "erlang", "halt"], + open_port({spawn_executable, Exec}, + [binary, exit_status, hide, {args, Args}, + {env, [{"ERL_CRASH_DUMP_SECONDS", "0"}]}]). + +fd_packet_result(_Port, <<>>, true) -> + ok; +fd_packet_result(Port, Remaining, ExitStatus) -> + receive + {Port, {data, Data}} -> + fd_packet_result(Port, compare(Data, Remaining), ExitStatus); + {Port, {exit_status, 0}} -> + fd_packet_result(Port, Remaining, true); + Other -> + ct:fail({unexpected_child_result, Other}) + end. + %% Let the port program insert delays between characters sent back to %% Erlang, to test that the Erlang emulator can handle a packet coming in %% small chunks rather than all at once. @@ -322,6 +482,7 @@ basic_ping(Config) when is_list(Config) -> slow_writes(Config) when is_list(Config) -> ping(Config, [8], 4, "-s1", []), ping(Config, [10], 2, "-s2", []), + ping(Config, [8], {3, little}, "-s1", []), ok. %% Test that we get {'EXIT', Port, einval} if we try to send a bigger @@ -330,10 +491,12 @@ bad_packet(Config) when is_list(Config) -> PortTest = port_test(Config), process_flag(trap_exit, true), + ping(Config, [16#ffffff], 3, "", []), bad_packet(PortTest, 1, 256), bad_packet(PortTest, 1, 257), bad_packet(PortTest, 2, 65536), bad_packet(PortTest, 2, 65537), + bad_packet(PortTest, 3, 16#1000000), ok. bad_packet(PortTest, HeaderSize, PacketSize) -> @@ -2167,7 +2330,7 @@ ping(Config, Sizes, HSize, CmdLine, Options) -> %% expect_input(Sizes, HSize, CmdLine, Options) %% %% Sizes = Size of packets to generated. -%% HSize = Header size: 1, 2, or 4 +%% HSize = Header size: 1, 2, 3, or 4. %% CmdLine = Additional command line options. %% Options = Additional port options. @@ -2195,10 +2358,11 @@ build_cmd_line(FixedCmdLine, [], Result) -> %% port_expect(Actions, HSize, CmdLine, Options) %% %% Actions = [{Send, ExpectList}|Rest] -%% HSize = 0 (stream), or 1, 2, 4 (header size aka "packet bytes") +%% HSize = 0 (stream), 1, 2, 3, 4, or {2 | 3 | 4, Endian} %% CmdLine = Command line for port_test. Don't include -h. -%% Options = Options for open_port/2. Don't include {packet, Number} or -%% or stream. +%% Options = Options for open_port/2. If it contains a packet setting, +%% HSize configures only port_test's framing. Otherwise, HSize +%% also supplies the packet setting. Don't include stream. %% %% Send = false | list() %% ExpectList = List of lists or binaries. @@ -2209,13 +2373,31 @@ port_expect(Config, Actions, HSize, CmdLine, Options0) -> % io:format("port_expect(~p, ~p, ~p, ~p)", % [Actions, HSize, CmdLine, Options0]), PortTest = port_test(Config), - Cmd = lists:concat([PortTest, " -h", HSize, " ", CmdLine]), - PortType = - case HSize of - 0 -> stream; - _ -> {packet, HSize} - end, - Options = [PortType|Options0], + {HeaderSize, PortType, EndianArg} = + case HSize of + 0 -> + {0, stream, ""}; + HeaderSize0 when is_integer(HeaderSize0) -> + {HeaderSize0, {packet, HeaderSize0}, ""}; + {HeaderSize0, Endian} = PacketSetting -> + EndianArg0 = + case Endian of + big -> ""; + little -> " -L"; + native -> + case erlang:system_info(endian) of + big -> ""; + little -> " -L" + end + end, + {HeaderSize0, {packet, PacketSetting}, EndianArg0} + end, + Cmd = lists:concat([PortTest, " -h", HeaderSize, EndianArg, + " ", CmdLine]), + Options = case lists:keyfind(packet, 1, Options0) of + false -> [PortType|Options0]; + _ -> Options0 + end, ct:log("open_port({spawn, ~p}, ~p)", [Cmd, Options]), Port = open_port({spawn, Cmd}, Options), port_expect(Port, Actions, Options), @@ -2382,6 +2564,8 @@ sizes(1, [Packet_Size|Rest], Result) when Packet_Size < 256 -> sizes(1, Rest, [Packet_Size|Result]); sizes(2, [Packet_Size|Rest], Result) when Packet_Size < 65536 -> sizes(2, Rest, [Packet_Size|Result]); +sizes(3, [Packet_Size|Rest], Result) when Packet_Size < 16#1000000 -> + sizes(3, Rest, [Packet_Size|Result]); sizes(4, [Packet_Size|Rest], Result) -> sizes(4, Rest, [Packet_Size|Result]); sizes(_, _, Result) -> diff --git a/erts/emulator/test/port_SUITE_data/port_test.c b/erts/emulator/test/port_SUITE_data/port_test.c index 2e00be7f4b79..03271e2e48e7 100644 --- a/erts/emulator/test/port_SUITE_data/port_test.c +++ b/erts/emulator/test/port_SUITE_data/port_test.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include #include @@ -36,7 +38,6 @@ #ifndef __WIN32__ #include -#include #include @@ -60,10 +61,16 @@ extern int errno; +typedef enum { + PORT_TEST_PACKET_HEADER_ENDIAN_BIG = 0, + PORT_TEST_PACKET_HEADER_ENDIAN_LITTLE +} PortTestPacketHeaderEndianness; + typedef struct { char* progname; /* Name of this program (from argv[0]). */ - int header_size; /* Number of bytes in each packet header: - * 1, 2, or 4, or 0 for a continuous byte stream. */ + int header_size; /* Number of bytes in each packet header: + * 1, 2, 3, or 4, or 0 for a continuous byte stream. */ + PortTestPacketHeaderEndianness packet_header_endianness; int fd_from_erl; /* File descriptor from Erlang. */ int fd_to_erl; /* File descriptor to Erlang. */ unsigned char* io_buf; /* Buffer for file i/o. */ @@ -136,6 +143,7 @@ int main(int argc, char *argv[]) exit(1); } port_data->header_size = 0; + port_data->packet_header_endianness = PORT_TEST_PACKET_HEADER_ENDIAN_BIG; port_data->io_buf_size = 0; port_data->delay_mode = 0; port_data->fd_count = 0; @@ -176,6 +184,7 @@ int main(int argc, char *argv[]) case '0': port_data->header_size = 0; break; case '1': port_data->header_size = 1; break; case '2': port_data->header_size = 2; break; + case '3': port_data->header_size = 3; break; case '4': port_data->header_size = 4; break; case '\0': fprintf(stderr, "%s: missing header size for -h\n", port_data->progname); @@ -189,6 +198,10 @@ int main(int argc, char *argv[]) case 'l': port_data->limited_bytecount = atoi(argv[1]+2); break; + case 'L': /* Little-endian packet headers. */ + port_data->packet_header_endianness = + PORT_TEST_PACKET_HEADER_ENDIAN_LITTLE; + break; case 'n': /* No packet loop. */ port_data->no_packet_loop = 1; break; @@ -257,6 +270,7 @@ packet_loop(void) for (;;) { int packet_length; /* Length of current packet. */ + uint32_t parsed_packet_length; int i; int bytes_read; /* Number of bytes read. */ @@ -280,9 +294,25 @@ packet_loop(void) * Get the length of this packet. */ - packet_length = 0; - for (i = 0; i < port_data->header_size; i++) - packet_length = (packet_length << 8) | port_data->io_buf[i]; + parsed_packet_length = 0; + if (port_data->packet_header_endianness == + PORT_TEST_PACKET_HEADER_ENDIAN_LITTLE) { + for (i = port_data->header_size - 1; i >= 0; i--) + parsed_packet_length = + (parsed_packet_length << 8) | port_data->io_buf[i]; + } else { + for (i = 0; i < port_data->header_size; i++) + parsed_packet_length = + (parsed_packet_length << 8) | port_data->io_buf[i]; + } + /* Keep packet_length + 4 + 1 in range: reply() needs four prefix bytes, + and index 4 must exist for an empty packet. */ + if (parsed_packet_length > (uint32_t) (INT_MAX - 5)) { + fprintf(stderr, "%s: packet length %lu is too large\r\n", + port_data->progname, (unsigned long) parsed_packet_length); + return 1; + } + packet_length = (int) parsed_packet_length; } @@ -375,15 +405,19 @@ reply(buf, size) int n; /* Temporary to hold size. */ int i; /* Loop counter. */ - /* - * Fill the header starting with the least significant byte - * (this will work even if there is no header). - */ - n = size; - for (i = 0; i < port_data->header_size; i++) { - *--buf = (char) n; /* Store least significant byte. */ - n = n >> 8; + if (port_data->packet_header_endianness == + PORT_TEST_PACKET_HEADER_ENDIAN_LITTLE) { + buf -= port_data->header_size; + for (i = 0; i < port_data->header_size; i++) { + buf[i] = (char) n; + n = n >> 8; + } + } else { + for (i = 0; i < port_data->header_size; i++) { + *--buf = (char) n; /* Store least significant byte. */ + n = n >> 8; + } } size += port_data->header_size; @@ -592,12 +626,20 @@ char* spec; /* Specification for reply. */ size_t n; n = items[cur].size; - s += port_data->header_size; - for (i = 0; i < port_data->header_size; i++) { - *--s = (char) n; /* Store least significant byte. */ - n = n >> 8; - } - s += port_data->header_size; + if (port_data->packet_header_endianness == + PORT_TEST_PACKET_HEADER_ENDIAN_LITTLE) { + for (i = 0; i < port_data->header_size; i++) { + *s++ = (char) n; + n = n >> 8; + } + } else { + s += port_data->header_size; + for (i = 0; i < port_data->header_size; i++) { + *--s = (char) n; /* Store least significant byte. */ + n = n >> 8; + } + s += port_data->header_size; + } c = items[cur].start; for (i = 0; i < items[cur].size; i++) { @@ -610,4 +652,3 @@ char* spec; /* Specification for reply. */ } write_reply(buf, s-buf); } - diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam index 3aaae1f40369..64d7d60a894f 100644 Binary files a/erts/preloaded/ebin/erlang.beam and b/erts/preloaded/ebin/erlang.beam differ diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam index 05e4d6eb2682..51653ca0436a 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/erlang.erl b/erts/preloaded/src/erlang.erl index 53424e1f9afb..2eefbad5af35 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -1814,10 +1814,15 @@ returned. - **`raw | 0`** - No packet handling is done. The entire binary is returned unless it is empty. -- **`1 | 2 | 4`** - Packets consist of a header specifying the number of bytes - in the packet, followed by that number of bytes. The length of the header can - be one, two, or four bytes; the order of the bytes is big-endian. The header - is stripped off when the packet is returned. +- **`1 | 2 | 3 | 4`** - Packets consist of a header specifying the number of + bytes in the packet, followed by that number of bytes. The length of the + header can be one, two, three, or four bytes; the order of the bytes is + big-endian. The header is stripped off when the packet is returned. + +- **`{N, Endian}`** - Uses the same framing with a two-, three-, or four-byte + header, in the byte order specified by `Endian`, which can be `big`, `little`, + or `native`. `{N, big}` is equivalent to `N`; `native` uses the native byte + order of the runtime system. - **`line`** - A packet is a line-terminated by a delimiter byte, default is the latin-1 newline character. The delimiter byte is included in the returned @@ -1890,8 +1895,10 @@ Options: {ok, Packet, Rest} | {more, Length} | {error, Reason} when - Type :: 'raw' | 0 | 1 | 2 | 4 | 'asn1' | 'cdr' | 'sunrm' | 'fcgi' - | 'tpkt' | 'line' | 'http' | 'http_bin' | 'httph' | 'httph_bin', + Type :: 'raw' | 0 | 1 | 2 | 3 | 4 + | {N :: 2 | 3 | 4, Endian :: big | little | native} + | 'asn1' | 'cdr' | 'sunrm' | 'fcgi' | 'tpkt' | 'line' + | 'http' | 'http_bin' | 'httph' | 'httph_bin', Bin :: binary(), Options :: [Opt], Opt :: {packet_size, non_neg_integer()} @@ -7751,9 +7758,20 @@ encoding. For details, see the module `m:file`, the function follows: - **`{packet, N}`** - Messages are preceded by their length, sent in `N` bytes, - with the most significant byte first. The valid values for `N` are 1, 2, + with the most significant byte first. The valid values for `N` are 1, 2, 3, and 4. +- **`{packet, {N, Endian}}`** - Uses the same framing with a two-, three-, or + four-byte header, in the byte order specified by `Endian`, which can be + `big`, `little`, or `native`. This setting applies to messages in both + directions. `{packet, {N, big}}` is equivalent to `{packet, N}`; `native` + uses the native byte order of the runtime system. + + For a four-byte header, output retains the legacy unsigned 32-bit length + range. Input body length is limited to `2^31 - 1` bytes on Unix and + `2^31 - 5` bytes on Windows because the driver stores receive sizes as + signed integers. + - **`stream`** - Output messages are sent without packet lengths. A user-defined protocol must be used between the Erlang process and the external object. @@ -7768,7 +7786,8 @@ follows: following a newline sequence, the last line is also delivered with `Flag` set to `noeol`. Otherwise lines are delivered with `Flag` set to `eol`. - The `{packet, N}` and `{line, L}` settings are mutually exclusive. + The `{packet, N}` and `{packet, {N, Endian}}` settings are mutually exclusive + with `{line, L}`. - **`{cd, Dir}`** - Only valid for `{spawn, Command}` and `{spawn_executable, FileName}`. The external program starts using `Dir` as its @@ -7966,7 +7985,9 @@ by passing command-line flag [`+Q`](erl_cmd.md#max_ports) to [erl](erl_cmd.md). {spawn_executable, FileName :: file:name_all() } | {fd, In :: non_neg_integer(), Out :: non_neg_integer()}, PortSettings :: [Opt], - Opt :: {packet, N :: 1 | 2 | 4} + Opt :: {packet, N :: 1 | 2 | 3 | 4} + | {packet, {N :: 2 | 3 | 4, + Endian :: big | little | native}} | stream | {line, L :: non_neg_integer()} | {cd, Dir :: string() | binary()} diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl index 5f7bf55979c4..e7214a493159 100644 --- a/erts/preloaded/src/prim_inet.erl +++ b/erts/preloaded/src/prim_inet.erl @@ -1743,11 +1743,27 @@ type_opt_1(active) -> {true, ?INET_ACTIVE}, {once, ?INET_ONCE}, {multi, ?INET_MULTI}]}; -type_opt_1(packet) -> +type_opt_1(packet) -> + {Native2, Native3, Native4} = + case erlang:system_info(endian) of + big -> {?TCP_PB_2_BIG, ?TCP_PB_3_BIG, ?TCP_PB_4_BIG}; + little -> {?TCP_PB_2_LITTLE, ?TCP_PB_3_LITTLE, + ?TCP_PB_4_LITTLE} + end, {enum,[{0, ?TCP_PB_RAW}, {1, ?TCP_PB_1}, - {2, ?TCP_PB_2}, - {4, ?TCP_PB_4}, + {2, ?TCP_PB_2_BIG}, + {{2, big}, ?TCP_PB_2_BIG}, + {{2, little}, ?TCP_PB_2_LITTLE}, + {{2, native}, Native2}, + {3, ?TCP_PB_3_BIG}, + {{3, big}, ?TCP_PB_3_BIG}, + {{3, little}, ?TCP_PB_3_LITTLE}, + {{3, native}, Native3}, + {4, ?TCP_PB_4_BIG}, + {{4, big}, ?TCP_PB_4_BIG}, + {{4, little}, ?TCP_PB_4_LITTLE}, + {{4, native}, Native4}, {raw,?TCP_PB_RAW}, {sunrm, ?TCP_PB_RM}, {asn1, ?TCP_PB_ASN1}, @@ -1935,6 +1951,8 @@ type_value_1(Q, {record,Types}, undefined) -> type_value_1(Q, {record,Types}, Values) when tuple_size(Types) =:= tuple_size(Values) -> type_value_record(Q, Types, Values, 2); +type_value_1(_, {enum, _} = Type, Value) -> + type_value_2(Type, Value); type_value_1(Q, Types, Values) when tuple_size(Types) =:= tuple_size(Values) -> type_value_tuple(Q, Types, Values, 1); @@ -2137,6 +2155,8 @@ enc_value_1(Q, {record,Types}, undefined) -> enc_value_1(Q, {record,Types}, Values) when tuple_size(Types) =:= tuple_size(Values) -> enc_value_tuple(Q, Types, Values, 2); +enc_value_1(_, {enum, _} = Type, Value) -> + enc_value_2(Type, Value); enc_value_1(Q, Types, Values) when tuple_size(Types) =:= tuple_size(Values) -> enc_value_tuple(Q, Types, Values, 1); enc_value_1(_, Type, Value) -> diff --git a/lib/kernel/src/gen_tcp.erl b/lib/kernel/src/gen_tcp.erl index 621522b85469..bf98373b3989 100644 --- a/lib/kernel/src/gen_tcp.erl +++ b/lib/kernel/src/gen_tcp.erl @@ -283,7 +283,8 @@ way, option `send_timeout` comes in handy. {mode, list | binary} | list | binary | {nodelay, boolean()} | {packet, - 0 | 1 | 2 | 4 | raw | sunrm | asn1 | + 0 | 1 | 2 | 3 | 4 | {2 | 3 | 4, big | little | native} | + raw | sunrm | asn1 | cdr | fcgi | line | tpkt | http | httph | http_bin | httph_bin } | {packet_size, non_neg_integer()} | {priority, non_neg_integer()} | diff --git a/lib/kernel/src/gen_tcp_socket.erl b/lib/kernel/src/gen_tcp_socket.erl index 4b6e8c07b89e..47ada91eacb7 100644 --- a/lib/kernel/src/gen_tcp_socket.erl +++ b/lib/kernel/src/gen_tcp_socket.erl @@ -72,7 +72,7 @@ -define(MODULE_socket(Server, Socket), {'$inet', ?MODULE, {Server, Socket}}). -%% Standard length before data header for packet,1|2|4 +%% Standard length before data header for packet,1|2|3|4 -define(header(Packet, Size), (Size):(Packet)/unit:8-integer-big-unsigned). @@ -559,19 +559,41 @@ send(?MODULE_socket(Server, Socket), Data) -> {ok, #{packet := Packet, send_timeout := SendTimeout} = Meta} -> - if - Packet =:= 1; - Packet =:= 2; - Packet =:= 4 -> + PacketHeader = + case Packet of + PacketWidth + when is_integer(PacketWidth), + 1 =< PacketWidth, PacketWidth =< 4 -> + {PacketWidth, big}; + {PacketWidth, PacketEndianness} = PacketHeader0 + when is_integer(PacketWidth), + 2 =< PacketWidth, PacketWidth =< 4, + (PacketEndianness =:= big orelse + PacketEndianness =:= little orelse + PacketEndianness =:= native) -> + PacketHeader0; + _ -> + false + end, + case PacketHeader of + {Width, Endianness} -> Data2 = iolist_to_binary(Data), Size = byte_size(Data2), - %% ?DBG([{packet, Packet}, {data_size, Size}]), - Header = <>, + %% ?DBG([{packet, Packet}, {data_size, Size}]), + Header = + case Endianness of + big -> + <>; + little -> + <>; + native -> + <> + end, Header_Data = [Header, Data2], Result = socket_sendv(Socket, Header_Data, SendTimeout), send_result(Server, Header_Data, Meta, Result); - true -> + false -> Result = socket_send(Socket, Data, SendTimeout), send_result(Server, Data, Meta, Result) end; @@ -1432,13 +1454,19 @@ nopush_or_cork() -> end. %% -type packet_option_value() :: -%% 0 | 1 | 2 | 4 | raw | sunrm | asn1 | +%% 0 | 1 | 2 | 3 | 4 | {2 | 3 | 4, big | little | native} | +%% raw | sunrm | asn1 | %% cdr | fcgi | line | tpkt | http | httph | http_bin | httph_bin. -compile({inline, [is_packet_option_value/1]}). is_packet_option_value(Value) -> case Value of - 0 -> true; 1 -> true; 2 -> true; 4 -> true; + 0 -> true; 1 -> true; 2 -> true; 3 -> true; 4 -> true; + {Width, Endianness} + when is_integer(Width), 2 =< Width, Width =< 4, + (Endianness =:= big orelse + Endianness =:= little orelse + Endianness =:= native) -> true; raw -> true; sunrm -> true; asn1 -> true; @@ -2625,10 +2653,16 @@ decode_packet(D, Data, PacketType, Options) -> -compile({inline, [packet_header_length/1]}). packet_header_length(PacketType) -> case PacketType of + {Width, Endianness} + when is_integer(Width), 2 =< Width, Width =< 4, + (Endianness =:= big orelse + Endianness =:= little orelse + Endianness =:= native) -> Width; raw -> error(badarg, [PacketType]); 0 -> error(badarg, [PacketType]); 1 -> 1; 2 -> 2; + 3 -> 3; 4 -> 4; cdr -> 12; sunrm -> 4; @@ -3069,8 +3103,18 @@ call_setopts_server(P, D, State, Opts, Tag, Val) -> packet -> case is_packet_option_value(Val) of true -> + Packet = + case Val of + {Width, big} -> Width; + {Width, native} -> + case erlang:system_info(endian) of + big -> Width; + little -> {Width, little} + end; + _ -> Val + end, call_setopts( - P, maps:remove(recv_httph, D#{packet => Val}), + P, maps:remove(recv_httph, D#{packet => Packet}), State, Opts); false -> call_setopts_result({error, einval}, D) diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index bc8dbd4457eb..c09278003ae3 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -1240,13 +1240,19 @@ The following options are available: - **`raw | 0`** - No packaging is done. - - **`1 | 2 | 4`** - Packets consist of a header specifying the number of bytes - in the packet, followed by that number of bytes. The header length can be - one, two, or four bytes, and containing an unsigned integer in big-endian - byte order. Each send operation generates the header, and the header is - stripped off on each receive operation. + - **`1 | 2 | 3 | 4`** - Packets consist of a header specifying the number of + bytes in the packet, followed by that number of bytes. The header length can + be one, two, three, or four bytes and contains an unsigned integer in + big-endian byte order. Each send operation generates the header, and the + header is stripped off on each receive operation. - The 4-byte header is limited to 2Gb. + On receive, a 4-byte header and body must fit in the parser's signed + length. Sending retains the legacy unsigned 32-bit header range. + + - **`{N, Endian}`** - Uses the same framing with a two-, three-, or four-byte + header, in the byte order specified by `Endian`, which can be `big`, + `little`, or `native`. `{N, big}` is equivalent to `N`; `native` uses the + native byte order of the runtime system. - **`asn1 | cdr | sunrm | fcgi | tpkt | line`** - These packet types only have effect on receiving. When sending a packet, it is the responsibility of the @@ -4381,7 +4387,8 @@ info({'$inet', GenSocketMod, _} = S, F, Proto) when is_atom(GenSocketMod) -> end; packet -> case GenSocketMod:which_packet_type(S) of - {ok, Type} -> atom_to_list(Type); + {ok, Type} when is_atom(Type) -> atom_to_list(Type); + {ok, Type} -> lists:flatten(io_lib:format("~p", [Type])); _ -> " " end; type -> @@ -4437,6 +4444,7 @@ info(S, F, Proto) -> case prim_inet:getopt(S, packet) of {ok,Type} when is_atom(Type) -> atom_to_list(Type); {ok,Type} when is_integer(Type) -> integer_to_list(Type); + {ok,Type} -> lists:flatten(io_lib:format("~p", [Type])); _ -> " " end; type -> diff --git a/lib/kernel/src/inet_int.hrl b/lib/kernel/src/inet_int.hrl index c9bc4cc08423..0299c7d04a98 100644 --- a/lib/kernel/src/inet_int.hrl +++ b/lib/kernel/src/inet_int.hrl @@ -212,11 +212,10 @@ -define(INET_IFOPT_FLAGS, 6). -define(INET_IFOPT_HWADDR, 7). %% where support (e.g linux) -%% packet byte values, INET_LOPT_PACKET -define(TCP_PB_RAW, 0). -define(TCP_PB_1, 1). --define(TCP_PB_2, 2). --define(TCP_PB_4, 3). +-define(TCP_PB_2_BIG, 2). +-define(TCP_PB_4_BIG, 3). -define(TCP_PB_ASN1, 4). -define(TCP_PB_RM, 5). -define(TCP_PB_CDR, 6). @@ -228,6 +227,10 @@ -define(TCP_PB_SSL_TLS, 12). -define(TCP_PB_HTTP_BIN,13). -define(TCP_PB_HTTPH_BIN,14). +-define(TCP_PB_2_LITTLE, 15). +-define(TCP_PB_3_BIG, 16). +-define(TCP_PB_3_LITTLE, 17). +-define(TCP_PB_4_LITTLE, 18). %% getstat, INET_REQ_GETSTAT diff --git a/lib/kernel/test/gen_tcp_echo_SUITE.erl b/lib/kernel/test/gen_tcp_echo_SUITE.erl index f0ac37f9ea42..5b857a4c364d 100644 --- a/lib/kernel/test/gen_tcp_echo_SUITE.erl +++ b/lib/kernel/test/gen_tcp_echo_SUITE.erl @@ -29,6 +29,7 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2, end_per_testcase/2, + packet_change_pending_recv/1, packet_endian/1, active_echo/1, passive_echo/1, active_once_echo/1, slow_active_echo/1, slow_passive_echo/1, limit_active_echo/1, limit_passive_echo/1, @@ -56,9 +57,12 @@ all() -> end. groups() -> - [{inet_backend_default, [{group, read_ahead}, {group, no_read_ahead}]}, - {inet_backend_socket, [{group, read_ahead}, {group, no_read_ahead}]}, - {inet_backend_inet, [{group, read_ahead}, {group, no_read_ahead}]}, + [{inet_backend_default, [packet_change_pending_recv, packet_endian, + {group, read_ahead}, {group, no_read_ahead}]}, + {inet_backend_socket, [packet_endian, + {group, read_ahead}, {group, no_read_ahead}]}, + {inet_backend_inet, [packet_change_pending_recv, packet_endian, + {group, read_ahead}, {group, no_read_ahead}]}, %% {read_ahead, [{group, no_delay_send}, {group, delay_send}]}, {no_read_ahead, [{group, no_delay_send}, {group, delay_send}]}, @@ -237,6 +241,173 @@ large_limit_passive_echo(Config) when is_list(Config) -> Config, [{packet_size, 10},{active, false}], fun passive_echo/4, [{packet_size, (1 bsl 32) -1}, {echo, fun echo_server/0}]). +%% Test changing packet type while a passive receive is pending in the legacy +%% inet driver. +packet_change_pending_recv(Config) when is_list(Config) -> + {ok, Listen} = + kernel_test_lib:listen( + Config, 0, [binary, {active, false}, {packet, 2}]), + {ok, {_, Port}} = inet:sockname(Listen), + {ok, Socket} = + kernel_test_lib:connect( + Config, localhost, Port, + [binary, {active, false}, {packet, 2}]), + {ok, Peer} = gen_tcp:accept(Listen), + try + case is_port(Socket) of + true -> + %% <<3,"ABC">> is incomplete with a two-byte header + %% (16#0341), but complete with a one-byte header. Changing + %% the packet type must reparse the buffer and complete the + %% already-pending passive receive. + ok = gen_tcp:unrecv(Socket, <<3, $A, $B, $C>>), + {ok, Ref1} = prim_inet:async_recv(Socket, 0, -1), + ok = inet:setopts(Socket, [{packet, 1}]), + {ok, <<$A, $B, $C>>} = + receive + {inet_async, Socket, Ref1, Result1} -> Result1 + after 5000 -> + ct:fail(packet_change_pending_recv_timeout) + end, + + %% Re-entering through tcp_recv() must also retain its normal + %% error path when the same bytes are invalid in the new mode. + ok = inet:setopts( + Socket, [{packet, 2}, {packet_size, 3}]), + ok = gen_tcp:unrecv(Socket, <<0, 3, 255, 255>>), + {ok, Ref2} = prim_inet:async_recv(Socket, 0, -1), + ok = inet:setopts(Socket, [{packet, 4}]), + {error, emsgsize} = + receive + {inet_async, Socket, Ref2, Result2} -> Result2 + after 5000 -> + ct:fail(packet_change_pending_recv_error_timeout) + end; + false -> + {skip, "Legacy inet backend only"} + end + after + _ = gen_tcp:close(Peer), + _ = gen_tcp:close(Socket), + _ = gen_tcp:close(Listen) + end. + +%% Test endian-aware packet framing in both directions against a raw peer. +packet_endian(Config) when is_list(Config) -> + {ok, Listen} = + kernel_test_lib:listen( + Config, 0, [binary, {active, false}, {packet, {3, native}}]), + {ok, {_, Port}} = inet:sockname(Listen), + {ok, Socket} = + kernel_test_lib:connect( + Config, localhost, Port, + [binary, {active, false}, {packet, {3, native}}]), + {ok, Peer} = gen_tcp:accept(Listen), + try + NativePacket3 = + case erlang:system_info(endian) of + big -> 3; + little -> {3, little} + end, + {ok, [{packet, NativePacket3}]} = inet:getopts(Socket, [packet]), + {ok, [{packet, NativePacket3}]} = inet:getopts(Peer, [packet]), + ok = inet:setopts(Peer, [{packet, raw}]), + lists:foreach( + fun({Packet, Width}) -> + packet_endian(Socket, Peer, Packet, Width) + end, + [{{2, big}, 2}, {{2, little}, 2}, {{2, native}, 2}, + {{3, big}, 3}, {3, 3}, {{3, little}, 3}, {{3, native}, 3}, + {{4, big}, 4}, {{4, little}, 4}, {{4, native}, 4}]), + lists:foreach( + fun(BadPacket) -> + {error, einval} = + inet:setopts(Socket, [{packet, BadPacket}]) + end, + [{1, big}, {5, big}, {2, middle}, {2}, + {2, little, extra}, {2.0, big}]), + ok + after + _ = gen_tcp:close(Peer), + _ = gen_tcp:close(Socket), + _ = gen_tcp:close(Listen) + end. + +packet_endian(Socket, Peer, Packet, Width) -> + ok = inet:setopts(Socket, [{packet, Packet}]), + case Packet of + 3 -> + {ok, [{packet, 3}]} = inet:getopts(Socket, [packet]); + {Width, big} -> + {ok, [{packet, Width}]} = inet:getopts(Socket, [packet]); + {_, little} -> + {ok, [{packet, Packet}]} = inet:getopts(Socket, [packet]); + {Width, native} -> + Expected = + case erlang:system_info(endian) of + big -> Width; + little -> {Width, little} + end, + {ok, [{packet, Expected}]} = inet:getopts(Socket, [packet]); + _ -> + ok + end, + + {OutboundSize, InboundSize} = + case Width of + 3 -> {16#010203, 16#010204}; + _ -> {16#0102, 16#0201} + end, + Outbound = binary:copy(<<$O>>, OutboundSize), + Inbound = binary:copy(<<$I>>, InboundSize), + {OutboundWire, InboundWire} = + case Packet of + {2, big} -> + {<>, + <>}; + {2, little} -> + {<>, + <>}; + {2, native} -> + {<>, + <>}; + 3 -> + {<>, + <>}; + {3, big} -> + {<>, + <>}; + {3, little} -> + {<>, + <>}; + {3, native} -> + {<>, + <>}; + {4, big} -> + {<>, + <>}; + {4, little} -> + {<>, + <>}; + {4, native} -> + {<>, + <>} + end, + ok = gen_tcp:send(Socket, Outbound), + {ok, OutboundWire} = + gen_tcp:recv(Peer, Width + byte_size(Outbound), 5000), + + ok = gen_tcp:send(Peer, InboundWire), + {ok, Inbound} = gen_tcp:recv(Socket, 0, 5000), + case Packet of + {2, little} -> + {error, emsgsize} = + gen_tcp:send(Socket, binary:copy(<<0>>, 16#10000)); + _ -> + ok + end, + ok. + echo_test(Config, SockOpts_0, EchoFun, EchoOpts_0) -> SockOpts = SockOpts_0 ++ sockopts(Config), ct:log("SockOpts = ~p.", [SockOpts]), @@ -246,6 +417,7 @@ echo_test(Config, SockOpts_0, EchoFun, EchoOpts_0) -> echo_packet(Config, [{packet, 1}|SockOpts], EchoFun, EchoOpts), echo_packet(Config, [{packet, 2}|SockOpts], EchoFun, EchoOpts), + echo_packet(Config, [{packet, 3}|SockOpts], EchoFun, EchoOpts), echo_packet(Config, [{packet, 4}|SockOpts], EchoFun, EchoOpts), echo_packet(Config, [{packet, sunrm}|SockOpts], EchoFun, EchoOpts), echo_packet(Config, [{packet, cdr}|SockOpts], EchoFun, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 1509688ebd92..04d9c7fbd964 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -3937,10 +3937,28 @@ encode_packet(Packet, Data) -> case Packet of 1 when Len < (1 bsl 8) -> [<>|Data]; 2 when Len < (1 bsl 16) -> [<>|Data]; + 3 when Len < (1 bsl 24) -> [<>|Data]; 4 when Len < (1 bsl 32) -> [<>|Data]; - N when N =:= 1; N =:= 2; N =:= 4 -> + {N, big} + when (N =:= 2 orelse N =:= 3 orelse N =:= 4), + Len < (1 bsl (N bsl 3)) -> + [<>|Data]; + {N, little} + when (N =:= 2 orelse N =:= 3 orelse N =:= 4), + Len < (1 bsl (N bsl 3)) -> + [<>|Data]; + {N, native} + when (N =:= 2 orelse N =:= 3 orelse N =:= 4), + Len < (1 bsl (N bsl 3)) -> + [<>|Data]; + N when N =:= 1; N =:= 2; N =:= 3; N =:= 4 -> {error, {badarg, {packet_to_large, Len, (1 bsl (Packet bsl 3)) - 1}}}; + {N, Endian} + when (N =:= 2 orelse N =:= 3 orelse N =:= 4), + (Endian =:= big orelse Endian =:= little orelse Endian =:= native) -> + {error, + {badarg, {packet_to_large, Len, (1 bsl (N bsl 3)) - 1}}}; _ -> Data end. @@ -3955,4 +3973,3 @@ handle_trace(rle, {call, {?MODULE, listen, Args}}, Stack0) -> handle_trace(rle, {call, {?MODULE, connect, Args}}, Stack0) -> Role = client, {io_lib:format("(*~w) Args = ~W", [Role, Args, 10]), [{role, Role} | Stack0]}. - diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index 7d1fc074a60a..09a5cbb6addd 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -2094,7 +2094,17 @@ set_socket_opts(tls_gen_connection, Transport, Socket, Tab, [{packet, Packet}| O Packet == 0; Packet == 1; Packet == 2; + Packet == 3; Packet == 4; + Packet == {2, big}; + Packet == {2, little}; + Packet == {2, native}; + Packet == {3, big}; + Packet == {3, little}; + Packet == {3, native}; + Packet == {4, big}; + Packet == {4, little}; + Packet == {4, native}; Packet == asn1; Packet == cdr; Packet == sunrm; diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl index f5b387281082..99efe8f6ebf5 100644 --- a/lib/ssl/src/tls_socket.erl +++ b/lib/ssl/src/tls_socket.erl @@ -339,10 +339,20 @@ session_id_tracker(ListenSocket, _) -> get_emulated_opts(TrackerPid) -> call(TrackerPid, get_emulated_opts). set_emulated_opts(TrackerPid, InetValues) -> + validate_listener_packet_options(InetValues), call(TrackerPid, {set_emulated_opts, InetValues}). accept_options(TrackerPid) -> call(TrackerPid, accept_options). +validate_listener_packet_options([{packet, Value} | Rest]) + when is_tuple(Value) -> + validate_inet_option(packet, Value), + validate_listener_packet_options(Rest); +validate_listener_packet_options([_ | Rest]) -> + validate_listener_packet_options(Rest); +validate_listener_packet_options([]) -> + ok. + %%==================================================================== %% ssl_listen_tracker_sup API %%==================================================================== @@ -570,6 +580,10 @@ emulated_options([], Inet,Emulated) -> validate_inet_option(mode, Value) when Value =/= list, Value =/= binary -> throw({error, {options, {mode,Value}}}); +validate_inet_option(packet, {N, Endian}) + when (N =:= 2 orelse N =:= 3 orelse N =:= 4), + (Endian =:= big orelse Endian =:= little orelse Endian =:= native) -> + ok; validate_inet_option(packet, Value) when not (is_atom(Value) orelse is_integer(Value)) -> throw({error, {options, {packet,Value}}}); diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index 4ba0f1c5108e..a7684b7f3ba0 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -131,7 +131,9 @@ packet_size_active_setoptsgetopts/0, packet_size_active_setoptsgetopts/1, packet_switch/0, + packet_endian_switch/0, packet_switch/1, + packet_endian_switch/1, header_decode_one_byte_active/0, header_decode_one_byte_active/1, header_decode_two_bytes_active/0, @@ -169,7 +171,9 @@ packet_sunrm_decode_list/0, packet_sunrm_decode_list/1, packet_send_to_large/0, + packet_endian_send_to_large/0, packet_send_to_large/1, + packet_endian_send_to_large/1, packet_tpkt_decode/0, packet_tpkt_decode/1, packet_tpkt_decode_list/0, @@ -184,6 +188,7 @@ passive_recv_packet/3, passive_recv_packet_size/3, send/3, + send_packet_limit/2, send_incomplete/3, send_incomplete_after_client/3, active_once_raw/4, @@ -191,6 +196,7 @@ active_raw/3, active_once_raw/3, active_packet/3, + active_packet_limit/2, active_packet_size/3, assert_packet_opt/2, server_packet_decode/2, @@ -219,7 +225,9 @@ add_tpkt_header/1, client_reject_packet_opt/2, send_switch_packet/3, - recv_switch_packet/3 + send_endian_switch_packet/4, + recv_switch_packet/3, + recv_endian_switch_packet/3 ]). -define(BYTE(X), X:8/unsigned-big-integer). @@ -340,6 +348,7 @@ socket_active_packet_tests() -> packet_size_active, packet_size_active_setoptsgetopts, packet_switch, + packet_endian_switch, %% inet header option should be deprecated! header_decode_one_byte_active, header_decode_two_bytes_active, @@ -683,6 +692,45 @@ packet_send_to_large(Config) when is_list(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +packet_endian_send_to_large() -> + [{doc,"Test endian-aware packet length limits on the send side"}]. + +packet_endian_send_to_large(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Cases = [{1, 16#ff}, + {3, 16#ffffff}, + {{2, little}, 16#ffff}], + lists:foreach( + fun({Packet, Limit}) -> + Server = + ssl_test_lib:start_server( + [{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_packet_limit, [Limit]}}, + {options, [binary, {packet, Packet} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client( + [{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, active_packet_limit, [Limit]}}, + {options, [binary, {active, true}, {packet, Packet} | + ClientOpts]}]), + + ssl_test_lib:check_result( + Server, + {error, {badarg, + {packet_to_large, Limit + 1, Limit}}}, + Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client) + end, Cases). + %%-------------------------------------------------------------------- packet_wait_active() -> [{doc,"Test waiting when complete packages have not arrived"}]. @@ -982,6 +1030,78 @@ packet_switch(Config) when is_list(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +packet_endian_switch() -> + [{doc,"Test switching between endian-aware packet options"}]. + +packet_endian_switch(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% Non-palindromic sizes expose byte order at the raw peer; packet-3 also + %% uses its high byte so all three header bytes participate. + PacketOpts = [{2, 16#0102}, {4, 16#0102}, {3, 16#010000}, + {{2, big}, 16#0102}, + {{2, little}, 16#0102}, + {{2, native}, 16#0102}, + {{3, big}, 16#010000}, + {{3, little}, 16#010000}, + {{3, native}, 16#010000}, + {{4, big}, 16#0102}, + {{4, little}, 16#0102}, + {{4, native}, 16#0102}], + InvalidPackets = [{1, big}, {2, middle}, {2}, {2, big, extra}], + + lists:foreach( + fun(Packet) -> + {error, {options, {packet, Packet}}} = + ssl:listen(0, [{packet, Packet} | ServerOpts]) + end, InvalidPackets), + + {ok, ListenSocket} = ssl:listen(0, [{packet, 3} | ServerOpts]), + try + %% Listener packet options are inherited by accepted sockets. Reject + %% invalid tuple forms before they can replace that shared state. + ok = ssl:setopts(ListenSocket, [{packet, {2, little}}]), + lists:foreach( + fun(Packet) -> + {error, + {options, + {socket_options, [{packet, Packet}], _}}} = + ssl:setopts(ListenSocket, [{packet, Packet}]) + end, InvalidPackets), + {ok, [{packet, {2, little}}]} = + ssl:getopts(ListenSocket, [packet]) + after + ok = ssl:close(ListenSocket) + end, + + Server = + ssl_test_lib:start_server( + [{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_endian_switch_packet, + [self(), PacketOpts, InvalidPackets]}}, + {options, [binary, {active, false}, {nodelay, true}, + {packet, 3} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client( + [{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, recv_endian_switch_packet, [self(), PacketOpts]}}, + {options, [binary, {active, false}, {nodelay, true}, + {packet, {3, native}} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ready, Client, ready), + Server ! go, + Client ! go, + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + %%-------------------------------------------------------------------- packet_cdr_decode() -> @@ -2325,6 +2445,11 @@ send(Socket, Data, N) -> Other end. +send_packet_limit(Socket, Limit) -> + Data = binary:copy(<<0>>, Limit), + ok = ssl:send(Socket, Data), + ssl:send(Socket, [Data, <<0>>]). + send_incomplete(Socket, Data, N) -> send_incomplete(Socket, Data, N, <<>>). send_incomplete(Socket, _Data, 0, Prev) -> @@ -2401,6 +2526,15 @@ active_packet(Socket, Data, N) -> Other end. +active_packet_limit(Socket, Limit) -> + Data = binary:copy(<<0>>, Limit), + receive + {ssl, Socket, Data} -> + ok; + Other -> + Other + end. + active_packet_size(Socket, Data, Size) -> ok = ssl:setopts(Socket, [{packet_size, Size}]), {ok, [{packet_size, Size}]} = ssl:getopts(Socket, [packet_size]), @@ -2579,6 +2713,29 @@ send_switch_packet(SslSocket, Data, NextPacket) -> ok end end. + +send_endian_switch_packet(SslSocket, Parent, PacketOpts, InvalidPackets) -> + assert_packet_opt(SslSocket, 3), + lists:foreach( + fun(Packet) -> + {error, {options, {socket_options, {packet, Packet}}}} = + ssl:setopts(SslSocket, [{packet, Packet}]), + assert_packet_opt(SslSocket, 3) + end, InvalidPackets), + Parent ! {self(), ready}, + receive go -> ok end, + lists:foreach( + fun({Packet, Size}) -> + Data = binary:copy(<<$S>>, Size), + ok = ssl:setopts(SslSocket, [{packet, Packet}]), + assert_packet_opt(SslSocket, Packet), + ok = ssl:send(SslSocket, Data), + {ok, Data} = ssl:recv(SslSocket, 0) + end, PacketOpts), + ok. + + + recv_switch_packet(SslSocket, Data, NextPacket) -> receive {ssl, SslSocket, "Hello World"} -> @@ -2590,3 +2747,35 @@ recv_switch_packet(SslSocket, Data, NextPacket) -> ok end end. + +recv_endian_switch_packet(SslSocket, Parent, PacketOpts) -> + %% Keep this endpoint raw and compare prefixes before echoing frames. If + %% both peers decoded packets, matching endian bugs could cancel out. + assert_packet_opt(SslSocket, {3, native}), + ok = ssl:setopts(SslSocket, [{packet, raw}]), + assert_packet_opt(SslSocket, raw), + Parent ! {self(), ready}, + receive go -> ok end, + lists:foreach( + fun({Packet, Size}) -> + Header = + case Packet of + 2 -> <>; + 3 -> <>; + 4 -> <>; + {2, big} -> <>; + {2, little} -> <>; + {2, native} -> <>; + {3, big} -> <>; + {3, little} -> <>; + {3, native} -> <>; + {4, big} -> <>; + {4, little} -> <>; + {4, native} -> <> + end, + Data = binary:copy(<<$S>>, Size), + {ok, Header} = ssl:recv(SslSocket, byte_size(Header)), + {ok, Data} = ssl:recv(SslSocket, byte_size(Data)), + ok = ssl:send(SslSocket, [Header, Data]) + end, PacketOpts), + ok. diff --git a/system/doc/reference_manual/ports.md b/system/doc/reference_manual/ports.md index 7e31a3ba9f8d..ea2b3fdb01c6 100644 --- a/system/doc/reference_manual/ports.md +++ b/system/doc/reference_manual/ports.md @@ -76,9 +76,11 @@ is found, that driver is started. `PortSettings` is a list of settings (options) for the port. The list typically contains at least a tuple `{packet,N}`, which specifies that data sent between -the port and the external program are preceded by an N-byte length indicator. -Valid values for N are 1, 2, or 4. If binaries are to be used instead of lists -of bytes, the option `binary` must be included. +the port and the external program are preceded by an N-byte length indicator in +big-endian byte order. Valid values for N are 1, 2, 3, or 4. For two-, three-, +and four-byte headers, `{packet,{N,Endian}}` can be used to select `big`, +`little`, or the platform-native byte order. If binaries are to be used instead +of lists of bytes, the option `binary` must be included. The port owner `Pid` can communicate with the port `Port` by sending and receiving messages. (In fact, any process can send messages to the port, but