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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,15 @@ The kernel layer is deliberately generic. Protocol clients such as HTTPS,
Redis, MQTT, RabbitMQ/AMQP, and Kafka should be built on top of the shared
TCP/TLS socket surface rather than requiring protocol-specific kernel methods.

Passive TCP consumers use the kernel boundary as well: check
`supports_tcp_server()` before resolving or opening anything, pass the opaque
record returned by `resolve_passive_address()` unchanged to both `socket_open()`
and `socket_bind()`, then use the kernel's listen, accept, address-inspection,
and close operations. Address reuse has its own capability check because some
MicroPython ports support listeners without exposing `SO_REUSEADDR` constants.
The web app demo shows the complete setup and rollback pattern without importing
platform socket APIs.

## Clients

The current setup now includes first-party smallOS-native helpers for HTTP,
Expand Down
225 changes: 221 additions & 4 deletions SmallPackage/Kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ def _validate_socket_operation(operation):
)


class _PassiveAddressInfo:
"""Kernel-owned address record for a passive TCP endpoint."""

def __init__(self, owner, address_info):
self.owner = owner
self.address_info = address_info


def _unwrap_passive_address(owner, address_info):
"""Return backend address data only for a record created by ``owner``."""
if not isinstance(address_info, _PassiveAddressInfo) or address_info.owner is not owner:
raise ValueError('Passive address record belongs to a different kernel.')
return address_info.address_info


