# This file is part of the SlipLib project which is released under the MIT license.
# See https://github.com/rhjdjong/SlipLib for details.
#
# The MIT License (MIT)
#
# Copyright (c) 2015 Ruud de Jong
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import re
END = b"\xc0"
ESC = b"\xdb"
ESC_END = b"\xdc"
ESC_ESC = b"\xdd"
END_END = b"\xc0\xc0"
"""These constants represent the special SLIP bytes"""
class ProtocolError(ValueError):
"""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 :const:`ESC` byte followed
by any byte that is not an :const:`ESC_ESC` or :const:`ESC_END` byte,
or a trailing :const:`ESC` byte as last byte of the packet.
The :exc:`ProtocolError` carries the invalid packet
as the first (and only) element in in its :attr:`args` tuple.
"""
def encode(msg: bytes) -> bytes:
"""Encodes a message (a byte sequence) into a SLIP-encoded packet.
Args:
msg: The message that must be encoded
Returns:
The SLIP-encoded message
"""
if msg:
msg = bytes(msg)
else:
msg = b""
return END + msg.replace(ESC, ESC + ESC_ESC).replace(END, ESC + ESC_END) + END
def decode(packet: bytes) -> bytes:
"""Retrieves the message from the SLIP-encoded packet.
Args:
packet: The SLIP-encoded message.
Note that this must be exactly one complete packet.
The :func:`decode` function does not provide any buffering
for incomplete packages, nor does it provide support
for decoding data with multiple packets.
Returns:
The decoded message
Raises:
ProtocolError: if the packet contains an invalid byte sequence.
"""
if not is_valid(packet):
raise ProtocolError(packet)
return packet.strip(END).replace(ESC + ESC_END, END).replace(ESC + ESC_ESC, ESC)
def is_valid(packet: bytes) -> bool:
"""Indicates if the packet's contents conform to the SLIP specification.
A packet is valid if:
* It contains no :const:`END` bytes other than leading and/or trailing :const:`END` bytes, and
* Each :const:`ESC` byte is followed by either an :const:`ESC_END` or an :const:`ESC_ESC` byte.
Args:
packet: The packet to inspect.
Returns:
:const:`True` if the packet is valid, :const:`False` otherwise
"""
packet = packet.strip(END)
return not (
END in packet
or packet.endswith(ESC)
or re.search(ESC + b"[^" + ESC_END + ESC_ESC + b"]", packet)
)