Skip to content

Commit 0fea697

Browse files
committed
Provide net, datagram, timeout and exec; decline space in terms
This implementation declined all five interfaces openkal 0.8 added. Four of them this system has, and the fifth it does not --- which is a legitimate outcome rather than a gap, and is now stated as one. src/net.cpp Winsock. ⚠️ `WSASocketW' with a flags word of zero rather than `socket': the latter makes an OVERLAPPED handle, upon which `ReadFile' returns before the bytes have arrived. A non-overlapped socket is what lets a connection BE a stream here --- openkal.stream is `ReadFile' and `WriteFile' on this system --- with no second transfer path. A change to that one zero would neither fail to compile nor fail to link. src/datagram.cpp the same calls with SOCK_DGRAM. ⚠️ This system reports a truncated message as a FAILURE where the other two truncate silently; the bytes that fit are delivered either way, and the interface states that the excess is lost. src/timeout.cpp `WSAPoll', which answers for sockets and for nothing else. A bounded transfer upon a stream that is not a socket reports kal_err_not_supported, which the interface's own header anticipates in terms. `kal_timeout_wait_process' is the one operation of this interface the system provides DIRECTLY. src/exec.cpp VirtualAlloc, VirtualProtect, FlushInstructionCache. The third is not optional. openkal.space stays declined. `CreateProcessW' starts a NAMED PROGRAM, which is openkal.process and a different operation; constructing a copy of the calling address space out of it would be the simulation clause 3.1 forbids. A program that calls `kal_space_start' fails at the link naming the operation, which is clause 6.1's report. ⚠️ THREE CONSTANTS DIFFER FROM THE OTHER SYSTEMS' WITHOUT ANNOUNCING IT: AF_INET6 is 23 here, 30 on macOS and 10 on Linux; SOL_SOCKET is 0xffff here and on macOS and 1 on Linux; and this system's `poll' has no bit named POLLIN --- what it has is POLLRDNORM, and the Linux value asks about out-of-band data instead. ⚠️⚠️ AND THE CONFORMANCE RUN WAS SELECTING LESS THAN ITS STEP NAME SAID. `full' expands to the HOSTED set, so every optional interface --- random and terminal included, which this implementation has provided since before 0.8 --- was compiled with its section body removed and reported as not examined. The set is now enumerated: the six this system provides, and not `optional', because `optional' names `space'. Measured here rather than remembered: 16 objects, exported surface complete and conforming at 88 names, and no undefined symbol outside the permitted set.
1 parent d6fc05a commit 0fea697

12 files changed

Lines changed: 974 additions & 12 deletions

File tree

.github/workflows/ci.yml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,21 @@ jobs:
226226
run: |
227227
extra=''
228228
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
229-
bash .spec/tools/run-conformance.sh openkal-windows . full $extra
229+
# ⚠️ THE SET IS ENUMERATED AND `optional` IS NOT NAMED, AND THE
230+
# DIFFERENCE IS ONE INTERFACE.
231+
#
232+
# `optional` includes `space`, which this system does not provide:
233+
# there is no primitive here that copies an address space and starts a
234+
# context in the copy. A set demanding it would not report an
235+
# observation that did not hold — it would fail to LINK, naming
236+
# `kal_space_start`, which is clause 6.1's report arriving where a
237+
# report was not wanted.
238+
#
239+
# So the six this implementation does provide are named, and the
240+
# seventh is absent from the list for the same reason it is absent
241+
# from the objects.
242+
bash .spec/tools/run-conformance.sh openkal-windows . \
243+
full,exec,random,terminal,net,datagram,timeout $extra
230244
231245
# ---------------------------------------------------------------------------
232246
# From a system that is not this one.
@@ -343,7 +357,8 @@ jobs:
343357
env:
344358
WINEDEBUG: '-all'
345359
run: |
346-
bash .spec/tools/run-conformance.sh openkal-windows . full \
360+
bash .spec/tools/run-conformance.sh openkal-windows . \
361+
full,exec,random,terminal,net,datagram,timeout \
347362
--target x86_64-windows-gnu
348363
349364
- name: The exported surface is complete and contains nothing else

