%---------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% % Copyright (C) 2000, 2007, 2011 The University of Melbourne. % Copyright (C) 2014-2016, 2018 The Mercury team. % This file is distributed under the terms specified in COPYING.LIB. %---------------------------------------------------------------------------% % % Module: sockets % Main Author: pro@missioncriticalit.com % (based on code written by pma@missioncriticalit.com) % Largely rewritten by: Paul Bone % Stability: low % % Provide a low-level interface to sockets. % %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- module net.sockets. :- interface. :- import_module bitmap. :- import_module io. :- import_module maybe. :- import_module net.types. %---------------------------------------------------------------------------% :- type socket. %---------------------------------------------------------------------------% % socket(Domain, Type, Protocol, Result, !IO): % % Create a new socket. % :- pred socket(family::in, socktype::in, protocol_num::in, maybe_error(socket)::out, io::di, io::uo) is det. % socket(Domain, Type, Result, !IO): % % Create a new socket, use this variant to have the sockets library % detect the correct protocol (usually the only protocol). % :- pred socket(family::in, socktype::in, maybe_error(socket)::out, io::di, io::uo) is det. % connect(Socket, Addr, Addrlen, Result, !IO): % :- pred connect(socket::in, sockaddr::in, maybe_error::out, io::di, io::uo) is det. % bind(Socket, Addr, Result, !IO): % :- pred bind(socket::in, sockaddr::in, maybe_error::out, io::di, io::uo) is det. % listen(Socket, Backlog, Result, !IO): % :- pred listen(socket::in, int::in, maybe_error::out, io::di, io::uo) is det. :- type accept_result ---> accept_result( ar_socket :: socket, ar_address :: sockaddr ). % accept(Socket, Addr, Result, !IO): % % Accept will block until a connection to our socket is made. % :- pred accept(socket::in, maybe_error(accept_result)::out, io::di, io::uo) is det. % close(Socket, Result, !IO): % % This closes the socket with lingering enabled. The call will not % return until all the queued data has been sent or the timeout expires % (2 seconds). % :- pred close(socket::in, maybe_error::out, io::di, io::uo) is det. %---------------------------------------------------------------------------% :- type read_result(T) ---> ok(T) ; eof ; error(string). % The returned buffer may be smaller than the amount of requested data % if either 1) the end of file/stream was reached or 2) a smaller amount % of data is available. If the OS has no data then this call will block. % :- pred read(socket::in, int::in, sockets.read_result(bitmap)::out, io::di, io::uo) is det. %---------------------------------------------------------------------------% :- pred write(socket::in, bitmap::in, maybe_error::out, io::di, io::uo) is det. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. :- import_module bool. :- import_module int. :- import_module require. :- import_module net.errno. :- pragma foreign_decl("C", " #ifdef MR_WIN32 #include ""mercury_windows.h"" #include #include #else #include #include #include #include #include #endif "). %---------------------------------------------------------------------------% :- pragma foreign_type("C", socket, "MR_Integer", [can_pass_as_mercury_type]). %---------------------------------------------------------------------------% :- pragma foreign_decl("C", local, " #ifdef MR_WIN32 #define error() WSAGetLastError() #define SHUT_RDWR SD_BOTH #else /* !MR_WIN32 */ #define error() errno #define INVALID_SOCKET -1 #endif /* !MR_WIN32 */ "). %---------------------------------------------------------------------------% :- initialise init/2. :- pred init(io::di, io::uo) is det. :- pragma foreign_proc(c, init(_IO0::di, _IO::uo), [will_not_call_mercury, thread_safe, promise_pure, tabled_for_io], " #ifdef MR_WIN32 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 2, 2 ); err = WSAStartup(wVersionRequested, &wsaData); if ( err != 0 ) { MR_fatal_error(""Unable to find a usable winsock.dll\\n""); } if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { WSACleanup(); MR_fatal_error(""Unable to find a usable winsock.dll\\n""); } #endif /* MR_WIN32 */ "). %---------------------------------------------------------------------------% socket(Domain, Type, MaybeSocket, !IO) :- socket(Domain, Type, 0, MaybeSocket, !IO). socket(Domain, Type, Protocol, MaybeSocket, !IO) :- socket(Domain, Type, Protocol, Socket, Success, Errno, !IO), ( Success = yes, MaybeSocket = ok(Socket) ; Success = no, MaybeSocket = error(strerror(Errno)) ). :- pred socket(family::in, socktype::in, protocol_num::in, socket::out, bool::out, int::out, io::di, io::uo) is det. :- pragma foreign_proc(c, socket(Domain::in, Type::in, Protocol::in, Socket::out, Success::out, Errno::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe], " Socket = socket(Domain, Type, Protocol); if (Socket == INVALID_SOCKET) { Errno = error(); Success = MR_NO; } else { Success = MR_YES; } "). %---------------------------------------------------------------------------% connect(Socket, Addr, Result, !IO) :- connect(Socket, Addr, Success, Errno, !IO), ( Success = yes, Result = ok ; Success = no, Result = error(strerror(Errno)) ). :- pred connect(socket::in, sockaddr::in, bool::out, int::out, io::di, io::uo) is det. :- pragma foreign_proc("C", connect(Socket::in, Addr::in, Success::out, Errno::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe], " if (connect(Socket, &(Addr->raw), sock_addr_size(Addr)) == INVALID_SOCKET) { Errno = error(); Success = MR_NO; } else { Success = MR_YES; } "). %---------------------------------------------------------------------------% bind(Socket, Addr, Result, !IO) :- bind(Socket, Addr, Success, Errno, !IO), ( Success = yes, Result = ok ; Success = no, Result = error(strerror(Errno)) ). :- pred bind(socket::in, sockaddr::in, bool::out, int::out, io::di, io::uo) is det. :- pragma foreign_proc("C", bind(Socket::in, Addr::in, Success::out, Errno::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe], " if (bind(Socket, &(Addr->raw), sock_addr_size(Addr)) == INVALID_SOCKET) { Errno = error(); Success = MR_NO; } else { Success = MR_YES; } "). %---------------------------------------------------------------------------% listen(Socket, Backlog, Result, !IO) :- listen(Socket, Backlog, Success, Errno, !IO), ( Success = yes, Result = ok ; Success = no, Result = error(strerror(Errno)) ). :- pred listen(socket::in, int::in, bool::out, int::out, io::di, io::uo) is det. :- pragma foreign_proc("C", listen(Socket::in, BackLog::in, Success::out, Errno::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe], " if (listen(Socket, BackLog) == INVALID_SOCKET) { Errno = error(); Success = MR_NO; } else { Success = MR_YES; } "). %---------------------------------------------------------------------------% accept(Socket, Result, !IO) :- accept(Socket, NewSocket, Addr, Success, AddressOk, Errno, !IO), ( Success = yes, ( AddressOk = yes, Result = ok(accept_result(NewSocket, Addr)) ; AddressOk = no, close(NewSocket, _, !IO), Result = error("Could not decode peer address") ) ; Success = no, Result = error(strerror(Errno)) ). :- pred accept(socket::in, socket::out, sockaddr::out, bool::out, bool::out, int::out, io::di, io::uo) is det. :- pragma foreign_proc("C", accept(Socket::in, NewSocket::out, Addr::out, Success::out, AddressOk::out, Errno::out, _IO0::di, _IO::uo), [thread_safe, will_not_call_mercury, promise_pure, tabled_for_io], " socklen_t addrlen; Addr = MR_GC_NEW(union my_sockaddr); addrlen = sizeof(union my_sockaddr); NewSocket = accept(Socket, &(Addr->raw), &addrlen); if (NewSocket == INVALID_SOCKET) { Errno = error(); Success = MR_NO; AddressOk = MR_NO; } else if (addrlen > sizeof(union my_sockaddr)){ Success = MR_YES; AddressOk = MR_NO; } else { Success = MR_YES; AddressOk = MR_YES; } "). %---------------------------------------------------------------------------% close(Socket, Result, !IO) :- close(Socket, Success, Errno, !IO), ( Success = yes, Result = ok ; Success = no, Result = error(strerror(Errno)) ). :- pred close(socket::in, bool::out, int::out, io::di, io::uo) is det. :- pragma foreign_proc("C", close(Socket::in, Success::out, Errno::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe], " struct linger sockets_linger = { MR_TRUE, 2 }; setsockopt(Socket, SOL_SOCKET, SO_LINGER, (char *) &sockets_linger, sizeof(sockets_linger)); if (-1 == shutdown(Socket, SHUT_RDWR)) { Errno = error(); Success = MR_NO; } else { Success = MR_YES; } "). %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% read(Socket, Len0, Result, !IO) :- read(Socket, Len0, Bitmap0, BytesRead, Errno, !IO), ( if BytesRead > 0 then Bitmap = shrink_without_copying(Bitmap0, BytesRead*8), Result = ok(Bitmap) else if BytesRead = 0 then Result = eof else Result = error(strerror(Errno)) ). :- pred read(socket::in, int::in, bitmap::bitmap_uo, int::out, errno::out, io::di, io::uo) is det. :- pragma foreign_proc("C", read(Socket::in, Len::in, Bitmap::bitmap_uo, BytesRead::out, Errno::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io], " MR_allocate_bitmap_msg(Bitmap, Len*8, MR_ALLOC_ID); BytesRead = recv(Socket, Bitmap->elements, Len, 0); if (BytesRead == -1) { Errno = error(); } "). %---------------------------------------------------------------------------% write(Socket, Bitmap, Result, !IO) :- write(Socket, Bitmap, 0, Result, !IO). :- pred write(socket::in, bitmap::in, int::in, maybe_error::out, io::di, io::uo) is det. write(Socket, Bitmap, Offset, Result, !IO) :- ( if LenPrime = num_bytes(Bitmap) - Offset then Len = LenPrime else unexpected($pred, "Bitmap must have an integral number of bytes") ), write_c(Socket, Bitmap, Offset, Len, BytesWritten, Errno, !IO), ( if BytesWritten = Len then Result = ok else if BytesWritten = -1 then Result = error(strerror(Errno)) else if BytesWritten < Len then % Not all the bytes were written. Try again. write(Socket, Bitmap, Offset + BytesWritten, Result, !IO) else unexpected($file, $pred, "BytesWritten > Len") ). :- pred write_c(socket::in, bitmap::in, int::in, int::in, int::out, errno::out, io::di, io::uo) is det. :- pragma foreign_proc("C", write_c(Socket::in, Bitmap::in, Offset::in, Len::in, BytesWritten::out, Errno::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io], " BytesWritten = send(Socket, &Bitmap->elements[Offset], Len, 0); if (BytesWritten == -1) { Errno = error(); } "). %---------------------------------------------------------------------------% :- end_module sockets. %---------------------------------------------------------------------------%