Skip to content

Commit 2152d0a

Browse files
Provide net, datagram, timeout and exec; decline space in terms (#14)
* 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. * Reach the network at run time: its names are the C library's names ⚠️⚠️ THIS SYSTEM'S NETWORK LIBRARY EXPORTS THE BSD NAMES, AND SO DOES THE C LIBRARY ABOVE THIS IMPLEMENTATION. openkal-musl compiles musl's own `src/network/*.c', which define `bind', `listen', `accept' and `connect' and route them through this port. Naming `-lws2_32' on the link line put both definitions in one program: ld.exe: libws2_32.a(libws2_32s00165.o): multiple definition of `connect'; musl/src/network/connect.o: first defined here Measured on the GNU/PE row of the C library's own continuous integration, on the first run of this change. It is not an ordering problem: an import library's member defines the thunk AND the `__imp_' pointer together, so reaching for either brings both. ⭐ THE NAMES ARE THEREFORE REACHED THROUGH THE SYSTEM'S OWN LOADER --- `LoadLibraryW' and `GetProcAddress', both of which this package already links --- into a table resolved once. Nothing of ws2_32 enters the program's symbol table, so the C library above keeps its `bind' and this implementation still reaches the system's. `port/ws2_32.def', the `-lws2_32' flag and the `#pragma comment' all come out. The table is resolved ENTIRELY OR NOT AT ALL: a table with one null entry would make the operations that resolved work and the one that did not call through zero, which is the failure clause 6.1 exists to turn into a link error and this arrangement cannot. Measured here after the change: `examples/cross-hello' links and produces `cross-hello.exe' over this implementation and that C library; 16 objects; exported surface complete and conforming at 88 names; no undefined symbol outside the permitted set; and NO BSD NAME DEFINED in this package's own objects, which is the property the collision was about. * Export the four names openkal.exec calls, and refuse for one reason Two failures, both from the conformance run this change made selective. ⚠️ `VirtualAlloc', `VirtualProtect', `VirtualFree' and `FlushInstructionCache' were declared in src/win32.h and exported by no `.def'. This package's own check says so in as many words --- "declared in src/win32.h and exported by no .def" --- and it is the check that exists because an import library here is a list of names rather than code, so a name absent from the list is a link that finds nothing on a machine without a vendor SDK. ⚠️⚠️ AND `kal_timeout_read' UPON THE STANDARD INPUT HAD TWO REFUSALS WHERE THE INTERFACE HAS ONE. An earlier form answered a null or invalid handle with `kal_err_invalid' and a valid non-socket with `kal_err_not_supported'; a run whose standard input is not attached has a handle of zero, so both bounded reads in the suite were reported as not holding. ⭐ The early return was answering a DIFFERENT QUESTION. "Is this handle valid" is what the unbounded operation answers. What this interface answers is whether this implementation can bound an operation upon this resource, and timeout.h sanctions exactly one refusal for that: "AN IMPLEMENTATION MAY PROVIDE THIS FOR SOME OF ITS RESOURCES AND NOT OTHERS, and reports kal_err_not_supported for the rest." A handle that is not a socket is one of the rest, and zero is not a socket. The name/export reading is also performed here before pushing, so that a round is not spent discovering a fifth name: 49 declared, 55 exported, none missing. * Bound a pipe as well as a socket: a channel here is a pipe ⚠️ `WSAPoll' takes sockets and nothing else, so `kal_timeout_read' upon a pipe reported `kal_err_not_supported' --- and `openkal.process' makes a channel out of a pipe on this system. A C library above this implementation therefore reaches `poll' and `select' upon one, and openkal-musl's network probe reported it on the row that builds for this system: FAIL: select reports the read end ready (errno=38) A `select' that refuses a pipe makes every program waiting on a subprocess's output stop. ⭐ THREE ENQUIRIES, ONE PER KIND OF OBJECT, chosen by asking what the handle is: `WSAPoll' for a socket, `PeekNamedPipe' for a pipe, and always ready for a file, because a read from one does not wait. ⚠️ A socket also reports `FILE_TYPE_PIPE', so the socket enquiry is made FIRST and the file type only decides what a non-socket is. ⭐ `PeekNamedPipe' is the one NON-DESTRUCTIVE readiness enquiry in this whole ecosystem: it reports how many bytes are there and takes none, which is why this implementation needs no read-ahead where the port above it does. ⚠️ A closed writing end is READY and not an error --- the call then fails with `ERROR_BROKEN_PIPE' and the read that follows reports the end of input without waiting, which is what readiness asserts. Reporting the failure would make a program that reads until end-of-input wait for ever instead. What remains unbounded is a character device, and `kal_err_not_supported' is what timeout.h states for a resource an implementation does not cover. Measured here: 16 objects, exported surface complete at 88 names, no undefined symbol outside the permitted set, 50 declared names and none missing from a `.def'. * A bounded wait answers with one of three values and no others ⚠️⚠️ MEASURED TWICE, THE SECOND TIME ONLY UNDER WINE, WHICH IS WHAT MADE THE SHAPE VISIBLE. `kal_timeout_read' upon the standard input reported an error belonging to the RESOURCE where the interface defines a set for the WAIT: first `kal_err_invalid' for a handle of zero, then whatever `PeekNamedPipe' or `WSAPoll' had failed with. The conformance suite reported "a bounded read reports success, an expiry, or a refusal" as not holding both times, and the second time on one runner out of three --- which is to say, only where the system chose a different error for the same condition. ⇒ `await_stream' now answers with `kal_ok', `kal_err_again' or `kal_err_not_supported' and nothing else. An error belonging to the resource is the TRANSFER's to report, and the transfer follows the wait. ⭐ AND THE REAL ERROR IS KEPT WHERE THE RESOURCE IS KNOWN. `kal_timeout_accept' and `kal_timeout_recv_from' are reached with a socket this implementation made, so a failure there carries information a caller can act upon; the narrowing happens only on the path that takes a caller's stream, where the resource is not known. ⚠️ `is_socket' also stops depending on WHICH error a system reports for a handle that is not a socket. It read "it is a socket unless the failure was WSAENOTSOCK", and Wine does not choose the same value. Every socket this implementation hands out has been connected, bound or accepted, so `getsockname' succeeds upon all of them; the test is that it succeeds. --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com>
1 parent d6fc05a commit 2152d0a

10 files changed

Lines changed: 1164 additions & 10 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

mcpp.toml

Lines changed: 1 addition & 1 deletion
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

port/kernel32.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ CreatePipe
1010
CreateProcessW
1111
CreateThread
1212
FlushFileBuffers
13+
FlushInstructionCache
1314
FreeEnvironmentStringsW
1415
GetCommandLineW
1516
GetConsoleMode
@@ -23,13 +24,16 @@ GetFileType
2324
GetFinalPathNameByHandleW
2425
GetLastError
2526
GetLogicalDriveStringsW
27+
GetProcAddress
2628
GetProcessHeap
2729
GetStdHandle
2830
GetSystemTimePreciseAsFileTime
2931
HeapAlloc
3032
HeapFree
33+
LoadLibraryW
3134
LocalFree
3235
MultiByteToWideChar
36+
PeekNamedPipe
3337
QueryPerformanceCounter
3438
QueryPerformanceFrequency
3539
ReadFile
@@ -39,6 +43,9 @@ SetHandleInformation
3943
Sleep
4044
SwitchToThread
4145
TerminateProcess
46+
VirtualAlloc
47+
VirtualFree
48+
VirtualProtect
4249
WaitForSingleObject
4350
WideCharToMultiByte
4451
WriteFile

src/datagram.cpp

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

0 commit comments

Comments
 (0)