Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
initial commit
  • Loading branch information
tjfontaine committed Mar 24, 2013
0 parents commit 6f68510
Show file tree
Hide file tree
Showing 5 changed files with 421 additions and 0 deletions.
186 changes: 186 additions & 0 deletions cache.js
@@ -0,0 +1,186 @@
// Copyright 2012 Timothy J Fontaine <tjfontaine@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE

'use strict';

var MemoryStore = require('./memory').MemoryStore;
var utils = require('./lookup');
var Lookup = utils.Lookup;
var util = require('util');
var Heap = require('binaryheap');

var MemoryStoreExpire = function (store, zone, opts) {
opts = opts || {};
this._store = store;
this._zone = zone;
this._max_keys = opts.max_keys;
this._ttl = new Heap(true);
};

MemoryStoreExpire.prototype.get = function (domain, key, cb) {
var self = this;
this._store.get(domain, key, function (err, results) {
var i, j, type, record;
var nresults = {};
var now = Date.now();

for (i in results) {
type = results[i];
for (j in type) {
record = type[j];
record.ttl = Math.round((record._ttl_expires - now) / 1000)
if (record.ttl > 0) {
if (!nresults[i]) {
nresults[i] = [];
}
nresults[i].push(record);
} else {
self._ttl.remove(record);
self._store.delete(self._zone, record.name, record.type, function () {});
}
}
}

cb(err, nresults);
});
};

MemoryStoreExpire.prototype.set = function (domain, key, data, cb) {
var i, j, type, record, expires;
var self = this;
var now = Date.now();

key = utils.ensure_absolute(key);

for (i in data) {
type = data[i];
for (j in type) {
record = type[j];
expires = (record.ttl * 1000) + now;
record._ttl_expires = expires;
self._ttl.insert(record, expires);
}
}

while (this._ttl.length > this._max_keys) {
var record = this._ttl.pop();
this._store.delete(this._zone, record.name, record.type);
}

this._store.set(domain, key, data, function (err, results) {
if (cb)
cb(err, results);
});
};

MemoryStoreExpire.prototype.delete = function (domain, key, type, cb) {
if (!cb) {
cb = type;
type = undefined;
}

var self = this;

this._store.get(domain, utils.ensure_absolute(key), function (gerr, gresults) {
var i, j, ktype, record;

for (i in gresults) {
ktype = gresults[i];
for (j in ktype) {
record = ktype[j];
self._ttl.remove(record);
}
}

if (!gresults) {
if (cb)
cb(gerr, gresults);
return;
}

self._store.delete(domain, key, type, function (err, results) {
if (cb)
cb(err, results);
});
});
};

var Cache = module.exports = function (opts) {
opts = opts || {};
this._zone = '.' || opts.zone;
this._store = undefined;
this.purge = function () {
this._store = new MemoryStoreExpire(opts.store || new MemoryStore(), this._zone, opts);
}
this.purge();
};

Cache.prototype.store = function (packet) {
var self = this;
var c = {};

function each(record) {
var r = c[record.name.toLowerCase()];
var t;

if (!r)
r = c[record.name.toLowerCase()] = {};

t = r[record.type];

if (!t)
t = r[record.type] = [];

t.push(record);
}

packet.answer.forEach(each);
packet.authority.forEach(each);
packet.additional.forEach(each);

Object.keys(c).forEach(function (key) {
self._store.set(self._zone, utils.ensure_absolute(key), c[key]);
});
};

Cache.prototype.lookup = function (question, cb) {
var self = this;
Lookup(this._store, this._zone, question, function (err, results) {
var i, record, found = false;

for (i in results) {
record = results[i];
if (record.type == question.type) {
found = true;
break;
}
}

if (results && !found) {
self._store.delete(self._zone, utils.ensure_absolute(question.name));
results.forEach(function (rr) {
self._store.delete(self._zone, utils.ensure_absolute(rr.name));
});
results = null;
}

cb(results);
});
};
5 changes: 5 additions & 0 deletions index.js
@@ -0,0 +1,5 @@
module.exports = require('./cache');
module.exports.MemoryStore = require('./memory').MemoryStore;
module.exports.Lookup = require('./lookup').Lookup;
module.exports.is_absolute = require('./lookup').is_absolute;
module.exports.ensure_absolute = require('./lookup').ensure_absolute;
110 changes: 110 additions & 0 deletions lookup.js
@@ -0,0 +1,110 @@
// Copyright 2012 Timothy J Fontaine <tjfontaine@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE

var dgram = require('dgram'),
EventEmitter = require('events').EventEmitter,
net = require('net'),
util = require('util');

var is_absolute = exports.is_absolute = function (f) {
return f && /\.$/.test(f);
};

var ensure_absolute = exports.ensure_absolute = function (f) {
if (!is_absolute(f))
return f += '.';
return f;
};

var CNAME = require('native-dns-packet').consts.NAME_TO_QTYPE.CNAME;

var Lookup = exports.Lookup = function (store, zone, question, cb) {
if (!(this instanceof Lookup))
return new Lookup(store, zone, question, cb);

this.store = store;
this.zone = zone;
this.cb = cb;
this.question = question;
this.results = [];
this.wildcard = undefined;

this.name = ensure_absolute(question.name);

this.store.get(this.zone, this.name, this.lookup.bind(this));
};

Lookup.prototype.send = function (err) {
this.cb(err, this.results);
};

Lookup.prototype.lookup = function (err, results) {
var type, ret, name, self = this;

if (err)
return this.send(err);

if (!results) {
if (!this.wildcard)
this.wildcard = this.question.name;

if (this.wildcard.toLowerCase() == this.zone.toLowerCase())
return this.send();

name = this.wildcard = this.wildcard.split('.').splice(1).join('.');

// 'com.'.split('.').splice(1) will return empty string, we're at the end
if (!this.wildcard)
return this.send();

name = '*.' + name;
} else if (results[this.question.type]) {
type = this.question.type;
ret = results;
} else if (results[CNAME]) {
type = CNAME;
ret = results;
this.name = name = results[CNAME][0].data
}

if (ret) {
ret = ret[type];
ret.forEach(function (r) {
var rr, k;

if (self.wildcard && /^\*/.test(r.name)) {
rr = {};
for (k in r) {
rr[k] = r[k];
}
rr.name = self.name;
} else {
rr = r;
}

self.results.push(rr);
});
}

if (name)
this.store.get(this.zone, ensure_absolute(name), this.lookup.bind(this));
else
this.send();
};
93 changes: 93 additions & 0 deletions memory.js
@@ -0,0 +1,93 @@
// Copyright 2012 Timothy J Fontaine <tjfontaine@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

'use strict';

var MemoryStore = exports.MemoryStore = function (opts) {
this._store = {};
};

MemoryStore.prototype.get = function (domain, key, cb) {
var d = domain.toLowerCase();
var k = key.toLowerCase();
var result = this._store[d];

if (result)
result = result[k];

process.nextTick(function () {
cb(null, result);
});
};

MemoryStore.prototype.set = function (domain, key, data, cb) {
var d = domain.toLowerCase();
var k = key.toLowerCase();
var result_domain = this._store[d];

if (!result_domain)
result_domain = this._store[d] = {};

result_domain[k] = data;

if (cb) {
process.nextTick(function () {
cb(null, data);
});
}
};

MemoryStore.prototype.delete = function (domain, key, type, cb) {
var d, k;

if (!cb) {
cb = type;
type = undefined;
}

if (!cb) {
cb = key;
type = undefined;
}

d = this._store[domain.toLowerCase()];

if (d && key)
k = d[key.toLowerCase()];

if (domain && key && type) {
if (d && k) {
delete k[type];
}
} else if (domain && key) {
if (d) {
delete d[k];
}
} else if (domain) {
if (d) {
delete this._store[domain.toLowerCase()];
}
}

if (cb) {
process.nextTick(function () {
cb(null, domain);
});
}
};

0 comments on commit 6f68510

Please sign in to comment.