Skip to content

Python API

Sandwich Python API.

This API provides a way to use Sandwich from Python.

It wraps Sandwich primitives into classes for convenience. It also provides the protobuf API for building Sandwich Contexts and Tunnels.

The following classes are defined
  • Sandwich: general handle responsible for ffi using ctypes.CDLL.
  • IO: abstract class to define an I/O interface.

To be able to use this API, the user has to define its own I/O interface. See io.py for more information.

Sandwich

Top-level Sandwich context library.

Source code in pysandwich/sandwich.py
class Sandwich:
    """Top-level Sandwich context library."""

    _handle: ctypes.c_void_p

    def __init__(self):
        self._handle = sandwich().c_call("sandwich_lib_context_new")

    def _get_handle(self) -> ctypes.c_void_p:
        return self._handle

    def __del__(self):
        sandwich().c_call("sandwich_lib_context_free", self._handle)
        self._handle = ctypes.c_void_p(None)

TunnelConfigurationSerialized

Bases: Structure

The struct SandwichTunnelConfigurationSerialized.

Source code in pysandwich/sandwich.py
class TunnelConfigurationSerialized(ctypes.Structure):
    """The `struct SandwichTunnelConfigurationSerialized`."""

    _fields_ = [
        (
            "src",
            ctypes.c_char_p,
        ),
        (
            "n",
            ctypes.c_size_t,
        ),
    ]

TunnelContextConfigurationSerialized

Bases: Structure

The struct SandwichContextTunnelConfigurationSerialized.

Source code in pysandwich/sandwich.py
class TunnelContextConfigurationSerialized(ctypes.Structure):
    """The `struct SandwichContextTunnelConfigurationSerialized`."""

    _fields_ = [
        (
            "src",
            ctypes.c_char_p,
        ),
        (
            "n",
            ctypes.c_size_t,
        ),
    ]

sandwich()

Returns the global handler to _SandwichCLib. This function performs a lazy initialization of the Sandwich handler.

Source code in pysandwich/sandwich.py
def sandwich() -> _SandwichCLib:
    """Returns the global handler to _SandwichCLib.
    This function performs a lazy initialization of the Sandwich handler.
    """
    global _sandwich_hdl
    if _sandwich_hdl is None:
        _sandwich_hdl = _SandwichCLib()
    return _sandwich_hdl

Sandwich I/O API.

This API provides an I/O interface in Python, to use with Sandwich. It wraps a struct SandwichIO.

An I/O interface is an object requiring the following methods: * read(n: int) -> bytes * write(buf: bytes) -> int

All methods should either return the corresponding value (the read bytes for read and the amount of successfully written bytes for write), or raise an exception of type IOException.

The user has to define a class extending IO. An example can be found in tunnel_test.py.

IO

Bases: ABC

Abstraction of a struct SandwichIO handle.

The C handle is built by the tunnel, using the methods from this class.

