Created
March 9, 2020 12:55
-
-
Save JavaCS3/c940bedbdbd84367dbb48cceb57a6b3a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const assert = require('assert') | |
const ENCODERS = { | |
raw(len) { | |
return function(v) { | |
assert(v instanceof Buffer) | |
const buffer = Buffer.alloc(len) | |
v.copy(buffer) | |
return buffer | |
} | |
}, | |
u8(v) { | |
return Buffer.from([v]) | |
}, | |
u16(v) { | |
const buffer = Buffer.alloc(2) | |
buffer.writeInt16LE(v) | |
return buffer | |
}, | |
u32(v) { | |
const buffer = Buffer.alloc(4) | |
buffer.writeInt32LE(v) | |
return buffer | |
}, | |
charn(len) { | |
return function(v) { | |
const buffer = Buffer.alloc(len || v.length) | |
buffer.write(v) | |
return buffer | |
} | |
} | |
} | |
const DECODERS = { | |
raw(len) { | |
return function(buffer, offset) { | |
assert(buffer instanceof Buffer) | |
assert(buffer.length >= offset + len) | |
return { | |
value: buffer.slice(offset, offset + len), | |
size: len | |
} | |
} | |
}, | |
u8(buffer, offset) { | |
return { | |
value: buffer.readUInt8(offset), | |
size: 1 | |
} | |
}, | |
u16(buffer, offset) { | |
return { | |
value: buffer.readUInt16LE(offset), | |
size: 2 | |
} | |
}, | |
u32(buffer, offset) { | |
return { | |
value: buffer.readUInt32LE(offset), | |
size: 4 | |
} | |
}, | |
charn(len) { | |
return function(buffer, offset) { | |
return { | |
value: buffer.toString('utf8', offset, offset + len), | |
size: len | |
} | |
} | |
} | |
} | |
class Rule { | |
constructor(name, encoder, decoder) { | |
assert(name, 'Invalid rule name') | |
assert(typeof encoder === 'function', 'Encoder must be a function') | |
assert(typeof decoder === 'function', 'Decoder must be a function') | |
this.name = name | |
this.encoder = encoder | |
this.decoder = decoder | |
} | |
encode(value) { | |
return this.encoder(value) | |
} | |
decode(buffer, offset) { | |
return this.decoder(buffer, offset) | |
} | |
} | |
class Rules { | |
constructor() { | |
this.rules = [] | |
} | |
size() { | |
return this.rules.length | |
} | |
*[Symbol.iterator]() { | |
for (let i = 0; i < this.rules.length; i++) { | |
yield this.rules[i] | |
} | |
} | |
getRuleNames() { | |
return this.rules.map((r) => r.name) | |
} | |
add(rule) { | |
this.rules.push(rule) | |
} | |
get(index) { | |
return this.rules[index] | |
} | |
encode(...args) { | |
assert(args.length === this.size(), 'Encoding arguments mismatch with struct schema rules') | |
let buffer = Buffer.alloc(0) | |
for (let i = 0; i < args.length; i++) { | |
const value = args[i] | |
const rule = this.rules[i] | |
buffer = Buffer.concat([buffer, rule.encoder(value)]) | |
} | |
return buffer | |
} | |
decode(buffer, clazz) { | |
let offset = 0 | |
let keys = [] | |
this.rules.forEach((rule) => { | |
const { value, size } = rule.decode(buffer, offset) | |
keys.push({ | |
name: rule.name, | |
value: value | |
}) | |
offset += size | |
}) | |
return new clazz(...keys.map((f) => f.value)) | |
} | |
} | |
function CStruct(name) { | |
let rules = new Rules() | |
class Struct { | |
constructor(...args) { | |
const len = Math.min(args.length, rules.size()) | |
for (let i = 0; i < len; i++) { | |
const value = args[i] | |
const rule = rules.get(i) | |
this[rule.name] = value | |
} | |
} | |
buffer() { | |
const keys = rules.getRuleNames() | |
const values = keys.map((k) => this[k]) | |
return rules.encode(...values) | |
} | |
static decode(buffer) { | |
return rules.decode(buffer, Struct) | |
} | |
static from(buffer) { | |
return rules.decode(buffer, Struct) | |
} | |
static raw(name, len) { | |
rules.add(new Rule(name, ENCODERS.raw(len), DECODERS.raw(len))) | |
return Struct | |
} | |
static u8(name) { | |
rules.add(new Rule(name, ENCODERS.u8, DECODERS.u8)) | |
return Struct | |
} | |
static u16(name) { | |
rules.add(new Rule(name, ENCODERS.u16, DECODERS.u16)) | |
return Struct | |
} | |
static u32(name) { | |
rules.add(new Rule(name, ENCODERS.u32, DECODERS.u32)) | |
return Struct | |
} | |
static charn(name, len) { | |
rules.add(new Rule(name, ENCODERS.charn(len), DECODERS.charn(len))) | |
return Struct | |
} | |
} | |
Object.defineProperty(Struct, 'name', { | |
value: name || 'Struct' | |
}) | |
return Struct | |
} | |
module.exports = { | |
CStruct | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { CStruct } = require('../cstruct') | |
test('Test define struct', () => { | |
const Point = CStruct() | |
.u8('x') | |
.u8('y') | |
const p = new Point(1, 2) | |
expect(p.x).toBe(1) | |
expect(p.y).toBe(2) | |
}) | |
test('Test define struct with name', () => { | |
const Point = CStruct('Point') | |
.u8('x') | |
.u8('y') | |
const p = new Point(1, 2) | |
expect(p.constructor.name).toBe('Point') | |
}) | |
test('Test define struct and change properties', () => { | |
const Point = CStruct() | |
.u8('x') | |
.u8('y') | |
const p = new Point(1, 2) | |
expect(p.x).toBe(1) | |
expect(p.y).toBe(2) | |
p.x = 100 | |
p.y = 200 | |
expect(p.x).toBe(100) | |
expect(p.y).toBe(200) | |
}) | |
test('Test get struct buffer', () => { | |
const Point = CStruct() | |
.u8('x') | |
.u8('y') | |
const p = new Point(1, 2) | |
expect(p.buffer()).toEqual(Buffer.from([0x01, 0x02])) | |
}) | |
test('Test get struct buffer when propety changed', () => { | |
const Point = CStruct() | |
.u8('x') | |
.u8('y') | |
const p = new Point(1, 2) | |
expect(p.buffer()).toEqual(Buffer.from([0x01, 0x02])) | |
p.x = 100 | |
p.y = 200 | |
expect(p.buffer()).toEqual(Buffer.from([100, 200])) | |
}) | |
test('Test struct encode and decode', () => { | |
const Point = CStruct() | |
.u8('x') | |
.u8('y') | |
const p = new Point(1, 2) | |
const p1 = Point.decode(p.buffer()) | |
expect(p1.x).toBe(1) | |
expect(p1.y).toBe(2) | |
}) | |
test('Test struct encode/decode strn', () => { | |
const Person = CStruct() | |
.charn('name', 10) | |
.charn('sex', 10) | |
const p = new Person('James', 'Male') | |
const buffer = Buffer.alloc(20) | |
buffer.write('James') | |
buffer.write('Male', 10) | |
expect(p.buffer()).toEqual(buffer) | |
const p1 = Person.from(buffer) | |
expect(p1.name.replace(/\0/g, '')).toBe('James') // https://github.com/nodejs/node/issues/4775 | |
expect(p1.sex.replace(/\0/g, '')).toBe('Male') | |
}) | |
test('Test struct encode/decode raw', () => { | |
const Product = CStruct() | |
.charn('name', 10) | |
.raw('description', 100) | |
const p = new Product('MacBook', Buffer('A laptop produced by Apple Inc.')) | |
const buffer = Buffer.alloc(110) | |
buffer.write('MacBook') | |
buffer.write('A laptop produced by Apple Inc.', 10, 100) | |
expect(p.buffer()).toEqual(buffer) | |
const p1 = Product.from(buffer) | |
expect(p1.name.replace(/\0/g, '')).toBe('MacBook') // https://github.com/nodejs/node/issues/4775 | |
expect(p1.description.toString().replace(/\0/g, '')).toBe('A laptop produced by Apple Inc.') | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment