<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">\documentclass{article}

\usepackage{tabularx, tikz, amsmath, gensymb, titlepic, graphicx, listings}
\usepackage[margin=1in]{geometry}

\usetikzlibrary{shapes,arrows,fit}
\tikzstyle{block} = [draw, rectangle, text width=2cm, text centered, minimum height=1.2cm, node distance=3cm]

\definecolor{codegreen}{rgb}{0,0.6,0}
\definecolor{codegray}{rgb}{0.5,0.5,0.5}
\definecolor{codepurple}{rgb}{0.58,0,0.82}
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}

\lstdefinestyle{mystyle}{
    backgroundcolor=\color{backcolour},
    commentstyle=\color{codegreen},
    keywordstyle=\color{magenta},
    numberstyle=\tiny\color{codegray},
    stringstyle=\color{codepurple},
    basicstyle=\ttfamily\footnotesize,
    breakatwhitespace=false,
    breaklines=true,
    captionpos=b,
    keepspaces=true,
    numbers=left,
    numbersep=5pt,
    showspaces=false,
    showstringspaces=false,
    showtabs=false,
    tabsize=2
}

\lstset{style=mystyle}

\begin{document}

\title{CATS --- Communication And Tracking System \\ \large Protocol Standard}
\author{scd31.com}
\titlepic{\includegraphics{logo/logo.png}}
\maketitle

\newpage

\begin{abstract}
  APRS, while an excellent tool, is over 20 years old, and is beginning to show its age. The protocol itself is overly complex, and not particularly efficient. The modulation on which it rests --- AFSK over FM --- is also sub-optimal. The purpose of CATS is to design a modernized solution that overcomes these challenges by utilizing the latest technologies. CATS aims to deliver enhanced efficiency, simplicity in implementation, and improved performance compared to APRS.
\end{abstract}

\newpage

\tableofcontents

\newpage

\section{Introduction}

\subsection{Features of CATS}

CATS has many exciting features over APRS:

\begin{itemize}
\item 2-FSK is used instead of FM-AFSK for a 12 dB coding gain
\item Forward Error Correction (LDPC), further improving the coding gain
\item Bit-rate increased from 1200 bits/s to 9600 bits/s
\item Maximum packet size 8191 bytes
\item Data whitening used to prevent receiver desynchronization
\item The 70cm band is used instead of the 2m band by default
  \begin{itemize}
  \item Since 2m is more common for voice, it makes it easy to add CATS to an existing setup while still being able to use 2m repeaters --- with full duplexing, so that CATS transmissions do not cause loss of reception on 2m
  \end{itemize}
\item FELINET, which is the CATS equivalent of APRS-IS, will push and pull messages from APRS-IS to maintain some compatibility, at least initially. The eventual goal is to drop this link.
\end{itemize}

\subsection{The Pipeline}

CATS packets are created from raw information using the following flow. For reception, the pipeline is reversed. Note that all multi-byte fields are encoded little-endian unless otherwise indicated. \\

\begin{tikzpicture}

    \node [block, name=text1] {Raw data};
    \node [block, right of=text1] (text2) {Whiskers};
    \node [block, right of=text2] (text3) {CRC};
    \node [block, right of=text3] (text4) {Whitener};
    \node [block, below of=text4] (text5) {LDPC};
    \node [block, below of=text3] (text6) {Interleaver};
    \node [block, below of=text2] (text7) {Header};
    \node [block, below of=text1] (text8) {RF};

    \draw [-&gt;] (text1) -- (text2);
    \draw [-&gt;] (text2) -- (text3);
    \draw [-&gt;] (text3) -- (text4);
    \draw [-&gt;] (text4) -- (text5);
    \draw [-&gt;] (text5) -- (text6);
    \draw [-&gt;] (text6) -- (text7);
    \draw [-&gt;] (text7) -- (text8);

\end{tikzpicture}

\subsection{Invalid CATS Packets}

The APRS standard is plagued by the existence of invalid packets. A good APRS parser not only handles valid packets --- it also handles slightly malformed ones. Different parsers may handle malformed packets differently, since by definition, they are not in a unified standard. This complicates new implementations and leads to fragmentation of the ecosystem.

CATS aims to avoid this problem. A packet that does not conform rigidly to the standard must have its contents discarded. Even if only one whisker is malformed, all whiskers must be ignored. It is considered an implementation bug if this does not occur. Note that unknown whisker types are not invalid. This allows for future whisker types to be added without breaking compatibility with existing implementations.

\section{Whiskers}

\subsection{Overview}