Source code in pysandwich/io.py
class IO(abc.ABC):
    """Abstraction of a `struct SandwichIO` handle.

    The C handle is built by the tunnel, using the methods from this class.
    """

    Error = SandwichIOProto.IOError

    class Settings(ctypes.Structure):
        """The `struct SandwichIO`."""

        # typedef size_t(SandwichIOReadFunction)(void *uarg, void *buf, size_t count,
        # enum SandwichIOError *err);
        _READ_FN_TYPE = ctypes.CFUNCTYPE(
            ctypes.c_size_t,  # Return type
            ctypes.c_void_p,  # *uarg
            ctypes.c_void_p,  # *buf
            ctypes.c_size_t,  # count
            ctypes.POINTER(ctypes.c_int),  # *err
        )

        # typedef size_t(SandwichIOWriteFunction)(void *uarg, const void *buf,
        # size_t count,
        # enum SandwichIOError *err);
        _WRITE_FN_TYPE = ctypes.CFUNCTYPE(
            ctypes.c_size_t,  # Return type
            ctypes.c_void_p,  # *uarg
            ctypes.c_void_p,  # *buf
            ctypes.c_size_t,  # count
            ctypes.POINTER(ctypes.c_int),  # *err
        )

        # typedef enum SandwichIOError(SandwichIOFlushFunction)(void *uarg);
        _FLUSH_FN_TYPE = ctypes.CFUNCTYPE(
            ctypes.c_int,  # Return type
            ctypes.c_void_p,  # *uarg
        )

        _fields_ = [
            (
                "readfn",
                _READ_FN_TYPE,
            ),
            (
                "writefn",
                _WRITE_FN_TYPE,
            ),
            (
                "flushfn",
                _FLUSH_FN_TYPE,
            ),
            (
                "uarg",
                ctypes.c_void_p,
            ),
        ]

    @abc.abstractmethod
    def read(self, n: int) -> bytes:
        """Read function.

        Args:
            n:
                Amount of bytes to read.

        Raises:
            IOException: an error occured during reading

        Returns:
            Bytes successfully read.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def write(self, buf: bytes) -> int:
        """Write function.

        Args:
            buf:
                Buffer to write.

        Raises:
            IOException: an error occured during writing

        Returns:
            Amount of successfully written bytes.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def flush(self):
        """Flush function.

        Raises:
            IOException: an error occured while flushing
        """
        raise NotImplementedError

Settings

Bases: Structure

The struct SandwichIO.

Source code in pysandwich/io.py
class Settings(ctypes.Structure):
    """The `struct SandwichIO`."""

    # typedef size_t(SandwichIOReadFunction)(void *uarg, void *buf, size_t count,
    # enum SandwichIOError *err);
    _READ_FN_TYPE = ctypes.CFUNCTYPE(
        ctypes.c_size_t,  # Return type
        ctypes.c_void_p,  # *uarg
        ctypes.c_void_p,  # *buf
        ctypes.c_size_t,  # count
        ctypes.POINTER(ctypes.c_int),  # *err
    )

    # typedef size_t(SandwichIOWriteFunction)(void *uarg, const void *buf,
    # size_t count,
    # enum SandwichIOError *err);
    _WRITE_FN_TYPE = ctypes.CFUNCTYPE(
        ctypes.c_size_t,  # Return type
        ctypes.c_void_p,  # *uarg
        ctypes.c_void_p,  # *buf
        ctypes.c_size_t,  # count
        ctypes.POINTER(ctypes.c_int),  # *err
    )

    # typedef enum SandwichIOError(SandwichIOFlushFunction)(void *uarg);
    _FLUSH_FN_TYPE = ctypes.CFUNCTYPE(
        ctypes.c_int,  # Return type
        ctypes.c_void_p,  # *uarg
    )

    _fields_ = [
        (
            "readfn",
            _READ_FN_TYPE,
        ),
        (
            "writefn",
            _WRITE_FN_TYPE,
        ),
        (
            "flushfn",
            _FLUSH_FN_TYPE,
        ),
        (
            "uarg",
            ctypes.c_void_p,
        ),
    ]

flush() abstractmethod

Flush function.

Raises:

Type Description
IOException

an error occured while flushing

Source code in pysandwich/io.py
@abc.abstractmethod
def flush(self):
    """Flush function.

    Raises:
        IOException: an error occured while flushing
    """
    raise NotImplementedError

read(n) abstractmethod

Read function.

Parameters:

Name Type Description Default
n int

Amount of bytes to read.

required

Raises:

Type Description
IOException

an error occured during reading

Returns:

Type Description
bytes

Bytes successfully read.

Source code in pysandwich/io.py
@abc.abstractmethod
def read(self, n: int) -> bytes:
    """Read function.

    Args:
        n:
            Amount of bytes to read.

    Raises:
        IOException: an error occured during reading

    Returns:
        Bytes successfully read.
    """
    raise NotImplementedError

write(buf) abstractmethod

Write function.

Parameters:

Name Type Description Default
buf bytes

Buffer to write.

required

Raises:

