Module Contents

Introduction

The sliplib module implements the encoding and decoding functionality for SLIP packets, as described in RFC 1055. It defines encoding, decoding, and validation functions, as well as various classes that can be used to to wrap the SLIP protocol over different kinds of byte streams.

The SLIP protocol is described in RFC 1055 (A Nonstandard for Transmission of IP Datagrams over Serial Lines: SLIP, J. Romkey, June 1988). The original purpose of the protocol is to provide a mechanism to indicate the boundaries of IP packets, in particular when the IP packets are sent over a connection that does not provide a framing mechanism, such as serial lines or dial-up connections.

There is, however, nothing specific to IP in the SLIP protocol. The protocol describes a generic framing method that can be used for any type of data that must be transmitted over a (continuous) byte stream. In fact, the main reason for creating this module was the need to communicate with a third-party application that used SLIP over TCP (which is a continuous byte stream) to frame variable length data structures.

The SLIP protocol uses four special byte values:

Byte value

Name

Purpose

0xc0

END

to delimit messages

0xdb

ESC

to escape END or ESC bytes in the message

0xdc

ESC_END

the escaped value of the END byte

0xdd

ESC_ESC

the escaped value of the ESC byte

An END byte in the message is encoded as the sequence ESC+ESC_END (b'xdbxdc') in the slip packet, and an ESC byte in the message is encoded as the sequence ESC+ESC_ESC (b'xdbxdd').

Decoded

Encoded

b'xc0'

b'xdbxdc'

b'xdb'

b'xdbxdd'

As a consequence, an ESC byte in an encoded SLIP packet must always be followed by an ESC_END or an ESC_ESC byte; anything else is a protocol error.

Low level Usage

Constants

The following constants represent the special bytes used by SLIP for delimiting and encoding messages.

END = b'\xc0'

The SLIP END byte.

ESC = b'\xdb'

The SLIP ESC byte.

ESC_END = b'\xdc'

The SLIP byte that, when preceded by an ESC byte, represents an escaped END byte.

ESC_ESC = b'\xdd'

The SLIP byte that, when preceded by an ESC byte, represents an escaped ESC byte.

Functions

The following are lower-level functions, that should normally not be used directly.

encode(msg)[source]

Encodes a message (a byte sequence) into a SLIP-encoded packet.

Parameters:

msg (bytes) – The message that must be encoded

Return type:

bytes

Returns:

The SLIP-encoded message

decode(packet)[source]

Retrieves the message from the SLIP-encoded packet.

Parameters:

packet (bytes) – The SLIP-encoded message. Note that this must be exactly one complete packet. The decode() function does not provide any buffering for incomplete packages, nor does it provide support for decoding data with multiple packets.

Return type:

bytes

Returns:

The decoded message

Raises:

ProtocolError – if the packet contains an invalid byte sequence.

is_valid(packet)[source]

Indicates if the packet’s contents conform to the SLIP specification.

A packet is valid if:

  • It contains no END bytes other than leading and/or trailing END bytes, and

  • Each ESC byte is followed by either an ESC_END or an ESC_ESC byte.

Parameters:

packet (bytes) – The packet to inspect.

Return type:

bool

Returns:

True if the packet is valid, False otherwise

Classes

class Driver[source]

Class to handle the SLIP-encoding and decoding of messages

This class manages the handling of encoding and decoding of messages according to the SLIP protocol.

Class Driver offers the following methods:

send(message)[source]

Encodes a message into a SLIP-encoded packet.

The message can be any arbitrary byte sequence.

Parameters:

message (bytes) – The message that must be encoded.

Return type:

bytes

Returns:

A packet with the SLIP-encoded message.

get(*, block=True, timeout=None)[source]

Get the next decoded message.

Remove and decode a SLIP packet from the internal buffer, and return the resulting message. If block is True and timeout is None`(the default), then this method blocks until a message is available. If `timeout is a positive number, the blocking will last for at most timeout seconds, and the method will return None if no message became available in that time. If block is False the method returns immediately with either a message or None.

Return type:

Optional[bytes]

Returns:

A decoded SLIP message, or an empty bytestring b”” if no further message will come available.

Raises:

ProtocolError – When the packet that contained the message had an invalid byte sequence.

Parameters:
  • block (bool) –

  • timeout (float | None) –

New in version 0.7.

receive(data)[source]

Decodes data to extract the SLIP-encoded messages.

Processes data, which must be a bytes-like object, and extracts and buffers the SLIP messages contained therein.

A non-terminated SLIP packet in data is also buffered, and processed with the next call to receive().

Parameters:

data (Union[bytes, int]) –