One main feature of CATS is that packets are constructed from Whiskers. Each Whisker represents one possible attribute of data. One CATS packet can have up to 255 Whiskers, so long as the 8191-byte limit is respected. There are many types of Whiskers:

\begin{table}[!ht]
\setlength\extrarowheight{2pt}
\begin{tabularx}{\textwidth}{|X|X|}
  \hline
  \textbf{Whisker Type} &amp; \textbf{Notes} \\
  \hline
  Identification &amp; Contains the source callsign \\
  \hline
  Timestamp &amp; \\
  \hline
  GPS &amp; \\
  \hline
  Comment &amp; \\
  \hline
  Route &amp; The path that the packet took because the source station and the current station \\
  \hline
  Destination &amp; Who the CATS packet is destined for \\
  \hline
  Arbitrary &amp; Arbitrary array of bytes, intentionally not understood by CATS \\
  \hline
  Simplex &amp; A frequency for voice communications \\
  \hline
  Repeater &amp; Information for communicating with a repeater \\
  \hline
\end{tabularx}
\end{table}

\subsection {Structure}

Each Whisker has the same general structure.

\begin{tabular}{|l|l|l|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Data} \\
  \hline
  0 &amp; 1 &amp; Whisker type \\
  \hline
  1 &amp; 1 &amp; Whisker length in bytes $N$ \\
  \hline
  2 &amp; $N$ &amp; Whisker data \\
  \hline
\end{tabular} \\

If there are multiple Whiskers in a CATS packet, their bytes are concatenated together. Since the Whisker length is encoded as a single byte, it means the actual data (not including the type or length bytes) is a maximum of 255 bytes per Whisker. Also, because the length is encoded in the same place in each Whisker, a decoder does not need to support every type. If it sees a type it does not recognize, it can skip the next $N$ bytes and continue onto the next Whisker.

\section{CRC}

\subsection{Overview}

A CRC checksum is used to distinguish when a CATS packet is corrupt. In this case, the packet should be discarded.

\subsection{Structure}

The CRC algorithm used is the 16-bit IBM SDLC CRC algorithm. \\

\begin{tabular}{|l|l|l|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Data} \\
  \hline
  0 &amp; $N$ &amp; Compressed Data \\
  \hline
  $N$ &amp; 2 &amp; CRC checksum \\
  \hline
\end{tabular}

\section{Whitener}

\subsection{Overview}

Data whitening is a technique employed to enhance the randomness of data, making it resemble white noise to a greater extent. This process offers several advantages, with the elimination of prolonged sequences of repetitive bits being the most significant one. Ensuring such elimination is crucial to prevent synchronization issues between the transmitter and receiver.

In CATS, each 16 bytes of data is XORed with the hex string ``e9cf 6720 191a 07dc c072 7997 51f7 dd93'' to whiten the data.

\section{LDPC}

\subsection{Overview}

In APRS, a single flipped bit results in a ruined packet. In CATS, Forward Error Correction (FEC) is used to make the protocol tolerant of bit flips. LDPC has been chosen, as it is a mature algorithm which operates close to the Shannon Limit.

\subsection{Algorithm}

Like APRS, CATS packets are variable-length. This is advantageous as packets that contain small amounts of data don't need to be padded; this increases efficiency. However, LDPC encoding requires data to be of a constant width. The solution is to break our data into chunks, so that we can LDPC-encode each chunk individually.

The structure of the data chunks is not encoded in the packet. Instead, the data is broken into chunks deterministically. The same algorithm can be used by the receiver to break the chunks apart, decode them, and combine the resulting data together. The valid LDPC codes are TC128, TC256, TC512, TM2048, and TM8192, all with $r=1/2$. These codes are specified in detail in CCSDS document 231.1-0-1. The algorithm works as follows:

\begin{enumerate}
\item Pick the largest code with an input size $M \le N$
\item Take the first M bytes of data and pass them through the LDPC encoder
\item Set $N \leftarrow M - N$
\item Go to step 1, unless $N \le 32$ bits
\item Pad the remaining data with 0xAA to make it 8 bytes long
\item Run the padded data through TC128 encoder
\end{enumerate}

Note that on the final step, the 0xAA padding doesn't end up in the final packet. Only the original data and the parity data is included. \\