Type Description
IOException

an error occured during writing

Returns:

Type Description
int

Amount of successfully written bytes.

Source code in pysandwich/io.py
@abc.abstractmethod
def write(self, buf: bytes) -> int:
    """Write function.

    Args:
        buf:
            Buffer to write.

    Raises:
        IOException: an error occured during writing

    Returns:
        Amount of successfully written bytes.
    """
    raise NotImplementedError

IOClosedException

Bases: IOException

Closed pipe exception.

Source code in pysandwich/io.py
class IOClosedException(IOException):
    """Closed pipe exception."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(*kargs, code=SandwichIOProto.IOERROR_CLOSED, **kwargs)

IOException

Bases: SandwichException

Exception base class for I/O interface. Errors are defined in the protobuf io.proto, enum IOError. This exception handles the following cases: * IOERROR_IN_PROGRESS * IOERROR_WOULD_BLOCK * IOERROR_REFUSED * IOERROR_CLOSED * IOERROR_INVALID * IOERROR_UNKNOWN

Source code in pysandwich/io.py
class IOException(errors.SandwichException):
    """Exception base class for I/O interface.
    Errors are defined in the protobuf `io.proto`, `enum IOError`.
    This exception handles the following cases:
        * IOERROR_IN_PROGRESS
        * IOERROR_WOULD_BLOCK
        * IOERROR_REFUSED
        * IOERROR_CLOSED
        * IOERROR_INVALID
        * IOERROR_UNKNOWN
    """

    """The no-error error."""
    ERROR_OK = SandwichIOProto.IOERROR_OK

    """Map from the protobuf enum `IOError` to error string and subclass exception."""
    _ERRORS_MAP = {
        SandwichIOProto.IOERROR_IN_PROGRESS: {
            "msg": "The I/O interface is still connecting to the remote peer",
            "cls": lambda: IOInProgressException,
        },
        SandwichIOProto.IOERROR_WOULD_BLOCK: {
            "msg": "The I/O operation would block, but the I/O interface is non-blocking",
            "cls": lambda: IOWouldBlockException,
        },
        SandwichIOProto.IOERROR_REFUSED: {
            "msg": "The I/O interface has been refused connection",
            "cls": lambda: IORefusedException,
        },
        SandwichIOProto.IOERROR_CLOSED: {
            "msg": "This I/O interface is closed",
            "cls": lambda: IOClosedException,
        },
        SandwichIOProto.IOERROR_INVALID: {
            "msg": "This I/O interface isn't valid",
            "cls": lambda: IOInvalidException,
        },
        SandwichIOProto.IOERROR_UNKNOWN: {
            "msg": "This I/O interface raised an unknown error",
            "cls": lambda: IOUnknownException,
        },
    }

ERROR_OK = SandwichIOProto.IOERROR_OK class-attribute instance-attribute

Map from the protobuf enum IOError to error string and subclass exception.

IOInProgressException

Bases: IOException

In progress exception.

Source code in pysandwich/io.py
class IOInProgressException(IOException):
    """In progress exception."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(*kargs, code=SandwichIOProto.IOERROR_IN_PROGRESS, **kwargs)

IOInvalidException

Bases: IOException

Invalid I/O interface exception.

Source code in pysandwich/io.py
class IOInvalidException(IOException):
    """Invalid I/O interface exception."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(*kargs, code=SandwichIOProto.IOERROR_INVALID, **kwargs)

IORefusedException

Bases: IOException

Connection refused exception.

Source code in pysandwich/io.py
class IORefusedException(IOException):
    """Connection refused exception."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(*kargs, code=SandwichIOProto.IOERROR_REFUSED, **kwargs)

IOUnknownException

Bases: IOException

Unknown I/O exception.

Source code in pysandwich/io.py
class IOUnknownException(IOException):
    """Unknown I/O exception."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(*kargs, code=SandwichIOProto.IOERROR_UNKNOWN, **kwargs)

IOWouldBlockException

Bases: IOException

Would block exception.

Source code in pysandwich/io.py
class IOWouldBlockException(IOException):
    """Would block exception."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(*kargs, code=SandwichIOProto.IOERROR_WOULD_BLOCK, **kwargs)

