diff --git a/unix/dune b/unix/dune old mode 100644 new mode 100755 index 9031788a..d48f1743 --- a/unix/dune +++ b/unix/dune @@ -2,4 +2,8 @@ (name cstruct_unix) (wrapped false) (public_name cstruct-unix) + (foreign_stubs + (language c) + (names read_stubs write_stubs writev_stubs send_stubs recv_stubs + recvfrom_stubs sendto_stubs)) (libraries cstruct unix)) diff --git a/unix/read_stubs.c b/unix/read_stubs.c new file mode 100644 index 00000000..53afb45e --- /dev/null +++ b/unix/read_stubs.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +CAMLprim value stub_cstruct_read(value val_fd, value val_c) +{ + CAMLparam2(val_fd, val_c); + CAMLlocal3(val_buf, val_ofs, val_len); + uint8_t *buf; + size_t len; + ssize_t n = 0; +#ifdef WIN32 + int win32err = 0; + SOCKET s; + HANDLE h; + DWORD numread; + int ok; +#endif + val_buf = Field(val_c, 0); + val_ofs = Field(val_c, 1); + val_len = Field(val_c, 2); + + buf = (uint8_t *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); + len = (size_t)Long_val(val_len); + +#ifdef WIN32 + switch (Descr_kind_val(val_fd)) + { + case KIND_SOCKET: + s = Socket_val(val_fd); + + caml_release_runtime_system(); + n = recv(s, buf, len, 0); + win32err = WSAGetLastError(); + caml_acquire_runtime_system(); + + if (n == SOCKET_ERROR) + { + win32_maperr(win32err); + uerror("stub_cstruct_read", Nothing); + } + break; + case KIND_HANDLE: + h = Handle_val(val_fd); + caml_release_runtime_system(); + ok = ReadFile(h, buf, len, &numread, NULL); + win32err = GetLastError(); + n = numread; + caml_acquire_runtime_system(); + + if (!ok) + { + win32_maperr(win32err); + uerror("stub_cstruct_read", Nothing); + } + break; + default: + caml_failwith("unknown Descr_kind_val"); + } +#else + caml_release_runtime_system(); + n = read(Int_val(val_fd), buf, len); + caml_acquire_runtime_system(); + if (n < 0) + uerror("stub_cstruct_read", Nothing); +#endif + CAMLreturn(Val_int(n)); +} diff --git a/unix/recv_stubs.c b/unix/recv_stubs.c new file mode 100644 index 00000000..a95dbf62 --- /dev/null +++ b/unix/recv_stubs.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#include +#endif + +static int msg_flag_table[] = { + MSG_OOB, MSG_DONTROUTE, MSG_PEEK /* XXX */ +}; + +CAMLprim value stub_cstruct_recv(value val_fd, value val_c, value val_flags) +{ + CAMLparam3(val_fd, val_c, val_flags); + CAMLlocal3(val_buf, val_ofs, val_len); + uint8_t *buf; + size_t len; + ssize_t n = 0; + int cv_flags; +#ifdef WIN32 + int win32err = 0; + SOCKET s = Socket_val(val_fd); +#endif + + val_buf = Field(val_c, 0); + val_ofs = Field(val_c, 1); + val_len = Field(val_c, 2); + + buf = (uint8_t *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); + len = (size_t)Long_val(val_len); + cv_flags = caml_convert_flag_list(val_flags, msg_flag_table); +#ifdef WIN32 + if (Descr_kind_val(val_fd) != KIND_SOCKET) + unix_error(EINVAL, "stub_cstruct_recv", Nothing); + + caml_release_runtime_system(); + n = recv(s, buf, len, cv_flags); + win32err = WSAGetLastError(); + caml_acquire_runtime_system(); + + if (n == SOCKET_ERROR) + { + win32_maperr(win32err); + uerror("stub_cstruct_recv", Nothing); + } +#else + caml_release_runtime_system(); + n = recv(Int_val(val_fd), buf, len, cv_flags); + caml_acquire_runtime_system(); + + if (n < 0) + uerror("stub_cstruct_recv", Nothing); +#endif + CAMLreturn(Val_int(n)); +} diff --git a/unix/recvfrom_stubs.c b/unix/recvfrom_stubs.c new file mode 100644 index 00000000..53c0b023 --- /dev/null +++ b/unix/recvfrom_stubs.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif + +static int msg_flag_table[] = { + MSG_OOB, MSG_DONTROUTE, MSG_PEEK /* XXX */ +}; + +CAMLprim value stub_cstruct_recvfrom(value val_fd, value val_c, value val_flags) +{ + CAMLparam3(val_fd, val_c, val_flags); + CAMLlocal5(val_buf, val_ofs, val_len, val_addr, val_res); + uint8_t *buf; + size_t len; + ssize_t n; + int cv_flags; + union sock_addr_union addr; + socklen_param_type addr_len; +#ifdef WIN32 + int win32err = 0; + SOCKET s = Socket_val(val_fd); +#endif + + val_buf = Field(val_c, 0); + val_ofs = Field(val_c, 1); + val_len = Field(val_c, 2); + cv_flags = caml_convert_flag_list(val_flags, msg_flag_table); + + buf = (uint8_t *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); + len = Long_val(val_len); + addr_len = sizeof(addr); +#ifdef WIN32 + if (Descr_kind_val(val_fd) != KIND_SOCKET) + unix_error(EINVAL, "stub_cstruct_recvfrom", Nothing); + + caml_release_runtime_system(); + n = recvfrom(s, buf, len, cv_flags, &addr.s_gen, &addr_len); + win32err = WSAGetLastError(); + caml_acquire_runtime_system(); + + if (n == SOCKET_ERROR) + { + win32_maperr(win32err); + uerror("stub_cstruct_recvfrom", Nothing); + } +#else + caml_release_runtime_system(); + n = recvfrom(Int_val(val_fd), buf, len, cv_flags, &addr.s_gen, &addr_len); + caml_acquire_runtime_system(); + + if (n == -1) + uerror("stub_cstruct_recvfrom", Nothing); +#endif + + val_addr = alloc_sockaddr(&addr, addr_len, -1); + val_res = caml_alloc_small(2, 0); + Field(val_res, 0) = Val_int(n); + Field(val_res, 1) = val_addr; + + CAMLreturn (val_res); +} diff --git a/unix/send_stubs.c b/unix/send_stubs.c new file mode 100644 index 00000000..aa330407 --- /dev/null +++ b/unix/send_stubs.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#include +#endif + +static int msg_flag_table[] = { + MSG_OOB, MSG_DONTROUTE, MSG_PEEK /* XXX */ +}; + +CAMLprim value stub_cstruct_send(value val_fd, value val_c, value val_flags) +{ + CAMLparam3(val_fd, val_c, val_flags); + CAMLlocal3(val_buf, val_ofs, val_len); + uint8_t *buf; + size_t len; + ssize_t n = 0; + int cv_flags = caml_convert_flag_list(val_flags, msg_flag_table); +#ifdef WIN32 + int win32err = 0; + SOCKET s = Socket_val(val_fd); +#endif + + val_buf = Field(val_c, 0); + val_ofs = Field(val_c, 1); + val_len = Field(val_c, 2); + + buf = (uint8_t *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); + len = (size_t)Long_val(val_len); + +#ifdef WIN32 + if (Descr_kind_val(val_fd) != KIND_SOCKET) + unix_error(EINVAL, "stub_cstruct_send", Nothing); + + caml_release_runtime_system(); + n = send(s, buf, len, cv_flags); + win32err = WSAGetLastError(); + caml_acquire_runtime_system(); + + if (n == SOCKET_ERROR) + { + win32_maperr(win32err); + uerror("stub_cstruct_send", Nothing); + } +#else + caml_release_runtime_system(); + n = send(Int_val(val_fd), buf, len, cv_flags); + caml_acquire_runtime_system(); + if (n < 0) + uerror("stub_cstruct_send", Nothing); +#endif + CAMLreturn(Val_int(n)); +} diff --git a/unix/sendto_stubs.c b/unix/sendto_stubs.c new file mode 100644 index 00000000..02bb4c9c --- /dev/null +++ b/unix/sendto_stubs.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif + +static int msg_flag_table[] = { /* XXX */ + MSG_OOB, MSG_DONTROUTE, MSG_PEEK +}; + +CAMLprim value stub_cstruct_sendto(value val_fd, value val_c, value val_flags, value val_daddr) +{ + CAMLparam4(val_fd, val_c, val_flags, val_daddr); + CAMLlocal5(val_buf, val_ofs, val_len, val_addr, val_res); + union sock_addr_union addr; + socklen_param_type addr_len; + uint8_t *buf; + size_t len; + ssize_t n; + int cv_flags; +#ifdef WIN32 + int win32err = 0; + SOCKET s = Socket_val(val_fd); +#endif + + val_buf = Field(val_c, 0); + val_ofs = Field(val_c, 1); + val_len = Field(val_c, 2); + + buf = (uint8_t *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); + len = Long_val(val_len); + get_sockaddr(val_daddr, &addr, &addr_len); + cv_flags = caml_convert_flag_list(val_flags, msg_flag_table); + +#ifdef WIN32 + if (Descr_kind_val(val_fd) != KIND_SOCKET) + unix_error(EINVAL, "stub_cstruct_sendto", Nothing); + + caml_release_runtime_system(); + n = sendto(s, buf, len, cv_flags, &addr.s_gen, addr_len); + win32err = WSAGetLastError(); + caml_acquire_runtime_system(); + + if (n == SOCKET_ERROR) + { + win32_maperr(win32err); + uerror("stub_cstruct_sendto", Nothing); + } +#else + caml_release_runtime_system(); + n = sendto(Int_val(val_fd), buf, len, cv_flags, &addr.s_gen, addr_len); + caml_acquire_runtime_system(); + + if (n == -1) + uerror("stub_cstruct_sendto", Nothing); +#endif + CAMLreturn (Val_int(n)); +} diff --git a/unix/unix_cstruct.ml b/unix/unix_cstruct.ml index 5ac69851..27429284 100644 --- a/unix/unix_cstruct.ml +++ b/unix/unix_cstruct.ml @@ -17,3 +17,56 @@ let of_fd fd = let buffer = Bigarray.(array1_of_genarray (Unix.map_file fd char c_layout false [|-1|])) in Cstruct.of_bigarray buffer + +(* Returns 0 if there is no writev *) +external stub_iov_max: unit -> int = "stub_cstruct_iov_max" + +external stub_write: Unix.file_descr -> Cstruct.t -> int = "stub_cstruct_write" + +external stub_writev: Unix.file_descr -> Cstruct.t list -> int = "stub_cstruct_writev" + +let iov_max = stub_iov_max () + +(* return the first n fragments, suitable for writev *) +let rec first n rev_acc = function +| [] -> List.rev rev_acc +| _ when n = 0 -> List.rev rev_acc +| x :: xs -> first (n - 1) (x :: rev_acc) xs + +(* shift a list of fragments along by n bytes *) +let rec shift t x = + if x = 0 then t else match t with + | [] -> invalid_arg "foo" + | y :: ys -> + let y' = Cstruct.length y in + if y' > x + then Cstruct.shift y x :: ys + else shift ys (x - y') + +let rec write fd buf = + if Cstruct.length buf > 0 then begin + let n = stub_write fd buf in + write fd @@ Cstruct.shift buf n + end + +let writev fd bufs = + let rec use_writev = function + | [] -> () + | remaining -> + (* write at most iov_max at a time *) + let to_send = first iov_max [] remaining in + let n = stub_writev fd to_send in + let rest = shift remaining n in + use_writev rest in + let use_write_fallback = List.iter (write fd) in + (if iov_max = 0 then use_write_fallback else use_writev) bufs + +external send: Unix.file_descr -> Cstruct.t -> Unix.msg_flag list -> int = "stub_cstruct_send" + +external recv: Unix.file_descr -> Cstruct.t -> Unix.msg_flag list -> int = "stub_cstruct_recv" + +external read: Unix.file_descr -> Cstruct.t -> int = "stub_cstruct_read" + +external recvfrom : Unix.file_descr -> Cstruct.t -> Unix.msg_flag list -> int * Unix.sockaddr = "stub_cstruct_recvfrom" + +external sendto : Unix.file_descr -> Cstruct.t -> Unix.msg_flag list -> Unix.sockaddr -> int = "stub_cstruct_sendto" diff --git a/unix/unix_cstruct.mli b/unix/unix_cstruct.mli index 361191d2..02061099 100644 --- a/unix/unix_cstruct.mli +++ b/unix/unix_cstruct.mli @@ -1,5 +1,6 @@ (* * Copyright (c) 2012 Anil Madhavapeddy + * Copyright (c) 2022 David Scott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,3 +19,30 @@ val of_fd : Unix.file_descr -> Cstruct.t (** [of_fd fd] memory maps the [fd] and returns a cstruct *) + +val read: Unix.file_descr -> Cstruct.t -> int +(** [read fd cs] reads from the file descriptor into the buffer, returning the number of bytes read. + Like {! Unix.read}, but for Cstruct. *) + +val write: Unix.file_descr -> Cstruct.t -> unit +(** [write fd cs] writes the whole Cstruct to the file descriptor. Like {! Unix.write}, but for Cstruct. *) + +val writev: Unix.file_descr -> Cstruct.t list -> unit +(** [writev fd cs] writes the whole list of Cstructs to the file descriptor. + Like {! Unix.write}, but for a list of Cstructs. *) + +val send: Unix.file_descr -> Cstruct.t -> Unix.msg_flag list -> int +(** [send fd c flags] sends the Cstruct to the file descriptor, returning the number of bytes written. + Like {! Unix.send}, but for Cstruct. *) + +val recv: Unix.file_descr -> Cstruct.t -> Unix.msg_flag list -> int +(** [recv fd c flags] receives up to a Cstruct's worth of data from the file descriptor, returning the number of bytes written. + Like {! Unix.recv}, but for Cstruct. *) + +val recvfrom: Unix.file_descr -> Cstruct.t -> Unix.msg_flag list -> int * Unix.sockaddr +(** [recvfrom fd c flags] receives up to a Cstruct's worth of data from the file descriptor, returning the number of bytes + read and the address they were sent from. Like {! Unix.recvfrom}, but for Cstruct. *) + +val sendto: Unix.file_descr -> Cstruct.t -> Unix.msg_flag list -> Unix.sockaddr -> int +(** [sendto fd c flags addr] sends a Cstruct's worth of data to the address via the file descriptor. + Like {! Unix.sendto}, but for Cstruct. *) diff --git a/unix/write_stubs.c b/unix/write_stubs.c new file mode 100644 index 00000000..deb7f1d5 --- /dev/null +++ b/unix/write_stubs.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +CAMLprim value stub_cstruct_write(value val_fd, value val_c) +{ + CAMLparam2(val_fd, val_c); + CAMLlocal3(val_buf, val_ofs, val_len); + val_buf = Field(val_c, 0); + val_ofs = Field(val_c, 1); + val_len = Field(val_c, 2); + void *buf = (char *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); + size_t len = Long_val(val_len); + ssize_t n = 0; + +#ifdef _WIN32 + int win32err = 0; + switch (Descr_kind_val(val_fd)) + { + case KIND_SOCKET: + SOCKET s = Socket_val(val_fd); + + caml_release_runtime_system(); + n = send(s, buf, len, 0); + win32err = WSAGetLastError(); + caml_acquire_runtime_system(); + + if (n == SOCKET_ERROR) + { + win32_maperr(win32err); + unix_error(errno, "stub_cstruct_write", Nothing); + } + break; + case KIND_HANDLE: + HANDLE h = Handle_val(val_fd); + DWORD numwritten; + caml_release_runtime_system(); + int ok = WriteFile(h, buf, len, &numwritten, NULL); + win32err = GetLastError(); + + n = numwritten; + caml_acquire_runtime_system(); + + if (!ok) + { + win32_maperr(win32err); + uerror("stub_cstruct_write", Nothing); + } + break; + default: + caml_failwith("unknown Descr_kind_val"); + } +#else + caml_release_runtime_system(); + n = write(Int_val(val_fd), buf, len); + caml_acquire_runtime_system(); + if (n < 0) + uerror("stub_cstruct_write", Nothing); +#endif + CAMLreturn(Val_int(n)); +} diff --git a/unix/writev_stubs.c b/unix/writev_stubs.c new file mode 100644 index 00000000..fafff451 --- /dev/null +++ b/unix/writev_stubs.c @@ -0,0 +1,68 @@ +#define _XOPEN_SOURCE /* IOV_MAX */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifndef _WIN32 +#include +#include +#endif + +CAMLprim + value + stub_cstruct_iov_max(value unit) +{ + CAMLparam1(unit); +#ifdef IOV_MAX + CAMLreturn(Val_int(IOV_MAX)); +#else + CAMLreturn(Val_int(0)); +#endif +} + +CAMLprim value stub_cstruct_writev(value fd, value val_list) +{ + CAMLparam2(fd, val_list); + CAMLlocal5(next, head, val_buf, val_ofs, val_len); + int i; +#ifdef _WIN32 + caml_failwith("writev is not supported on Win32"); +#else + struct iovec iovec[IOV_MAX]; + int c_fd = Int_val(fd); + + next = val_list; + /* Calculate the length of the val_list */ + int length = 0; + for (next = val_list; next != Val_emptylist; next = Field(next, 1)) + length++; + /* Only copy up to the iovec array size */ + if (length > IOV_MAX) + length = IOV_MAX; + next = val_list; + for (i = 0; i < length; i++) + { + head = Field(next, 0); + val_buf = Field(head, 0); + val_ofs = Field(head, 1); + val_len = Field(head, 2); + iovec[i].iov_base = (char *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); + iovec[i].iov_len = Long_val(val_len); + next = Field(next, 1); + } + + caml_release_runtime_system(); + ssize_t n = writev(c_fd, iovec, length); + caml_acquire_runtime_system(); + if (n < 0) + uerror("stub_cstruct_writev", Nothing); + CAMLreturn(Val_int(n)); +#endif +} diff --git a/unix_test/dune b/unix_test/dune new file mode 100644 index 00000000..45191d7c --- /dev/null +++ b/unix_test/dune @@ -0,0 +1,10 @@ +(executable + (libraries cstruct alcotest cstruct-unix threads) + (modules tests) + (name tests)) + +(rule + (alias runtest) + (package cstruct-unix) + (action + (run ./tests.exe -e))) diff --git a/unix_test/tests.ml b/unix_test/tests.ml new file mode 100644 index 00000000..96d9a3d0 --- /dev/null +++ b/unix_test/tests.ml @@ -0,0 +1,174 @@ + +let mk_temp_dir () = + let rand_num = Random.int 1000000 |> string_of_int in + let tmp_dir = Filename.get_temp_dir_name () ^ "/test-cstruct-unix-" ^ rand_num in + try + Unix.mkdir tmp_dir 0o700; + tmp_dir + with Unix.Unix_error(Unix.EEXIST, _, _) -> + (* re-use the old one *) + tmp_dir + | e -> raise (Sys_error ("Cannot create temp dir " ^ tmp_dir ^ " " ^ (Printexc.to_string e))) + +let rmdir path = + (* non-recursive for safety *) + match Sys.is_directory path with + | true -> + Sys.readdir path |> + Array.iter (fun name -> Sys.remove (Filename.concat path name)); + Unix.rmdir path + | false -> Sys.remove path + +let finally f g = + try + let r = f () in + g (); + r + with e -> + g (); + raise e + +let with_tmp_dir f = + let dir = mk_temp_dir () in + finally (fun () -> f dir) (fun () -> rmdir dir) + +let test_message_list =[ + Cstruct.of_string "hello"; + Cstruct.of_string " "; + Cstruct.of_string "cstruct"; + Cstruct.create 0; + Cstruct.of_string " "; + Cstruct.of_string "world"; +] + +let read_and_check fd sent_message = + let expected = Cstruct.(to_string @@ concat sent_message) in + let buf = Cstruct.create 1024 in + let read = ref 0 in + while !read < (String.length expected) do + let n = Unix_cstruct.read fd (Cstruct.shift buf !read) in + if n = 0 then raise End_of_file; + read := !read + n + done; + let actual = Cstruct.(to_string @@ sub buf 0 !read) in + Alcotest.(check string) "read contents" expected actual + +let test_writev_file () = + with_tmp_dir + (fun dir -> + let test = Filename.concat dir "test" in + let fd = Unix.openfile test [ Unix.O_CREAT; Unix.O_RDWR ] 0o644 in + finally (fun () -> + Unix_cstruct.writev fd test_message_list; + let ofs = Unix.lseek fd 0 Unix.SEEK_SET in + Alcotest.(check int) "file offset" 0 ofs; + read_and_check fd test_message_list + ) (fun () -> Unix.close fd) + ) + +let with_sock_stream f = + let s = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in + finally (fun () -> f s) (fun () -> Unix.close s) + +let localhost = Unix.inet_addr_of_string "127.0.0.1" + +let bind_random_port s = + Unix.bind s (Unix.ADDR_INET(localhost, 0)); + match Unix.getsockname s with + | Unix.ADDR_INET(_, port) -> port + | _ -> assert false + +let test_writev_socket () = + with_sock_stream (fun s -> + let port = bind_random_port s in + Unix.listen s 1; + let t = Thread.create (fun () -> + let client, _ = Unix.accept s in + finally + (fun () -> + read_and_check client test_message_list + ) (fun () -> Unix.close client) + ) () in + with_sock_stream (fun c -> + Unix.connect c (Unix.ADDR_INET(localhost, port)); + Unix_cstruct.writev c test_message_list; + ); + Thread.join t + ) + +let with_sock_dgram f = + let s = Unix.socket Unix.PF_INET Unix.SOCK_DGRAM 0 in + finally (fun () -> f s) (fun () -> Unix.close s) + +let with_mutex m f = + Mutex.lock m; + finally f (fun () -> Mutex.unlock m) + +let test_send_recv () = + let test_message = Cstruct.concat test_message_list in + with_sock_dgram (fun s -> + let port = bind_random_port s in + let m = Mutex.create () in + let finished = ref false in + let t = Thread.create (fun () -> + let buf = Cstruct.create 1024 in + let n = Unix_cstruct.recv s buf [] in + Alcotest.(check int) "recv length" (Cstruct.length test_message) n; + let expected = Cstruct.to_string test_message in + let actual = Cstruct.(to_string @@ sub buf 0 n) in + Alcotest.(check string) "read contents" expected actual; + with_mutex m (fun () -> finished := true) + ) () in + with_sock_dgram (fun c -> + Unix.connect c (Unix.ADDR_INET(localhost, port)); + while with_mutex m (fun () -> not !finished) do + let n = Unix_cstruct.send c test_message [] in + Alcotest.(check int) "send length" (Cstruct.length test_message) n; + Thread.delay 0.1 + done + ); + Thread.join t + ) + +let test_sendto_recvfrom () = + let test_message = Cstruct.concat test_message_list in + with_sock_dgram @@ fun s -> + with_sock_dgram @@ fun c -> + let sport = bind_random_port s in + let cport = bind_random_port c in (* So we can assert the sender on receiver port *) + let server () = + let buf = Cstruct.create 1024 in + let n, (addr:Unix.sockaddr) = Unix_cstruct.recvfrom s buf [] in + let addr, port = match addr with + | ADDR_INET (a, p) -> Unix.string_of_inet_addr a, p + | _ -> Alcotest.fail "Bad AF_FAMILY" + in + Alcotest.(check string) "recvfrom inetaddr" addr "127.0.0.1"; + Alcotest.(check int) "recvfrom port" port cport; + Alcotest.(check int) "recvfrom length" (Cstruct.length test_message) n; + let expected = Cstruct.to_string test_message in + let actual = Cstruct.(to_string @@ sub buf 0 n) in + Alcotest.(check string) "read contents" expected actual + in + let client () = + let addr = Unix.ADDR_INET (Unix.inet_addr_loopback, sport) in + let n = Unix_cstruct.sendto c test_message [] addr in + Alcotest.(check int) "sendto length" (Cstruct.length test_message) n; + in + client (); + server () + +let suite = [ + "writev", [ + "test read and writev via a file", `Quick, test_writev_file; + "test read and writev via a socket", `Quick, test_writev_socket; + ]; + "send recv", [ + "test send and recv", `Quick, test_send_recv; + ]; + "sendto recvfrom", [ + "test sendto and recvfrom", `Quick, test_sendto_recvfrom; + ] +] + +let () = Alcotest.run "cstruct.unix" suite