For example, imagine our data after adding the parity CRC is 2349 bytes long. The algorithm would work as follows:
\begin{enumerate}
\item Encode the first 512 bytes with TM8192 (1837 bytes remaining)
\item Encode the next 512 bytes with TM8192 (1325 bytes remaining)
\item Encode the next 512 bytes with TM8192 (813 bytes remaining)
\item Encode the next 512 bytes with TM8192 (301 bytes remaining)
\item Encode the next 128 bytes with TM2048 (173 bytes remaining)
\item Encode the next 128 bytes with TM2048 (45 bytes remaining)
\item Encode the next 32 bytes with TC512 (13 bytes remaining)
\item Encode the next 8 bytes with TC128 (5 bytes remaining)
\item Pad the remaining 5 bytes out to 8 bytes with 0xAA 0xAA 0xAA, then encode with TC128
\end{enumerate}

Note that the final packet length will be:
\begin{align*}
  L &amp;= 2 + N + (512 * 4) + (128 * 2) + 32 + (8 * 2)\\
  &amp;= 4703
\end{align*}

\subsection{Structure}

\begin{tabularx}{\textwidth}{|l|l|X|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Data} \\
  \hline
  0 &amp; $D_1$ &amp; First data chunk \\
  \hline
  $D_1$ &amp; $D_2$ &amp; Second data chunk \\
  \hline
  $D_1 + D_2$ &amp; $D_3$ &amp; Third data chunk \\
  \hline
  ... &amp; ... &amp; ... \\
  \hline
  $\sum_{i=1}^{n-1} D_i$ &amp; $D_n$ &amp; $n$th data chunk \\
  \hline
  $\sum_{i=1}^{n} D_i$ &amp; $P_1$ &amp; LDPC parity for first chunk \\
  \hline
  $\sum_{i=1}^{n} D_i + P_1$ &amp; $P_2$ &amp; LDPC parity for second chunk \\
  \hline
  $\sum_{i=1}^{n} D_i + P_1 + P_2$ &amp; $P_3$ &amp; LDPC parity for third chunk \\
  \hline
  ... &amp; ... &amp; ... \\
  \hline
  $\sum_{i=1}^{n} D_i + \sum_{i=1}^{n-1} P_i$ &amp; $P_n$ &amp; LDPC parity for $n$th chunk \\
  \hline
  $\sum_{i=1}^{n} D_i + \sum_{i=1}^{n} P_i$ &amp; 2 &amp; Number of bytes after adding CRC parity (pre-LDPC encoding) \\
  \hline
\end{tabularx}

\section{Interleaver}

\subsection{Overview}

LDPC makes our CATS packet robust against single bit flips. However, it is not particularly robust against burst errors. This is because a burst will flip several bits in only one (or possibly two) of our LDPC chunks, while the rest remain untouched. If too many bits flip in a single chunk, it will not be possible to recover. Instead, it is better to split this burst across as many chunks as possible, to lower the likelihood that a chunk will be unrecoverable. The interleaver is responsible for reordering the CATS bits in such a way to be robust against bit flips.

In CATS, a 32-bit block interleaver is used. For a bit-string input $b_0 b_1 b_2 b_3 ... b_n$, the output from the interleaver is $b_0 b_{32} b_{64} ... b_1 b_{33} b_{65} ... b_3 b_{34} b_{66} ...$ . The receiver is responsible for de-interleaving the bit stream to get the original data.

\section{Header}

\subsection{Overview}

Before transmission, a header must be affixed to the packet. This is used so that the receiver can detect the packet and synchronize with the transmitter's phase. It's also used so that the receiver knows how many bytes to listen for before attempting to decode.

\subsection{Structure}

\begin{tabular}{|l|l|l|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Data} \\
  \hline
  0 &amp; 4 &amp; Preamble (0x55 0x55 0x55 0x55) \\
  \hline
  4 &amp; 4 &amp; Sync word (0xAB 0xCD 0xEF 0x12) \\
  \hline
  8 &amp; 2 &amp; Data length in bytes, $L$ \\
  \hline
  10 &amp; $L$ &amp; LDPC-encoded data \\
  \hline
\end{tabular}

Note that $L \le 8191$.

\section{RF}

\subsection{Overview}

After affixing the header, the packet is ready to be transmitted as RF. The following modulation parameters should be used:

\begin{itemize}
\item Modulation Scheme: 2-FSK
\item Bit rate: 9600 bits/s
\item Deviation: 4.8 KHz
\item Frequency: 430.500 MHz
\end{itemize}

\section{Whisker Types}

\subsection{Identification}

As an example of CATS' extreme flexibility, even the packet's identification exists in a whisker. When using CATS as intended, this whisker will likely always be included. There are some situations where it may not be, however. For example, to save data it could be omitted in some packets, as long as it is included often enough for legal station identification. It could also be omitted if CATS is being used outside of amateur frequencies. \textbf{A valid CATS packet contains a maximum of one identification whisker.}