OwnedIO

Bases: Structure

The struct SandwichIO.

Source code in pysandwich/io.py
class OwnedIO(ctypes.Structure):
    """The `struct SandwichIO`."""

    _IO_TYPE = ctypes.POINTER(IO.Settings)

    # void(SandwhichIOOwnedFreeFunction)(struct SandwhichCIOSettings *cio)
    _FREE_PTR_TYPE = ctypes.CFUNCTYPE(
        None, ctypes.POINTER(IO.Settings)  # Return type  # cio
    )

    _fields_ = [
        (
            "io",
            _IO_TYPE,
        ),
        (
            "freeptr",
            _FREE_PTR_TYPE,
        ),
    ]

TunnelIO

Bases: IO

Abstraction of a struct SandwichTunnelIO handle.

The C handle is built by the tunnel, using the methods from this class and SandwichIO.

Source code in pysandwich/io.py
class TunnelIO(IO):
    """Abstraction of a `struct SandwichTunnelIO` handle.

    The C handle is built by the tunnel, using the methods from this class and
    `SandwichIO`.
    """

    class CTunnelIO(ctypes.Structure):
        """The `struct SandwichTunnelIO`."""

        # typedef void(SandwichTunnelIOSetStateFunction)(void *uarg, enum
        # SandwichTunnelState tunnel_state);
        _SET_STATE_FN_TYPE = ctypes.CFUNCTYPE(
            None,  # Return type
            ctypes.c_void_p,  # *uarg
            ctypes.c_int,  # tunnel_state
        )

        _fields_ = [
            (
                "base",
                IO.Settings,
            ),
            (
                "set_statefn",
                _SET_STATE_FN_TYPE,
            ),
        ]

    @abc.abstractmethod
    def set_state(self, tunnel_state: SandwichTunnelProto.State):
        """Set the state of the tunnel.

        Args:
            tunnel_state:
                Current state of the tunnel.

        It is guaranteed that the state of the tunnel will not change between
        two calls to set_state.
        """
        raise NotImplementedError

CTunnelIO

Bases: Structure

The struct SandwichTunnelIO.

Source code in pysandwich/io.py
class CTunnelIO(ctypes.Structure):
    """The `struct SandwichTunnelIO`."""

    # typedef void(SandwichTunnelIOSetStateFunction)(void *uarg, enum
    # SandwichTunnelState tunnel_state);
    _SET_STATE_FN_TYPE = ctypes.CFUNCTYPE(
        None,  # Return type
        ctypes.c_void_p,  # *uarg
        ctypes.c_int,  # tunnel_state
    )

    _fields_ = [
        (
            "base",
            IO.Settings,
        ),
        (
            "set_statefn",
            _SET_STATE_FN_TYPE,
        ),
    ]

set_state(tunnel_state) abstractmethod

Set the state of the tunnel.

Parameters:

Name Type Description Default
tunnel_state State

Current state of the tunnel.

required

It is guaranteed that the state of the tunnel will not change between two calls to set_state.

Source code in pysandwich/io.py
@abc.abstractmethod
def set_state(self, tunnel_state: SandwichTunnelProto.State):
    """Set the state of the tunnel.

    Args:
        tunnel_state:
            Current state of the tunnel.

    It is guaranteed that the state of the tunnel will not change between
    two calls to set_state.
    """
    raise NotImplementedError

Sandwich Error API.

This API provides error types by inheriting the Exception class. Error codes come from the .proto file.

All sandwich exceptions are based on SandwichException.

This file defines the following exception families
  • SandwichGlobalException: exceptions that can happen all across the library.
  • HandshakeException: exceptions happening during the handshake stage (from Tunnel.handshake()).
  • HandshakeError: exceptions relating to errors encountered by the implementation
  • RecordPlaneException: exceptions happening in Tunnel.read or Tunnel.write.
  • IOException: exceptions happening in the I/O interface (see io.py).