README.md

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ An implementation of [openkal](https://github.com/mcpplibs/openkal) for Windows.
44

55
```toml
66
[dependencies]
7-
openkal = "0.5.1"
7+
openkal = "0.8.0"
88

99
[target.'cfg(windows)'.dependencies]
10-
openkal-windows = "0.1.1"
10+
openkal-windows = "0.3.0"
1111
```
1212

1313
Its purpose is as much to test the specification as to be used. openkal was
@@ -33,6 +33,20 @@ path with `GetFinalPathNameByHandleW` and concatenate, which is a name resolver
3333
inside an implementation and is what clause 7.1 excludes. The interface asked
3434
for the operation this system already had.
3535

36+
**A declined interface is a legitimate outcome, and this system is where that
37+
stops being theory.** `openkal.space` starts a context in a copy of the calling
38+
address space. There is no primitive here that does it: `CreateProcessW` starts
39+
a *named program*, which is `openkal.process` and is a different operation.
40+
Clause 3 says an implementation provides an interface in whole or not at all,
41+
and this implementation provides fourteen of the fifteen — so a program that
42+
calls `kal_space_start` fails at the **link**, naming the operation, which is
43+
clause 6.1's report and is the loudest one available.
44+
45+
Constructing a copy out of `CreateProcessW` plus a mechanism for carrying the
46+
caller's memory across would be the simulation clause 3.1 forbids: it would be
47+
present, it would look like the operation, and what it produced would not be a
48+
copy of the caller.
49+
3650
**Duplication of the calling image is not a Unix preference.** `openkal.process`
3751
starts a program and does not duplicate one, and the specification's reason is
3852
that duplication cannot be performed faithfully everywhere. This system has no
@@ -95,17 +109,32 @@ function here, because this environment's loader has already established the
95109
argument vector, the named values and thread-local storage before it transfers
96110
control.
97111

112+
## Interfaces provided
113+
114+
Fourteen of the fifteen. `openkal.space` is declined, for the reason above.
115+
116+
The four that openkal 0.8 added and this implementation now provides:
117+
118+
| | on this system |
119+
| --- | --- |
120+
| `openkal.net` | Winsock, started once at the first socket and never stopped. ⚠️ `WSASocketW` with a flags word of zero rather than `socket`: the latter makes an **overlapped** handle, and `ReadFile` upon one of those returns before the bytes arrive. A non-overlapped socket is what lets a connection be a stream here with no second transfer path |
121+
| `openkal.datagram` | the same calls with `SOCK_DGRAM`. ⚠️ This system reports a truncated message as a **failure** where the other two truncate silently; the bytes that fit are delivered either way, and the interface says the excess is lost |
122+
| `openkal.timeout` | `WSAPoll`, which answers for sockets and for nothing else. A bounded read of a stream that is not a socket reports `kal_err_not_supported` — which the interface's own header anticipates in terms. `kal_timeout_wait_process` is the one operation of the interface this system provides **directly**, because a bounded wait upon an object is the primitive here |
123+
| `openkal.exec` | `VirtualAlloc` writable, `VirtualProtect` executable, `FlushInstructionCache`. The third call is not optional and the other two systems' implementations do not need to make it explicit |
124+
98125
## Verification
99126

100-
The conformance suite in the specification package, built for this target:
127+
The conformance suite in the specification package, built for this target. The
128+
feature set is enumerated rather than `optional`, and the difference is the one
129+
interface this implementation declines: a set demanding `space` would fail to
130+
link naming `kal_space_start` rather than report an observation.
101131

102132
```bash
103-
mcpp build --target x86_64-windows-gnu --features full
104-
./target/*/*/bin/openkal-conformance.exe
133+
git clone https://github.com/mcpplibs/openkal .spec
134+
bash .spec/tools/run-conformance.sh openkal-windows . \
135+
full,exec,random,terminal,net,datagram,timeout --target x86_64-windows-gnu
105136
```
106137

107-
Ninety-one observations held, none failed, none went unexamined.
108-
109138
## Toolchains
110139

111140
`x86_64-windows-gnu` (GCC producing PE), and the MSVC ABI through `llvm`. The

build.mcpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ int main() {
117117
mcpp::rerun_if_changed("port/shell32.def");
118118
mcpp::rerun_if_changed("port/synchronization.def");
119119
mcpp::rerun_if_changed("port/bcrypt.def");
120+
mcpp::rerun_if_changed("port/ws2_32.def");
120121

121122
if (host_is_windows()) return 0;
122123

@@ -139,7 +140,7 @@ int main() {
139140
const std::string tool = dlltool();
140141

141142
for (auto name : { "kernel32", "ntdll", "shell32", "synchronization",
142-
"bcrypt" }) {
143+
"bcrypt", "ws2_32" }) {
143144
const auto def = std::format("{}/port/{}.def", root, name);
144145
const auto lib = std::format("{}/lib{}.a", out, name);
145146
// ⚠️ `-m i386:x86-64` is stated. See port/README.md: the 32-bit ABI

mcpp.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-windows"
4-
version = "0.2.0"
4+
version = "0.3.0"
55
description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol."
66
license = "Apache-2.0"
77

@@ -49,7 +49,8 @@ openkal = "0.8.0"
4949
# predicate the second one linked with none of these libraries and failed on
5050
# `GetStdHandle`.
5151
[target.'cfg(all(windows, not(env = "msvc")))'.build]
52-
ldflags = ["-lntdll", "-lsynchronization", "-lshell32", "-lkernel32", "-lbcrypt"]
52+
ldflags = ["-lntdll", "-lsynchronization", "-lshell32", "-lkernel32", "-lbcrypt",
53+
"-lws2_32"]
5354

5455
# Exceptions and run-time type information, on the one ABI where their absence
5556
# is asserted.

port/ws2_32.def

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
; This system's network interface, and nothing else from this library.
2+
;
3+
; ⭐ THE LIST IS WHAT src/net.cpp, src/datagram.cpp AND src/timeout.cpp CALL,
4+
; obtained the way the other four lists were obtained: the objects of a complete
5+
; build were given to `nm' and the undefined names are these. A list produced by
6+
; reading the header would contain names the configured build never uses.
7+
;
8+
; ⚠️ `WSASocketW' AND NOT `socket', AND THE DIFFERENCE IS LOAD-BEARING. `socket'
9+
; makes an OVERLAPPED handle, upon which `ReadFile' returns before the bytes
10+
; have arrived; the flags word `WSASocketW' takes makes one that is not, and
11+
; that is what lets openkal.stream's operations here --- which are `ReadFile'
12+
; and `WriteFile' --- transfer a connection's bytes with no second code path.
13+
; src/win32.h records the same thing beside the declaration.
14+
;
15+
; ⚠️ `WSACleanup' IS DELIBERATELY ABSENT. This implementation starts the network
16+
; interface once and never stops it: the interface stays available for the life
17+
; of the image, which is what a program that opened a socket wants, and a
18+
; reference count that reached zero would close every socket the program still
19+
; held. src/endpoint.h records it beside the one call that starts it.
20+
;
21+
; Generated into an import library by build.mcpp; see port/README.md.
22+
LIBRARY ws2_32.dll
23+
EXPORTS
24+
WSAStartup
25+
WSAGetLastError
26+
WSASocketW
27+
WSAPoll
28+
closesocket
29+
bind
30+
listen
31+
accept
32+
connect
33+
shutdown
34+
getsockname
35+
getpeername
36+
sendto
37+
recvfrom

src/datagram.cpp

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#include "win.h"
2+
#include "endpoint.h"
3+
#include <openkal/datagram.h>
4+
5+
// openkal.datagram upon this system's network interface.
6+
//
7+
// A DATAGRAM IS NOT PACKED AS A kal_stream, and the handle type is its own for
8+
// that reason: kal_stream_read reports a count and not a boundary, so reading a
9+
// datagram through it would lose the property that distinguishes this interface.
10+
// The packing is the same, the type is not, and the type is what prevents the
11+
// mistake.
12+
13+
namespace {
14+
15+
SOCKET socket_of(kal_datagram d) { return okw::unpack_socket(d.h); }
16+
17+
bool bad(SOCKET s) { return s == INVALID_SOCKET; }
18+
19+
// The largest transfer one call accepts. This system states a count as an
20+
// `int', and a datagram larger than that cannot exist, so the clamp is a
21+
// statement about the type rather than a limit this implementation imposes.
22+
constexpr kal_uintptr kMaxOne = 0x7fffffffu;
23+
24+
} // namespace
25+
26+
extern "C" {
27+
28+
int kal_datagram_open(const kal_endpoint* local, kal_datagram* out) {
29+
if (out == nullptr) return kal_err_invalid;
30+
31+
// A null local endpoint asks for one that may send and whose receiving
32+
// address is unspecified. IPv4 is chosen for it, because a family must be
33+
// named at the point the socket is made and this is the one every
34+
// environment that has a network at all provides.
35+
int family = AF_INET_;
36+
if (local != nullptr) {
37+
family = okw::family_of(*local);
38+
if (family < 0) return kal_err_invalid;
39+
}
40+
41+
okw::ensure_network();
42+
const SOCKET s = WSASocketW(family, SOCK_DGRAM_, IPPROTO_UDP_, nullptr, 0, 0);
43+
if (bad(s)) return okw::last_socket_error();
44+
45+
if (local != nullptr) {
46+
ksockaddr_storage ss{};
47+
int len = 0;
48+
if (const int rc = okw::to_system(*local, ss, len); rc != kal_ok) {
49+
closesocket(s);
50+
return rc;
51+
}
52+
if (bind(s, &ss, len) != 0) {
53+
const int e = okw::last_socket_error();
54+
closesocket(s);
55+
return e;
56+
}
57+
}
58+
59+
out->h = okw::pack_socket(s);
60+
if (out->h == 0) { closesocket(s); return kal_err_no_memory; }
61+
return kal_ok;
62+
}
63+
64+
int kal_datagram_local(kal_datagram d, kal_endpoint* out) {
65+
if (out == nullptr) return kal_err_invalid;
66+
const SOCKET s = socket_of(d);
67+
if (bad(s)) return kal_err_invalid;
68+
69+
ksockaddr_storage ss{};
70+
int len = static_cast<int>(sizeof ss);
71+
if (getsockname(s, &ss, &len) != 0) return okw::last_socket_error();
72+
return okw::from_system(ss, *out);
73+
}
74+
75+
kal_io_result kal_datagram_send_to(kal_datagram d, const void* buf, kal_uintptr len,
76+
const kal_endpoint* to) {
77+
const SOCKET s = socket_of(d);
78+
if (bad(s) || to == nullptr) return { 0, kal_err_invalid };
79+
if (len > kMaxOne) return { 0, kal_err_invalid };
80+
81+
ksockaddr_storage ss{};
82+
int addrlen = 0;
83+
if (const int rc = okw::to_system(*to, ss, addrlen); rc != kal_ok)
84+
return { 0, rc };
85+
86+
const int r = sendto(s, static_cast<const char*>(buf), static_cast<int>(len),
87+
0, &ss, addrlen);
88+
if (r < 0) return { 0, okw::last_socket_error() };
89+
90+
// A MESSAGE IS SENT WHOLE OR NOT AT ALL, which is what this interface
91+
// states. The system reports a count anyway; a count short of the length
92+
// would mean the medium had split the message, which for a datagram socket
93+
// it does not do. Reporting the short count as success would give a caller a
94+
// partial send this interface says cannot occur, so it is reported as a
95+
// failure of the medium instead.
96+
const kal_uintptr n = static_cast<kal_uintptr>(r);
97+
return { n, n == len ? kal_ok : kal_err_io };
98+
}
99+
100+
kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
101+
kal_endpoint* from) {
102+
const SOCKET s = socket_of(d);
103+
if (bad(s)) return { 0, kal_err_invalid };
104+
if (len > kMaxOne) len = kMaxOne;
105+
106+
ksockaddr_storage ss{};
107+
int addrlen = static_cast<int>(sizeof ss);
108+
109+
const int r = recvfrom(s, static_cast<char*>(buf), static_cast<int>(len),
110+
0, &ss, &addrlen);
111+
if (r < 0) {
112+
// ⚠️ THE ONE FAILURE THIS SYSTEM REPORTS THAT THE OTHER TWO DO NOT.
113+
//
114+
// A message longer than the buffer is truncated here AND reported as a
115+
// failure --- `WSAEMSGSIZE' --- where the other two systems truncate
116+
// silently. This interface states that "a message longer than the
117+
// buffer is truncated and the excess is lost, which is what the medium
118+
// does", so the truncation is the specified behaviour and the report is
119+
// this system's addition. The bytes that fit are in the caller's buffer
120+
// either way; refusing them would lose a message the interface says was
121+
// delivered.
122+
//
123+
// The count is not recoverable from this call, so what is reported is
124+
// the whole of the buffer, which is what was filled.
125+
if (WSAGetLastError() == okw::WSAEMSGSIZE) {
126+
if (from != nullptr && okw::from_system(ss, *from) != kal_ok) {
127+
for (auto& b : from->addr) b = 0;
128+
from->addr_len = 0;
129+
from->port = 0;
130+
}
131+
return { len, kal_ok };
132+
}
133+
return { 0, okw::last_socket_error() };
134+
}
135+
136+
if (from != nullptr) {
137+
// A sender whose family this implementation does not know leaves the
138+
// endpoint zeroed rather than partly filled. The transfer still happened
139+
// and is reported; what is unknown is who sent it.
140+
if (okw::from_system(ss, *from) != kal_ok) {
141+
for (auto& b : from->addr) b = 0;
142+
from->addr_len = 0;
143+
from->port = 0;
144+
}
145+
}
146+
return { static_cast<kal_uintptr>(r), kal_ok };
147+
}
148+
149+
void kal_datagram_close(kal_datagram d) {
150+
const SOCKET s = socket_of(d);
151+
if (bad(s)) return;
152+
closesocket(s);
153+
okw::retire(d.h);
154+
}
155+
156+
// Broadcast is not claimed. The system provides it only after SO_BROADCAST has
157+
// been set, and this interface has no operation that would set it; a word
158+
// claiming a facility no operation reaches is the disagreement clause 6.2 exists
159+
// to prevent.
160+
const kal_uintptr kal_datagram_props = KAL_DGRAM_PROP_IPV6;
161+
162+
} // extern "C"

0 commit comments

Comments
 (0)