The callsign is an arbitrary string of UTF-8 up to 252 bytes long. In most cases, it should be much shorter. One remaining byte at the end is for the SSID. This allows up to 255 different stations to share a single callsign.

The identification whisker also has 2 bytes which specify an icon. CATS icons are loosely equivalent to APRS symbols. Two bytes allows a maximum of 65536 different icons. For now, only a handful are defined, though this list will evolve with the standard. All icon IDs are valid, even those that don't currently correspond with an icon. This property is essential to allow the icon list to grow while ensuring backwards-compatibility with older nodes.

\subsubsection{Structure}

\begin{tabularx}{\textwidth}{|l|l|l|X|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Value} &amp; \textbf{Description} \\
  \hline
  0 &amp; 1 &amp; 0x00 &amp; Whisker type \\
  \hline
  1 &amp; 1 &amp; $N$ &amp; Whisker length in bytes \\
  \hline
  2 &amp; 2 &amp; &amp; Icon ID \\
  \hline
  4 &amp; $N - 3$ &amp; &amp; Callsign data \\
  \hline
  $N + 1$ &amp; 1 &amp; &amp; SSID \\
  \hline
\end{tabularx}

\subsubsection{Icon Table}

\begin{tabular}{|l|l|}
  \hline
  \textbf{Icon ID} &amp; \textbf{Icon Name} \\
  \hline
  0 &amp; [No Icon] \\
  \hline
  1 &amp; Cat \\
  \hline
  2 &amp; Car \\
  \hline
  3 &amp; House \\
  \hline
  4 &amp; Office building \\
  \hline
  5 &amp; Truck \\
  \hline
  6 &amp; Van \\
  \hline
  7 &amp; Red Square \\
  \hline
  8 &amp; Orange Square \\
  \hline
  9 &amp; Yellow Square \\
  \hline
  10 &amp; Green Square \\
  \hline
  11 &amp; Blue Square \\
  \hline
  12 &amp; Purple Square \\
  \hline
  13 &amp; Balloon \\
  \hline
  14 &amp; Airplane \\
  \hline
  15 &amp; Helicopter \\
  \hline
  16 &amp; Bus \\
  \hline
  17 &amp; Train \\
  \hline
  18 &amp; Person \\
  \hline
  19 &amp; Warning \\
  \hline
  20 &amp; Error \\
  \hline
  21 &amp; Satellite \\
  \hline
  22 &amp; Computer \\
  \hline
  23 &amp; Bicycle \\
  \hline
\end{tabular}

\subsection{Timestamp}

The timestamp whisker is used to mark the time that a CATS packet occurred at. It is encoded in the Unix timestamp --- that is, seconds since January 1, 1970, in the UTC timezone.

\subsubsection{Structure}

\begin{tabular}{|l|l|l|l|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Value} &amp; \textbf{Description} \\
  \hline
  0 &amp; 1 &amp; 0x01 &amp; Whisker type \\
  \hline
  1 &amp; 1 &amp; 5 &amp; Whisker length in bytes \\
  \hline
  2 &amp; 5 &amp; &amp; Current time (Unix timestamp) \\
  \hline
\end{tabular}

\subsection{GPS}

The GPS whisker is used to encode latitude, longitude, altitude, precision, heading, and speed. The latitude, longitude, and altitude define a precise point in 3D space. An error is specified in meters. This defines a sphere in space, of which the actual location is somewhere inside. If the latitude, longitude, and altitude are precise, the error should be 0. \textbf{A valid CATS packet contains a maximum of one GPS whisker.}

\subsubsection{Structure}

{\def\arraystretch{1.3}
\begin{tabularx}{\textwidth}{|l|l|l|X|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Value} &amp; \textbf{Description} \\
  \hline
  0 &amp; 1 &amp; 0x02 &amp; Whisker type \\
  \hline
  1 &amp; 1 &amp; 14 &amp; Whisker length in bytes \\
  \hline
  2 &amp; 4 &amp; &amp; Latitude (signed integer, ${2^{31} \over 90 \degree}x$) \\
  \hline
  6 &amp; 4 &amp; &amp; Longitude (signed integer, ${2^{31} \over 180 \degree}x$) \\
  \hline
  10 &amp; 2 &amp; &amp; Altitude (16-bit float, meters) \\
  \hline
  12 &amp; 1 &amp; &amp; Maximum location error (unsigned integer, meters) \\
  \hline
  13 &amp; 1 &amp; &amp; Heading (unsigned integer, radians$* {128\over{\pi}}$). Clockwise relative to north \\
  \hline
  14 &amp; 2 &amp; &amp; Speed (16-bit float, meters per second) \\
  \hline
\end{tabularx}
}