A bytes-like object to be processed.

An empty data parameter indicates that no more data will follow.

To accommodate iteration over byte sequences, an integer in the range(0, 256) is also accepted.

Return type:

None

Returns:

None.

Changed in version 0.7: receive() no longer returns a list of decoded messages.

High Level Usage

SlipWrapper

ByteStream = TypeVar(ByteStream)

Invariant TypeVar.

ByteStream is a TypeVar that stands for a generic byte stream.

class SlipWrapper(stream)[source]

Bases: Generic[ByteStream]

Base class that provides a message based interface to a byte stream

SlipWrapper combines a Driver instance with a (generic) byte stream. The SlipWrapper class is an abstract base class. It offers the methods send_msg() and recv_msg() to send and receive single messages over the byte stream, but it does not of itself provide the means to interact with the stream.

To interact with a concrete stream, a derived class must implement the methods send_bytes() and recv_bytes() to write to and read from the stream.

A SlipWrapper instance can be iterated over. Each iteration will provide the next message that is received from the byte stream.

Changed in version 0.5: Allow iteration over a SlipWrapper instance.

To instantiate a SlipWrapper, the user must provide an existing byte stream

Parameters:

stream (~ByteStream) – The byte stream that will be wrapped.

Class SlipWrapper offers the following methods and attributes:

recv_msg()[source]

Receive a single message from the stream.

Returns:

A SLIP-decoded message

Return type:

bytes

Raises:

ProtocolError – when a SLIP protocol error has been encountered. A subsequent call to recv_msg() (after handling the exception) will return the message from the next packet.

send_msg(message)[source]

Send a SLIP-encoded message over the stream.

Parameters:

message (bytes) – The message to encode and send

Return type:

None

driver

The SlipWrapper’s Driver instance.

stream

The wrapped ByteStream.

In addition, SlipWrapper requires that derived classes implement the following methods:

recv_bytes()[source]

Receive data from the stream.

Derived classes must implement this method.

Note

The convention used within the SlipWrapper class is that recv_bytes() returns an empty bytes object to indicate that the end of the byte stream has been reached and no further data will be received. Derived implementations must ensure that this convention is followed.

Return type:

bytes

Returns:

The bytes received from the stream

send_bytes(packet)[source]

Send a packet over the stream.

Derived classes must implement this method.

Parameters:

packet (bytes) – the packet to send over the stream

Return type:

None

SlipStream

protocol IOStream[source]

Bases: Protocol

Protocol class for wrappable byte streams.

Any object that produces and consumes a byte stream and contains the two required methods can be used. Typically, an IOStream is a subclass of io.RawIOBase, io.BufferedIOBase, io.FileIO, or similar classes, but this is not required.

Classes that implement this protocol must have the following methods / attributes:

read(chunksize)[source]

Read chunksize bytes from the stream

Parameters:

chunksize (int) – The number of bytes to read from the IOStream.

Return type:

bytes

Returns:

The bytes read from the IOStream. May be less than the number specified by chunksize.

write(data)[source]

Write data to the stream.

Parameters:

data (bytes) – The bytes to write on to IOStream.

Return type:

int

Returns:

The number of bytes actually written. This may be less than the number of bytes contained in data.

class SlipStream(stream, chunk_size=8192)[source]

Bases: SlipWrapper[IOStream]

Class that wraps an IO stream with a Driver

SlipStream combines a Driver instance with a concrete byte stream. The byte stream must support the methods read() and write(). To avoid conflicts and ambiguities caused by different newline conventions, streams that have an encoding attribute (such as io.StringIO objects, or text files that are not opened in binary mode) are not accepted as a byte stream.

The SlipStream class has all the methods and attributes from its base class SlipWrapper. In addition, it directly exposes all methods and attributes of the contained stream, except for the following:

  • read*() and write*(). These methods are not supported, because byte-oriented read and write operations would invalidate the internal state maintained by SlipStream.

  • Similarly, seek(), tell(), and truncate() are not supported, because repositioning or truncating the stream would invalidate the internal state.

  • raw(), detach() and other methods that provide access to or manipulate the stream’s internal data.

Instead of the read*() and write*() methods a SlipStream object provides the method recv_msg() and send_msg() to read and write SLIP-encoded messages.

Deprecated since version 0.6: Direct access to the methods and attributes of the contained stream will be removed in version 1.0

To instantiate a SlipStream object, the user must provide a pre-constructed open byte stream that is ready for reading and/or writing

