Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,21 @@ jobs:
run: |
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
bash .spec/tools/run-conformance.sh openkal-windows . full $extra
# ⚠️ THE SET IS ENUMERATED AND `optional` IS NOT NAMED, AND THE
# DIFFERENCE IS ONE INTERFACE.
#
# `optional` includes `space`, which this system does not provide:
# there is no primitive here that copies an address space and starts a
# context in the copy. A set demanding it would not report an
# observation that did not hold — it would fail to LINK, naming
# `kal_space_start`, which is clause 6.1's report arriving where a
# report was not wanted.
#
# So the six this implementation does provide are named, and the
# seventh is absent from the list for the same reason it is absent
# from the objects.
bash .spec/tools/run-conformance.sh openkal-windows . \
full,exec,random,terminal,net,datagram,timeout $extra

# ---------------------------------------------------------------------------
# From a system that is not this one.
Expand Down Expand Up @@ -343,7 +357,8 @@ jobs:
env:
WINEDEBUG: '-all'
run: |
bash .spec/tools/run-conformance.sh openkal-windows . full \
bash .spec/tools/run-conformance.sh openkal-windows . \
full,exec,random,terminal,net,datagram,timeout \
--target x86_64-windows-gnu

- name: The exported surface is complete and contains nothing else
Expand Down
43 changes: 36 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ An implementation of [openkal](https://github.com/mcpplibs/openkal) for Windows.

```toml
[dependencies]
openkal = "0.5.1"
openkal = "0.8.0"

[target.'cfg(windows)'.dependencies]
openkal-windows = "0.1.1"
openkal-windows = "0.3.0"
```

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

**A declined interface is a legitimate outcome, and this system is where that
stops being theory.** `openkal.space` starts a context in a copy of the calling
address space. There is no primitive here that does it: `CreateProcessW` starts
a *named program*, which is `openkal.process` and is a different operation.
Clause 3 says an implementation provides an interface in whole or not at all,
and this implementation provides fourteen of the fifteen — so a program that
calls `kal_space_start` fails at the **link**, naming the operation, which is
clause 6.1's report and is the loudest one available.

Constructing a copy out of `CreateProcessW` plus a mechanism for carrying the
caller's memory across would be the simulation clause 3.1 forbids: it would be
present, it would look like the operation, and what it produced would not be a
copy of the caller.

**Duplication of the calling image is not a Unix preference.** `openkal.process`
starts a program and does not duplicate one, and the specification's reason is
that duplication cannot be performed faithfully everywhere. This system has no
Expand Down Expand Up @@ -95,17 +109,32 @@ function here, because this environment's loader has already established the
argument vector, the named values and thread-local storage before it transfers
control.

## Interfaces provided

Fourteen of the fifteen. `openkal.space` is declined, for the reason above.

The four that openkal 0.8 added and this implementation now provides:

| | on this system |
| --- | --- |
| `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 |
| `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 |
| `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 |
| `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 |

## Verification

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

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

Ninety-one observations held, none failed, none went unexamined.

## Toolchains

`x86_64-windows-gnu` (GCC producing PE), and the MSVC ABI through `llvm`. The
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-windows"
version = "0.2.0"
version = "0.3.0"
description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol."
license = "Apache-2.0"

Expand Down
7 changes: 7 additions & 0 deletions port/kernel32.def
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ CreatePipe
CreateProcessW
CreateThread
FlushFileBuffers
FlushInstructionCache
FreeEnvironmentStringsW
GetCommandLineW
GetConsoleMode
Expand All @@ -23,13 +24,16 @@ GetFileType
GetFinalPathNameByHandleW
GetLastError
GetLogicalDriveStringsW
GetProcAddress
GetProcessHeap
GetStdHandle
GetSystemTimePreciseAsFileTime
HeapAlloc
HeapFree
LoadLibraryW
LocalFree
MultiByteToWideChar
PeekNamedPipe
QueryPerformanceCounter
QueryPerformanceFrequency
ReadFile
Expand All @@ -39,6 +43,9 @@ SetHandleInformation
Sleep
SwitchToThread
TerminateProcess
VirtualAlloc
VirtualFree
VirtualProtect
WaitForSingleObject
WideCharToMultiByte
WriteFile
171 changes: 171 additions & 0 deletions src/datagram.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#include "win.h"
#include "endpoint.h"
#include <openkal/datagram.h>

// openkal.datagram upon this system's network interface.
//
// A DATAGRAM IS NOT PACKED AS A kal_stream, and the handle type is its own for
// that reason: kal_stream_read reports a count and not a boundary, so reading a
// datagram through it would lose the property that distinguishes this interface.
// The packing is the same, the type is not, and the type is what prevents the
// mistake.

namespace {

SOCKET socket_of(kal_datagram d) { return okw::unpack_socket(d.h); }

okw::network_calls* net() { return okw::net_or_null(); }

bool bad(SOCKET s) { return s == INVALID_SOCKET; }

// The largest transfer one call accepts. This system states a count as an
// `int', and a datagram larger than that cannot exist, so the clamp is a
// statement about the type rather than a limit this implementation imposes.
constexpr kal_uintptr kMaxOne = 0x7fffffffu;

} // namespace

extern "C" {

int kal_datagram_open(const kal_endpoint* local, kal_datagram* out) {
if (out == nullptr) return kal_err_invalid;

// A null local endpoint asks for one that may send and whose receiving
// address is unspecified. IPv4 is chosen for it, because a family must be
// named at the point the socket is made and this is the one every
// environment that has a network at all provides.
int family = AF_INET_;
if (local != nullptr) {
family = okw::family_of(*local);
if (family < 0) return kal_err_invalid;
}

auto* n = net();
if (n == nullptr) return kal_err_io;
const SOCKET s = n->socket(family, SOCK_DGRAM_, IPPROTO_UDP_, nullptr, 0, 0);
if (bad(s)) return okw::last_socket_error();

if (local != nullptr) {
ksockaddr_storage ss{};
int len = 0;
if (const int rc = okw::to_system(*local, ss, len); rc != kal_ok) {
n->close(s);
return rc;
}
if (n->bind(s, &ss, len) != 0) {
const int e = okw::last_socket_error();
n->close(s);
return e;
}
}

out->h = okw::pack_socket(s);
if (out->h == 0) { n->close(s); return kal_err_no_memory; }
return kal_ok;
}

int kal_datagram_local(kal_datagram d, kal_endpoint* out) {
if (out == nullptr) return kal_err_invalid;
const SOCKET s = socket_of(d);
if (bad(s)) return kal_err_invalid;

auto* n = net();
if (n == nullptr) return kal_err_io;
ksockaddr_storage ss{};
int len = static_cast<int>(sizeof ss);
if (n->sockname(s, &ss, &len) != 0) return okw::last_socket_error();
return okw::from_system(ss, *out);
}

kal_io_result kal_datagram_send_to(kal_datagram d, const void* buf, kal_uintptr len,
const kal_endpoint* to) {
const SOCKET s = socket_of(d);
if (bad(s) || to == nullptr) return { 0, kal_err_invalid };
if (len > kMaxOne) return { 0, kal_err_invalid };

auto* n = net();
if (n == nullptr) return { 0, kal_err_io };
ksockaddr_storage ss{};
int addrlen = 0;
if (const int rc = okw::to_system(*to, ss, addrlen); rc != kal_ok)
return { 0, rc };

const int r = n->send_to(s, static_cast<const char*>(buf), static_cast<int>(len),
0, &ss, addrlen);
if (r < 0) return { 0, okw::last_socket_error() };

// A MESSAGE IS SENT WHOLE OR NOT AT ALL, which is what this interface
// states. The system reports a count anyway; a count short of the length
// would mean the medium had split the message, which for a datagram socket
// it does not do. Reporting the short count as success would give a caller a
// partial send this interface says cannot occur, so it is reported as a
// failure of the medium instead.
const kal_uintptr sent = static_cast<kal_uintptr>(r);
return { sent, sent == len ? kal_ok : kal_err_io };
}

kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
kal_endpoint* from) {
const SOCKET s = socket_of(d);
if (bad(s)) return { 0, kal_err_invalid };
if (len > kMaxOne) len = kMaxOne;

auto* n = net();
if (n == nullptr) return { 0, kal_err_io };
ksockaddr_storage ss{};
int addrlen = static_cast<int>(sizeof ss);

const int r = n->recv_from(s, static_cast<char*>(buf), static_cast<int>(len),
0, &ss, &addrlen);
if (r < 0) {
// ⚠️ THE ONE FAILURE THIS SYSTEM REPORTS THAT THE OTHER TWO DO NOT.
//
// A message longer than the buffer is truncated here AND reported as a
// failure --- `WSAEMSGSIZE' --- where the other two systems truncate
// silently. This interface states that "a message longer than the
// buffer is truncated and the excess is lost, which is what the medium
// does", so the truncation is the specified behaviour and the report is
// this system's addition. The bytes that fit are in the caller's buffer
// either way; refusing them would lose a message the interface says was
// delivered.
//
// The count is not recoverable from this call, so what is reported is
// the whole of the buffer, which is what was filled.
if (n->last_error() == okw::WSAEMSGSIZE) {
if (from != nullptr && okw::from_system(ss, *from) != kal_ok) {
for (auto& b : from->addr) b = 0;
from->addr_len = 0;
from->port = 0;
}
return { len, kal_ok };
}
return { 0, okw::last_socket_error() };
}

if (from != nullptr) {
// A sender whose family this implementation does not know leaves the
// endpoint zeroed rather than partly filled. The transfer still happened
// and is reported; what is unknown is who sent it.
if (okw::from_system(ss, *from) != kal_ok) {
for (auto& b : from->addr) b = 0;
from->addr_len = 0;
from->port = 0;
}
}
return { static_cast<kal_uintptr>(r), kal_ok };
}

void kal_datagram_close(kal_datagram d) {
const SOCKET s = socket_of(d);
if (bad(s)) return;
if (auto* n = net()) n->close(s);
okw::retire(d.h);
}

// Broadcast is not claimed. The system provides it only after SO_BROADCAST has
// been set, and this interface has no operation that would set it; a word
// claiming a facility no operation reaches is the disagreement clause 6.2 exists
// to prevent.
const kal_uintptr kal_datagram_props = KAL_DGRAM_PROP_IPV6;

} // extern "C"
Loading
Loading