\subsection{Comment}

The comment whisker specifies a textual comment on the CATS packet. The content is an arbitrary byte string. \textbf{A valid CATS packet may contain zero or more comment whiskers.} In this case, their content should be concatenated together. This allows for comments longer than 255 bytes. After concatenation, the result must be valid UTF-8. Note that an individual comment whisker may or may not be valid UTF-8.

\subsubsection{Structure}

\begin{tabular}{|l|l|l|l|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Value} &amp; \textbf{Description} \\
  \hline
  0 &amp; 1 &amp; 0x03 &amp; Whisker type \\
  \hline
  1 &amp; 1 &amp; $N$ &amp; Whisker length in bytes \\
  \hline
  2 &amp; $N$ &amp; &amp; Comment content \\
  \hline
\end{tabular}

\subsection{Route}

The route of a CATS packet is an ordered list of stations which have digipeated the packet. Each station consists of a callsign of arbitrary length, as well as a 1-byte SSID. The callsign must be valid UTF-8. This is identical to what is allowed in an identification whisker. \textbf{A valid CATS packet contains a maximum of one route whisker.}

Each callsign is represented as a set of bytes, which are valid UTF-8. After the last byte of the callsign should be a single byte of 0xFF, which acts as a delimiter. The next byte is the SSID for that callsign. Following that is the beginning of the next callsign. To represent a hop over the Internet, the byte 0xFE is used. This byte must be put where the start of a callsign is detected. The following byte will be used as the start of the callsign for the following hop. Multiple 0xFE bytes may be used in succession to represent multiple hops over the Internet.

Instead of the 0xFF delimiter, 0xFD may be used. This signifies that the previous callsign is not part of the route yet. The first callsign in the route with a 0xFD delimiter must be the next digipeater. No other digipeaters should repeat the packet. A route may consist of a mixture of 0xFF and 0xFD delimiters, as long as there are no 0xFF delimiters after the first 0xFD delimiter.

A callsign with a 0xFF delimiter is known as a ``past'' hop. A callsign with a 0xFD delimiter is known as a ``future'' hop.

The route whisker also contains a maximum amount of hops. This does not include Internet links. The maximum hops is not decremented --- instead, the number of callsigns in the route whisker is compared to the maximum number of hops to determine if a packet should be digipeated.

When digipeating, the callsign of the digipeater should be added to the route. The CATS packet should not be modified beyond this.

\subsubsection{Structure}

\begin{tabular}{|l|l|l|l|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Value} &amp; \textbf{Description} \\
  \hline
  0 &amp; 1 &amp; 0x04 &amp; Whisker type \\
  \hline
  1 &amp; 1 &amp; $N$ &amp; Whisker length in bytes \\
  \hline
  2 &amp; 1 &amp; &amp; Maximum allowable digipeats \\
  \hline
  3 &amp; $N - 1$ &amp; &amp; Callsigns and SSIDs \\
  \hline
\end{tabular}

\subsubsection{Examples}