All exceptions are based on the error codes defined by the following protobuf: * errors.proto: SandwichGlobalException * tunnel.proto: HandshakeException and RecordPlaneException * io.proto: IOException.

SandwichException exposes a code method to get the corresponding error code. This error code is compatible with the C++ library.

HandshakeErrorStateException

Bases: HandshakeException

Handshake general error

Source code in pysandwich/errors.py
class HandshakeErrorStateException(HandshakeException):
    """Handshake general error"""

    def __init__(self, *kargs, **kwargs):
        super().__init__(
            *kargs, code=SandwichTunnelProto.HANDSHAKESTATE_ERROR, **kwargs
        )

HandshakeException

Bases: SandwichException

Exception base class for the handshake state. This exception handles the following cases: * HANDSHAKESTATE_IN_PROGRESS * HANDSHAKESTATE_WANT_READ * HANDSHAKESTATE_WANT_WRITE * HANDSHAKESTATE_ERROR

Source code in pysandwich/errors.py
class HandshakeException(SandwichException):
    """Exception base class for the handshake state.
    This exception handles the following cases:
        * HANDSHAKESTATE_IN_PROGRESS
        * HANDSHAKESTATE_WANT_READ
        * HANDSHAKESTATE_WANT_WRITE
        * HANDSHAKESTATE_ERROR
    """

    """The no-error error."""
    ERROR_OK = SandwichTunnelProto.HANDSHAKESTATE_DONE

    """Map from the protobuf enum 'HandshakeState" to error string."""
    _ERRORS_MAP = {
        SandwichTunnelProto.HANDSHAKESTATE_IN_PROGRESS: {
            "msg": "The operation is still in progress",
            "cls": lambda: HandshakeInProgressException,
        },
        SandwichTunnelProto.HANDSHAKESTATE_WANT_READ: {
            "msg": (
                "The implementation wants to read from the wire, "
                "but the underlying I/O is non-blocking"
            ),
            "cls": lambda: HandshakeWantReadException,
        },
        SandwichTunnelProto.HANDSHAKESTATE_WANT_WRITE: {
            "msg": (
                "The implementation wants to write data to the wire, "
                "but the underlying I/O is non-blocking"
            ),
            "cls": lambda: HandshakeWantWriteException,
        },
        SandwichTunnelProto.HANDSHAKESTATE_ERROR: {
            "msg": "A critical error occurred",
            "cls": lambda: HandshakeErrorStateException,
        },
    }

ERROR_OK = SandwichTunnelProto.HANDSHAKESTATE_DONE class-attribute instance-attribute

Map from the protobuf enum 'HandshakeState" to error string.

HandshakeInProgressException

Bases: HandshakeException

Handshake in progress

Source code in pysandwich/errors.py
class HandshakeInProgressException(HandshakeException):
    """Handshake in progress"""

    def __init__(self, *kargs, **kwargs):
        super().__init__(
            *kargs, code=SandwichTunnelProto.HANDSHAKESTATE_IN_PROGRESS, **kwargs
        )

HandshakeWantReadException

Bases: HandshakeException

Handshake wants to read

Source code in pysandwich/errors.py
class HandshakeWantReadException(HandshakeException):
    """Handshake wants to read"""

    def __init__(self, *kargs, **kwargs):
        super().__init__(
            *kargs, code=SandwichTunnelProto.HANDSHAKESTATE_WANT_READ, **kwargs
        )

HandshakeWantWriteException

Bases: HandshakeException

Handshake wants to write

Source code in pysandwich/errors.py
class HandshakeWantWriteException(HandshakeException):
    """Handshake wants to write"""

    def __init__(self, *kargs, **kwargs):
        super().__init__(
            *kargs, code=SandwichTunnelProto.HANDSHAKESTATE_WANT_WRITE, **kwargs
        )

RecordPlaneBeingShutdownException

