defmodule Cobs do @moduledoc """ Elixir implementation of [Consistent Overhead Byte Stuffing](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing) """ @doc """ Encode a binary (with `0` bytes) into a COBS encoded binary (without `0` bytes). ## Example iex> Cobs.encode(<<0x01, 0x02, 0x00, 0x03>>) {:ok, <<0x03, 0x01, 0x02, 0x02, 0x03>>} """ @spec encode(binary()) :: {:ok, binary()} | {:error | String.t} def encode(binary) do if byte_size(binary) <= 254 do {:ok, do_encode(<<>>, <<>>, binary)} else {:error, "Binary too long"} end end @spec do_encode(binary(), binary(), binary()) :: binary() defp do_encode(head, block, tail) defp do_encode(head, block, <<>>), do: head <> <> <> block defp do_encode(head, block, <<0, tail :: binary>>), do: do_encode(head <> <> <> block, <<>>, tail) defp do_encode(head, block, <>), do: do_encode(head, block <> <>, tail) @doc """ Encode a binary (with `0` bytes) into a COBS encoded binary (without `0` bytes). Raise an `ArgumentError` on input data exceeding the allowed length (254 bytes) ## Example iex> Cobs.encode!(<<0x01, 0x02, 0x00, 0x03>>) <<0x03, 0x01, 0x02, 0x02, 0x03>> """ @spec encode!(binary()) :: binary() def encode!(binary) do case encode(binary) do {:ok, result} -> result {:error, message} -> raise ArgumentError, message _ -> raise ArgumentError end end @doc """ Decode COBS encoded binary (without `0` bytes) into a binary (with `0` bytes). ## Example iex> Cobs.decode(<<0x03, 0x01, 0x02, 0x02, 0x03>>) {:ok, <<0x01, 0x02, 0x00, 0x03>>} """ @spec decode(binary()) :: {:ok, binary()} | {:error | String.t} def decode(binary) def decode(binary) do do_decode(<<>>, binary) end @spec do_decode(binary(), binary()) :: {:ok, binary()} | {:error | any()} defp do_decode(head, tail) defp do_decode(head, <<>>) do {:ok, head} end defp do_decode(head, <>) do block_length = ohb - 1 if block_length > byte_size(tail) do {:error, "Offset byte specifies more bytes than available"} else <> = tail new_head = if byte_size(remaining) > 0, do: head <> block <> <<0>>, else: head <> block do_decode(new_head, remaining) end end @doc """ Decode COBS encoded binary (without `0` bytes) into a binary (with `0` bytes). Raise an `ArgumentError` on invalid input data. ## Example iex> Cobs.decode!(<<0x03, 0x01, 0x02, 0x02, 0x03>>) <<0x01, 0x02, 0x00, 0x03>> """ @spec decode!(binary()) :: binary() def decode!(binary) do case decode(binary) do {:ok, result} -> result {:error, message} -> raise ArgumentError, message _ -> raise ArgumentError end end end