def _import_first(*module_names: str) -> Any | None:
"""
Import the first available module name from a list.
Expand Down Expand Up @@ -524,6 +539,14 @@ def supports_external_wait_objects(self) -> bool:
"""Whether ``io_wait`` can wake on adapter-owned readiness objects."""
return False

def supports_tcp_server(self) -> bool:
"""Whether this kernel implements the complete passive TCP contract."""
return False

def supports_reuse_address(self) -> bool:
"""Whether this kernel can configure address reuse on a listener."""
return False

def validate_io_wait_object(
self, obj: Any
) -> tuple[bool, BaseException | None]:
Expand Down Expand Up @@ -556,11 +579,32 @@ def validate_io_wait_object(
def resolve_address(self, host: str, port: int) -> Any:
return None

def resolve_passive_address(self, host: str, port: int) -> Any:
raise NotImplementedError('Passive TCP address resolution is not supported.')

def socket_open(self, address_info: Any) -> Any:
return None
raise NotImplementedError('TCP sockets are not supported.')

def socket_setblocking(self, sock: Any, flag: bool) -> None:
return
raise NotImplementedError('TCP sockets are not supported.')

def socket_set_reuse_address(self, sock: Any, enabled: bool) -> None:
raise NotImplementedError('TCP address reuse is not supported.')

def socket_bind(self, sock: Any, address_info: Any) -> None:
raise NotImplementedError('TCP listeners are not supported.')

def socket_listen(self, sock: Any, backlog: int) -> None:
raise NotImplementedError('TCP listeners are not supported.')

def socket_accept(self, listener: Any) -> tuple[Any, Any]:
raise NotImplementedError('TCP listeners are not supported.')

def socket_local_address(self, sock: Any) -> Any:
raise NotImplementedError('Socket address inspection is not supported.')

def socket_peer_address(self, sock: Any) -> Any | None:
raise NotImplementedError('Socket address inspection is not supported.')

def socket_connect(self, sock: Any, sockaddr: Any) -> bool:
return True
Expand All @@ -576,7 +620,7 @@ def socket_recv(self, sock: Any, buffer_size: int) -> bytes:
return b''

def socket_close(self, sock: Any) -> None:
return
raise NotImplementedError('TCP sockets are not supported.')

def socket_wrap_tls_client(
self,
Expand Down Expand Up @@ -760,17 +804,72 @@ def validate_io_wait_object(self, obj: Any) -> tuple[bool, BaseException | None]
def supports_external_wait_objects(self) -> bool:
return True

def supports_tcp_server(self) -> bool:
return True

def supports_reuse_address(self) -> bool:
return (
hasattr(self._socket, 'SOL_SOCKET')
and hasattr(self._socket, 'SO_REUSEADDR')
and callable(getattr(self._socket.socket, 'setsockopt', None))
)

def resolve_address(self, host, port):
return self._socket.getaddrinfo(host, port, type=self._socket.SOCK_STREAM)[0]

def resolve_passive_address(self, host, port):
passive_host = None if host == '' else host
address_info = self._socket.getaddrinfo(
passive_host,
port,
type=self._socket.SOCK_STREAM,
flags=self._socket.AI_PASSIVE,
)[0]
return _PassiveAddressInfo(self, address_info)

def socket_open(self, address_info):
if isinstance(address_info, _PassiveAddressInfo):
address_info = _unwrap_passive_address(self, address_info)
family, socktype, proto, _, _ = address_info
return self._socket.socket(family, socktype, proto)

def socket_setblocking(self, sock, flag):
sock.setblocking(flag)
return

def socket_set_reuse_address(self, sock, enabled):
if not self.supports_reuse_address():
raise NotImplementedError('SO_REUSEADDR is not supported on this platform.')
sock.setsockopt(
self._socket.SOL_SOCKET,
self._socket.SO_REUSEADDR,
1 if enabled else 0,
)
return

def socket_bind(self, sock, address_info):
address_info = _unwrap_passive_address(self, address_info)
sock.bind(address_info[4])
return

def socket_listen(self, sock, backlog):
sock.listen(backlog)
return

def socket_accept(self, listener):
return listener.accept()

def socket_local_address(self, sock):
return sock.getsockname()

def socket_peer_address(self, sock):
try:
return sock.getpeername()
except OSError as exc:
if self._extract_errno(exc) == self._errno.ENOTCONN:
return None
raise

def socket_connect(self, sock, sockaddr):
err = sock.connect_ex(sockaddr)
pending = {
Expand Down Expand Up @@ -980,17 +1079,135 @@ def create_io_wait_set(self):
def supports_external_wait_objects(self) -> bool:
return bool(self._poll_factory)

def supports_tcp_server(self) -> bool:
if not callable(getattr(self._socket, 'getaddrinfo', None)):
return False
socket_factory = getattr(self._socket, 'socket', None)
if not callable(socket_factory):
return False
required = (
'setblocking', 'bind', 'listen', 'accept', 'getsockname',
'getpeername', 'send', 'recv', 'close',
)
return all(callable(getattr(socket_factory, name, None)) for name in required)

def supports_reuse_address(self) -> bool:
socket_factory = getattr(self._socket, 'socket', None)
return (
hasattr(self._socket, 'SOL_SOCKET')
and hasattr(self._socket, 'SO_REUSEADDR')
and callable(getattr(socket_factory, 'setsockopt', None))
)

def resolve_address(self, host, port):
return self._socket.getaddrinfo(host, port)[0]

def resolve_passive_address(self, host, port):
if not self.supports_tcp_server():
raise NotImplementedError('TCP server support is not available on this port.')
passive_host = '0.0.0.0' if host == '' else host
sock_stream = getattr(self._socket, 'SOCK_STREAM', None)
ai_passive = getattr(self._socket, 'AI_PASSIVE', None)
attempts = []
if sock_stream is not None and ai_passive is not None:
attempts.append((passive_host, port, 0, sock_stream, 0, ai_passive))
if sock_stream is not None:
attempts.append((passive_host, port, 0, sock_stream))
attempts.append((passive_host, port))
last_error = None
for arguments in attempts:
try:
resolved = self._socket.getaddrinfo(*arguments)[0]
break
except TypeError as exc:
last_error = exc
else:
if last_error is None:
raise RuntimeError('Passive TCP address resolution did not run.')
raise last_error
return _PassiveAddressInfo(self, resolved)

def socket_open(self, address_info):
is_passive = isinstance(address_info, _PassiveAddressInfo)
if is_passive:
address_info = _unwrap_passive_address(self, address_info)
family, socktype, proto, _, _ = address_info
return self._socket.socket(family, socktype, proto)
sock = self._socket.socket(family, socktype, proto)
if not is_passive:
return sock
required = (
'setblocking', 'bind', 'listen', 'accept', 'getsockname',
'getpeername', 'send', 'recv', 'close',
)
missing = [name for name in required if not callable(getattr(sock, name, None))]
if not missing:
return sock
closer = getattr(sock, 'close', None)
if callable(closer):
try:
closer()
except BaseException:
pass
raise NotImplementedError(
'TCP server socket is missing required operations: {}.'.format(', '.join(missing))
)

def socket_setblocking(self, sock, flag):
sock.setblocking(flag)
return

def socket_set_reuse_address(self, sock, enabled):
if not self.supports_reuse_address():
raise NotImplementedError('SO_REUSEADDR is not supported on this port.')
setter = getattr(sock, 'setsockopt', None)
if not callable(setter):
raise NotImplementedError('Socket instance does not support SO_REUSEADDR.')
setter(
self._socket.SOL_SOCKET,
self._socket.SO_REUSEADDR,
1 if enabled else 0,
)
return

def socket_bind(self, sock, address_info):
address_info = _unwrap_passive_address(self, address_info)
binder = getattr(sock, 'bind', None)
if not callable(binder):
raise NotImplementedError('TCP bind is not supported on this port.')
binder(address_info[4])
return

def socket_listen(self, sock, backlog):
listener = getattr(sock, 'listen', None)
if not callable(listener):
raise NotImplementedError('TCP listen is not supported on this port.')
listener(backlog)
return

def socket_accept(self, listener):
acceptor = getattr(listener, 'accept', None)
if not callable(acceptor):
raise NotImplementedError('TCP accept is not supported on this port.')
return acceptor()

def socket_local_address(self, sock):
getter = getattr(sock, 'getsockname', None)
if not callable(getter):
raise NotImplementedError('Local address inspection is not supported.')
return getter()

def socket_peer_address(self, sock):
getter = getattr(sock, 'getpeername', None)
if not callable(getter):
raise NotImplementedError('Peer address inspection is not supported.')
try:
return getter()
except OSError as exc:
not_connected = getattr(self._errno, 'ENOTCONN', None)
if not_connected is not None and self._extract_errno(exc) == not_connected:
return None
raise

def socket_connect(self, sock, sockaddr):
try:
sock.connect(sockaddr)
Expand Down
17 changes: 17 additions & 0 deletions SmallPackage/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ def io_wait(
) -> tuple[Sequence[Any], Sequence[Any]]: ...


class PassiveTCPKernelLike(Protocol):
"""Platform-neutral passive TCP operations used by server consumers."""

def supports_tcp_server(self) -> bool: ...
def supports_reuse_address(self) -> bool: ...
def resolve_passive_address(self, host: str, port: int) -> Any: ...
def socket_open(self, address_info: Any) -> Any: ...
def socket_setblocking(self, sock: Any, flag: bool) -> None: ...
def socket_set_reuse_address(self, sock: Any, enabled: bool) -> None: ...
def socket_bind(self, sock: Any, address_info: Any) -> None: ...
def socket_listen(self, sock: Any, backlog: int) -> None: ...
def socket_accept(self, listener: Any) -> tuple[Any, Any]: ...
def socket_local_address(self, sock: Any) -> Any: ...
def socket_peer_address(self, sock: Any) -> Any | None: ...
def socket_close(self, sock: Any) -> None: ...


class SocketKernelLike(Protocol):
"""Optional kernel boundary for operation-aware byte-stream I/O."""

Expand Down
Loading
Loading