Bases: RecordPlaneException

Record plane is being closed.

Source code in pysandwich/errors.py
class RecordPlaneBeingShutdownException(RecordPlaneException):
    """Record plane is being closed."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(
            *kargs, code=SandwichTunnelProto.RECORDERROR_BEING_SHUTDOWN, **kwargs
        )

RecordPlaneClosedException

Bases: RecordPlaneException

Record plane is closed.

Source code in pysandwich/errors.py
class RecordPlaneClosedException(RecordPlaneException):
    """Record plane is closed."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(*kargs, code=SandwichTunnelProto.RECORDERROR_CLOSED, **kwargs)

RecordPlaneException

Bases: SandwichException

Exception base class for the record plane. Errors are defined in the protobuf, enum RecordError. This exception handles the following cases: * HANDSHAKESTATE_IN_PROGRESS * HANDSHAKESTATE_WANT_READ * HANDSHAKESTATE_WANT_WRITE * HANDSHAKESTATE_ERROR

Source code in pysandwich/errors.py
class RecordPlaneException(SandwichException):
    """Exception base class for the record plane.
    Errors are defined in the protobuf, `enum RecordError`.
    This exception handles the following cases:
        * HANDSHAKESTATE_IN_PROGRESS
        * HANDSHAKESTATE_WANT_READ
        * HANDSHAKESTATE_WANT_WRITE
        * HANDSHAKESTATE_ERROR
    """

    """The no-error error."""
    ERROR_OK = SandwichTunnelProto.RECORDERROR_OK

    """Map from the protobuf enum 'RecordError" to error string and subclass exception."""
    _ERRORS_MAP = {
        SandwichTunnelProto.RECORDERROR_WANT_READ: {
            "msg": (
                "Tunnel wants to read data, but the underlying "
                "I/O interface is non-blocking."
            ),
            "cls": lambda: RecordPlaneWantReadException,
        },
        SandwichTunnelProto.RECORDERROR_WANT_WRITE: {
            "msg": (
                "Tunnel wants to write data, but the underlying "
                "I/O interface is non-blocking."
            ),
            "cls": lambda: RecordPlaneWantWriteException,
        },
        SandwichTunnelProto.RECORDERROR_BEING_SHUTDOWN: {
            "msg": "Tunnel is being closed",
            "cls": lambda: RecordPlaneBeingShutdownException,
        },
        SandwichTunnelProto.RECORDERROR_CLOSED: {
            "msg": "Tunnel is closed.",
            "cls": lambda: RecordPlaneClosedException,
        },
        SandwichTunnelProto.RECORDERROR_UNKNOWN: {
            "msg": "An unknown error occurred.",
            "cls": lambda: RecordPlaneUnknownErrorException,
        },
    }

ERROR_OK = SandwichTunnelProto.RECORDERROR_OK class-attribute instance-attribute

Map from the protobuf enum 'RecordError" to error string and subclass exception.

RecordPlaneUnknownErrorException

Bases: RecordPlaneException

An unknown error occurred.

Source code in pysandwich/errors.py
class RecordPlaneUnknownErrorException(RecordPlaneException):
    """An unknown error occurred."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(*kargs, code=SandwichTunnelProto.RECORDERROR_UNKNOWN, **kwargs)

RecordPlaneWantReadException

Bases: RecordPlaneException

Record plane wants to read.

Source code in pysandwich/errors.py
class RecordPlaneWantReadException(RecordPlaneException):
    """Record plane wants to read."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(
            *kargs, code=SandwichTunnelProto.RECORDERROR_WANT_READ, **kwargs
        )

RecordPlaneWantWriteException

Bases: RecordPlaneException

Record plane wants to write.

Source code in pysandwich/errors.py
class RecordPlaneWantWriteException(RecordPlaneException):
    """Record plane wants to write."""

    def __init__(self, *kargs, **kwargs):
        super().__init__(
            *kargs, code=SandwichTunnelProto.RECORDERROR_WANT_WRITE, **kwargs
        )