defmodule RNS.HDLC do @moduledoc """ HDLC framing. This seems to be based on [HDLC](https://en.wikipedia.org/wiki/HDLC) but does not have addresses or anything, just the flag and escaping... """ @doc """ Frame raw data into HDLC. Escapes the data and puts it between two HDLC flags. The code might be a bit ugly, so let me explain it: It returns a binary, and you can see that it ends and starts with `0x7E`, which is the flag that show the start and end of the HDLC data. The complicated part is with the `String.replace/4`, it replaces the characters `0x7E`(the flag) and `0x7D`(the escape character) by `0x7D`(the escape character) and the character that is getting replaced xored with `0x20`. There is also `String.to_charlist/1` and `hd/1`, that's just to convert the binary(`String`) to an `Integer`. Basically, `<<0x7E>>` is replaced by `<<0x7D, 0x5E>>` and `<<0x7D>>` by `<<0x7D, 0x5D>>`. """ @spec frame(binary()) :: binary() def frame(raw) do <<0x7E, String.replace(raw, [<<0x7E>>, <<0x7D>>], fn char -> int = char |> String.to_charlist() |> hd <<0x7D, Bitwise.bxor(int, 0x20)>> end)::binary, 0x7E>> end @doc """ Unframes HDLC data. Removes HDLC flags or gives an error if they are not present, and unescapes the characters. """ @spec unframe(binary()) :: {:ok, binary()} | {:error, :no_hldc_flags} def unframe(raw) do case raw do <<0x7E, unframed::binary-size(byte_size(raw) - 2), 0x7E>> -> escaped = String.replace(unframed, [<<0x7D, 0x5E>>, <<0x7D, 0x5D>>], fn string -> int = String.at(string, 1) |> String.to_charlist() |> hd Bitwise.bxor(int, 0x20) end) {:ok, escaped} _ -> {:error, :no_hdlc_flags} end end end