DEV Community

Cover image for NodeJS + ROHC
Stefan Werfling
Stefan Werfling

Posted on

NodeJS + ROHC

From the idea to implementation

I would like to introduce you to my idea and how it came about giving “ROHC” a binding in NodeJS.

I wanted to implement a VPN that runs over Web-Socket. The advantages would be that the service would be hidden over HTTPS. With HTTP3 this would be even more optimized. So I started playing around with the TunTap2 Module for NodeJS, which I had to patch first.

Always fascinated by wireless technology, at some point I came across “LoRa” and with it a project “IP2Lora”.

Image description

Image source

In this project “IP2Lora”, the IP packets were shortened to save 40 bytes, which is very important for the transfer; with a radio band of 434 MHz or 868 MHz, not that much can be transferred.

Image description

Image source

In the graphic you can clearly see how the IP packet size decreases.

Unfortunately there was only one lib binding for Python.

So why not write a node lib binding yourself!?

The result can now be seen.
https://www.npmjs.com/package/node-rohc

You can find out more about how ROHC works in the links to the project or simply search for it. I won't explain it here so as not to make the post too long.

Installation Lib

I installed under Linux Debian/Mint. I think this should be similar to other Linux versions.

(By the way, I also had to patch the ROHC-lib to the new kernel.)

sudo apt-get install autotools-dev
sudo apt-get install automake
sudo apt-get install libtool
sudo apt-get install libpcap-dev
sudo apt-get install -y libcmocka-dev

git clone https://github.com/stefanwerfling/rohc.git
cd rohc

./autogen.sh --prefix=/usr

make all
sudo make install
Enter fullscreen mode Exit fullscreen mode

Installation NPM

Now we can go into our project and install the module.

cd yourProject
npm i node-rohc
Enter fullscreen mode Exit fullscreen mode

Now we have to create the NodeJS binding (this has to be compiled for each CPU architecture itself).

cd yourProject/node_modules/node-rohc
npm run build --loglevel verbose
Enter fullscreen mode Exit fullscreen mode

The installation is now complete.

Coding/API usage

Now let's assume we get an IP packet that we want to compress into the following packets to save bytes.

const ipU8Packet = new Uint8Array(ipPacketBufferWithContent);
console.log(ipU8Packet);
Enter fullscreen mode Exit fullscreen mode
Uint8Array(52) [
   69,   0,   0,  52,   0,   0,   0,   0,  64,  6, 249,
  112, 192, 168,   0,   1, 192, 168,   0,   2, 72, 101,
  108, 108, 111,  44,  32, 116, 104, 105, 115, 32, 105,
  115,  32, 116, 104, 101,  32, 100,  97, 116, 97,  32,
  112,  97, 121, 108, 111,  97, 100,  33
]
Enter fullscreen mode Exit fullscreen mode

The module is now imported and the Unit8Array in which the IP packet is given to the Rhoc object for compression.

import {Rohc} from 'node-rohc';

const r = new Rohc([
  RohcProfiles.ROHC_PROFILE_UNCOMPRESSED,
  RohcProfiles.ROHC_PROFILE_IP,
  RohcProfiles.ROHC_PROFILE_TCP,
  RohcProfiles.ROHC_PROFILE_UDP,
  RohcProfiles.ROHC_PROFILE_ESP,
  RohcProfiles.ROHC_PROFILE_RTP
]);

try {
    const compress = r.compress(ipU8Packet);
    console.log(compress);
} catch (e) {
    console.error(e);
}
Enter fullscreen mode Exit fullscreen mode
Uint8Array(53) [
  253,   4,  69,  64,   6, 192, 168,   0,   1, 192, 168,
    0,   2,   0,  64,   0,   0,  32,   0, 251, 103,  72,
  101, 108, 108, 111,  44,  32, 116, 104, 105, 115,  32,
  105, 115,  32, 116, 104, 101,  32, 100,  97, 116,  97,
   32, 112,  97, 121, 108, 111,  97, 100,  33
]
Enter fullscreen mode Exit fullscreen mode

