diff --git a/.codacy.yml b/.codacy.yml
index 292d9ae796..62d5fddcf7 100644
--- a/.codacy.yml
+++ b/.codacy.yml
@@ -25,8 +25,8 @@
# package name contains capital letter and such names are conventional.
---
exclude_paths:
- - "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Posix/BindSyscall.java"
- - "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Posix/ListenSyscall.java"
- - "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Posix/AcceptSyscall.java"
- - "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Posix/SendSyscall.java"
- - "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Posix/RecvSyscall.java"
+ - "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/BindFuncCall.java"
+ - "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/ListenFuncCall.java"
+ - "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/AcceptFuncCall.java"
+ - "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/SendFuncCall.java"
+ - "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/RecvFuncCall.java"
diff --git a/eo-runtime/src/main/eo/org/eolang/net/socket.eo b/eo-runtime/src/main/eo/org/eolang/net/socket.eo
index 82d468d28e..2fd926ff75 100644
--- a/eo-runtime/src/main/eo/org/eolang/net/socket.eo
+++ b/eo-runtime/src/main/eo/org/eolang/net/socket.eo
@@ -250,12 +250,8 @@
closed.eq -1
error
sprintf
- "Couldn't close a posix socket '%d' connected to '%s:%d', reason: '%s'"
- *
- sockfd
- ^.address
- ^.port
- ^.strerror.code
+ "Couldn't close a posix socket '%d', reason: '%s'"
+ * sockfd ^.strerror.code
true
# Safe posix socket that ensures that socket is closed even if error is occurred.
@@ -356,41 +352,110 @@
error ex > [ex]
sock.closed-socket client-sockfd
+ # The win32 platform specified socket.
+ #
+ # Attention! The object is for internal usage only, please don't use
+ # the object programmatically outside of `socket` object.
[address port] > win-socket
- [scope] > connect
- code. > started-up
- win32
- "WSAStartup"
- * win32.winsock-version-2-2
- code. > sd
- win32
- "socket"
- * win32.af-inet win32.sock-stream win32.ipproto-tcp
- code. > inet-addr
+ code. > sd
+ win32
+ "socket"
+ * win32.af-inet win32.sock-stream win32.ipproto-tcp
+ code. > inet-addr
+ win32
+ "inet_addr"
+ * address
+ if. > inet-addr-as-int
+ inet-addr.eq win32.inaddr-none
+ error
+ sprintf
+ "Couldn't convert an IPv4 address '%s' into a 32-bit integer via 'inet_addr' win32 function call, WSA error code: %d"
+ * ^.address last-error-code
+ inet-addr.as-i32
+ win32.sockaddr-in > sockaddr
+ win32.af-inet.as-i16
+ ^.htons port
+ inet-addr-as-int
+
+ # Scoped win32 socket that is passed as argument
+ # to scope of `win32-socket.connect`, `win32-socket.listen` and `scoped-socket.accept`.
+ # Here `sockfd` is the socket descriptor.
+ [sockfd] > scoped-socket
+ # Makes an `input` from win32 `socket`.
+ # The object allows to use `socket` as input stream and `read` from it sequentially.
+ ^.^.^.as-input recv > as-input
+ # Makes an `output` from win32 socket.
+ # The object allows to use `socket` as output stream and `write` to it sequentially.
+ ^.^.^.as-output send > as-output
+
+ # Send bytes through the socket.
+ # Here `buffer` must be an object that can be dataized.
+ # On success the `number` of sent bytes is returned, otherwise `error` is returned.
+ [buffer] > send
+ buffer > buff!
+ code. > sent
+ win32
+ "send"
+ * ^.sockfd buff buff.size 0
+ if. > @
+ sent.eq -1
+ error
+ sprintf
+ "Failed to send message through the socket '%d', WSA error code: %d"
+ * ^.sockfd ^.^.last-error.code
+ sent
+
+ # Receive bytes from the socket.
+ # Here `size` must be a `number` of desired bytes to receive.
+ # On success the received `bytes` are returned, otherwise `error` is returned.
+ [size] > recv
+ called. > received
+ win32
+ "recv"
+ * ^.sockfd size 0
+ if. > @
+ received.code.eq -1
+ error
+ sprintf
+ "Failed to receive data from the socket '%d', WSA error code: %d"
+ * ^.sockfd ^.^.last-error.code
+ received.output
+
+ # Get last error.
+ #
+ # Attention! The object is for internal usage only, please don't use
+ # the object programmatically outside of `socket` object.
+ win32 "WSAGetLastError" * > [] > last-error
+
+ # Close socket by given descriptor `sockfd`.
+ # Returns `true` on success, returns `error` otherwise.
+ #
+ # Attention! The object is for internal usage only, please don't use
+ # the object programmatically outside of `socket` object.
+ [sockfd] > closed-socket
+ code. > closed
win32
- "inet_addr"
- * ^.address
- if. > inet-addr-as-int
- inet-addr.eq win32.inaddr-none
+ "closesocket"
+ * sockfd
+ if. > @
+ closed.eq win32.socket-error
error
sprintf
- "Couldn't convert an IPv4 address '%s' into a 32-bit integer via 'inet_addr' win32 function call, WSA error code: %d"
- * ^.address last-error-code
- inet-addr.as-i32
- win32.sockaddr-in > sockaddr
- win32.af-inet.as-i16
- ^.^.htons ^.port
- inet-addr-as-int
- code. > connected
- win32
- "connect"
- * sd sockaddr sockaddr.size
- code. > closed
+ "Couldn't close a win32 socket '%d', WSA error code: %d"
+ * sockfd ^.last-error.code
+ true
+
+ # Safe win32 socket that ensures that socket is closed and Winsock resources are
+ # cleaned even if error is occurred.
+ #
+ # Attention! The object is for internal usage only, please don't use
+ # the object programmatically outside of `socket` object.
+ [scope] > safe-socket
+ code. > started-up
win32
- "closesocket"
- * sd
+ "WSAStartup"
+ * win32.winsock-version-2-2
(win32 "WSACleanup" *).code > cleaned-up
- (win32 "WSAGetLastError" *).code > last-error-code
if. > @
(started-up.eq 0).not
error
@@ -399,34 +464,99 @@
* started-up
try
if.
- sd.eq -1
+ ^.sd.eq -1
error
sprintf
"Couldn't create a win32 socket, WSA error code: %d"
- * last-error-code
+ * ^.last-error.code
try
- if.
- connected.eq -1
- error
- sprintf
- "Couldn't connect to '%s:%d' on win32 socket '%d', WSA error code: %d"
- * ^.address ^.port sd last-error-code
- as-bytes.
- dataized
- scope
- ^.scoped-win-socket sd
+ scope
error ex > [ex]
- if.
- closed.eq win32.socket-error
- error
- sprintf
- "Couldn't close a win32 socket '%d', WSA error code: %d"
- * sd last-error-code
- true
+ ^.closed-socket ^.sd
error ex > [ex]
if.
cleaned-up.eq win32.socket-error
error "Couldn't cleanup Winsock resources"
true
- [descriptor] > scoped-win-socket
+ # Initiate a connection on a socket.
+ # Here `scope` must be an abstract object with one free attribute which will be
+ # set to `scoped-socket` object and used as connector for socket messaging.
+ [scope] > connect
+ ^.safe-socket > @
+ [] >>
+ ^.^ > sock
+ code. > connected
+ win32
+ "connect"
+ * sock.sd sock.sockaddr sock.sockaddr.size
+ if. > @
+ connected.eq -1
+ error
+ sprintf
+ "Couldn't connect to '%s:%d' on win32 socket '%d', WSA error code: %d"
+ * sock.address sock.port sock.sd sock.last-error.code
+ as-bytes.
+ dataized
+ scope
+ sock.scoped-socket
+ sock.sd
+
+ # Listen for connections on a socket.
+ # Here `scope` must be an abstract object with one free attribute, but unlike `connect`,
+ # this free attribute is set to abstract object which decorates `scoped-socket` and also
+ # has attribute `accept` to accept other socket connections.
+ [scope] > listen
+ ^.safe-socket > @
+ [] >>
+ ^.^ > sock
+ code. > bound
+ win32
+ "bind"
+ * sock.sd sock.sockaddr sock.sockaddr.size
+ code. > listened
+ win32
+ "listen"
+ * sock.sd 2048
+ if. > @
+ bound.eq -1
+ error
+ sprintf
+ "Couldn't bind win32 socket '%d' to '%s:%d', WSA error code: %d"
+ * sock.sd sock.address sock.port sock.last-error.code
+ if.
+ listened.eq -1
+ error
+ sprintf
+ "Failed to listen for connections to '%s:%d' on socket '%d', WSA error code: %d"
+ * sock.address sock.port sock.sd sock.last-error.code
+ as-bytes.
+ dataized
+ scope
+ [] >>
+ ^.sock.scoped-socket ^.sock.sd > @
+
+ # Accept incoming connection on socket
+ # On success the client socket is returned, otherwise `error` is returned.
+ # Client socket decorates `scoped-socket` object, so all the attributes like
+ # `send`, `recv`, `as-input`, `as-output` are available.
+ [scope] > accept
+ ^.^.sock > sock
+ code. > client-sockfd
+ win32
+ "accept"
+ * sock.sd sock.sockaddr sock.sockaddr.size
+ try > @
+ if.
+ client-sockfd.eq -1
+ error
+ sprintf
+ "Failed to accept a connection on win32 socket '%d', WSA error code: %d"
+ * sock.sd sock.last-error.code
+ as-bytes.
+ dataized
+ scope
+ sock.scoped-socket
+ client-sockfd
+ error ex > [ex]
+ sock.closed-socket client-sockfd
diff --git "a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/EOwin32$EO\317\206.java" "b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/EOwin32$EO\317\206.java"
index a91f370126..95c49be5fc 100644
--- "a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/EOwin32$EO\317\206.java"
+++ "b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/EOwin32$EO\317\206.java"
@@ -27,13 +27,18 @@
*/
package EOorg.EOeolang.EOsys; // NOPMD
+import EOorg.EOeolang.EOsys.Win32.AcceptFuncCall;
+import EOorg.EOeolang.EOsys.Win32.BindFuncCall;
import EOorg.EOeolang.EOsys.Win32.ClosesocketFuncCall;
import EOorg.EOeolang.EOsys.Win32.ConnectFuncCall;
import EOorg.EOeolang.EOsys.Win32.GetCurrentProcessIdFuncCall;
import EOorg.EOeolang.EOsys.Win32.GetEnvironmentVariableFuncCall;
import EOorg.EOeolang.EOsys.Win32.GetSystemTimeFuncCall;
import EOorg.EOeolang.EOsys.Win32.InetAddrFuncCall;
+import EOorg.EOeolang.EOsys.Win32.ListenFuncCall;
import EOorg.EOeolang.EOsys.Win32.ReadFileFuncCall;
+import EOorg.EOeolang.EOsys.Win32.RecvFuncCall;
+import EOorg.EOeolang.EOsys.Win32.SendFuncCall;
import EOorg.EOeolang.EOsys.Win32.SocketFuncCall;
import EOorg.EOeolang.EOsys.Win32.WSACleanupFuncCall;
import EOorg.EOeolang.EOsys.Win32.WSAGetLastErrorFuncCall;
@@ -75,6 +80,11 @@ public final class EOwin32$EOφ extends PhDefault implements Atom {
EOwin32$EOφ.FUNCTIONS.put("WSAGetLastError", WSAGetLastErrorFuncCall::new);
EOwin32$EOφ.FUNCTIONS.put("socket", SocketFuncCall::new);
EOwin32$EOφ.FUNCTIONS.put("connect", ConnectFuncCall::new);
+ EOwin32$EOφ.FUNCTIONS.put("accept", AcceptFuncCall::new);
+ EOwin32$EOφ.FUNCTIONS.put("bind", BindFuncCall::new);
+ EOwin32$EOφ.FUNCTIONS.put("listen", ListenFuncCall::new);
+ EOwin32$EOφ.FUNCTIONS.put("send", SendFuncCall::new);
+ EOwin32$EOφ.FUNCTIONS.put("recv", RecvFuncCall::new);
EOwin32$EOφ.FUNCTIONS.put("closesocket", ClosesocketFuncCall::new);
EOwin32$EOφ.FUNCTIONS.put("inet_addr", InetAddrFuncCall::new);
}
diff --git a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/AcceptFuncCall.java b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/AcceptFuncCall.java
new file mode 100644
index 0000000000..ff2f187e3b
--- /dev/null
+++ b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/AcceptFuncCall.java
@@ -0,0 +1,79 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016-2024 Objectionary.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * @checkstyle PackageNameCheck (4 lines)
+ * @checkstyle TrailingCommentCheck (3 lines)
+ */
+package EOorg.EOeolang.EOsys.Win32; // NOPMD
+
+import EOorg.EOeolang.EOsys.SockaddrIn;
+import EOorg.EOeolang.EOsys.Syscall;
+import com.sun.jna.ptr.IntByReference;
+import org.eolang.Data;
+import org.eolang.Dataized;
+import org.eolang.PhDefault;
+import org.eolang.Phi;
+
+/**
+ * The socket WS2_32 function call.
+ * @see here for details
+ * @since 0.40.0
+ */
+public final class AcceptFuncCall implements Syscall {
+ /**
+ * Win32 object.
+ */
+ private final Phi win;
+
+ /**
+ * Ctor.
+ * @param win Win32 object
+ */
+ public AcceptFuncCall(final Phi win) {
+ this.win = win;
+ }
+
+ @Override
+ public Phi make(final Phi... params) {
+ final Phi result = this.win.take("return").copy();
+ result.put(
+ 0,
+ new Data.ToPhi(
+ Winsock.INSTANCE.accept(
+ new Dataized(params[0]).asNumber().intValue(),
+ new SockaddrIn(
+ new Dataized(params[1].take("sin-family")).take(Short.class),
+ new Dataized(params[1].take("sin-port")).take(Short.class),
+ new Dataized(params[1].take("sin-addr")).take(Integer.class),
+ new Dataized(params[1].take("sin-zero")).take()
+ ),
+ new IntByReference(new Dataized(params[2]).asNumber().intValue())
+ )
+ )
+ );
+ result.put(1, new PhDefault());
+ return result;
+ }
+}
diff --git a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/BindFuncCall.java b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/BindFuncCall.java
new file mode 100644
index 0000000000..258409100d
--- /dev/null
+++ b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/BindFuncCall.java
@@ -0,0 +1,78 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016-2024 Objectionary.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * @checkstyle PackageNameCheck (4 lines)
+ * @checkstyle TrailingCommentCheck (3 lines)
+ */
+package EOorg.EOeolang.EOsys.Win32; // NOPMD
+
+import EOorg.EOeolang.EOsys.SockaddrIn;
+import EOorg.EOeolang.EOsys.Syscall;
+import org.eolang.Data;
+import org.eolang.Dataized;
+import org.eolang.PhDefault;
+import org.eolang.Phi;
+
+/**
+ * The socket WS2_32 function call.
+ * @see here for details
+ * @since 0.40.0
+ */
+public final class BindFuncCall implements Syscall {
+ /**
+ * Win32 object.
+ */
+ private final Phi win;
+
+ /**
+ * Ctor.
+ * @param win Win32 object
+ */
+ public BindFuncCall(final Phi win) {
+ this.win = win;
+ }
+
+ @Override
+ public Phi make(final Phi... params) {
+ final Phi result = this.win.take("return").copy();
+ result.put(
+ 0,
+ new Data.ToPhi(
+ Winsock.INSTANCE.bind(
+ new Dataized(params[0]).asNumber().intValue(),
+ new SockaddrIn(
+ new Dataized(params[1].take("sin-family")).take(Short.class),
+ new Dataized(params[1].take("sin-port")).take(Short.class),
+ new Dataized(params[1].take("sin-addr")).take(Integer.class),
+ new Dataized(params[1].take("sin-zero")).take()
+ ),
+ new Dataized(params[2]).asNumber().intValue()
+ )
+ )
+ );
+ result.put(1, new PhDefault());
+ return result;
+ }
+}
diff --git a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/InetAddrFuncCall.java b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/InetAddrFuncCall.java
index 3775b60495..8e5996f692 100644
--- a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/InetAddrFuncCall.java
+++ b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/InetAddrFuncCall.java
@@ -66,7 +66,7 @@ public Phi make(final Phi... params) {
).getAddress();
final ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.put(bytes);
- result.put(0, new Data.ToPhi(buffer.getInt(0)));
+ result.put(0, new Data.ToPhi(Integer.reverseBytes(buffer.getInt(0))));
} catch (final UnknownHostException exception) {
Kernel32.INSTANCE.SetLastError(Winsock.WSAEINVAL);
result.put(0, new Data.ToPhi(-1));
diff --git a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/ListenFuncCall.java b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/ListenFuncCall.java
new file mode 100644
index 0000000000..60a95e80d6
--- /dev/null
+++ b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/ListenFuncCall.java
@@ -0,0 +1,71 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016-2024 Objectionary.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * @checkstyle PackageNameCheck (4 lines)
+ * @checkstyle TrailingCommentCheck (3 lines)
+ */
+package EOorg.EOeolang.EOsys.Win32; // NOPMD
+
+import EOorg.EOeolang.EOsys.Syscall;
+import org.eolang.Data;
+import org.eolang.Dataized;
+import org.eolang.PhDefault;
+import org.eolang.Phi;
+
+/**
+ * WriteFile kernel32 function call.
+ * @see here for details
+ * @since 0.40.0
+ */
+public final class ListenFuncCall implements Syscall {
+ /**
+ * Win32 object.
+ */
+ private final Phi win;
+
+ /**
+ * Ctor.
+ * @param win Win32 object
+ */
+ public ListenFuncCall(final Phi win) {
+ this.win = win;
+ }
+
+ @Override
+ public Phi make(final Phi... params) {
+ final Phi result = this.win.take("return").copy();
+ result.put(
+ 0,
+ new Data.ToPhi(
+ Winsock.INSTANCE.listen(
+ new Dataized(params[0]).asNumber().intValue(),
+ new Dataized(params[1]).asNumber().intValue()
+ )
+ )
+ );
+ result.put(1, new PhDefault());
+ return result;
+ }
+}
diff --git a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/RecvFuncCall.java b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/RecvFuncCall.java
new file mode 100644
index 0000000000..1291435759
--- /dev/null
+++ b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/RecvFuncCall.java
@@ -0,0 +1,71 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016-2024 Objectionary.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * @checkstyle PackageNameCheck (4 lines)
+ * @checkstyle TrailingCommentCheck (3 lines)
+ */
+package EOorg.EOeolang.EOsys.Win32; // NOPMD
+
+import EOorg.EOeolang.EOsys.Syscall;
+import java.util.Arrays;
+import org.eolang.Data;
+import org.eolang.Dataized;
+import org.eolang.Phi;
+
+/**
+ * ReadFile kernel32 function call.
+ * @see here for details
+ * @since 0.40.0
+ */
+public final class RecvFuncCall implements Syscall {
+ /**
+ * Win32 object.
+ */
+ private final Phi win;
+
+ /**
+ * Ctor.
+ * @param win Win32 object
+ */
+ public RecvFuncCall(final Phi win) {
+ this.win = win;
+ }
+
+ @Override
+ public Phi make(final Phi... params) {
+ final Phi result = this.win.take("return").copy();
+ final int size = new Dataized(params[1]).asNumber().intValue();
+ final byte[] buf = new byte[(int) size];
+ final int received = Winsock.INSTANCE.recv(
+ new Dataized(params[0]).asNumber().intValue(),
+ buf,
+ size,
+ new Dataized(params[2]).asNumber().intValue()
+ );
+ result.put(0, new Data.ToPhi(received));
+ result.put(1, new Data.ToPhi(Arrays.copyOf(buf, received)));
+ return result;
+ }
+}
diff --git a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/SendFuncCall.java b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/SendFuncCall.java
new file mode 100644
index 0000000000..d0c8fe60ab
--- /dev/null
+++ b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/SendFuncCall.java
@@ -0,0 +1,73 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016-2024 Objectionary.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * @checkstyle PackageNameCheck (4 lines)
+ * @checkstyle TrailingCommentCheck (3 lines)
+ */
+package EOorg.EOeolang.EOsys.Win32; // NOPMD
+
+import EOorg.EOeolang.EOsys.Syscall;
+import org.eolang.Data;
+import org.eolang.Dataized;
+import org.eolang.PhDefault;
+import org.eolang.Phi;
+
+/**
+ * WriteFile kernel32 function call.
+ * @see here for details
+ * @since 0.40.0
+ */
+public final class SendFuncCall implements Syscall {
+ /**
+ * Win32 object.
+ */
+ private final Phi win;
+
+ /**
+ * Ctor.
+ * @param win Win32 object
+ */
+ public SendFuncCall(final Phi win) {
+ this.win = win;
+ }
+
+ @Override
+ public Phi make(final Phi... params) {
+ final Phi result = this.win.take("return").copy();
+ result.put(
+ 0,
+ new Data.ToPhi(
+ Winsock.INSTANCE.send(
+ new Dataized(params[0]).asNumber().intValue(),
+ new Dataized(params[1]).take(),
+ new Dataized(params[2]).asNumber().intValue(),
+ new Dataized(params[3]).asNumber().intValue()
+ )
+ )
+ );
+ result.put(1, new PhDefault());
+ return result;
+ }
+}
diff --git a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/Winsock.java b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/Winsock.java
index bea6616050..b4fef22e9f 100644
--- a/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/Winsock.java
+++ b/eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/Winsock.java
@@ -30,6 +30,7 @@
import EOorg.EOeolang.EOsys.SockaddrIn;
import com.sun.jna.Native;
+import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIOptions;
@@ -40,7 +41,7 @@
* @checkstyle ParameterNumberCheck (1000 lines)
* @checkstyle AbbreviationAsWordInNameCheck (1000 lines)
*/
-@SuppressWarnings("PMD.MethodNamingConventions")
+@SuppressWarnings({"PMD.MethodNamingConventions", "PMD.TooManyMethods"})
public interface Winsock extends StdCallLibrary {
/**
* Instance.
@@ -125,6 +126,57 @@ public interface Winsock extends StdCallLibrary {
*/
int connect(int sockfd, SockaddrIn addr, int addrlen);
+ /**
+ * Assigns the address specified by {@code addr} to the socket referred to
+ * by the file descriptor {@code sockfd}.
+ * @param sockfd Socket descriptor
+ * @param addr Address structure
+ * @param addrlen The size of the address structure
+ * @return Zero on success, -1 on error
+ */
+ int bind(int sockfd, SockaddrIn addr, int addrlen);
+
+ /**
+ * Listen for incoming connections on socket.
+ * @param sockfd Socket descriptor
+ * @param backlog Specifies the queue length for completely established sockets
+ * waiting to be accepted
+ * @return Zero on success, -1 on error
+ */
+ int listen(int sockfd, int backlog);
+
+ /**
+ * Accept connection on socket.
+ * @param sockfd Socket descriptor
+ * @param addr Address structure
+ * @param addrlen The size of the address structure
+ * @return On success, file descriptor for the accepted socket (a nonnegative integer)
+ * is returned. On error, -1 is returned.
+ */
+ int accept(int sockfd, SockaddrIn addr, IntByReference addrlen);
+
+ /**
+ * Send a message to a socket.
+ * @param sockfd Socket descriptor
+ * @param buf Byte buffer to store sent bytes
+ * @param len Size of sent data
+ * @param flags Flags
+ * @return The number of sent bytes on success, -1 on error
+ * @checkstyle ParameterNumberCheck (5 lines)
+ */
+ int send(int sockfd, byte[] buf, int len, int flags);
+
+ /**
+ * Receive a message from a socket.
+ * @param sockfd Socket descriptor
+ * @param buf Byte buffer to store received bytes
+ * @param len Size of received data
+ * @param flags Flags
+ * @return The number of received bytes on success, -1 on error
+ * @checkstyle ParameterNumberCheck (5 lines)
+ */
+ int recv(int sockfd, byte[] buf, int len, int flags);
+
/**
* Retrieve the last error from winsock.
* @return The code of the last winsock error.
diff --git a/eo-runtime/src/test/java/EOorg/EOeolang/EOnet/EOsocketTest.java b/eo-runtime/src/test/java/EOorg/EOeolang/EOnet/EOsocketTest.java
index 168b0472b7..6886ca895a 100644
--- a/eo-runtime/src/test/java/EOorg/EOeolang/EOnet/EOsocketTest.java
+++ b/eo-runtime/src/test/java/EOorg/EOeolang/EOnet/EOsocketTest.java
@@ -53,7 +53,6 @@
import org.eolang.Phi;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
@@ -64,15 +63,13 @@
/**
* Test case for {@link EOsocket}.
* @since 0.40
- * @todo #3251:30min Enable the tests for windows. For some reason socket tests for windows
- * are flaky. We need to deal with them somehow and enable.
* @checkstyle TypeNameCheck (100 lines)
*/
@SuppressWarnings({
"PMD.TooManyMethods",
- "JTCOP.RuleAllTestsHaveProductionClass",
"PMD.AvoidUsingHardCodedIP",
- "PMD.CloseResource"
+ "PMD.CloseResource",
+ "JTCOP.RuleAllTestsHaveProductionClass"
})
@Execution(ExecutionMode.SAME_THREAD)
final class EOsocketTest {
@@ -87,29 +84,30 @@ final class EOsocketTest {
private static final Random RANDOM = new Random();
@Test
- @DisabledOnOs(OS.WINDOWS)
void connectsToLocalServerViaSocketObject() throws IOException {
final RandomServer server = new RandomServer().started();
- final Phi socket = Phi.Φ.take("org.eolang.net.socket").copy();
- socket.put(0, new Data.ToPhi(EOsocketTest.LOCALHOST));
- socket.put(1, new Data.ToPhi(server.port));
- final Phi connected = socket.take("connect").copy();
- connected.put(0, new Simple());
- final byte[] expected = {1};
- final byte[] actual = new Dataized(connected).take();
- MatcherAssert.assertThat(
- String.format(
- "The 'socket.connect' should have been successfully connected to local server, but it didn't, reason: %s",
- new String(actual, StandardCharsets.UTF_8)
- ),
- actual,
- Matchers.equalTo(expected)
- );
- server.stop();
+ try {
+ final Phi socket = Phi.Φ.take("org.eolang.net.socket").copy();
+ socket.put(0, new Data.ToPhi(EOsocketTest.LOCALHOST));
+ socket.put(1, new Data.ToPhi(server.port));
+ final Phi connected = socket.take("connect").copy();
+ connected.put(0, new Simple());
+ final byte[] expected = {1};
+ final byte[] actual = new Dataized(connected).take();
+ MatcherAssert.assertThat(
+ String.format(
+ "The 'socket.connect' should have been successfully connected to local server, but it didn't, reason: %s",
+ new String(actual, StandardCharsets.UTF_8)
+ ),
+ actual,
+ Matchers.equalTo(expected)
+ );
+ } finally {
+ server.stop();
+ }
}
@Test
- @DisabledOnOs(OS.WINDOWS)
void sendsAndReceivesMessageViaSocketObject() throws InterruptedException, IOException {
final String msg = "Hello, Socket!";
final AtomicReference bytes = new AtomicReference<>();
@@ -172,19 +170,17 @@ private static int randomPort() {
*/
@Nested
@DisabledOnOs({OS.MAC, OS.LINUX})
- @Disabled
@Execution(ExecutionMode.SAME_THREAD)
final class WindowsSocketTest {
@Test
- @DisabledOnOs({OS.LINUX, OS.MAC})
- void connectsToLocalServerViaWindowsSyscall() throws IOException {
+ void connectsToLocalServerViaSyscall() throws IOException, InterruptedException {
final RandomServer server = new RandomServer().started();
final int started = this.startup();
try {
this.ensure(started == 0);
final int socket = this.openSocket();
try {
- this.ensure(socket >= 0);
+ this.ensure(socket > 0);
final SockaddrIn addr = this.sockaddr(server.port);
MatcherAssert.assertThat(
String.format(
@@ -204,13 +200,13 @@ void connectsToLocalServerViaWindowsSyscall() throws IOException {
}
@Test
- void refusesConnectionViaWindowsSyscall() throws UnknownHostException {
+ void refusesConnectionViaSyscall() throws UnknownHostException {
final int started = this.startup();
try {
this.ensure(started == 0);
final int socket = this.openSocket();
try {
- this.ensure(socket >= 0);
+ this.ensure(socket > 0);
final SockaddrIn addr = new SockaddrIn(
(short) Winsock.AF_INET,
EOsocketTest.htons(8080),
@@ -225,7 +221,200 @@ void refusesConnectionViaWindowsSyscall() throws UnknownHostException {
this.closeSocket(socket);
}
} finally {
- Winsock.INSTANCE.WSACleanup();
+ this.cleanup();
+ }
+ }
+
+ @Test
+ void bindsSocketSuccessfullyViaSyscall() throws UnknownHostException {
+ final int started = this.startup();
+ try {
+ this.ensure(started == 0);
+ final int socket = this.openSocket();
+ try {
+ this.ensure(socket > 0);
+ MatcherAssert.assertThat(
+ String.format(
+ "Win socket should have been bound to localhost via syscall, but it didn't, error code is: %d",
+ this.getError()
+ ),
+ this.bindSocket(socket, EOsocketTest.randomPort()),
+ Matchers.equalTo(0)
+ );
+ } finally {
+ this.closeSocket(socket);
+ }
+ } finally {
+ this.cleanup();
+ }
+ }
+
+ @Test
+ void startsListenOnPosixSocket() throws UnknownHostException {
+ final int started = this.startup();
+ try {
+ this.ensure(started == 0);
+ final int socket = this.openSocket();
+ try {
+ this.ensure(socket > 0);
+ this.ensure(this.bindSocket(socket, EOsocketTest.randomPort()) == 0);
+ MatcherAssert.assertThat(
+ String.format(
+ "Posix socket should have been bound to localhost via syscall, but it didn't, reason: %s",
+ this.getError()
+ ),
+ Winsock.INSTANCE.listen(socket, 2),
+ Matchers.equalTo(0)
+ );
+ } finally {
+ this.closeSocket(socket);
+ }
+ } finally {
+ this.cleanup();
+ }
+ }
+
+ @Test
+ @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
+ void acceptsConnectionOnSocket() throws InterruptedException, UnknownHostException {
+ final int started = this.startup();
+ try {
+ this.ensure(started == 0);
+ final AtomicInteger accept = new AtomicInteger(0);
+ final AtomicInteger error = new AtomicInteger();
+ final AtomicInteger port = new AtomicInteger(EOsocketTest.randomPort());
+ final Thread server = new Thread(
+ () -> {
+ final int socket = this.openSocket();
+ try {
+ this.ensure(socket > 0);
+ while (this.bindSocket(socket, port.get()) != 0) {
+ port.set(EOsocketTest.randomPort());
+ }
+ this.ensure(Winsock.INSTANCE.listen(socket, 5) == 0);
+ final SockaddrIn addr = new SockaddrIn();
+ final int accepted = Winsock.INSTANCE.accept(
+ socket, addr, new IntByReference(addr.size())
+ );
+ Logger.info(this, "Accepted socket: %d", accepted);
+ accept.set(accepted);
+ if (accepted < 0) {
+ error.set(this.getError());
+ }
+ } catch (final UnknownHostException exception) {
+ throw new RuntimeException(exception);
+ } finally {
+ if (accept.get() > 0) {
+ this.closeSocket(accept.get());
+ }
+ this.closeSocket(socket);
+ }
+ }
+ );
+ server.start();
+ Thread.sleep(2000);
+ final int client = this.openSocket();
+ try {
+ this.ensure(client >= 0);
+ final SockaddrIn sockaddr = this.sockaddr(port.get());
+ MatcherAssert.assertThat(
+ String.format(
+ "Socket should have been connected to local server on sockets, but it didn't, reason: %s",
+ this.getError()
+ ),
+ Winsock.INSTANCE.connect(client, sockaddr, sockaddr.size()),
+ Matchers.equalTo(0)
+ );
+ server.join();
+ MatcherAssert.assertThat(
+ String.format(
+ "Accepted client socket must be positive, but it isn't, reason: %s",
+ error.get()
+ ),
+ accept.get(),
+ Matchers.greaterThan(0)
+ );
+ } finally {
+ this.closeSocket(client);
+ }
+ } finally {
+ this.cleanup();
+ }
+ }
+
+ @Test
+ @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
+ void sendsAndReceivesMessagesViaSyscalls()
+ throws InterruptedException, UnknownHostException {
+ final int started = this.startup();
+ try {
+ this.ensure(started == 0);
+ final AtomicInteger received = new AtomicInteger(-1);
+ final AtomicReference bytes = new AtomicReference<>();
+ final AtomicInteger port = new AtomicInteger(EOsocketTest.randomPort());
+ final Thread server = new Thread(
+ () -> {
+ final int socket = this.openSocket();
+ int accepted = 0;
+ try {
+ this.ensure(socket > 0);
+ while (this.bindSocket(socket, port.get()) != 0) {
+ port.set(EOsocketTest.randomPort());
+ }
+ this.ensure(Winsock.INSTANCE.listen(socket, 5) == 0);
+ final SockaddrIn addr = new SockaddrIn();
+ accepted = Winsock.INSTANCE.accept(
+ socket, addr, new IntByReference(addr.size())
+ );
+ Logger.info(this, "Accepted socket: %d", accepted);
+ this.ensure(accepted > 0);
+ final byte[] buf = new byte[1024];
+ received.set(Winsock.INSTANCE.recv(accepted, buf, buf.length, 0));
+ bytes.set(Arrays.copyOf(buf, received.get()));
+ } catch (final UnknownHostException exception) {
+ throw new RuntimeException(exception);
+ } finally {
+ this.closeSocket(accepted);
+ this.closeSocket(socket);
+ }
+ }
+ );
+ server.start();
+ Thread.sleep(2000);
+ final int client = this.openSocket();
+ try {
+ this.ensure(client >= 0);
+ final SockaddrIn sockaddr = this.sockaddr(port.get());
+ this.ensure(Winsock.INSTANCE.connect(client, sockaddr, sockaddr.size()) == 0);
+ final byte[] buf = "Hello, Socket!".getBytes(StandardCharsets.UTF_8);
+ final int sent = Winsock.INSTANCE.send(client, buf, buf.length, 0);
+ MatcherAssert.assertThat(
+ String.format(
+ "Client had to sent message to the server, but it didn't, reason: %s",
+ this.getError()
+ ),
+ sent,
+ Matchers.equalTo(buf.length)
+ );
+ server.join();
+ MatcherAssert.assertThat(
+ String.format(
+ "Server hat to receive message from the client, but it didn't, reason: %s",
+ this.getError()
+ ),
+ received.get(),
+ Matchers.equalTo(buf.length)
+ );
+ MatcherAssert.assertThat(
+ "Received bytes must be equal to sent, but they didn't",
+ new String(bytes.get(), StandardCharsets.UTF_8),
+ Matchers.equalTo(new String(buf, StandardCharsets.UTF_8))
+ );
+ } finally {
+ this.closeSocket(client);
+ }
+ } finally {
+ this.cleanup();
}
}
@@ -295,6 +484,20 @@ private int getError() {
return Winsock.INSTANCE.WSAGetLastError();
}
+ /**
+ * Bind socket.
+ * @param socket Socket
+ * @param port Port
+ * @return Zero on success, -1 on error
+ */
+ private int bindSocket(final int socket, final int port) throws UnknownHostException {
+ return Winsock.INSTANCE.bind(
+ socket,
+ this.sockaddr(port),
+ 16
+ );
+ }
+
/**
* Call posix inet addr.
* @param address IP address
@@ -304,7 +507,7 @@ private int inetAddr(final String address) throws UnknownHostException {
final byte[] bytes = InetAddress.getByName(address).getAddress();
final ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.put(bytes);
- return buffer.getInt(0);
+ return Integer.reverseBytes(buffer.getInt(0));
}
/**
@@ -334,7 +537,7 @@ void connectsToLocalServerViaSyscall() throws IOException {
final RandomServer server = new RandomServer().started();
final int socket = this.openSocket();
try {
- this.ensure(socket >= 0);
+ this.ensure(socket > 0);
final SockaddrIn addr = this.sockaddr(server.port);
MatcherAssert.assertThat(
String.format(
@@ -354,7 +557,7 @@ void connectsToLocalServerViaSyscall() throws IOException {
void refusesConnectionViaSyscall() {
final int socket = this.openSocket();
try {
- this.ensure(socket >= 0);
+ this.ensure(socket > 0);
final SockaddrIn addr = this.sockaddr(1234);
final int connected = CStdLib.INSTANCE.connect(socket, addr, addr.size());
MatcherAssert.assertThat(
@@ -371,7 +574,7 @@ void refusesConnectionViaSyscall() {
void bindsSocketSuccessfullyViaSyscall() {
final int socket = this.openSocket();
try {
- this.ensure(socket >= 0);
+ this.ensure(socket > 0);
MatcherAssert.assertThat(
String.format(
"Posix socket should have been bound to localhost via syscall, but it didn't, reason: %s",
@@ -386,11 +589,10 @@ void bindsSocketSuccessfullyViaSyscall() {
}
@Test
- @DisabledOnOs(OS.WINDOWS)
void startsListenOnPosixSocket() {
final int socket = this.openSocket();
try {
- this.ensure(socket >= 0);
+ this.ensure(socket > 0);
this.ensure(this.bindSocket(socket, EOsocketTest.randomPort()) == 0);
MatcherAssert.assertThat(
String.format(
@@ -406,7 +608,6 @@ void startsListenOnPosixSocket() {
}
@Test
- @DisabledOnOs(OS.WINDOWS)
void acceptsConnectionOnSocket() throws InterruptedException {
final AtomicInteger accept = new AtomicInteger(0);
final AtomicReference error = new AtomicReference<>();
@@ -415,7 +616,7 @@ void acceptsConnectionOnSocket() throws InterruptedException {
() -> {
final int socket = this.openSocket();
try {
- this.ensure(socket >= 0);
+ this.ensure(socket > 0);
while (this.bindSocket(socket, port.get()) != 0) {
port.set(EOsocketTest.randomPort());
}
@@ -644,6 +845,7 @@ RandomServer started() {
this.socket.setReuseAddress(true);
this.socket.bind(new InetSocketAddress(EOsocketTest.LOCALHOST, this.port));
bound = true;
+ Logger.info(this, "Server started on port %d", this.port);
} catch (final IOException exception) {
Logger.info(this, "Port %d is unavailable, trying another port...", this.port);
}