Parameters:
  • stream (IOStream) – The byte stream that will be wrapped.

  • chunk_size (int) –

    The number of bytes to read per read operation. The default value for chunck_size is io.DEFAULT_BUFFER_SIZE.

    Setting the chunk_size is useful when the stream has a low bandwidth and/or bursty data (e.g. a serial port interface). In such cases it is useful to have a chunk_size of 1, to avoid that the application hangs or becomes unresponsive.

New in version 0.6: The chunk_size parameter.

A SlipStream instance can e.g. be useful to read slip-encoded messages from a file:

with open('/path/to/a/slip/encoded/file', mode='rb') as f:
    slip_file = SlipStream(f)
    for msg in slip_file:
        # Do something with the message

A SlipStream instance has the following attributes in addition to the attributes offered by its base class SlipWrapper:

chunk_size

The number of bytes to read during each read operation.

readable

Type:    property

Indicates if the wrapped stream is readable. The value is True if the readability of the wrapped stream cannot be determined.

Return type:

bool

writable

Type:    property

Indicates if the wrapped stream is writable. The value is True if the writabilty of the wrapped stream cannot be determined.

Return type:

bool

SlipSocket

TCPAddress(*args, **kwargs)

TCPAddress stands for either an IPv4 address, i.e. a (host, port) tuple, or an IPv6 address, i.e. a (host, port, flowinfo, scope_id) tuple.

alias of Union[Tuple[str, int], Tuple[str, int, int, int]]

class SlipSocket(sock)[source]

Bases: SlipWrapper[socket]

Class that wraps a TCP socket with a Driver

SlipSocket combines a Driver instance with a socket. The SlipSocket class has all the methods from its base class SlipWrapper. In addition it directly exposes all methods and attributes of the contained socket, except for the following:

  • send*() and recv*(). These methods are not supported, because byte-oriented send and receive operations would invalidate the internal state maintained by SlipSocket.

  • Similarly, makefile() is not supported, because byte- or line-oriented read and write operations would invalidate the internal state.

  • share() (Windows only) and dup(). The internal state of the SlipSocket would have to be duplicated and shared to make these methods meaningful. Because of the lack of a convincing use case for this, sharing and duplication is not supported.

  • The accept() method is delegated to the contained socket, but the socket that is returned by the socket’s accept() method is automatically wrapped in a SlipSocket object.

Instead of the socket’s send*() and recv*() methods a SlipSocket provides the method send_msg() and recv_msg() to send and receive SLIP-encoded messages.

Deprecated since version 0.6: Direct access to the methods and attributes of the contained socket other than family, type, and proto will be removed in version 1.0

Only TCP sockets are supported. Using the SLIP protocol on UDP sockets is not supported for the following reasons:

  • UDP is datagram-based. Using SLIP with UDP therefore introduces ambiguity: should SLIP packets be allowed to span multiple UDP datagrams or not?

  • UDP does not guarantee delivery, and does not guarantee that datagrams are delivered in the correct order.

To instantiate a SlipSocket, the user must provide a pre-constructed TCP socket. An alternative way to instantiate s SlipSocket is to use the class method create_connection().

Parameters:

sock (socket.socket) – An existing TCP socket, i.e. a socket with type socket.SOCK_STREAM

Class SlipSocket offers the following methods in addition to the methods offered by its base class SlipWrapper:

accept()[source]

Accepts an incoming connection.

Returns:

A tuple with a SlipSocket object and the remote IP address.

Return type:

(SlipSocket, TCPAddress)

classmethod create_connection(address, timeout=None, source_address=None)[source]

Create a SlipSocket connection.

This convenience method creates a connection to a socket at the specified address using the socket.create_connection() function. The socket that is returned from that call is automatically wrapped in a SlipSocket object.

Parameters:
  • address (TCPAddress) – The remote address.

  • timeout (float) – Optional timeout value.

  • source_address (TCPAddress) – Optional local address for the near socket.

Returns:

A SlipSocket that is connected to the socket at the remote address.

Return type:

SlipSocket

Note

The accept() and create_connection() methods do not magically turn the socket at the remote address into a SlipSocket. For the connection to work properly, the remote socket must already have been configured to use the SLIP protocol.

The following commonly used socket.socket methods are exposed through a SlipSocket object. These methods are simply delegated to the wrapped socket instance. See the documentation for socket.socket for more information on these methods.

bind(address)[source]

Bind the SlipSocket to address.

Parameters:

address (TCPAddress) – The address to bind to.

Return type:

None

close()[source]

Close the SlipSocket.

Return type:

None

connect(address)[source]

Connect SlipSocket to a remote socket at address.

Parameters:

address (TCPAddress) – The IP address of the remote socket.

Return type:

None

connect_ex(address)[source]