In the constructor of the Rohc object we specify the profiles that should be used for compression in an array.

Then comes compression. In the output we see the new package. But why isn't it smaller?

The first packet still contains the information about port/IP-Address etc. Only the following packets become significantly smaller.

To convert the Rohc packet back into a normal IP packet we use decompress.

try {
    const decompress = r.decompress(compress);
    console.log(decompress);
} catch (e) {
    console.error(e);
}
Enter fullscreen mode Exit fullscreen mode
Uint8Array(52) [
   69,   0,   0,  52,   0,   0,   0,   0,  64,  6, 249,
  112, 192, 168,   0,   1, 192, 168,   0,   2, 72, 101,
  108, 108, 111,  44,  32, 116, 104, 105, 115, 32, 105,
  115,  32, 116, 104, 101,  32, 100,  97, 116, 97,  32,
  112,  97, 121, 108, 111,  97, 100,  33
]
Enter fullscreen mode Exit fullscreen mode

What is important is the beginning, the first packet is compressed and transmitted to the destination and the destination has decompressed the packet, the instance must be maintained. So that the connection ID remains known. This means that the program has to keep the object instance running. If one of the two pages (source with compression or destination with decompression) is stopped, both pages must be restarted.

Additional function with useful information:

Last compress/decompress status

import {Rohc, RohcStatus} from 'node-rohc';

    if (r.getLastStatus() === RohcStatus.ROHC_OK) {
      console.log('All OK');
    }
Enter fullscreen mode Exit fullscreen mode

During compression or decompression, the status is remembered; this can be queried again immediately afterwards to get more detailed information about what happened.

Last compress/decompress packet info

console.log(r.compressLastPacketInfo());
console.log(r.decompressLastPacketInfo());
Enter fullscreen mode Exit fullscreen mode
{
  version_major: 0,
  version_minor: 0,
  context_id: 0,
  is_context_init: true,
  context_mode: 1,
  context_state: 1,
  context_used: true,
  profile_id: 4,
  packet_type: 0,
  total_last_uncomp_size: 52,
  header_last_uncomp_size: 20,
  total_last_comp_size: 53,
  header_last_comp_size: 21
}
{
  version_major: 0,
  version_minor: 0,
  context_mode: 2,
  context_state: 3,
  profile_id: 4,
  nr_lost_packets: 0,
  nr_misordered_packets: 0,
  is_duplicated: false,
  corrected_crc_failures: 11745388377929038000,
  corrected_sn_wraparounds: 14987979559889062000,
  corrected_wrong_sn_updates: 12105675798372346000,
  packet_type: 449595,
  total_last_comp_size: 18407961667527770000,
  header_last_comp_size: 1940628627783807,
  total_last_uncomp_size: 18407961667125117000,
  header_last_uncomp_size: 217316637802623
}
Enter fullscreen mode Exit fullscreen mode

Information about the last compression or decompression.

General compress/decompress info

console.log(r.compressGeneralInfo());
console.log(r.decompressGeneralInfo());
Enter fullscreen mode Exit fullscreen mode
{
  version_major: 0,
  version_minor: 0,
  contexts_nr: 1,
  packets_nr: 1,
  uncomp_bytes_nr: 52,
  comp_bytes_nr: 53
}
{
  version_major: 0,
  version_minor: 0,
  contexts_nr: 1,
  packets_nr: 1,
  comp_bytes_nr: 53,
  uncomp_bytes_nr: 52,
  corrected_crc_failures: 0,
  corrected_sn_wraparounds: 8518447232180027000,
  corrected_wrong_sn_updates: 4295000063
}
Enter fullscreen mode Exit fullscreen mode

General information about compression and decompression.

Final word

I hope you enjoyed my little post. I am always open to improvements.

Top comments (0)