Skip to content

Commit

Permalink
Merge pull request #429 from tynes/rpc-validate-resource
Browse files Browse the repository at this point in the history
node rpc: validateresource
  • Loading branch information
boymanjor authored Apr 19, 2020
2 parents cb182f4 + d5d9821 commit c3d3aa2
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 32 deletions.
88 changes: 56 additions & 32 deletions lib/dns/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,11 @@ class Resource extends Struct {
}

fromJSON(json) {
assert(json && typeof json === 'object');
assert(Array.isArray(json.records));
assert(json && typeof json === 'object', 'Invalid json.');
assert(Array.isArray(json.records), 'Invalid records.');

for (const item of json.records) {
assert(item && typeof item === 'object');
assert(item && typeof item === 'object', 'Invalid record.');

const RD = stringToClass(item.type);

Expand Down Expand Up @@ -437,13 +437,19 @@ class DS extends Struct {
}

fromJSON(json) {
assert(json && typeof json === 'object');
assert(json.type === 'DS');
assert((json.keyTag & 0xffff) === json.keyTag);
assert((json.algorithm & 0xff) === json.algorithm);
assert((json.digestType & 0xff) === json.digestType);
assert(typeof json.digest === 'string');
assert((json.digest.length >>> 1) <= 255);
assert(json && typeof json === 'object', 'Invalid DS record.');
assert(json.type === 'DS',
'Invalid DS record. Type must be "DS".');
assert((json.keyTag & 0xffff) === json.keyTag,
'Invalid DS record. KeyTag must be a uint16.');
assert((json.algorithm & 0xff) === json.algorithm,
'Invalid DS record. Algorithm must be a uint8.');
assert((json.digestType & 0xff) === json.digestType,
'Invalid DS record. DigestType must be a uint8.');
assert(typeof json.digest === 'string',
'Invalid DS record. Digest must be a String.');
assert((json.digest.length >>> 1) <= 255,
'Invalid DS record. Digest is too large.');

this.keyTag = json.keyTag;
this.algorithm = json.algorithm;
Expand Down Expand Up @@ -495,9 +501,12 @@ class NS extends Struct {
}

fromJSON(json) {
assert(json && typeof json === 'object');
assert(json.type === 'NS');
assert(isName(json.ns));
assert(json && typeof json === 'object',
'Invalid NS record.');
assert(json.type === 'NS',
'Invalid NS record. Type must be "NS".');
assert(isName(json.ns),
'Invalid NS record. ns must be a valid name.');

this.ns = json.ns;

Expand Down Expand Up @@ -554,10 +563,13 @@ class GLUE4 extends Struct {
}

fromJSON(json) {
assert(json && typeof json === 'object');
assert(json.type === 'GLUE4');
assert(isName(json.ns));
assert(IP.isIPv4String(json.address));
assert(json && typeof json === 'object', 'Invalid GLUE4 record.');
assert(json.type === 'GLUE4',
'Invalid GLUE4 record. Type must be "GLUE4".');
assert(isName(json.ns),
'Invalid GLUE4 record. ns must be a valid name.');
assert(IP.isIPv4String(json.address),
'Invalid GLUE4 record. Address must be a valid IPv4 address.');

this.ns = json.ns;
this.address = IP.normalize(json.address);
Expand Down Expand Up @@ -615,10 +627,13 @@ class GLUE6 extends Struct {
}

fromJSON(json) {
assert(json && typeof json === 'object');
assert(json.type === 'GLUE6');
assert(isName(json.ns));
assert(IP.isIPv6String(json.address));
assert(json && typeof json === 'object', 'Invalid GLUE6 record.');
assert(json.type === 'GLUE6',
'Invalid GLUE6 record. Type must be "GLUE6".');
assert(isName(json.ns),
'Invalid GLUE6 record. ns must be a valid name.');
assert(IP.isIPv6String(json.address),
'Invalid GLUE6 record. Address must be a valid IPv6 address.');

this.ns = json.ns;
this.address = IP.normalize(json.address);
Expand Down Expand Up @@ -677,9 +692,11 @@ class SYNTH4 extends Struct {
}

fromJSON(json) {
assert(json && typeof json === 'object');
assert(json.type === 'SYNTH4');
assert(IP.isIPv4String(json.address));
assert(json && typeof json === 'object', 'Invalid SYNTH4 record.');
assert(json.type === 'SYNTH4',
'Invalid SYNTH4 record. Type must be "SYNTH4".');
assert(IP.isIPv4String(json.address),
'Invalid SYNTH4 record. Address must be a valid IPv4 address.');

this.address = IP.normalize(json.address);

Expand Down Expand Up @@ -737,9 +754,11 @@ class SYNTH6 extends Struct {
}

fromJSON(json) {
assert(json && typeof json === 'object');
assert(json.type === 'SYNTH6');
assert(IP.isIPv6String(json.address));
assert(json && typeof json === 'object', 'Invalid SYNTH6 record.');
assert(json.type === 'SYNTH6',
'Invalid SYNTH6 record. Type must be "SYNTH6".');
assert(IP.isIPv6String(json.address),
'Invalid SYNTH6 record. Address must be a valid IPv6 address.');

this.address = IP.normalize(json.address);

Expand Down Expand Up @@ -812,13 +831,18 @@ class TXT extends Struct {
}

fromJSON(json) {
assert(json && typeof json === 'object');
assert(json.type === 'TXT');
assert(Array.isArray(json.txt));
assert(json && typeof json === 'object',
'Invalid TXT record.');
assert(json.type === 'TXT',
'Invalid TXT record. Type must be "TXT".');
assert(Array.isArray(json.txt),
'Invalid TXT record. txt must be an Array.');

for (const txt of json.txt) {
assert(typeof txt === 'string');
assert(txt.length <= 255);
assert(typeof txt === 'string',
'Invalid TXT record. Entries in txt Array must be type String.');
assert(txt.length <= 255,
'Invalid TXT record. Entries in txt Array must be <= 255 in length.');

this.txt.push(txt);
}
Expand Down
22 changes: 22 additions & 0 deletions lib/node/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ class RPC extends RPCBase {
this.add('grindname', this.grindName);
this.add('sendrawclaim', this.sendRawClaim);
this.add('sendrawairdrop', this.sendRawAirdrop);
this.add('validateresource', this.validateResource);

// Compat
// this.add('getnameinfo', this.getNameInfo);
Expand Down Expand Up @@ -2477,6 +2478,27 @@ class RPC extends RPCBase {
return proof.hash().toString('hex');
}

async validateResource(args, help) {
if (help || args.length < 1 || args.length > 2)
throw new RPCError(errs.MISC_ERROR, 'validateresource'
+ ' \'{"records": [{...}]}\'');

const valid = new Validator(args);
const data = valid.obj(0);

if (!data)
throw new RPCError(errs.TYPE_ERROR, 'Invalid resource object.');

let resource;
try {
resource = Resource.fromJSON(data);
} catch (e) {
throw new RPCError(errs.PARSE_ERROR, e.message);
}

return resource.toJSON();
}

/*
* Helpers
*/
Expand Down
94 changes: 94 additions & 0 deletions test/node-rpc-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,99 @@ describe('RPC', function() {
const info = await nclient.execute('getblock', [hash]);
assert.deepEqual(info.confirmations, -1);
});

it('should validateresource (valid)', async () => {
const records = [
[{type: 'NS', ns: 'ns1.handshake.org.'}],
[{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xff, digest: '00'.repeat(32)}],
[{type: 'TXT', txt: ['i like turtles', 'then who phone']}],
[{type: 'GLUE4', ns: 'ns1.nam.ek.', address: '192.168.0.1'}],
[{type: 'GLUE6', ns: 'ns2.nam.ek.', address: '::'}],
[{type: 'SYNTH4', address: '192.168.0.1'}],
[{type: 'SYNTH6', address: '::'}]
];

for (const record of records) {
const data = {records: record};
const info = await nclient.execute('validateresource', [data]);
assert.deepEqual(info, data);
}
});

it('should validateresource (invalid)', async () => {
const records = [
[
// No trailing dot
[{type: 'NS', ns: 'ns1.handshake.org'}],
'Invalid NS record. ns must be a valid name.'
],
[
[{type: 'DS', keyTag: 0xffffff}],
'Invalid DS record. KeyTag must be a uint16.'
],
[
[{type: 'DS', keyTag: 0xffff, algorithm: 0xffff}],
'Invalid DS record. Algorithm must be a uint8.'
],
[
[{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xffff}],
'Invalid DS record. DigestType must be a uint8.'
],
[
[{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xff, digest: Buffer.alloc(0)}],
'Invalid DS record. Digest must be a String.'
],
[
[{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xff, digest: '00'.repeat(256)}],
'Invalid DS record. Digest is too large.'
],
[
[{type: 'TXT', txt: 'foobar'}],
'Invalid TXT record. txt must be an Array.'
],
[
[{type: 'TXT', txt: [{}]}],
'Invalid TXT record. Entries in txt Array must be type String.'
],
[
[{type: 'TXT', txt: ['0'.repeat(256)]}],
'Invalid TXT record. Entries in txt Array must be <= 255 in length.'
],
[
[{type: 'GLUE4', ns: 'ns1.nam.ek', address: '192.168.0.1'}],
'Invalid GLUE4 record. ns must be a valid name.'
],
[
[{type: 'GLUE4', ns: 'ns1.nam.ek.', address: '::'}],
'Invalid GLUE4 record. Address must be a valid IPv4 address.'
],
[
[{type: 'GLUE6', ns: 'ns1.nam.ek', address: '::'}],
'Invalid GLUE6 record. ns must be a valid name.'
],
[
[{type: 'GLUE6', ns: 'ns1.nam.ek.', address: '0.0.0.0'}],
'Invalid GLUE6 record. Address must be a valid IPv6 address.'
],
[
[{type: 'SYNTH4', address: '::'}],
'Invalid SYNTH4 record. Address must be a valid IPv4 address.'
],
[
[{type: 'SYNTH6', address: '127.0.0.1'}],
'Invalid SYNTH6 record. Address must be a valid IPv6 address.'
]
];

for (const [record, reason] of records) {
try {
const data = {records: record};
await nclient.execute('validateresource', [data]);
assert.fail();
} catch (e) {
assert.equal(e.message, reason);
}
}
});
});
});

0 comments on commit c3d3aa2

Please sign in to comment.