Connect SlipSocket to a remote socket at address.

Parameters:

address (TCPAddress) – The IP address of the remote socket.

Return type:

None

fileno()[source]

Get the socket’s file descriptor.

Return type:

int

Returns:

The wrapped socket’s file descriptor, or -1 on failure.

getpeername()[source]

Get the IP address of the remote socket to which SlipSocket is connected.

Returns:

The remote IP address.

Return type:

TCPAddress

getsockname()[source]

Get SlipSocket’s own address.

Returns:

The local IP address.

Return type:

TCPAddress

getsockopt(*args)[source]

Get the socket option from the embedded socket.

Return type:

Union[int, bytes]

Returns:

The integer or bytes representing the value of the socket option.

Parameters:

args (Any) –

listen(backlog=None)[source]

Enable a SlipSocket server to accept connections.

Parameters:

backlog (int) – The maximum number of waiting connections.

Return type:

None

setsockopt(*args)[source]

Get the socket option from the embedded socket.

Return type:

None

Returns:

The integer or bytes representing the value of the socket option.

Parameters:

args (Any) –

shutdown(how)[source]

Shutdown the connection.

Parameters:

how (int) – Flag to indicate which halves of the connection must be shut down.

Return type:

None

Since the wrapped socket is available as the socket attribute, any other socket.socket method can be invoked through that attribute.

Warning

Avoid using socket.socket methods that affect the bytes that are sent or received through the socket. Doing so will invalidate the internal state of the enclosed Driver instance, resulting in corrupted SLIP messages. In particular, do not use any of the recv*() or send*() methods on the socket attribute.

A SlipSocket instance has the following attributes and read-only properties in addition to the attributes offered by its base class SlipWrapper:

socket

The wrapped socket. This is actually just an alias for the stream attribute in the base class.

family

Type:    property

The wrapped socket’s address family.

Usually socket.AF_INET (IPv4) or socket.AF_INET6 (IPv6).

Return type:

int

type

Type:    property

The wrapped socket’s type.

Always socket.SOCK_STREAM.

Return type:

int

proto

Type:    property

The wrapped socket’s protocol number.

Usually 0.

Return type:

int

SlipRequestHandler

class SlipRequestHandler(request, client_address, server)[source]

Bases: BaseRequestHandler

Base class for request handlers for SLIP-based communication

This class is derived from socketserver.BaseRequestHandler for the purpose of creating TCP server instances that can handle incoming SLIP-based connections.

To do anything useful, a derived class must define a new handle() method that uses self.request to send and receive SLIP-encoded messages.

Other methods can of course also be overridden if necessary.

Initializes the request handler.

The type of the request parameter depends on the type of server that instantiates the request handler. If the server is a SlipServer, then request is a SlipSocket. Otherwise, it is a regular socket, and must be wrapped in a SlipSocket before it can be used.

Parameters:
handle()[source]

Services the request. Must be defined by a derived class.

Note that in general it does not make sense to use a SlipRequestHandler object to handle a single transmission, as is e.g. common with HTTP. The purpose of the SLIP protocol is to allow separation of messages in a continuous byte stream. As such, it is expected that the handle() method of a derived class is capable of handling multiple SLIP messages, for example:

def handle(self):
    while True:
        msg = self.request.recv_msg()
        if msg == b"":
            break
        # Do something with the message
Return type:

None

finish()[source]

Performs any cleanup actions.

The default implementation does nothing.

Return type:

None

class SlipServer(server_address, handler_class, bind_and_activate=True)[source]

Bases: TCPServer

Base class for SlipSocket based server classes.

This is a convenience class, that offers a minor enhancement over the regular TCPServer from the standard library. The class TCPServer is hardcoded to use only IPv4 addresses. It must be subclassed in order to use IPv6 addresses. The SlipServer class uses the address that is provided during instantiation to determine if it must muse an IPv4 or IPv6 socket.

Parameters:
  • server_address (Union[Tuple[str, int], Tuple[str, int, int, int]]) – The address on which the server listens

  • handler_class (type[SlipRequestHandler]) – The class that will be instantiated to handle an incoming request

  • bind_and_activate (bool) – Flag to indicate if the server must be bound and activated at creation time.

Exceptions

exception ProtocolError[source]

Exception to indicate that a SLIP protocol error has occurred.

This exception is raised when an attempt is made to decode a packet with an invalid byte sequence. An invalid byte sequence is either an ESC byte followed by any byte that is not an ESC_ESC or ESC_END byte, or a trailing ESC byte as last byte of the packet.

The ProtocolError carries the invalid packet as the first (and only) element in in its args tuple.