././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1752973078.7661269 cobs-1.2.2/0000777000000000000000000000000015037037427007404 5ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/LICENSE.txt0000666000000000000000000000227414716562427011242 0ustar00---------------------------------------------------------------------------- Copyright (c) 2010 Craig McQueen 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. ---------------------------------------------------------------------------- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/MANIFEST.in0000666000000000000000000000004614716562427011150 0ustar00include *.txt recursive-include src * ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1752973078.7592504 cobs-1.2.2/PKG-INFO0000666000000000000000000002734215037037427010511 0ustar00Metadata-Version: 2.4 Name: cobs Version: 1.2.2 Summary: Consistent Overhead Byte Stuffing (COBS) Author-email: Craig McQueen License: ---------------------------------------------------------------------------- Copyright (c) 2010 Craig McQueen 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. ---------------------------------------------------------------------------- Project-URL: Homepage, https://github.com/cmcqueen/cobs-python Project-URL: Bug Tracker, https://github.com/cmcqueen/cobs-python/issues Keywords: byte stuffing Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Communications Requires-Python: >=3.6 Description-Content-Type: text/x-rst License-File: LICENSE.txt Dynamic: license-file ======================================== Consistent Overhead Byte Stuffing (COBS) ======================================== :Author: Craig McQueen :Contact: http://craig.mcqueen.id.au/ :Copyright: 2010 Craig McQueen Python functions for encoding and decoding COBS. ----- Intro ----- The ``cobs`` package is provided, which contains modules containing functions for encoding and decoding according to COBS methods. What Is COBS? ````````````` COBS is a method of encoding a packet of bytes into a form that contains no bytes with value zero (0x00). The input packet of bytes can contain bytes in the full range of 0x00 to 0xFF. The COBS encoded packet is guaranteed to generate packets with bytes only in the range 0x01 to 0xFF. Thus, in a communication protocol, packet boundaries can be reliably delimited with 0x00 bytes. The COBS encoding does have to increase the packet size to achieve this encoding. However, compared to other byte-stuffing methods, the packet size increase is reasonable and predictable. COBS always adds 1 byte to the message length. Additionally, for longer packets of length *n*, it *may* add n/254 (rounded down) additional bytes to the encoded packet size. For example, compare to the PPP protocol, which uses 0x7E bytes to delimit PPP packets. The PPP protocol uses an "escape" style of byte stuffing, replacing all occurences of 0x7E bytes in the packet with 0x7D 0x5E. But that byte-stuffing method can potentially double the size of the packet in the worst case. COBS uses a different method for byte-stuffing, which has a much more reasonable worst-case overhead. For more details about COBS, see the references [#ieeeton]_ [#ppp]_. I have included a variant on COBS, `COBS/R`_, which slightly modifies COBS to often avoid the +1 byte overhead of COBS. So in many cases, especially for smaller packets, the size of a COBS/R encoded packet is the same size as the original packet. See below for more details about `COBS/R`_. References `````````` .. [#ieeeton] | `Consistent Overhead Byte Stuffing`__ | Stuart Cheshire and Mary Baker | IEEE/ACM Transations on Networking, Vol. 7, No. 2, April 1999 .. __: .. _Consistent Overhead Byte Stuffing (for IEEE): http://www.stuartcheshire.org/papers/COBSforToN.pdf .. [#ppp] | `PPP Consistent Overhead Byte Stuffing (COBS)`_ | PPP Working Group Internet Draft | James Carlson, IronBridge Networks | Stuart Cheshire and Mary Baker, Stanford University | November 1997 .. _PPP Consistent Overhead Byte Stuffing (COBS): http://tools.ietf.org/html/draft-ietf-pppext-cobs-00 ---------------- Modules Provided ---------------- ================== ================== =============================================================== Module Short Name Long Name ================== ================== =============================================================== ``cobs.cobs`` COBS Consistent Overhead Byte Stuffing (basic method) [#ieeeton]_ ``cobs.cobsr`` `COBS/R`_ `Consistent Overhead Byte Stuffing--Reduced`_ ================== ================== =============================================================== "`Consistent Overhead Byte Stuffing--Reduced`_" (`COBS/R`_) is my own invention, a modification of basic COBS encoding, and is described in more detail below. The following are not implemented: ================== ====================================================================== Short Name Long Name ================== ====================================================================== COBS/ZPE Consistent Overhead Byte Stuffing--Zero Pair Elimination [#ieeeton]_ COBS/ZRE Consistent Overhead Byte Stuffing--Zero Run Elimination [#ppp]_ ================== ====================================================================== A pure Python implementation and a C extension implementation are provided. If the C extension is not available for some reason, the pure Python version will be used. ----- Usage ----- The modules provide an ``encode`` and a ``decode`` function. The input should be a byte string, not a Unicode string. Basic usage:: >>> from cobs import cobs >>> encoded = cobs.encode(b'Hello world\x00This is a test') >>> encoded b'\x0cHello world\x0fThis is a test' >>> cobs.decode(encoded) b'Hello world\x00This is a test' `COBS/R`_ usage is almost identical:: >>> from cobs import cobsr >>> encoded = cobsr.encode(b'Hello world\x00This is a test') >>> encoded b'\x0cHello worldtThis is a tes' >>> cobsr.decode(encoded) b'Hello world\x00This is a test' Any type that implements the buffer protocol, providing a single block of bytes, is also acceptable as input:: >>> from cobs import cobs >>> encoded = cobs.encode(bytearray(b'Hello world\x00This is a test')) >>> encoded b'\x0cHello world\x0fThis is a test' >>> cobs.decode(encoded) b'Hello world\x00This is a test' Note that the ``encode`` function does not add any framing ``0x00`` byte at the end (or beginning) of the encoded data. Similarly, the ``decode`` function does not strip or split on any framing ``0x00`` bytes, but treats any ``0x00`` bytes as a data input error. The details of data framing is application-specific, so it is the user's application's responsibility to implement the framing and deframing that is suitable for the needs of the application. ------------------------- Supported Python Versions ------------------------- Python >= 3.10 are supported, and have both a C extension and a pure Python implementation. Python versions < 3.10 might work, but have not been tested. ------------ Installation ------------ The cobs package is installed using ``distutils``. If you have the tools installed to build a Python extension module, run the following command:: python setup.py install If you cannot build the C extension, you may install just the pure Python implementation, using the following command:: python setup.py build_py install --skip-build ------------ Unit Testing ------------ Basic unit testing is in the ``test`` sub-module, e.g. ``cobs.cobs.test``. To run it:: python -m cobs.cobs.test python -m cobs.cobsr.test ------------- Documentation ------------- Documentation is written with Sphinx. Source files are provided in the ``doc`` directory. It can be built using Sphinx 0.6.5. It uses the ``pngmath`` Sphinx extension, which requires Latex and ``dvipng`` to be installed. The documentation is available online at: http://packages.python.org/cobs/ ------- License ------- The code is released under the MIT license. See LICENSE.txt for details. .. _COBS/R: .. _Consistent Overhead Byte Stuffing--Reduced: --------------------------------------------------- Consistent Overhead Byte Stuffing--Reduced (COBS/R) --------------------------------------------------- A modification of COBS, which I'm calling "Consistent Overhead Byte Stuffing--Reduced" (COBS/R), is provided in the ``cobs.cobsr`` module. Its purpose is to save one byte from the encoded form in some cases. Plain COBS encoding always has a +1 byte encoding overhead. See the references for details [#ieeeton]_. COBS/R can often avoid the +1 byte, which can be a useful savings if it is mostly small messages that are being encoded. In plain COBS, the last length code byte in the message has some inherent redundancy: if it is greater than the number of remaining bytes, this is detected as an error. In COBS/R, instead we opportunistically replace the final length code byte with the final data byte, whenever the value of the final data byte is greater than or equal to what the final length value would normally be. This variation can be unambiguously decoded: the decoder notices that the length code is greater than the number of remaining bytes. Examples ```````` The byte values in the examples are in hex. First example: Input: ====== ====== ====== ====== ====== ====== 2F A2 00 92 73 02 ====== ====== ====== ====== ====== ====== This example is encoded the same in COBS and COBS/R. Encoded (length code bytes are bold): ====== ====== ====== ====== ====== ====== ====== **03** 2F A2 **04** 92 73 02 ====== ====== ====== ====== ====== ====== ====== Second example: The second example is almost the same, except the final data byte value is greater than what the length byte would be. Input: ====== ====== ====== ====== ====== ====== 2F A2 00 92 73 26 ====== ====== ====== ====== ====== ====== Encoded in plain COBS (length code bytes are bold): ====== ====== ====== ====== ====== ====== ====== **03** 2F A2 **04** 92 73 26 ====== ====== ====== ====== ====== ====== ====== Encoded in COBS/R: ====== ====== ====== ====== ====== ====== **03** 2F A2 **26** 92 73 ====== ====== ====== ====== ====== ====== Because the last data byte (**26**) is greater than the usual length code (**04**), the last data byte can be inserted in place of the length code, and removed from the end of the sequence. This avoids the usual +1 byte overhead of the COBS encoding. The decoder detects this variation on the encoding simply by detecting that the length code is greater than the number of remaining bytes. That situation would be a decoding error in regular COBS, but in COBS/R it is used to save one byte in the encoded message. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/README.rst0000666000000000000000000002264114716562427011106 0ustar00======================================== Consistent Overhead Byte Stuffing (COBS) ======================================== :Author: Craig McQueen :Contact: http://craig.mcqueen.id.au/ :Copyright: 2010 Craig McQueen Python functions for encoding and decoding COBS. ----- Intro ----- The ``cobs`` package is provided, which contains modules containing functions for encoding and decoding according to COBS methods. What Is COBS? ````````````` COBS is a method of encoding a packet of bytes into a form that contains no bytes with value zero (0x00). The input packet of bytes can contain bytes in the full range of 0x00 to 0xFF. The COBS encoded packet is guaranteed to generate packets with bytes only in the range 0x01 to 0xFF. Thus, in a communication protocol, packet boundaries can be reliably delimited with 0x00 bytes. The COBS encoding does have to increase the packet size to achieve this encoding. However, compared to other byte-stuffing methods, the packet size increase is reasonable and predictable. COBS always adds 1 byte to the message length. Additionally, for longer packets of length *n*, it *may* add n/254 (rounded down) additional bytes to the encoded packet size. For example, compare to the PPP protocol, which uses 0x7E bytes to delimit PPP packets. The PPP protocol uses an "escape" style of byte stuffing, replacing all occurences of 0x7E bytes in the packet with 0x7D 0x5E. But that byte-stuffing method can potentially double the size of the packet in the worst case. COBS uses a different method for byte-stuffing, which has a much more reasonable worst-case overhead. For more details about COBS, see the references [#ieeeton]_ [#ppp]_. I have included a variant on COBS, `COBS/R`_, which slightly modifies COBS to often avoid the +1 byte overhead of COBS. So in many cases, especially for smaller packets, the size of a COBS/R encoded packet is the same size as the original packet. See below for more details about `COBS/R`_. References `````````` .. [#ieeeton] | `Consistent Overhead Byte Stuffing`__ | Stuart Cheshire and Mary Baker | IEEE/ACM Transations on Networking, Vol. 7, No. 2, April 1999 .. __: .. _Consistent Overhead Byte Stuffing (for IEEE): http://www.stuartcheshire.org/papers/COBSforToN.pdf .. [#ppp] | `PPP Consistent Overhead Byte Stuffing (COBS)`_ | PPP Working Group Internet Draft | James Carlson, IronBridge Networks | Stuart Cheshire and Mary Baker, Stanford University | November 1997 .. _PPP Consistent Overhead Byte Stuffing (COBS): http://tools.ietf.org/html/draft-ietf-pppext-cobs-00 ---------------- Modules Provided ---------------- ================== ================== =============================================================== Module Short Name Long Name ================== ================== =============================================================== ``cobs.cobs`` COBS Consistent Overhead Byte Stuffing (basic method) [#ieeeton]_ ``cobs.cobsr`` `COBS/R`_ `Consistent Overhead Byte Stuffing--Reduced`_ ================== ================== =============================================================== "`Consistent Overhead Byte Stuffing--Reduced`_" (`COBS/R`_) is my own invention, a modification of basic COBS encoding, and is described in more detail below. The following are not implemented: ================== ====================================================================== Short Name Long Name ================== ====================================================================== COBS/ZPE Consistent Overhead Byte Stuffing--Zero Pair Elimination [#ieeeton]_ COBS/ZRE Consistent Overhead Byte Stuffing--Zero Run Elimination [#ppp]_ ================== ====================================================================== A pure Python implementation and a C extension implementation are provided. If the C extension is not available for some reason, the pure Python version will be used. ----- Usage ----- The modules provide an ``encode`` and a ``decode`` function. The input should be a byte string, not a Unicode string. Basic usage:: >>> from cobs import cobs >>> encoded = cobs.encode(b'Hello world\x00This is a test') >>> encoded b'\x0cHello world\x0fThis is a test' >>> cobs.decode(encoded) b'Hello world\x00This is a test' `COBS/R`_ usage is almost identical:: >>> from cobs import cobsr >>> encoded = cobsr.encode(b'Hello world\x00This is a test') >>> encoded b'\x0cHello worldtThis is a tes' >>> cobsr.decode(encoded) b'Hello world\x00This is a test' Any type that implements the buffer protocol, providing a single block of bytes, is also acceptable as input:: >>> from cobs import cobs >>> encoded = cobs.encode(bytearray(b'Hello world\x00This is a test')) >>> encoded b'\x0cHello world\x0fThis is a test' >>> cobs.decode(encoded) b'Hello world\x00This is a test' Note that the ``encode`` function does not add any framing ``0x00`` byte at the end (or beginning) of the encoded data. Similarly, the ``decode`` function does not strip or split on any framing ``0x00`` bytes, but treats any ``0x00`` bytes as a data input error. The details of data framing is application-specific, so it is the user's application's responsibility to implement the framing and deframing that is suitable for the needs of the application. ------------------------- Supported Python Versions ------------------------- Python >= 3.10 are supported, and have both a C extension and a pure Python implementation. Python versions < 3.10 might work, but have not been tested. ------------ Installation ------------ The cobs package is installed using ``distutils``. If you have the tools installed to build a Python extension module, run the following command:: python setup.py install If you cannot build the C extension, you may install just the pure Python implementation, using the following command:: python setup.py build_py install --skip-build ------------ Unit Testing ------------ Basic unit testing is in the ``test`` sub-module, e.g. ``cobs.cobs.test``. To run it:: python -m cobs.cobs.test python -m cobs.cobsr.test ------------- Documentation ------------- Documentation is written with Sphinx. Source files are provided in the ``doc`` directory. It can be built using Sphinx 0.6.5. It uses the ``pngmath`` Sphinx extension, which requires Latex and ``dvipng`` to be installed. The documentation is available online at: http://packages.python.org/cobs/ ------- License ------- The code is released under the MIT license. See LICENSE.txt for details. .. _COBS/R: .. _Consistent Overhead Byte Stuffing--Reduced: --------------------------------------------------- Consistent Overhead Byte Stuffing--Reduced (COBS/R) --------------------------------------------------- A modification of COBS, which I'm calling "Consistent Overhead Byte Stuffing--Reduced" (COBS/R), is provided in the ``cobs.cobsr`` module. Its purpose is to save one byte from the encoded form in some cases. Plain COBS encoding always has a +1 byte encoding overhead. See the references for details [#ieeeton]_. COBS/R can often avoid the +1 byte, which can be a useful savings if it is mostly small messages that are being encoded. In plain COBS, the last length code byte in the message has some inherent redundancy: if it is greater than the number of remaining bytes, this is detected as an error. In COBS/R, instead we opportunistically replace the final length code byte with the final data byte, whenever the value of the final data byte is greater than or equal to what the final length value would normally be. This variation can be unambiguously decoded: the decoder notices that the length code is greater than the number of remaining bytes. Examples ```````` The byte values in the examples are in hex. First example: Input: ====== ====== ====== ====== ====== ====== 2F A2 00 92 73 02 ====== ====== ====== ====== ====== ====== This example is encoded the same in COBS and COBS/R. Encoded (length code bytes are bold): ====== ====== ====== ====== ====== ====== ====== **03** 2F A2 **04** 92 73 02 ====== ====== ====== ====== ====== ====== ====== Second example: The second example is almost the same, except the final data byte value is greater than what the length byte would be. Input: ====== ====== ====== ====== ====== ====== 2F A2 00 92 73 26 ====== ====== ====== ====== ====== ====== Encoded in plain COBS (length code bytes are bold): ====== ====== ====== ====== ====== ====== ====== **03** 2F A2 **04** 92 73 26 ====== ====== ====== ====== ====== ====== ====== Encoded in COBS/R: ====== ====== ====== ====== ====== ====== **03** 2F A2 **26** 92 73 ====== ====== ====== ====== ====== ====== Because the last data byte (**26**) is greater than the usual length code (**04**), the last data byte can be inserted in place of the length code, and removed from the end of the sequence. This avoids the usual +1 byte overhead of the COBS encoding. The decoder detects this variation on the encoding simply by detecting that the length code is greater than the number of remaining bytes. That situation would be a decoding error in regular COBS, but in COBS/R it is used to save one byte in the encoded message. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1752973078.7552476 cobs-1.2.2/cobs.egg-info/0000777000000000000000000000000015037037427012024 5ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1752973078.0 cobs-1.2.2/cobs.egg-info/PKG-INFO0000666000000000000000000002734215037037426013130 0ustar00Metadata-Version: 2.4 Name: cobs Version: 1.2.2 Summary: Consistent Overhead Byte Stuffing (COBS) Author-email: Craig McQueen License: ---------------------------------------------------------------------------- Copyright (c) 2010 Craig McQueen 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. ---------------------------------------------------------------------------- Project-URL: Homepage, https://github.com/cmcqueen/cobs-python Project-URL: Bug Tracker, https://github.com/cmcqueen/cobs-python/issues Keywords: byte stuffing Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Communications Requires-Python: >=3.6 Description-Content-Type: text/x-rst License-File: LICENSE.txt Dynamic: license-file ======================================== Consistent Overhead Byte Stuffing (COBS) ======================================== :Author: Craig McQueen :Contact: http://craig.mcqueen.id.au/ :Copyright: 2010 Craig McQueen Python functions for encoding and decoding COBS. ----- Intro ----- The ``cobs`` package is provided, which contains modules containing functions for encoding and decoding according to COBS methods. What Is COBS? ````````````` COBS is a method of encoding a packet of bytes into a form that contains no bytes with value zero (0x00). The input packet of bytes can contain bytes in the full range of 0x00 to 0xFF. The COBS encoded packet is guaranteed to generate packets with bytes only in the range 0x01 to 0xFF. Thus, in a communication protocol, packet boundaries can be reliably delimited with 0x00 bytes. The COBS encoding does have to increase the packet size to achieve this encoding. However, compared to other byte-stuffing methods, the packet size increase is reasonable and predictable. COBS always adds 1 byte to the message length. Additionally, for longer packets of length *n*, it *may* add n/254 (rounded down) additional bytes to the encoded packet size. For example, compare to the PPP protocol, which uses 0x7E bytes to delimit PPP packets. The PPP protocol uses an "escape" style of byte stuffing, replacing all occurences of 0x7E bytes in the packet with 0x7D 0x5E. But that byte-stuffing method can potentially double the size of the packet in the worst case. COBS uses a different method for byte-stuffing, which has a much more reasonable worst-case overhead. For more details about COBS, see the references [#ieeeton]_ [#ppp]_. I have included a variant on COBS, `COBS/R`_, which slightly modifies COBS to often avoid the +1 byte overhead of COBS. So in many cases, especially for smaller packets, the size of a COBS/R encoded packet is the same size as the original packet. See below for more details about `COBS/R`_. References `````````` .. [#ieeeton] | `Consistent Overhead Byte Stuffing`__ | Stuart Cheshire and Mary Baker | IEEE/ACM Transations on Networking, Vol. 7, No. 2, April 1999 .. __: .. _Consistent Overhead Byte Stuffing (for IEEE): http://www.stuartcheshire.org/papers/COBSforToN.pdf .. [#ppp] | `PPP Consistent Overhead Byte Stuffing (COBS)`_ | PPP Working Group Internet Draft | James Carlson, IronBridge Networks | Stuart Cheshire and Mary Baker, Stanford University | November 1997 .. _PPP Consistent Overhead Byte Stuffing (COBS): http://tools.ietf.org/html/draft-ietf-pppext-cobs-00 ---------------- Modules Provided ---------------- ================== ================== =============================================================== Module Short Name Long Name ================== ================== =============================================================== ``cobs.cobs`` COBS Consistent Overhead Byte Stuffing (basic method) [#ieeeton]_ ``cobs.cobsr`` `COBS/R`_ `Consistent Overhead Byte Stuffing--Reduced`_ ================== ================== =============================================================== "`Consistent Overhead Byte Stuffing--Reduced`_" (`COBS/R`_) is my own invention, a modification of basic COBS encoding, and is described in more detail below. The following are not implemented: ================== ====================================================================== Short Name Long Name ================== ====================================================================== COBS/ZPE Consistent Overhead Byte Stuffing--Zero Pair Elimination [#ieeeton]_ COBS/ZRE Consistent Overhead Byte Stuffing--Zero Run Elimination [#ppp]_ ================== ====================================================================== A pure Python implementation and a C extension implementation are provided. If the C extension is not available for some reason, the pure Python version will be used. ----- Usage ----- The modules provide an ``encode`` and a ``decode`` function. The input should be a byte string, not a Unicode string. Basic usage:: >>> from cobs import cobs >>> encoded = cobs.encode(b'Hello world\x00This is a test') >>> encoded b'\x0cHello world\x0fThis is a test' >>> cobs.decode(encoded) b'Hello world\x00This is a test' `COBS/R`_ usage is almost identical:: >>> from cobs import cobsr >>> encoded = cobsr.encode(b'Hello world\x00This is a test') >>> encoded b'\x0cHello worldtThis is a tes' >>> cobsr.decode(encoded) b'Hello world\x00This is a test' Any type that implements the buffer protocol, providing a single block of bytes, is also acceptable as input:: >>> from cobs import cobs >>> encoded = cobs.encode(bytearray(b'Hello world\x00This is a test')) >>> encoded b'\x0cHello world\x0fThis is a test' >>> cobs.decode(encoded) b'Hello world\x00This is a test' Note that the ``encode`` function does not add any framing ``0x00`` byte at the end (or beginning) of the encoded data. Similarly, the ``decode`` function does not strip or split on any framing ``0x00`` bytes, but treats any ``0x00`` bytes as a data input error. The details of data framing is application-specific, so it is the user's application's responsibility to implement the framing and deframing that is suitable for the needs of the application. ------------------------- Supported Python Versions ------------------------- Python >= 3.10 are supported, and have both a C extension and a pure Python implementation. Python versions < 3.10 might work, but have not been tested. ------------ Installation ------------ The cobs package is installed using ``distutils``. If you have the tools installed to build a Python extension module, run the following command:: python setup.py install If you cannot build the C extension, you may install just the pure Python implementation, using the following command:: python setup.py build_py install --skip-build ------------ Unit Testing ------------ Basic unit testing is in the ``test`` sub-module, e.g. ``cobs.cobs.test``. To run it:: python -m cobs.cobs.test python -m cobs.cobsr.test ------------- Documentation ------------- Documentation is written with Sphinx. Source files are provided in the ``doc`` directory. It can be built using Sphinx 0.6.5. It uses the ``pngmath`` Sphinx extension, which requires Latex and ``dvipng`` to be installed. The documentation is available online at: http://packages.python.org/cobs/ ------- License ------- The code is released under the MIT license. See LICENSE.txt for details. .. _COBS/R: .. _Consistent Overhead Byte Stuffing--Reduced: --------------------------------------------------- Consistent Overhead Byte Stuffing--Reduced (COBS/R) --------------------------------------------------- A modification of COBS, which I'm calling "Consistent Overhead Byte Stuffing--Reduced" (COBS/R), is provided in the ``cobs.cobsr`` module. Its purpose is to save one byte from the encoded form in some cases. Plain COBS encoding always has a +1 byte encoding overhead. See the references for details [#ieeeton]_. COBS/R can often avoid the +1 byte, which can be a useful savings if it is mostly small messages that are being encoded. In plain COBS, the last length code byte in the message has some inherent redundancy: if it is greater than the number of remaining bytes, this is detected as an error. In COBS/R, instead we opportunistically replace the final length code byte with the final data byte, whenever the value of the final data byte is greater than or equal to what the final length value would normally be. This variation can be unambiguously decoded: the decoder notices that the length code is greater than the number of remaining bytes. Examples ```````` The byte values in the examples are in hex. First example: Input: ====== ====== ====== ====== ====== ====== 2F A2 00 92 73 02 ====== ====== ====== ====== ====== ====== This example is encoded the same in COBS and COBS/R. Encoded (length code bytes are bold): ====== ====== ====== ====== ====== ====== ====== **03** 2F A2 **04** 92 73 02 ====== ====== ====== ====== ====== ====== ====== Second example: The second example is almost the same, except the final data byte value is greater than what the length byte would be. Input: ====== ====== ====== ====== ====== ====== 2F A2 00 92 73 26 ====== ====== ====== ====== ====== ====== Encoded in plain COBS (length code bytes are bold): ====== ====== ====== ====== ====== ====== ====== **03** 2F A2 **04** 92 73 26 ====== ====== ====== ====== ====== ====== ====== Encoded in COBS/R: ====== ====== ====== ====== ====== ====== **03** 2F A2 **26** 92 73 ====== ====== ====== ====== ====== ====== Because the last data byte (**26**) is greater than the usual length code (**04**), the last data byte can be inserted in place of the length code, and removed from the end of the sequence. This avoids the usual +1 byte overhead of the COBS encoding. The decoder detects this variation on the encoding simply by detecting that the length code is greater than the number of remaining bytes. That situation would be a decoding error in regular COBS, but in COBS/R it is used to save one byte in the encoded message. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1752973078.0 cobs-1.2.2/cobs.egg-info/SOURCES.txt0000666000000000000000000000075415037037426013715 0ustar00LICENSE.txt MANIFEST.in README.rst pyproject.toml setup.py cobs.egg-info/PKG-INFO cobs.egg-info/SOURCES.txt cobs.egg-info/dependency_links.txt cobs.egg-info/top_level.txt src/ext/_cobs_ext.c src/ext/_cobsr_ext.c src/cobs/__init__.py src/cobs/_version/__init__.py src/cobs/cobs/__init__.py src/cobs/cobs/_cobs_py.py src/cobs/cobs/test.py src/cobs/cobsr/__init__.py src/cobs/cobsr/_cobsr_py.py src/cobs/cobsr/test.py src/ext/_cobs_ext.c src/ext/_cobsr_ext.c test/test_cobs.py test/test_cobsr.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1752973078.0 cobs-1.2.2/cobs.egg-info/dependency_links.txt0000666000000000000000000000000115037037426016071 0ustar00 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1752973078.0 cobs-1.2.2/cobs.egg-info/top_level.txt0000666000000000000000000000000515037037426014550 0ustar00cobs ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1752970895.0 cobs-1.2.2/pyproject.toml0000666000000000000000000000135315037033217012313 0ustar00[build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "cobs" version = "1.2.2" authors = [ { name = "Craig McQueen", email = "python@craig.mcqueen.id.au" }, ] description = "Consistent Overhead Byte Stuffing (COBS)" keywords = [ "byte stuffing" ] readme = "README.rst" requires-python = ">=3.6" license = { file = "LICENSE.txt" } classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Topic :: Communications", ] [project.urls] "Homepage" = "https://github.com/cmcqueen/cobs-python" "Bug Tracker" = "https://github.com/cmcqueen/cobs-python/issues" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1752973078.7672343 cobs-1.2.2/setup.cfg0000666000000000000000000000005215037037427011222 0ustar00[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1752970906.0 cobs-1.2.2/setup.py0000666000000000000000000000120315037033232011100 0ustar00#!/usr/bin/python3 import sys from setuptools import setup, Extension if sys.version_info[0] == 2: raise Exception('Python 2.x is no longer supported') setup_dict = dict( name="cobs", version="1.2.2", packages=[ 'cobs', 'cobs.cobs', 'cobs.cobsr', 'cobs._version', ], package_dir={ 'cobs' : 'src/cobs', }, ext_modules=[ Extension('cobs.cobs._cobs_ext', [ 'src/ext/_cobs_ext.c', ]), Extension('cobs.cobsr._cobsr_ext', [ 'src/ext/_cobsr_ext.c', ]), ], ) try: setup(**setup_dict) except KeyboardInterrupt: raise except: del setup_dict['ext_modules'] setup(**setup_dict) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1752973078.5994706 cobs-1.2.2/src/0000777000000000000000000000000015037037427010173 5ustar00././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1752973078.6971388 cobs-1.2.2/src/cobs/0000777000000000000000000000000015037037427011121 5ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/src/cobs/__init__.py0000666000000000000000000000053314716562427013241 0ustar00"""Consistent Overhead Byte Stuffing (COBS) Encoding and decoding functions for COBS and variants. The following sub-modules are provided: * ``cobs.cobs`` which implements plain COBS. * ``cobs.cobsr`` which implements COBS/Reduced. """ __all__ = [ 'cobs', 'cobsr', ] #from . import cobs #from . import cobsr from ._version import * ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1752973078.7002604 cobs-1.2.2/src/cobs/_version/0000777000000000000000000000000015037037427012745 5ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/src/cobs/_version/__init__.py0000666000000000000000000000051214716562427015062 0ustar00""" _version Version information for cobs. """ __all__ = [ 'VERSION_MAJOR', 'VERSION_MINOR', 'VERSION_PATCH', 'VERSION_STRING', '__version__', ] VERSION_MAJOR = 1 VERSION_MINOR = 2 VERSION_PATCH = 1 VERSION_STRING = '%s.%s.%s' % (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) __version__ = VERSION_STRING ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1752973078.714252 cobs-1.2.2/src/cobs/cobs/0000777000000000000000000000000015037037427012047 5ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/src/cobs/cobs/__init__.py0000666000000000000000000000227314716562427014172 0ustar00""" Consistent Overhead Byte Stuffing (COBS) encoding and decoding. Functions are provided for encoding and decoding according to the basic COBS method. The COBS variant "Zero Pair Elimination" (ZPE) is not implemented. A pure Python implementation and a C extension implementation are provided. If the C extension is not available for some reason, the pure Python version will be used. References: http://www.stuartcheshire.org/papers/COBSforToN.pdf http://tools.ietf.org/html/draft-ietf-pppext-cobs-00 """ try: from ._cobs_ext import * _using_extension = True except ImportError: from ._cobs_py import * _using_extension = False DecodeError.__module__ = 'cobs.cobs' from .._version import * def encoding_overhead(source_len): """Calculates the maximum overhead when encoding a message with the given length. The overhead is a maximum of [n/254] bytes (one in 254 bytes) rounded up.""" if source_len == 0: return 1 return (source_len + 253) // 254 def max_encoded_length(source_len): """Calculates how maximum possible size of an encoded message given the length of the source message.""" return source_len + encoding_overhead(source_len) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1752923961.0 cobs-1.2.2/src/cobs/cobs/_cobs_py.py0000666000000000000000000000553215036677471014233 0ustar00""" Consistent Overhead Byte Stuffing (COBS) This version is for Python 3.x. """ class DecodeError(Exception): pass def _get_buffer_view(in_bytes): mv = memoryview(in_bytes) if mv.ndim > 1 or mv.itemsize > 1: raise BufferError('object must be a single-dimension buffer of bytes.') try: if mv.format != 'B': mv = mv.cast('B') except AttributeError: pass return mv def encode(in_bytes): """Encode a string using Consistent Overhead Byte Stuffing (COBS). Input is any byte string. Output is also a byte string. Encoding guarantees no zero bytes in the output. The output string will be expanded slightly, by a predictable amount. An empty string is encoded to '\\x01'""" if isinstance(in_bytes, str): raise TypeError('Unicode-objects must be encoded as bytes first') in_bytes_mv = _get_buffer_view(in_bytes) final_zero = True out_bytes = bytearray() idx = 0 search_start_idx = 0 for in_char in in_bytes_mv: if in_char == 0: final_zero = True out_bytes.append(idx - search_start_idx + 1) out_bytes += in_bytes_mv[search_start_idx:idx] search_start_idx = idx + 1 else: if idx - search_start_idx == 0xFD: final_zero = False out_bytes.append(0xFF) out_bytes += in_bytes_mv[search_start_idx:idx+1] search_start_idx = idx + 1 idx += 1 if idx != search_start_idx or final_zero: out_bytes.append(idx - search_start_idx + 1) out_bytes += in_bytes_mv[search_start_idx:idx] return bytes(out_bytes) def decode(in_bytes): """Decode a string using Consistent Overhead Byte Stuffing (COBS). Input should be a byte string that has been COBS encoded. Output is also a byte string. A cobs.DecodeError exception will be raised if the encoded data is invalid.""" if isinstance(in_bytes, str): raise TypeError('Unicode-objects are not supported; byte buffer objects only') in_bytes_mv = _get_buffer_view(in_bytes) out_bytes = bytearray() idx = 0 if len(in_bytes_mv) > 0: while True: length = in_bytes_mv[idx] if length == 0: raise DecodeError("zero byte found in input") idx += 1 end = idx + length - 1 copy_mv = in_bytes_mv[idx:end] if 0 in copy_mv: raise DecodeError("zero byte found in input") out_bytes += copy_mv idx = end if idx > len(in_bytes_mv): raise DecodeError("not enough input bytes for length code") if idx < len(in_bytes_mv): if length < 0xFF: out_bytes.append(0) else: break return bytes(out_bytes) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/src/cobs/cobs/test.py0000666000000000000000000002364314716562427013416 0ustar00""" Consistent Overhead Byte Stuffing (COBS) Unit Tests This version is for Python 3.x. """ from array import array import random import unittest from .. import cobs as cobs #from ..cobs import _cobs_py as cobs def infinite_non_zero_generator(): while True: for i in range(1,50): for j in range(1,256, i): yield j def non_zero_generator(length): non_zeros = infinite_non_zero_generator() for i in range(length): yield next(non_zeros) def non_zero_bytes(length): return b''.join(bytes([i]) for i in non_zero_generator(length)) class PredefinedEncodingsTests(unittest.TestCase): predefined_encodings = [ [ b"", b"\x01" ], [ b"1", b"\x021" ], [ b"12345", b"\x0612345" ], [ b"12345\x006789", b"\x0612345\x056789" ], [ b"\x0012345\x006789", b"\x01\x0612345\x056789" ], [ b"12345\x006789\x00", b"\x0612345\x056789\x01" ], [ b"\x00", b"\x01\x01" ], [ b"\x00\x00", b"\x01\x01\x01" ], [ b"\x00\x00\x00", b"\x01\x01\x01\x01" ], [ bytes(bytearray(range(1, 254))), bytes(b"\xfe" + bytearray(range(1, 254))) ], [ bytes(bytearray(range(1, 255))), bytes(b"\xff" + bytearray(range(1, 255))) ], [ bytes(bytearray(range(1, 256))), bytes(b"\xff" + bytearray(range(1, 255)) + b"\x02\xff") ], [ bytes(bytearray(range(0, 256))), bytes(b"\x01\xff" + bytearray(range(1, 255)) + b"\x02\xff") ], ] def test_predefined_encodings(self): for (test_string, expected_encoded_string) in self.predefined_encodings: encoded = cobs.encode(test_string) self.assertEqual(encoded, expected_encoded_string) def test_decode_predefined_encodings(self): for (test_string, expected_encoded_string) in self.predefined_encodings: decoded = cobs.decode(expected_encoded_string) self.assertEqual(test_string, decoded) class PredefinedDecodeErrorTests(unittest.TestCase): decode_error_test_strings = [ b"\x00", b"\x05123", b"\x051234\x00", b"\x0512\x004", ] def test_predefined_decode_error(self): for test_encoded in self.decode_error_test_strings: with self.assertRaises(cobs.DecodeError): cobs.decode(test_encoded) class ZerosTest(unittest.TestCase): def test_zeros(self): for length in range(520): test_string = b'\x00' * length encoded = cobs.encode(test_string) expected_encoded = b'\x01' * (length + 1) self.assertEqual(encoded, expected_encoded, "encoding zeros failed for length %d" % length) decoded = cobs.decode(encoded) self.assertEqual(decoded, test_string, "decoding zeros failed for length %d" % length) class NonZerosTest(unittest.TestCase): def simple_encode_non_zeros_only(self, in_bytes): out_list = [] for i in range(0, len(in_bytes), 254): data_block = in_bytes[i: i+254] out_list.append(bytes([ len(data_block) + 1 ])) out_list.append(data_block) return b''.join(out_list) def test_non_zeros(self): for length in range(1, 1000): test_string = non_zero_bytes(length) encoded = cobs.encode(test_string) expected_encoded = self.simple_encode_non_zeros_only(test_string) self.assertEqual(encoded, expected_encoded, "encoded != expected_encoded for length %d\nencoded: %s\nexpected_encoded: %s" % (length, repr(encoded), repr(expected_encoded))) def test_non_zeros_and_trailing_zero(self): for length in range(1, 1000): non_zeros_string = non_zero_bytes(length) test_string = non_zeros_string + b'\x00' encoded = cobs.encode(test_string) if (len(non_zeros_string) % 254) == 0: expected_encoded = self.simple_encode_non_zeros_only(non_zeros_string) + b'\x01\x01' else: expected_encoded = self.simple_encode_non_zeros_only(non_zeros_string) + b'\x01' self.assertEqual(encoded, expected_encoded, "encoded != expected_encoded for length %d\nencoded: %s\nexpected_encoded: %s" % (length, repr(encoded), repr(expected_encoded))) class RandomDataTest(unittest.TestCase): NUM_TESTS = 5000 MAX_LENGTH = 2000 def test_random(self): try: for _test_num in range(self.NUM_TESTS): length = random.randint(0, self.MAX_LENGTH) test_string = bytes(random.randint(0,255) for x in range(length)) encoded = cobs.encode(test_string) self.assertTrue(b'\x00' not in encoded, "encoding contains zero byte(s):\noriginal: %s\nencoded: %s" % (repr(test_string), repr(encoded))) self.assertTrue(len(encoded) <= len(test_string) + 1 + (len(test_string) // 254), "encoding too big:\noriginal: %s\nencoded: %s" % (repr(test_string), repr(encoded))) decoded = cobs.decode(encoded) self.assertEqual(decoded, test_string, "encoding and decoding random data failed:\noriginal: %s\ndecoded: %s" % (repr(test_string), repr(decoded))) except KeyboardInterrupt: pass class InputTypesTest(unittest.TestCase): predefined_encodings = [ [ b"", b"\x01" ], [ b"1", b"\x021" ], [ b"12345", b"\x0612345" ], [ b"12345\x006789", b"\x0612345\x056789" ], [ b"\x0012345\x006789", b"\x01\x0612345\x056789" ], [ b"12345\x006789\x00", b"\x0612345\x056789\x01" ], ] def test_unicode_string(self): """Test that Unicode strings are not encoded or decoded. They should raise a TypeError.""" for (test_string, expected_encoded_string) in self.predefined_encodings: unicode_test_string = test_string.decode('latin') with self.assertRaises(TypeError): cobs.encode(unicode_test_string) unicode_encoded_string = expected_encoded_string.decode('latin') with self.assertRaises(TypeError): cobs.decode(unicode_encoded_string) def test_bytearray(self): """Test that bytearray objects can be encoded or decoded.""" for (test_string, expected_encoded_string) in self.predefined_encodings: bytearray_test_string = bytearray(test_string) encoded = cobs.encode(bytearray_test_string) self.assertEqual(encoded, expected_encoded_string) bytearray_encoded_string = bytearray(expected_encoded_string) decoded = cobs.decode(bytearray_encoded_string) self.assertEqual(decoded, test_string) def test_array_of_bytes(self): """Test that array of bytes objects (array('B', ...)) can be encoded or decoded.""" for (test_string, expected_encoded_string) in self.predefined_encodings: array_test_string = array('B', test_string) encoded = cobs.encode(array_test_string) self.assertEqual(encoded, expected_encoded_string) array_encoded_string = array('B', expected_encoded_string) decoded = cobs.decode(array_encoded_string) self.assertEqual(decoded, test_string) def test_array_of_half_words(self): """Test that array of half-word objects (array('H', ...)) are not encoded or decoded. They should raise a BufferError.""" # Array typecodes for types with size greater than 1. # Don't include 'u' for Unicode because it expects a Unicode initialiser. typecodes = [ 'H', 'h', 'i', 'I', 'l', 'L', 'f', 'd' ] for typecode in typecodes: array_test_string = array(typecode, [ 49, 50, 51, 52, 53 ]) with self.assertRaises(BufferError): cobs.encode(array_test_string) array_encoded_string = array(typecode, [6, 49, 50, 51, 52, 53 ]) with self.assertRaises(BufferError): cobs.decode(array_encoded_string) class UtilTests(unittest.TestCase): def test_encoded_len_calc(self): self.assertEqual(cobs.encoding_overhead(5), 1) self.assertEqual(cobs.max_encoded_length(5), 6) def test_encoded_len_calc_empty_packet(self): self.assertEqual(cobs.encoding_overhead(0), 1) self.assertEqual(cobs.max_encoded_length(0), 1) def test_encoded_len_calc_still_one_byte_overhead(self): self.assertEqual(cobs.encoding_overhead(254), 1) self.assertEqual(cobs.max_encoded_length(254), 255) def test_encoded_len_calc_two_byte_overhead(self): self.assertEqual(cobs.encoding_overhead(255), 2) self.assertEqual(cobs.max_encoded_length(255), 257) def runtests(): unittest.main() if __name__ == '__main__': runtests() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1752973078.7282484 cobs-1.2.2/src/cobs/cobsr/0000777000000000000000000000000015037037427012231 5ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/src/cobs/cobsr/__init__.py0000666000000000000000000000200214716562427014342 0ustar00""" Consistent Overhead Byte Stuffing/Reduced (COBS/R) encoding and decoding. Functions are provided for encoding and decoding according to the COBS/R method. A pure Python implementation and a C extension implementation are provided. If the C extension is not available for some reason, the pure Python version will be used. """ try: from ._cobsr_ext import * _using_extension = True except ImportError: from ._cobsr_py import * _using_extension = False DecodeError.__module__ = 'cobs.cobsr' from .._version import * def encoding_overhead(source_len): """Calculates the maximum overhead when encoding a message with the given length. The overhead is a maximum of [n/254] bytes (one in 254 bytes) rounded up.""" if source_len == 0: return 1 return (source_len + 253) // 254 def max_encoded_length(source_len): """Calculates how maximum possible size of an encoded message given the length of the source message.""" return source_len + encoding_overhead(source_len) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1752923961.0 cobs-1.2.2/src/cobs/cobsr/_cobsr_py.py0000666000000000000000000000625715036677471014604 0ustar00""" Consistent Overhead Byte Stuffing/Reduced (COBS/R) This version is for Python 3.x. """ class DecodeError(Exception): pass def _get_buffer_view(in_bytes): mv = memoryview(in_bytes) if mv.ndim > 1 or mv.itemsize > 1: raise BufferError('object must be a single-dimension buffer of bytes.') try: if mv.format != 'B': mv = mv.cast('B') except AttributeError: pass return mv def encode(in_bytes): """Encode a string using Consistent Overhead Byte Stuffing/Reduced (COBS/R). Input is any byte string. Output is also a byte string. Encoding guarantees no zero bytes in the output. The output string may be expanded slightly, by a predictable amount. An empty string is encoded to '\\x01'""" if isinstance(in_bytes, str): raise TypeError('Unicode-objects must be encoded as bytes first') in_bytes_mv = _get_buffer_view(in_bytes) out_bytes = bytearray() idx = 0 search_start_idx = 0 for in_char in in_bytes_mv: if idx - search_start_idx == 0xFE: out_bytes.append(0xFF) out_bytes += in_bytes_mv[search_start_idx:idx] search_start_idx = idx if in_char == 0: out_bytes.append(idx - search_start_idx + 1) out_bytes += in_bytes_mv[search_start_idx:idx] search_start_idx = idx + 1 idx += 1 try: final_byte_value = in_bytes_mv[-1] except IndexError: final_byte_value = 0 length_value = idx - search_start_idx + 1 if final_byte_value < length_value: # Encoding same as plain COBS out_bytes.append(length_value) out_bytes += in_bytes_mv[search_start_idx:idx] else: # Special COBS/R encoding: length code is final byte, # and final byte is removed from data sequence. out_bytes.append(final_byte_value) out_bytes += in_bytes_mv[search_start_idx:idx - 1] return bytes(out_bytes) def decode(in_bytes): """Decode a string using Consistent Overhead Byte Stuffing/Reduced (COBS/R). Input should be a byte string that has been COBS/R encoded. Output is also a byte string. A cobsr.DecodeError exception will be raised if the encoded data is invalid. That is, if the encoded data contains zeros.""" if isinstance(in_bytes, str): raise TypeError('Unicode-objects are not supported; byte buffer objects only') in_bytes_mv = _get_buffer_view(in_bytes) out_bytes = bytearray() idx = 0 if len(in_bytes_mv) > 0: while True: length = in_bytes_mv[idx] if length == 0: raise DecodeError("zero byte found in input") idx += 1 end = idx + length - 1 copy_mv = in_bytes_mv[idx:end] if 0 in copy_mv: raise DecodeError("zero byte found in input") out_bytes += copy_mv idx = end if idx > len(in_bytes_mv): out_bytes.append(length) break elif idx < len(in_bytes_mv): if length < 0xFF: out_bytes.append(0) else: break return bytes(out_bytes) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/src/cobs/cobsr/test.py0000666000000000000000000003117414716562427013576 0ustar00""" Consistent Overhead Byte Stuffing/Reduced (COBS/R) Unit Tests This version is for Python 3.x. """ from array import array import random import unittest from .. import cobsr as cobsr #from ..cobsr import _cobsr_py as cobsr def infinite_non_zero_generator(): while True: for i in range(1,50): for j in range(1,256, i): yield j def non_zero_generator(length): non_zeros = infinite_non_zero_generator() for i in range(length): yield next(non_zeros) def non_zero_bytes(length): return b''.join(bytes([i]) for i in non_zero_generator(length)) class PredefinedEncodingsTests(unittest.TestCase): predefined_encodings = [ [ b"", b"\x01" ], [ b"\x01", b"\x02\x01" ], [ b"\x02", b"\x02" ], [ b"\x03", b"\x03" ], [ b"\x7E", b"\x7E" ], [ b"\x7F", b"\x7F" ], [ b"\x80", b"\x80" ], [ b"\xD5", b"\xD5" ], [ b"\xFE", b"\xFE" ], [ b"\xFF", b"\xFF" ], [ b"a\x02", b"\x03a\x02" ], [ b"a\x03", b"\x03a" ], [ b"a\xFF", b"\xFFa" ], [ b"\x05\x04\x03\x02\x01", b"\x06\x05\x04\x03\x02\x01" ], [ b"12345", b"51234" ], [ b"12345\x00\x04\x03\x02\x01", b"\x0612345\x05\x04\x03\x02\x01" ], [ b"12345\x006789", b"\x06123459678" ], [ b"\x0012345\x006789", b"\x01\x06123459678" ], [ b"12345\x006789\x00", b"\x0612345\x056789\x01" ], [ b"\x00", b"\x01\x01" ], [ b"\x00\x00", b"\x01\x01\x01" ], [ b"\x00\x00\x00", b"\x01\x01\x01\x01" ], [ bytes(bytearray(range(1, 254))), bytes(b"\xfe" + bytearray(range(1, 254))) ], [ bytes(bytearray(range(1, 255))), bytes(b"\xff" + bytearray(range(1, 255))) ], [ bytes(bytearray(range(1, 256))), bytes(b"\xff" + bytearray(range(1, 255)) + b"\xff") ], [ bytes(bytearray(range(0, 256))), bytes(b"\x01\xff" + bytearray(range(1, 255)) + b"\xff") ], [ bytes(bytearray(range(2, 256))), bytes(b"\xff" + bytearray(range(2, 255))) ], ] def test_predefined_encodings(self): for (test_string, expected_encoded_string) in self.predefined_encodings: encoded = cobsr.encode(test_string) self.assertEqual(encoded, expected_encoded_string) def test_decode_predefined_encodings(self): for (test_string, expected_encoded_string) in self.predefined_encodings: decoded = cobsr.decode(expected_encoded_string) self.assertEqual(test_string, decoded) class PredefinedDecodeErrorTests(unittest.TestCase): decode_error_test_strings = [ b"\x00", b"\x051234\x00", b"\x0512\x004", ] def test_predefined_decode_error(self): for test_encoded in self.decode_error_test_strings: with self.assertRaises(cobsr.DecodeError): cobsr.decode(test_encoded) class ZerosTest(unittest.TestCase): def test_zeros(self): for length in range(520): test_string = b'\x00' * length encoded = cobsr.encode(test_string) expected_encoded = b'\x01' * (length + 1) self.assertEqual(encoded, expected_encoded, "encoding zeros failed for length %d" % length) decoded = cobsr.decode(encoded) self.assertEqual(decoded, test_string, "decoding zeros failed for length %d" % length) class NonZerosTest(unittest.TestCase): def simple_encode_non_zeros_only(self, in_bytes): out_list = [] for i in range(0, len(in_bytes), 254): data_block = in_bytes[i: i+254] out_list.append(bytes([ len(data_block) + 1 ])) out_list.append(data_block) return b''.join(out_list) def cobsr_encode_final_non_zeros_only(self, in_bytes): out_list = [] for i in range(0, len(in_bytes), 254): data_block = in_bytes[i: i+254] out_list.append(bytes([ len(data_block) + 1 ])) out_list.append(data_block) last_block = out_list[-1] last_char_value = last_block[-1] if last_char_value >= len(last_block) + 1: del(out_list[-2:]) out_list.append(bytes([ last_char_value ])) out_list.append(last_block[:-1]) return b''.join(out_list) def test_non_zeros(self): for length in range(1, 1000): test_string = non_zero_bytes(length) encoded = cobsr.encode(test_string) expected_encoded = self.cobsr_encode_final_non_zeros_only(test_string) self.assertEqual(encoded, expected_encoded, "encoded != expected_encoded for length %d\nencoded: %s\nexpected_encoded: %s" % (length, repr(encoded), repr(expected_encoded))) def test_non_zeros_and_trailing_zero(self): for length in range(1, 1000): non_zeros_string = non_zero_bytes(length) test_string = non_zeros_string + b'\x00' encoded = cobsr.encode(test_string) if (len(non_zeros_string) % 254) == 0: expected_encoded = self.simple_encode_non_zeros_only(non_zeros_string) + b'\x01\x01' else: expected_encoded = self.simple_encode_non_zeros_only(non_zeros_string) + b'\x01' self.assertEqual(encoded, expected_encoded, "encoded != expected_encoded for length %d\nencoded: %s\nexpected_encoded: %s" % (length, repr(encoded), repr(expected_encoded))) class RandomDataTest(unittest.TestCase): NUM_TESTS = 5000 MAX_LENGTH = 2000 def test_random(self): try: for _test_num in range(self.NUM_TESTS): length = random.randint(0, self.MAX_LENGTH) test_string = bytes(random.randint(0,255) for x in range(length)) encoded = cobsr.encode(test_string) self.assertTrue(b'\x00' not in encoded, "encoding contains zero byte(s):\noriginal: %s\nencoded: %s" % (repr(test_string), repr(encoded))) self.assertTrue(len(encoded) <= len(test_string) + 1 + (len(test_string) // 254), "encoding too big:\noriginal: %s\nencoded: %s" % (repr(test_string), repr(encoded))) decoded = cobsr.decode(encoded) self.assertEqual(decoded, test_string, "encoding and decoding random data failed:\noriginal: %s\ndecoded: %s" % (repr(test_string), repr(decoded))) except KeyboardInterrupt: pass class InputTypesTest(unittest.TestCase): predefined_encodings = [ [ b"", b"\x01" ], [ b"\x01", b"\x02\x01" ], [ b"\x02", b"\x02" ], [ b"\x7F", b"\x7F" ], [ b"\x80", b"\x80" ], [ b"\xD5", b"\xD5" ], [ b"1", b"1" ], [ b"\x05\x04\x03\x02\x01", b"\x06\x05\x04\x03\x02\x01" ], [ b"12345", b"51234" ], [ b"12345\x00\x04\x03\x02\x01", b"\x0612345\x05\x04\x03\x02\x01" ], [ b"12345\x006789", b"\x06123459678" ], ] def test_unicode_string(self): """Test that Unicode strings are not encoded or decoded. They should raise a TypeError.""" for (test_string, expected_encoded_string) in self.predefined_encodings: unicode_test_string = test_string.decode('latin') with self.assertRaises(TypeError): cobsr.encode(unicode_test_string) unicode_encoded_string = expected_encoded_string.decode('latin') with self.assertRaises(TypeError): cobsr.decode(unicode_encoded_string) def test_bytearray(self): """Test that bytearray objects can be encoded or decoded.""" for (test_string, expected_encoded_string) in self.predefined_encodings: bytearray_test_string = bytearray(test_string) encoded = cobsr.encode(bytearray_test_string) self.assertEqual(encoded, expected_encoded_string) bytearray_encoded_string = bytearray(expected_encoded_string) decoded = cobsr.decode(bytearray_encoded_string) self.assertEqual(decoded, test_string) def test_array_of_bytes(self): """Test that array of bytes objects (array('B', ...)) can be encoded or decoded.""" for (test_string, expected_encoded_string) in self.predefined_encodings: array_test_string = array('B', test_string) encoded = cobsr.encode(array_test_string) self.assertEqual(encoded, expected_encoded_string) array_encoded_string = array('B', expected_encoded_string) decoded = cobsr.decode(array_encoded_string) self.assertEqual(decoded, test_string) def test_array_of_half_words(self): """Test that array of half-word objects (array('H', ...)) are not encoded or decoded. They should raise a BufferError.""" # Array typecodes for types with size greater than 1. # Don't include 'u' for Unicode because it expects a Unicode initialiser. typecodes = [ 'H', 'h', 'i', 'I', 'l', 'L', 'f', 'd' ] for typecode in typecodes: array_test_string = array(typecode, [ 49, 50, 51, 52, 53 ]) with self.assertRaises(BufferError): cobsr.encode(array_test_string) array_encoded_string = array(typecode, [6, 49, 50, 51, 52, 53 ]) with self.assertRaises(BufferError): cobsr.decode(array_encoded_string) class UtilTests(unittest.TestCase): def test_encoded_len_calc(self): self.assertEqual(cobsr.encoding_overhead(5), 1) self.assertEqual(cobsr.max_encoded_length(5), 6) def test_encoded_len_calc_empty_packet(self): self.assertEqual(cobsr.encoding_overhead(0), 1) self.assertEqual(cobsr.max_encoded_length(0), 1) def test_encoded_len_calc_still_one_byte_overhead(self): self.assertEqual(cobsr.encoding_overhead(254), 1) self.assertEqual(cobsr.max_encoded_length(254), 255) def test_encoded_len_calc_two_byte_overhead(self): self.assertEqual(cobsr.encoding_overhead(255), 2) self.assertEqual(cobsr.max_encoded_length(255), 257) def runtests(): unittest.main() if __name__ == '__main__': runtests() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1752973078.740131 cobs-1.2.2/src/ext/0000777000000000000000000000000015037037427010773 5ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/src/ext/_cobs_ext.c0000666000000000000000000002720514716562427013120 0ustar00/* * Consistent Overhead Byte Stuffing (COBS) * * Python C extension for COBS encoding and decoding functions. * * Copyright (c) 2010 Craig McQueen * * 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. */ /***************************************************************************** * Includes ****************************************************************************/ // Force Py_ssize_t to be used for s# conversions. #define PY_SSIZE_T_CLEAN #include /***************************************************************************** * Defines ****************************************************************************/ #ifndef FALSE #define FALSE (0) #endif #ifndef TRUE #define TRUE (!FALSE) #endif #define GETSTATE(M) ((struct module_state *) PyModule_GetState(M)) /* * Given a PyObject* obj, fill in the Py_buffer* viewp with the result * of PyObject_GetBuffer. Sets and exception and issues a return NULL * on any errors. */ #define GET_BUFFER_VIEW_OR_ERROUT(obj, viewp) do { \ if (!PyObject_CheckBuffer((obj))) { \ PyErr_SetString(PyExc_TypeError, \ "object supporting the buffer API is required"); \ return NULL; \ } \ if (PyObject_GetBuffer((obj), (viewp), PyBUF_FORMAT) == -1) { \ return NULL; \ } \ if (((viewp)->ndim > 1) || ((viewp)->itemsize > 1)) { \ PyErr_SetString(PyExc_BufferError, \ "object must be a single-dimension buffer of bytes"); \ PyBuffer_Release((viewp)); \ return NULL; \ } \ } while(0); #define COBS_ENCODE_DST_BUF_LEN_MAX(SRC_LEN) ((SRC_LEN) + ((SRC_LEN)/254u) + 1) #define COBS_DECODE_DST_BUF_LEN_MAX(SRC_LEN) (((SRC_LEN) <= 1) ? 1 : ((SRC_LEN) - 1)) /***************************************************************************** * Types ****************************************************************************/ struct module_state { /* cobs.DecodeError exception class. */ PyObject * CobsDecodeError; }; /***************************************************************************** * Functions ****************************************************************************/ static int cobs_traverse(PyObject *m, visitproc visit, void *arg) { Py_VISIT(GETSTATE(m)->CobsDecodeError); return 0; } static int cobs_clear(PyObject *m) { Py_CLEAR(GETSTATE(m)->CobsDecodeError); return 0; } /* * cobs.encode */ PyDoc_STRVAR(cobs_encode__doc__, "Encode a string using Consistent Overhead Byte Stuffing (COBS).\n" "\n" "Input is any byte string. Output is also a byte string.\n" "\n" "Encoding guarantees no zero bytes in the output. The output\n" "string will be expanded slightly, by a predictable amount.\n" "\n" "An empty string is encoded to '\\x01'." ); /* * This Python C extension function uses arguments method METH_O, * meaning the arg parameter contains the single parameter * to the function. */ static PyObject* cobs_encode(PyObject* module, PyObject* arg) { Py_buffer src_py_buffer; const char * src_ptr; Py_ssize_t src_len; const char * src_end_ptr; char * dst_buf_ptr; char * dst_code_write_ptr; char * dst_write_ptr; char src_byte; unsigned char search_len; PyObject * dst_py_obj_ptr; if (PyUnicode_Check((arg))) { PyErr_SetString(PyExc_TypeError, "Unicode-objects must be encoded as bytes first"); return NULL; } GET_BUFFER_VIEW_OR_ERROUT(arg, &src_py_buffer); src_ptr = src_py_buffer.buf; src_len = src_py_buffer.len; src_end_ptr = src_ptr + src_len; /* Make an output string */ dst_py_obj_ptr = PyBytes_FromStringAndSize(NULL, COBS_ENCODE_DST_BUF_LEN_MAX(src_len)); if (dst_py_obj_ptr == NULL) { return NULL; } dst_buf_ptr = PyBytes_AsString(dst_py_obj_ptr); /* Encode */ dst_code_write_ptr = dst_buf_ptr; dst_write_ptr = dst_code_write_ptr + 1; search_len = 1; /* Iterate over the source bytes */ if (src_len != 0) { for (;;) { src_byte = *src_ptr++; if (src_byte == 0) { /* We found a zero byte */ *dst_code_write_ptr = (char) search_len; dst_code_write_ptr = dst_write_ptr++; search_len = 1; if (src_ptr >= src_end_ptr) { break; } } else { /* Copy the non-zero byte to the destination buffer */ *dst_write_ptr++ = src_byte; search_len++; if (src_ptr >= src_end_ptr) { break; } if (search_len == 0xFF) { /* We have a long string of non-zero bytes */ *dst_code_write_ptr = (char) search_len; dst_code_write_ptr = dst_write_ptr++; search_len = 1; } } } } /* We're done with the input buffer now, so we have to release the PyBuffer. */ PyBuffer_Release(&src_py_buffer); /* We've reached the end of the source data. * Finalise the remaining output. In particular, write the code (length) byte. * Update the pointer to calculate the final output length. */ *dst_code_write_ptr = (char) search_len; /* Calculate the output length, from the value of dst_code_write_ptr */ _PyBytes_Resize(&dst_py_obj_ptr, dst_write_ptr - dst_buf_ptr); return dst_py_obj_ptr; } /* * cobs.decode */ PyDoc_STRVAR(cobs_decode__doc__, "Decode a string using Consistent Overhead Byte Stuffing (COBS).\n" "\n" "Input should be a byte string that has been COBS encoded. Output\n" "is also a byte string.\n" "\n" "A cobs.DecodeError exception will be raised if the encoded data\n" "is invalid." ); /* * This Python C extension function uses arguments method METH_O, * meaning the arg parameter contains the single parameter * to the function. */ static PyObject* cobs_decode(PyObject* module, PyObject* arg) { Py_buffer src_py_buffer; const char * src_ptr; Py_ssize_t src_len; const char * src_end_ptr; char * dst_buf_ptr; char * dst_write_ptr; Py_ssize_t remaining_bytes; unsigned char len_code; unsigned char src_byte; unsigned char i; PyObject * dst_py_obj_ptr; if (PyUnicode_Check((arg))) { PyErr_SetString(PyExc_TypeError, "Unicode-objects are not supported; byte buffer objects only"); return NULL; } GET_BUFFER_VIEW_OR_ERROUT(arg, &src_py_buffer); src_ptr = src_py_buffer.buf; src_len = src_py_buffer.len; src_end_ptr = src_ptr + src_len; /* Make an output string */ dst_py_obj_ptr = PyBytes_FromStringAndSize(NULL, COBS_DECODE_DST_BUF_LEN_MAX(src_len)); if (dst_py_obj_ptr == NULL) { PyBuffer_Release(&src_py_buffer); return NULL; } dst_buf_ptr = PyBytes_AsString(dst_py_obj_ptr); /* Decode */ dst_write_ptr = dst_buf_ptr; if (src_len != 0) { for (;;) { len_code = (unsigned char) *src_ptr++; if (len_code == 0) { PyBuffer_Release(&src_py_buffer); Py_DECREF(dst_py_obj_ptr); PyErr_SetString(GETSTATE(module)->CobsDecodeError, "zero byte found in input"); return NULL; } len_code--; remaining_bytes = src_end_ptr - src_ptr; if (len_code > remaining_bytes) { PyBuffer_Release(&src_py_buffer); Py_DECREF(dst_py_obj_ptr); PyErr_SetString(GETSTATE(module)->CobsDecodeError, "not enough input bytes for length code"); return NULL; } for (i = len_code; i != 0; i--) { src_byte = *src_ptr++; if (src_byte == 0) { PyBuffer_Release(&src_py_buffer); Py_DECREF(dst_py_obj_ptr); PyErr_SetString(GETSTATE(module)->CobsDecodeError, "zero byte found in input"); return NULL; } *dst_write_ptr++ = src_byte; } if (src_ptr >= src_end_ptr) { break; } /* Add a zero to the end */ if (len_code != 0xFE) { *dst_write_ptr++ = 0; } } } /* We're done with the input buffer now, so we have to release the PyBuffer. */ PyBuffer_Release(&src_py_buffer); /* Calculate the output length, from the value of dst_code_write_ptr */ _PyBytes_Resize(&dst_py_obj_ptr, dst_write_ptr - dst_buf_ptr); return dst_py_obj_ptr; } /***************************************************************************** * Module definitions ****************************************************************************/ PyDoc_STRVAR(module__doc__, "Consistent Overhead Byte Stuffing (COBS)" ); static PyMethodDef methodTable[] = { { "encode", cobs_encode, METH_O, cobs_encode__doc__ }, { "decode", cobs_decode, METH_O, cobs_decode__doc__ }, { NULL, NULL, 0, NULL } }; static struct PyModuleDef moduleDef = { PyModuleDef_HEAD_INIT, "_cobs_ext", // name of module module__doc__, // module documentation sizeof(struct module_state), // size of per-interpreter state of the module methodTable, NULL, cobs_traverse, cobs_clear, NULL }; /***************************************************************************** * Module initialisation ****************************************************************************/ PyMODINIT_FUNC PyInit__cobs_ext(void) { PyObject * module; struct module_state * st; /* Initialise cobs module C extension cobs._cobsext */ module = PyModule_Create(&moduleDef); if (module == NULL) { return NULL; } st = GETSTATE(module); /* Initialise cobs.DecodeError exception class. */ st->CobsDecodeError = PyErr_NewException("_cobs_ext.DecodeError", NULL, NULL); if (st->CobsDecodeError == NULL) { Py_DECREF(module); return NULL; } Py_INCREF(st->CobsDecodeError); PyModule_AddObject(module, "DecodeError", st->CobsDecodeError); return module; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/src/ext/_cobsr_ext.c0000666000000000000000000003213314716562427013276 0ustar00/* * Consistent Overhead Byte Stuffing/Reduced (COBS/R) * * Python C extension for COBS/R encoding and decoding functions. * * Copyright (c) 2010 Craig McQueen * * 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. */ /***************************************************************************** * Includes ****************************************************************************/ // Force Py_ssize_t to be used for s# conversions. #define PY_SSIZE_T_CLEAN #include /***************************************************************************** * Defines ****************************************************************************/ #ifndef FALSE #define FALSE (0) #endif #ifndef TRUE #define TRUE (!FALSE) #endif #define GETSTATE(M) ((struct module_state *) PyModule_GetState(M)) /* * Given a PyObject* obj, fill in the Py_buffer* viewp with the result * of PyObject_GetBuffer. Sets and exception and issues a return NULL * on any errors. */ #define GET_BUFFER_VIEW_OR_ERROUT(obj, viewp) do { \ if (!PyObject_CheckBuffer((obj))) { \ PyErr_SetString(PyExc_TypeError, \ "object supporting the buffer API is required"); \ return NULL; \ } \ if (PyObject_GetBuffer((obj), (viewp), PyBUF_FORMAT) == -1) { \ return NULL; \ } \ if (((viewp)->ndim > 1) || ((viewp)->itemsize > 1)) { \ PyErr_SetString(PyExc_BufferError, \ "object must be a single-dimension buffer of bytes"); \ PyBuffer_Release((viewp)); \ return NULL; \ } \ } while(0); #define COBSR_ENCODE_DST_BUF_LEN_MAX(SRC_LEN) ((SRC_LEN) + ((SRC_LEN)/254u) + 1) #define COBSR_DECODE_DST_BUF_LEN_MAX(SRC_LEN) (((SRC_LEN) <= 1) ? 1 : (SRC_LEN)) /***************************************************************************** * Types ****************************************************************************/ struct module_state { /* cobsr.DecodeError exception class. */ PyObject * CobsrDecodeError; }; /***************************************************************************** * Functions ****************************************************************************/ static int cobsr_traverse(PyObject *m, visitproc visit, void *arg) { Py_VISIT(GETSTATE(m)->CobsrDecodeError); return 0; } static int cobsr_clear(PyObject *m) { Py_CLEAR(GETSTATE(m)->CobsrDecodeError); return 0; } /* * cobsr.encode */ PyDoc_STRVAR(cobsr_encode__doc__, "Encode a string using Consistent Overhead Byte Stuffing/Reduced (COBS/R).\n" "\n" "Input is any byte string. Output is also a byte string.\n" "\n" "Encoding guarantees no zero bytes in the output. The output\n" "string may be expanded slightly, by a predictable amount.\n" "\n" "An empty string is encoded to '\\x01'." ); /* * This Python C extension function uses arguments method METH_O, * meaning the arg parameter contains the single parameter * to the function. */ static PyObject* cobsr_encode(PyObject* module, PyObject* arg) { Py_buffer src_py_buffer; const char * src_ptr; Py_ssize_t src_len; const char * src_end_ptr; char * dst_buf_ptr; char * dst_code_write_ptr; char * dst_write_ptr; unsigned char src_byte; unsigned char search_len; PyObject * dst_py_obj_ptr; if (PyUnicode_Check((arg))) { PyErr_SetString(PyExc_TypeError, "Unicode-objects must be encoded as bytes first"); return NULL; } GET_BUFFER_VIEW_OR_ERROUT(arg, &src_py_buffer); src_ptr = src_py_buffer.buf; src_len = src_py_buffer.len; src_end_ptr = src_ptr + src_len; /* Make an output string */ dst_py_obj_ptr = PyBytes_FromStringAndSize(NULL, COBSR_ENCODE_DST_BUF_LEN_MAX(src_len)); if (dst_py_obj_ptr == NULL) { return NULL; } dst_buf_ptr = PyBytes_AsString(dst_py_obj_ptr); /* Encode */ dst_code_write_ptr = dst_buf_ptr; dst_write_ptr = dst_code_write_ptr + 1; search_len = 1; src_byte = 0; /* Iterate over the source bytes */ if (src_len != 0) { for (;;) { src_byte = *src_ptr++; if (src_byte == 0) { /* We found a zero byte */ *dst_code_write_ptr = (char) search_len; dst_code_write_ptr = dst_write_ptr++; search_len = 1; if (src_ptr >= src_end_ptr) { break; } } else { /* Copy the non-zero byte to the destination buffer */ *dst_write_ptr++ = src_byte; search_len++; if (src_ptr >= src_end_ptr) { break; } if (search_len == 0xFF) { /* We have a long string of non-zero bytes */ *dst_code_write_ptr = (char) search_len; dst_code_write_ptr = dst_write_ptr++; search_len = 1; } } } } /* We're done with the input buffer now, so we have to release the PyBuffer. */ PyBuffer_Release(&src_py_buffer); /* We've reached the end of the source data. * Finalise the remaining output. In particular, write the code (length) byte. * * For COBS/R, the final code (length) byte is special: if the final data byte is * greater than or equal to what would normally be the final code (length) byte, * then replace the final code byte with the final data byte, and remove the final * data byte from the end of the sequence. This saves one byte in the output. * * Update the pointer to calculate the final output length. */ if (src_byte < search_len) { /* Encoding same as plain COBS */ *dst_code_write_ptr = (char) search_len; } else { /* Special COBS/R encoding: length code is final byte, * and final byte is removed from data sequence. */ *dst_code_write_ptr = (char) src_byte; dst_write_ptr--; } /* Calculate the output length, from the value of dst_code_write_ptr */ _PyBytes_Resize(&dst_py_obj_ptr, dst_write_ptr - dst_buf_ptr); return dst_py_obj_ptr; } /* * cobsr.decode */ PyDoc_STRVAR(cobsr_decode__doc__, "Decode a string using Consistent Overhead Byte Stuffing/Reduced (COBS/R).\n" "\n" "Input should be a byte string that has been COBS/R encoded. Output\n" "is also a byte string.\n" "\n" "A cobsr.DecodeError exception will be raised if the encoded data\n" "is invalid. That is, if the encoded data contains zeros." ); /* * This Python C extension function uses arguments method METH_O, * meaning the arg parameter contains the single parameter * to the function. */ static PyObject* cobsr_decode(PyObject* module, PyObject* arg) { Py_buffer src_py_buffer; const char * src_ptr; Py_ssize_t src_len; const char * src_end_ptr; char * dst_buf_ptr; char * dst_write_ptr; Py_ssize_t remaining_bytes; unsigned char len_code; unsigned char src_byte; unsigned char i; PyObject * dst_py_obj_ptr; if (PyUnicode_Check((arg))) { PyErr_SetString(PyExc_TypeError, "Unicode-objects are not supported; byte buffer objects only"); return NULL; } GET_BUFFER_VIEW_OR_ERROUT(arg, &src_py_buffer); src_ptr = src_py_buffer.buf; src_len = src_py_buffer.len; src_end_ptr = src_ptr + src_len; /* Make an output string */ dst_py_obj_ptr = PyBytes_FromStringAndSize(NULL, COBSR_DECODE_DST_BUF_LEN_MAX(src_len)); if (dst_py_obj_ptr == NULL) { PyBuffer_Release(&src_py_buffer); return NULL; } dst_buf_ptr = PyBytes_AsString(dst_py_obj_ptr); /* Decode */ dst_write_ptr = dst_buf_ptr; if (src_len != 0) { for (;;) { len_code = (unsigned char) *src_ptr++; if (len_code == 0) { PyBuffer_Release(&src_py_buffer); Py_DECREF(dst_py_obj_ptr); PyErr_SetString(GETSTATE(module)->CobsrDecodeError, "zero byte found in input"); return NULL; } remaining_bytes = src_end_ptr - src_ptr; if ((len_code - 1) < remaining_bytes) { for (i = len_code - 1; i != 0; i--) { src_byte = *src_ptr++; if (src_byte == 0) { PyBuffer_Release(&src_py_buffer); Py_DECREF(dst_py_obj_ptr); PyErr_SetString(GETSTATE(module)->CobsrDecodeError, "zero byte found in input"); return NULL; } *dst_write_ptr++ = src_byte; } /* Add a zero to the end */ if (len_code != 0xFF) { *dst_write_ptr++ = 0; } } else { /* We've reached the last length code, so write the remaining * bytes and then exit the loop. */ for (i = remaining_bytes; i != 0; i--) { src_byte = *src_ptr++; if (src_byte == 0) { PyBuffer_Release(&src_py_buffer); Py_DECREF(dst_py_obj_ptr); PyErr_SetString(GETSTATE(module)->CobsrDecodeError, "zero byte found in input"); return NULL; } *dst_write_ptr++ = src_byte; } /* Write final data byte, if applicable for COBS/R encoding. */ if (len_code - 1 > remaining_bytes) { *dst_write_ptr++ = len_code; } /* Exit the loop */ break; } } } /* We're done with the input buffer now, so we have to release the PyBuffer. */ PyBuffer_Release(&src_py_buffer); /* Calculate the output length, from the value of dst_code_write_ptr */ _PyBytes_Resize(&dst_py_obj_ptr, dst_write_ptr - dst_buf_ptr); return dst_py_obj_ptr; } /***************************************************************************** * Module definitions ****************************************************************************/ PyDoc_STRVAR(module__doc__, "Consistent Overhead Byte Stuffing/Reduced (COBS/R)" ); static PyMethodDef methodTable[] = { { "encode", cobsr_encode, METH_O, cobsr_encode__doc__ }, { "decode", cobsr_decode, METH_O, cobsr_decode__doc__ }, { NULL, NULL, 0, NULL } }; static struct PyModuleDef moduleDef = { PyModuleDef_HEAD_INIT, "_cobsr_ext", // name of module module__doc__, // module documentation sizeof(struct module_state), // size of per-interpreter state of the module methodTable, NULL, cobsr_traverse, cobsr_clear, NULL }; /***************************************************************************** * Module initialisation ****************************************************************************/ PyMODINIT_FUNC PyInit__cobsr_ext(void) { PyObject * module; struct module_state * st; /* Initialise cobsr module C extension cobsr._cobsr_ext */ module = PyModule_Create(&moduleDef); if (module == NULL) { return NULL; } st = GETSTATE(module); /* Initialise cobsr.DecodeError exception class. */ st->CobsrDecodeError = PyErr_NewException("_cobsr_ext.DecodeError", NULL, NULL); if (st->CobsrDecodeError == NULL) { Py_DECREF(module); return NULL; } Py_INCREF(st->CobsrDecodeError); PyModule_AddObject(module, "DecodeError", st->CobsrDecodeError); return module; } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1752973078.750245 cobs-1.2.2/test/0000777000000000000000000000000015037037427010363 5ustar00././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/test/test_cobs.py0000666000000000000000000000011614716562427012726 0ustar00 import unittest import cobs.cobs.test unittest.main(module=cobs.cobs.test) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731912983.0 cobs-1.2.2/test/test_cobsr.py0000666000000000000000000000012014716562427013103 0ustar00 import unittest import cobs.cobsr.test unittest.main(module=cobs.cobsr.test)