Skip to content

Commit

Permalink
crc refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
latysheff committed Jan 5, 2018
1 parent 68b4c80 commit 23d0b01
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 214 deletions.
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Module implements sockets interface ([RFC6458]) in Node.js [Net] API.
Implementation of [RFC4960] is currently incomplete, use at your own risk!

Also raw-socket module seem to sometimes loose ip packets (extremely rarely). This is recovered by SCTP mechanisms of retransmission.

## Disable LK-SCTP

On Linux LK-SCTP should be disabled, because it conflicts with any other implementation. To prevent the "sctp" kernel module from being loaded, add the following line to a file in the directory "/etc/modprobe.d":
Expand Down Expand Up @@ -64,10 +66,43 @@ extra socket options:
* options.OS - requested outbound streams (integer, default: 2)
* options.logger - logger object for debugging purposes (e.g. console or log4js' logger)

**sctp.defaults(options)**

Function sets default module default parameters. Names follow net.sctp conventions.
See `sysctl -a | grep sctp`. Example:

```
sctp.defaults({
rto_initial: 500,
rto_min: 300,
rto_max: 1000,
sack_timeout: 150,
sack_freq: 2,
})
```
Returns current default parameters.

**sctp.protocol**

sctp.protocol is a dictionary object with [SCTP Payload Protocol Identifiers][ppid]

```
{
SCTP: 0,
IUA: 1,
M2UA: 2,
M3UA: 3,
SUA: 4,
M2PA: 5,
V5UA: 6,
H248: 7,
BICC: 8,
...
}
```

See example below.

**socket.SCTP_DEFAULT_SEND_PARAM(options)**

Set socket options related to write operations. Argument 'options' is an object with the following keys (all optional):
Expand Down Expand Up @@ -112,8 +147,7 @@ socket.on('data', function (buffer) {

## Credits
* Inspiration and some ideas are taken from [smpp] module
* crc32c function is from fast-crc32c module
* SerialNumber is from serial-arithmetic module
* CRC algorithm ported from https://pycrc.org/

[raw-socket]: https://www.npmjs.com/package/raw-socket
[Net]: https://nodejs.org/api/net.html
Expand Down
7 changes: 4 additions & 3 deletions lib/association.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Association extends EventEmitter {
}

this.log('trace', 'create association', options)

// todo provide also way to iterate
endpoint.associations[this.remoteAddress + ':' + this.remotePort] = this

Expand Down Expand Up @@ -1315,12 +1316,12 @@ class Association extends EventEmitter {
-> result
*/

if (this.state === 'CLOSED') {
this.log('trace', 'API SHUTDOWN in state', this.state)
if (this.state !== 'ESTABLISHED') {
this.log('trace', 'just destroy association')
this._destroy()
return
}
if (this.state === 'SHUTDOWN-RECEIVED') return
this.log('trace', 'API SHUTDOWN')
this.state = 'SHUTDOWN-PENDING'
/*
Upon receipt of the SHUTDOWN primitive from its upper layer, the
Expand Down
154 changes: 154 additions & 0 deletions lib/crc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
https://en.wikipedia.org/wiki/Cyclic_redundancy_check
Algorithms ported from https://pycrc.org/
*/

class CRC {
constructor(width, poly, xor_in, xor_out, reflect) {
this.width = width
this.poly = poly
this.xor_in = xor_in
this.xor_out = xor_out
this.reflect = reflect
this.msb_mask = 1 << (this.width - 1)
this.mask = ((this.msb_mask - 1) << 1) | 1
this.crc_shift = this.width < 8 ? 8 - this.width : 0
let table = this.gen_table()
this.table = table

// optimized for crc32c
if (width === 32 && reflect && xor_in === 0xffffffff && xor_out === 0xffffffff) {
this.calculate = function (buffer) {
buffer = preprocess(buffer)
let crc = -1
for (let i = 0; i < buffer.length; i++)
crc = table[(crc ^ buffer[i]) & 0xff] ^ (crc >>> 8)
return (crc ^ -1) >>> 0
}
}
}

calculate(buffer) {
buffer = preprocess(buffer)
let crc
if (this.reflect) {
crc = this.xor_in === 0xffffffff ? -1 : reflect(this.xor_in, this.width)
for (let i = 0; i < buffer.length; i++) {
let key = (crc ^ buffer[i]) & 0xff
crc = (this.table[key] ^ (crc >>> 8)) & this.mask
}
} else {
crc = this.xor_in << this.crc_shift
for (let i = 0; i < buffer.length; i++) {
let key = ((crc >> (this.width - 8 + this.crc_shift)) ^ buffer[i]) & 0xff
crc <<= 8 - this.crc_shift
crc ^= this.table[key] << this.crc_shift
crc &= this.mask << this.crc_shift
}
crc >>= this.crc_shift
}
crc ^= this.xor_out
return crc >>> 0
}

calculate_no_table(buffer) {
buffer = preprocess(buffer)
let crc = this.xor_in
for (let i = 0; i < buffer.length; i++) {
let octet = buffer[i]
if (this.reflect) octet = reflect(octet, 8)
for (let i = 0; i < 8; i++) {
let topbit = crc & this.msb_mask
if (octet & (0x80 >> i)) topbit ^= this.msb_mask
crc <<= 1
if (topbit) crc ^= this.poly
}
crc &= this.mask
}
if (this.reflect) crc = reflect(crc, this.width)
crc ^= this.xor_out
return crc >>> 0
}

gen_table() {
let table_length = 256
let table = []
for (let i = 0; i < table_length; i++) {
let reg = i
if (this.reflect) reg = reflect(reg, 8)
reg = reg << (this.width - 8 + this.crc_shift)
for (let j = 0; j < 8; j++) {
if ((reg & (this.msb_mask << this.crc_shift)) !== 0) {
reg <<= 1
reg ^= this.poly << this.crc_shift
} else {
reg <<= 1
}
}
if (this.reflect) reg = reflect(reg >> this.crc_shift, this.width) << this.crc_shift
reg = (reg >> this.crc_shift) & this.mask
table[i] = reg >>> 0
}
return table
}

print() {
let table = this.table
let digits = Math.ceil(this.width / 4)
let shift = (digits < 4) ? 4 : 3
let rows = table.length >> shift
let columns = 1 << shift
let result = ''
for (let r = 0; r < rows; r++) {
let row = ''
for (let c = 0; c < columns; c++) {
let val = table[r << shift | c]
val = '000000000' + val.toString(16)
val = val.substr(val.length - digits)
row += '0x' + val + ', '
}
result += ' ' + row + '\n'
}
result = '[\n' + result.slice(0, -3) + '\n]'
return result
}

}


function preprocess(data) {
if (Buffer.isBuffer(data)) return data
switch (typeof data) {
case 'number':
let buffer = Buffer.alloc(4)
buffer.writeUInt32BE(data)
return buffer
case 'string':
return Buffer.from(data)
default:
throw new Error()
}
}


function reflect(data, width) {
let res = 0
for (let i = 0; i < width; i++) {
res = res << 1 | data & 1
data >>= 1
}
return res
}


module.exports = {
CRC,
crc6: new CRC(6, 0x2F),
crc8: new CRC(8, 0x07),
crc10: new CRC(10, 0x233),
crc32: new CRC(32, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, true),
crc32c: new CRC(32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true)
}
38 changes: 37 additions & 1 deletion lib/defs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ let _ = require('lodash')
let ip = require('ip')

let net_sctp = {
G: 10,
G: 50, // granularity
RWND: 256000,
rto_initial: 3000,
rto_min: 1000,
Expand All @@ -25,6 +25,42 @@ let net_sctp = {
sack_freq: 2
}

/*
todo
sysctl -a | grep sctp
net.sctp.addip_enable = 0
net.sctp.addip_noauth_enable = 0
net.sctp.addr_scope_policy = 1
net.sctp.association_max_retrans = 10
net.sctp.auth_enable = 0
net.sctp.cookie_hmac_alg = sha1
net.sctp.cookie_preserve_enable = 1
net.sctp.default_auto_asconf = 0
net.sctp.hb_interval = 30000
net.sctp.max_autoclose = 2147483
net.sctp.max_burst = 4
net.sctp.max_init_retransmits = 8
net.sctp.path_max_retrans = 5
net.sctp.pf_retrans = 0
net.sctp.prsctp_enable = 1
net.sctp.rcvbuf_policy = 0
net.sctp.rto_alpha_exp_divisor = 3
net.sctp.rto_beta_exp_divisor = 2
net.sctp.rto_initial = 3000
net.sctp.rto_max = 60000
net.sctp.rto_min = 1000
net.sctp.rwnd_update_shift = 4
net.sctp.sack_timeout = 200
net.sctp.sctp_mem = 42486 56648 84972
net.sctp.sctp_rmem = 4096 865500 1812736
net.sctp.sctp_wmem = 4096 16384 1812736
net.sctp.sndbuf_policy = 0
net.sctp.valid_cookie_life = 60000
*/

let types = {
int8: {
read: function (buffer, offset) {
Expand Down
24 changes: 11 additions & 13 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,16 @@ function connect(options, connectListener) {
return socket
}

/*
module.exports.net_sctp = function (params) {
Object.assign(defs.net_sctp, params)
if (defs.net_sctp.sack_timeout > 500) defs.net_sctp.sack_timeout = 500
if (defs.net_sctp.RWND < 1500) defs.net_sctp.RWND = 1500
}
*/

function SCTP_RTOINFO(params) {
defs.net_sctp.rto_initial = params.rto_initial || defs.net_sctp.rto_initial
defs.net_sctp.rto_min = params.rto_min || defs.net_sctp.rto_min
defs.net_sctp.rto_max = params.rto_max || defs.net_sctp.rto_max
function defaults(params) {
params = params || {}
for (let param in defs.net_sctp) {
if (param in params) {
defs.net_sctp[param] = params[param]
}
}
if (defs.net_sctp.sack_timeout > 500) defs.net_sctp.sack_timeout = 500
if (defs.net_sctp.RWND < 1500) defs.net_sctp.RWND = 1500
return defs.net_sctp
}


Expand All @@ -53,5 +51,5 @@ module.exports = {
Server,
PPID: defs.PPID,
protocol: defs.PPID,
SCTP_RTOINFO
defaults
}
Loading

0 comments on commit 23d0b01

Please sign in to comment.