\begin{tabularx}{\textwidth}{|X|l|X|l|}
  \hline
  \textbf{Route} &amp; \textbf{Max \# hops} &amp; \textbf{Whisker Data (Hex)} &amp; \textbf{Should digipeat?} \\
  \hline
  VE1ABC\~{}0 \break VE2DEF\~{}234 \break VE3XYZ\~{}14 &amp; 4 &amp; 04 19 04 56 45 31 41 42 43 FF 00 56 45 32 44 45 46 FF EA 56 45 33 58 59 5A FF 0E &amp; Yes  \\
  \hline
  VE1ABC\~{}0 \break [FELINET] \break VE2DEF\~{}234 \break [FELINET] \break VE3XYZ\~{}14 &amp; 3 &amp; 04 1B 03 56 45 31 41 42 43 FF 00 FE 56 45 32 44 45 46 FF EA FE 56 45 33 58 59 5A FF 0E &amp; Yes \\
  \hline
  VE1ABC\~{}0 \break [FELINET] \break [FELINET] &amp; 0 &amp; 04 0B 00 56 45 31 41 42 43 FF 00 FE FE &amp; No \\
  \hline
\end{tabularx}

\subsection{Destination}

CATS packets can optionally have one or more destinations. This can be useful for e.g. sending a message to another amateur radio operator, or for communicating with a service. The destination consists of a UTF-8 callsign and an SSID byte. \textbf{A valid CATS packet may contain zero or more destination whiskers.}

The destination whisker also allows requesting an acknowledgement from the station, to confirm that the packet was received successfully. This is specified in the acknowledgement byte. If the byte is 0, no acknowledgement is requested. Otherwise, an acknowledgement ID is specified in the 7 least significant bits. The MSB must be cleared. To acknowledge, a CATS packet must be crafted with a destination of the original station's callsign. The acknowledgement byte must have the same acknowledgement ID, but with the MSB set. The MSB is how acknowledgement requests are differentiated from acknowledgement responses.

\subsubsection{Structure}

\begin{tabular}{|l|l|l|l|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Value} &amp; \textbf{Description} \\
  \hline
  0 &amp; 1 &amp; 0x05 &amp; Whisker type \\
  \hline
  1 &amp; 1 &amp; $N$ &amp; Whisker length in bytes \\
  \hline
  2 &amp; 1 &amp; &amp; Acknowledgement byte \\
  \hline
  3 &amp; $N - 2$ &amp; &amp; UTF-8 callsign \\
  \hline
  $N - 1$ &amp; 1 &amp; &amp; SSID \\
  \hline
\end{tabular}

\subsection{Arbitrary}

The arbitrary whisker is a special type of whisker. Its content is intentionally not understood by the CATS standard. Instead, any content can be encoded. Most stations will ignore this content. This is also useful if the same packet is to be sent multiple times. To prevent de-duplication by receiving nodes, a different arbitrary whisker can be affixed to each one. \textbf{A valid CATS packet may contain zero or more arbitrary whiskers.}

\subsubsection{Structure}

\begin{tabular}{|l|l|l|l|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Value} &amp; \textbf{Description} \\
  \hline
  0 &amp; 1 &amp; 0x06 &amp; Whisker type \\
  \hline
  1 &amp; 1 &amp; $N$ &amp; Whisker length in bytes \\
  \hline
  2 &amp; $N$ &amp; &amp; Data \\
  \hline
\end{tabular}

\subsection{Simplex}

A CATS station may want to articulate a frequency for voice purposes. This would usually be for transmitting what frequencies the station is monitoring. Each simplex whisker represents a single frequency. \textbf{A valid CATS packet may contain zero or more simplex whiskers.}

\subsubsection{Structure}

{\def\arraystretch{1.3}
\begin{tabular}{|l|l|l|l|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Value} &amp; \textbf{Description} \\
  \hline
  0 &amp; 1 &amp; 0x07 &amp; Whisker type \\
  \hline
  1 &amp; 1 &amp; 6 &amp; Whisker length in bytes \\
  \hline
  2 &amp; 4 &amp; &amp; Frequency (unsigned integer, Hz) \\
  \hline
  6 &amp; 1 &amp; &amp; Modulation type (See modulation table) \\ % TODO
  \hline
  7 &amp; 1 &amp; &amp; Power (unsigned integer, dBm, 255 = unknown) \\
  \hline
\end{tabular}
}

\subsubsection{Modulation Table}

\begin{tabular}{|l|l|}
  \hline
  \textbf{Modulation ID} &amp; \textbf{Modulation type} \\
  \hline
  0 &amp; Unknown/unlisted \\
  \hline
  1 &amp; CATS \\
  \hline
  2 &amp; FM \\
  \hline
  3 &amp; AM \\
  \hline
  4 &amp; USB \\
  \hline
  5 &amp; LSB \\
  \hline
  6 &amp; CW \\
  \hline
  7 &amp; FreeDV \\
  \hline
  8 &amp; M17 \\
  \hline
  9 &amp; D-STAR \\
  \hline
  10 &amp; DMR \\
  \hline
  11 &amp; Fusion \\
  \hline
  12 &amp; P25 \\
  \hline
\end{tabular}

\subsection{Repeater}

A CATS station may want to articulate repeater information. For instance, a user may want to transmit the repeaters they are monitoring, or a ham club may wish to transmit a list of repeaters for their local area. Hypothetically, a CATS-compliant radio could listen to these repeater whiskers and automatically program them into its own memory. \textbf{A valid CATS packet may contain zero or more repeater whiskers.}

Each repeater whisker represents a single repeater.

\subsubsection{Structure}

{\def\arraystretch{1.3}
\begin{tabular}{|l|l|l|l|}
  \hline
  \textbf{Byte offset} &amp; \textbf{Length} &amp; \textbf{Value} &amp; \textbf{Description} \\
  \hline
  0 &amp; 1 &amp; 0x08 &amp; Whisker type \\
  \hline
  1 &amp; 1 &amp; $N$ &amp; Whisker length in bytes \\
  \hline
  2 &amp; 4 &amp; &amp; Uplink frequency (unsigned integer, Hz) \\
  \hline
  6 &amp; 4 &amp; &amp; Downlink frequency (unsigned integer, Hz) \\
  \hline
  10 &amp; 1 &amp; &amp; Modulation type (See simplex whisker) \\
  \hline
  11 &amp; 3 &amp; &amp; Tone information (See tones below) \\
  \hline
  14 &amp; 1 &amp; &amp; Power (unsigned integer, dBm, 255 = unknown) \\
  \hline
  15 &amp; 2 &amp; &amp; Latitude (signed integer, ${2^{15} \over 90 \degree}x$) \\
  \hline
  17 &amp; 2 &amp; &amp; Longitude (signed integer, ${2^{15} \over 180 \degree}x$) \\
  \hline
  19 &amp; $N - 17$ &amp; &amp; Repeater name (UTF-8) \\
  \hline
\end{tabular}
}

\subsubsection{Tones}

Uplink and downlink tones are encoded in 3 bytes. CTCSS and DCS are both supported. The uplink tone is encoded in the 12 MSBs and the downlink tone is encoded in the 12 LSBs. \newline

\noindent
\begin{tabular}{|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|}
  \hline
  MSB &amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp; LSB \\
  \hline
  23 &amp; 22 &amp; 21 &amp; 20 &amp; 19 &amp; 18 &amp; 17 &amp; 16 &amp; 15 &amp; 14 &amp; 13 &amp; 12 &amp; 11 &amp; 10 &amp; 9 &amp; 8 &amp; 7 &amp; 6 &amp; 5 &amp; 4 &amp; 3 &amp; 2 &amp; 1 &amp; 0 \\
  \hline
  $U_e$ &amp; 0 &amp; \multicolumn{10}{|c|}{$U_v$} &amp; $D_e$ &amp; 0 &amp; \multicolumn{10}{|c|}{$D_v$} \\
  \hline
\end{tabular} \\
\newline \noindent
Where:

\begin{itemize}
\item $U_e$ and $D_e$ represent the encoding type (0 for CTCSS, 1 for DCS)
\item $U_v$ and $D_v$ represent the tone value
  \begin{itemize}
  \item If the type is CTCSS, the value is an unsigned integer from the CTCSS table (see below)
  \item If the type is DCS, the value is the DCS code, encoded as an unsigned integer
  \item To encode no tone, set the type to CTCSS (0) and set the value to 0
  \end{itemize}
\end{itemize}

\subsubsection{CTCSS Table}

\begin{tabular}{|l|l|l|l|l|l|l|l|}
  \hline
  \textbf{Tone ID} &amp; \textbf{PL tone (Hz)} &amp; &amp; \textbf{Tone ID} &amp; \textbf{PL tone (Hz)} &amp; &amp; \textbf{Tone ID} &amp; \textbf{PL tone (Hz)} \\
  \hline
  0 &amp; [No tone] &amp; &amp; &amp; &amp; &amp; &amp; \\
  \hline
  1 &amp; 67.0 &amp; &amp; 15 &amp; 107.2 &amp; &amp; 29 &amp; 173.8 \\
  \hline
  2 &amp; 69.3 &amp; &amp; 16 &amp; 110.9 &amp; &amp; 30 &amp; 179.9 \\
  \hline
  3 &amp; 71.9 &amp; &amp; 17 &amp; 114.8 &amp; &amp; 31 &amp; 186.2 \\
  \hline
  4 &amp; 74.4 &amp; &amp; 18 &amp; 118.8 &amp; &amp; 32 &amp; 192.8 \\
  \hline
  5 &amp; 77.0 &amp; &amp; 19 &amp; 123.0 &amp; &amp; 33 &amp; 203.5 \\
  \hline
  6 &amp; 79.7 &amp; &amp; 20 &amp; 127.3 &amp; &amp; 34 &amp; 206.5 \\
  \hline
  7 &amp; 82.5 &amp; &amp; 21 &amp; 131.8 &amp; &amp; 35 &amp; 210.7 \\
  \hline
  8 &amp; 85.4 &amp; &amp; 22 &amp; 136.5 &amp; &amp; 36 &amp; 218.1 \\
  \hline
  9 &amp; 88.5 &amp; &amp; 23 &amp; 141.3 &amp; &amp; 37 &amp; 225.7 \\
  \hline
  10 &amp; 91.5 &amp; &amp; 24 &amp; 146.2 &amp; &amp; 38 &amp; 229.1 \\
  \hline
  11 &amp; 94.8 &amp; &amp; 25 &amp; 151.4 &amp; &amp; 39 &amp; 233.6 \\
  \hline
  12 &amp; 97.4 &amp; &amp; 26 &amp; 156.7 &amp; &amp; 40 &amp; 241.8 \\
  \hline
  13 &amp; 100.0 &amp; &amp; 27 &amp; 162.2 &amp; &amp; 41 &amp; 250.3 \\
  \hline
  14 &amp; 103.5 &amp; &amp; 28 &amp; 167.9 &amp; &amp; 42 &amp; 254.1 \\
  \hline
\end{tabular}

\section{FELINET}

\subsection{Introduction}

FELINET is the Internet counterpart of the CATS standard. Internet gates are responsible for sending received packets to FELINET. Packets received over FELINET can be gated back to RF. Although not part of the standard, it is possible for gateways to move packets between FELINET and APRS-IS. Not all CATS packets can be represented in APRS, so it is preferable to use FELINET when possible.

FELINET is composed of one or more servers. Each server has a list zero or more upstream servers, which it forwards received CATS packets to. Downstream servers request packets from the upstream servers over the same link. Internet gates, or IGates, connect to servers in the same fashion. From the perspective of an upstream server, there is no difference between an IGate and a downstream server.

A FELINET server must discard any received packets that are bitwise identical to any packets it has seen in the previous 10 seconds. This prevents unintentional loops from repeating packets indefinitely and bogging down the network.

To prepare raw data for FELINET, it must go through the Whiskers and CRC section of The Pipeline. In other words, CATS packets are gated to the FELINET network before the Whitener section of The Pipeline. A CATS packet in this binary form is referred to as a ``semi-encoded CATS packet''. It is only ``semi-encoded'' because it does not pass through the end stages of The Pipeline.

\subsection{gRPC}

FELINET nodes communicate with each other via gRPC. A FELINET server implements a $PushPackets$ method, for streaming packets to the server, and a $GetPackets$ method, for streaming packets from the server. The Proto file which defines the FELINET interface is as follows:

\lstinputlisting[language=C]{proto/felinet.proto}

\noindent There are a few points of note to clarify.
\begin{itemize}
\item In $PacketIn.raw$ and $PacketOut.raw$, the value is a semi-encoded CATS packet.
\item In $PacketFilter.filter$, a value must be specified.
\item In $PacketFilter.filter.all$, the value given must be ignored. Protobuf does not have a null type, so a uint32 is used. Again, this value does not signify anything, so a compliant FELINET server must ignore it.
\item In $DistanceFilter.distance$, the unit is meters.
\item In $CallsignFilter.ssid$, the valid values are 0-255, like in all other SSIDs. Protobuf does not have a fixed single-byte type, so a uint32 is used instead. If an invalid value is supplied, the server must reject it, and close the stream.
\end{itemize}

\subsection{Authentication}

FELINET defines a $Login$ endpoint, which is meant to be used for authentication. The exact usage of this endpoint is up to the server. For example, it may be used to allow any access, it may be used to allow write access, or it may even blindly accept all credentials. This endpoint is meant to be as flexible as possible. The $Login$ endpoint returns a UUID, which must be passed into future requests. This UUID is used to track state.

\subsection{Filtering}

When a FELINET node calls the $GetPackets$ method, it must specify an array of filters, which are used to limit what packets are sent to the node from the server. This is done primarily for bandwidth reasons. As such, the filters are very coarse. It is the node's responsibility to filter more on the client-side if required. There are several types of packet filters. When multiple filters are specified, packets which match any filters will be passed to the node. The filter types are as follows:

\begin{description}
\item [All] No filter. All valid packets the server receives will be forwarded to the node.
\item [Distance] Return only packets containing a GPS whisker within the specified latitude (degrees), longitude (degrees), and distance (meters).
\item [Callsign] Return only packets containing the specified callsign/SSID pair. This pair can appear in an identification whisker, route whisker, or destination whisker.
\item [Heard] Return only packets containing a destination or future hop that has been reported to the FELINET server by us in the last $n$ seconds. The value $n$ is specified in the filter. For example, if we send a packet to FELINET with a destination callsign/SSID pair of (ABCXYZ, 6), and less than $n$ seconds later, FELINET receives a packet with (ABCXYZ, 6) as a destination, that packet will be sent to us.
\end{description}

\subsection{Pings}

When a UUID is not used for a period of time, the FELINET server may choose to deallocate its corresponding state. This can be problematic for services that open a connection to FELINET but only send or receive data sparingly. The solution is the $Ping$ endpoint. This endpoint allows ``refreshing'' a UUID, and should be called every 10 minutes for as long as the UUID is used.

\end{document}
</pre></body></html>