dns2 2.0.5 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # dns2
2
2
 
3
- ![NPM version](https://img.shields.io/npm/v/dns2.svg?style=flat)
4
- [![Node.js CI](https://github.com/song940/node-dns/actions/workflows/node.js.yml/badge.svg)](https://github.com/song940/node-dns/actions/workflows/node.js.yml)
3
+ ![NPM version][npm-img] [![Build Status][ci-img]][ci-url]
5
4
 
6
5
  > A DNS Server and Client Implementation in Pure JavaScript with no dependencies.
7
6
 
@@ -27,10 +26,9 @@ DNS client will use UDP by default.
27
26
  const dns2 = require('dns2');
28
27
 
29
28
  const options = {
30
- // available options
31
- // dns: dns server ip address or hostname (string),
32
- // port: dns server port (number),
33
- // recursive: Recursion Desired flag (boolean, default true, since > v1.4.2)
29
+ // nameServers: ['8.8.8.8'] — array of DNS server IPs (default: Google + 114dns)
30
+ // port: 53 — DNS server port (number)
31
+ // recursive: true — Recursion Desired flag (boolean, default true)
34
32
  };
35
33
  const dns = new dns2(options);
36
34
 
@@ -40,6 +38,47 @@ const dns = new dns2(options);
40
38
  })();
41
39
  ```
42
40
 
41
+ The high-level `DNS` class exposes convenience methods for common record types:
42
+
43
+ | Method | Record type | Key answer fields |
44
+ |---|---|---|
45
+ | `resolveA(domain)` | A | `address` |
46
+ | `resolveAAAA(domain)` | AAAA | `address` |
47
+ | `resolveMX(domain)` | MX | `exchange`, `priority` |
48
+ | `resolveCNAME(domain)` | CNAME | `domain` |
49
+ | `resolveSOA(domain)` | SOA | `primary`, `admin`, `serial`, `refresh`, `retry`, `expiration`, `minimum` |
50
+ | `resolvePTR(domain)` | PTR | `domain` |
51
+ | `resolveDNSKEY(domain)` | DNSKEY | `publicKey`, `algorithm` |
52
+ | `resolveRRSIG(domain)` | RRSIG | varies |
53
+
54
+ For any record type not listed above, use `dns.resolve(domain, 'TYPE')` directly.
55
+
56
+ #### Example: SOA record lookup
57
+
58
+ SOA (Start of Authority) records contain the authoritative zone information for a domain.
59
+ If the authoritative nameserver returns the SOA record in the answer section, it will appear
60
+ in `result.answers`. Otherwise check `result.authorities`.
61
+
62
+ ```js
63
+ const dns2 = require('dns2');
64
+
65
+ const dns = new dns2({ nameServers: ['8.8.8.8'] });
66
+
67
+ (async () => {
68
+ const result = await dns.resolveSOA('google.com');
69
+ const soa = result.answers[0] || result.authorities[0];
70
+ if (soa) {
71
+ console.log(soa.primary); // ns1.google.com
72
+ console.log(soa.admin); // dns-admin.google.com
73
+ console.log(soa.serial); // zone serial number
74
+ console.log(soa.refresh); // refresh interval (seconds)
75
+ console.log(soa.retry); // retry interval (seconds)
76
+ console.log(soa.expiration); // expiry (seconds)
77
+ console.log(soa.minimum); // minimum TTL (seconds)
78
+ }
79
+ })();
80
+ ```
81
+
43
82
  Another way to instanciate dns2 UDP Client:
44
83
 
45
84
  ```js
@@ -162,7 +201,6 @@ server.listen({
162
201
  udp: {
163
202
  port: 5333,
164
203
  address: "127.0.0.1",
165
- type: "udp4", // IPv4 or IPv6 (Must be either "udp4" or "udp6")
166
204
  },
167
205
 
168
206
  // Optionally specify port and/or address for tcp server:
@@ -185,6 +223,52 @@ $ dig @127.0.0.1 -p5333 lsong.org
185
223
  Note that when implementing your own lookups, the contents of the query
186
224
  will be found in `request.questions[0].name`.
187
225
 
226
+ ### Responding with DNS Error Codes
227
+
228
+ Use `Packet.RCODE` to send standard DNS error responses from your handler:
229
+
230
+ | Constant | Value | Meaning |
231
+ |---|---|---|
232
+ | `Packet.RCODE.NOERROR` | 0 | No error |
233
+ | `Packet.RCODE.FORMERR` | 1 | Format error |
234
+ | `Packet.RCODE.SERVFAIL` | 2 | Server failure |
235
+ | `Packet.RCODE.NXDOMAIN` | 3 | Non-existent domain |
236
+ | `Packet.RCODE.NOTIMP` | 4 | Not implemented |
237
+ | `Packet.RCODE.REFUSED` | 5 | Query refused |
238
+
239
+ ```js
240
+ const dns2 = require('dns2');
241
+ const { Packet } = dns2;
242
+
243
+ const server = dns2.createServer({
244
+ udp: true,
245
+ handle: (request, send) => {
246
+ const response = Packet.createResponseFromRequest(request);
247
+ const [ question ] = request.questions;
248
+
249
+ if (question.name.endsWith('.internal')) {
250
+ // Refuse queries for internal names
251
+ response.header.rcode = Packet.RCODE.REFUSED;
252
+ return send(response);
253
+ }
254
+
255
+ if (!isKnownDomain(question.name)) {
256
+ // Domain does not exist
257
+ response.header.rcode = Packet.RCODE.NXDOMAIN;
258
+ return send(response);
259
+ }
260
+
261
+ // Normal answer ...
262
+ response.answers.push({ name: question.name, type: Packet.TYPE.A, class: Packet.CLASS.IN, ttl: 300, address: '1.2.3.4' });
263
+ send(response);
264
+ }
265
+ });
266
+ ```
267
+
268
+ ### Performance and Benchmarking
269
+
270
+ see [benchmark/README.md](benchmark/README.md)
271
+
188
272
  ### Relevant Specifications
189
273
 
190
274
  + [RFC-1034 - Domain Names - Concepts and Facilities](https://tools.ietf.org/html/rfc1034)
@@ -225,3 +309,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
225
309
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
226
310
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
227
311
  THE SOFTWARE.
312
+
313
+ [npm-img]: https://img.shields.io/npm/v/dns2.svg?style=flat
314
+ [ci-img]: https://github.com/song940/node-dns/actions/workflows/node.js.yml/badge.svg
315
+ [ci-url]: https://github.com/song940/node-dns/actions/workflows/node.js.yml
package/client/doh.js CHANGED
@@ -1,49 +1,89 @@
1
+ const http = require('node:http');
2
+ const https = require('node:https');
3
+ const http2 = require('node:http2');
1
4
  const Packet = require('../packet');
2
5
 
3
- const defaultGet = url => new Promise((resolve, reject) => {
4
- const headers = {
5
- accept: 'application/dns-message',
6
- };
7
- const base = url.startsWith('https') ? require('https') : require('http');
8
- const req = base.get(url, { headers }, resolve);
9
- req.on('error', reject);
10
- });
6
+ const protocols = {
7
+ 'http:' : http.get,
8
+ 'https:' : https.get,
9
+ 'h2:' : (url, options, done) => {
10
+ const urlObj = new URL(url);
11
+ const client = http2.connect(url.replace('h2:', 'https:'));
12
+ const req = client.request({
13
+ ':path' : `${urlObj.pathname}${urlObj.search}`,
14
+ ':method' : 'GET',
15
+ ...options.headers,
16
+ });
17
+
18
+ req.on('response', headers => {
19
+ client.close();
20
+ done({
21
+ headers,
22
+ statusCode : headers[':status'],
23
+ on : req.on.bind(req),
24
+ });
25
+ });
26
+
27
+ req.on('error', err => {
28
+ client.close();
29
+ throw err;
30
+ });
11
31
 
12
- const readStream = stream => {
13
- const buffer = [];
14
- return new Promise((resolve, reject) => {
15
- stream
16
- .on('error', reject)
17
- .on('data', chunk => buffer.push(chunk))
18
- .on('end', () => resolve(Buffer.concat(buffer)));
19
- });
32
+ req.end();
33
+ },
20
34
  };
21
35
 
22
- /**
23
- * @docs https://tools.ietf.org/html/rfc8484
24
- * @param {*} param0
25
- */
26
- const DOHClient = ({ dns, http, get = defaultGet } = {}) => {
27
- return (name, type = 'A', cls = Packet.CLASS.IN, { clientIp, recursive = true } = {}) => {
28
- const packet = new Packet();
29
- // see https://github.com/song940/node-dns/issues/29
30
- if (recursive) {
31
- packet.header.rd = 1;
32
- }
33
- if (clientIp) {
34
- packet.additionals.push(Packet.Resource.EDNS([
35
- Packet.Resource.EDNS.ECS(clientIp),
36
- ]));
37
- }
38
- packet.questions.push({
39
- name,
40
- class : cls,
41
- type : Packet.TYPE[type],
36
+ const makeRequest = (url, query) => new Promise((resolve, reject) => {
37
+ const index = url.indexOf('://');
38
+ if (index === -1) url = `https://${url}`;
39
+ const u = new URL(url);
40
+ // The DNS query is included in a single variable named “dns” in the
41
+ // query component of the request URI. The value of the “dns” variable
42
+ // is the content of the DNS request message, encoded with base64url
43
+ // [RFC4648](https://datatracker.ietf.org/doc/html/rfc8484#section-4.1).
44
+ const searchParams = u.searchParams;
45
+ searchParams.set('dns', query);
46
+ u.search = searchParams.toString();
47
+ const get = protocols[u.protocol];
48
+ if (!get) throw new Error(`Unsupported protocol: ${u.protocol}, must be specified (http://, https:// or h2://)`);
49
+ const req = get(u.toString(), { headers: { accept: 'application/dns-message' } }, resolve);
50
+ if (req) req.on('error', reject);
51
+ });
52
+
53
+ const readStream = res => new Promise((resolve, reject) => {
54
+ const chunks = [];
55
+ res
56
+ .on('error', reject)
57
+ .on('data', chunk => chunks.push(chunk))
58
+ .on('end', () => {
59
+ const data = Buffer.concat(chunks);
60
+ if (res.statusCode !== 200) {
61
+ reject(new Error(`HTTP ${res.statusCode}: ${data.toString()}`));
62
+ }
63
+ resolve(data);
42
64
  });
43
- const query = packet.toBase64URL();
44
- return Promise.resolve(get(`http${http ? '' : 's'}://${dns}/dns-query?dns=${query}`))
45
- .then(readStream)
46
- .then(Packet.parse);
65
+ });
66
+
67
+ const buildQuery = ({ name, type = 'A', cls = Packet.CLASS.IN, clientIp, recursive = true }) => {
68
+ const packet = new Packet();
69
+ packet.header.rd = recursive ? 1 : 0;
70
+
71
+ if (clientIp) {
72
+ packet.additionals.push(Packet.Resource.EDNS([
73
+ Packet.Resource.EDNS.ECS(clientIp),
74
+ ]));
75
+ }
76
+
77
+ packet.questions.push({ name, class: cls, type: Packet.TYPE[type] });
78
+ return packet.toBase64URL();
79
+ };
80
+
81
+ const DOHClient = ({ dns }) => {
82
+ return async(name, type, cls, options = {}) => {
83
+ const query = buildQuery({ name, type, cls, ...options });
84
+ const response = await makeRequest(dns, query);
85
+ const data = await readStream(response);
86
+ return Packet.parse(data);
47
87
  };
48
88
  };
49
89
 
package/client/google.js CHANGED
@@ -1,4 +1,4 @@
1
- const https = require('https');
1
+ const https = require('node:https');
2
2
 
3
3
  const get = url => new Promise(resolve =>
4
4
  https.get(url, resolve));
package/client/tcp.js CHANGED
@@ -1,34 +1,49 @@
1
- const tcp = require('net');
1
+ const tls = require('node:tls');
2
+ const tcp = require('node:net');
2
3
  const Packet = require('../packet');
3
4
 
4
- module.exports = ({ dns = '1.1.1.1', port = 53 } = {}) => {
5
- return async(name, type = 'A', cls = Packet.CLASS.IN, { clientIp, recursive = true } = {}) => {
6
- const packet = new Packet();
7
-
8
- // see https://github.com/song940/node-dns/issues/29
9
- if (recursive) {
10
- packet.header.rd = 1;
11
- }
12
- if (clientIp) {
13
- packet.additionals.push(Packet.Resource.EDNS([
14
- Packet.Resource.EDNS.ECS(clientIp),
15
- ]));
16
- }
17
-
18
- packet.questions.push({
19
- name,
20
- class : cls,
21
- type : Packet.TYPE[type],
22
- });
23
- const message = packet.toBuffer();
24
- const len = Buffer.alloc(2);
25
- len.writeUInt16BE(message.length);
26
- const client = tcp.connect({ host: dns, port });
27
- client.end(Buffer.concat([ len, message ]));
5
+ const makeQuery = ({ name, type = 'A', cls = Packet.CLASS.IN, clientIp, recursive = true }) => {
6
+ const packet = new Packet();
7
+ packet.header.rd = recursive ? 1 : 0;
8
+
9
+ if (clientIp) {
10
+ packet.additionals.push(Packet.Resource.EDNS([
11
+ Packet.Resource.EDNS.ECS(clientIp),
12
+ ]));
13
+ }
14
+
15
+ packet.questions.push({ name, class: cls, type: Packet.TYPE[type] });
16
+ return packet.toBuffer();
17
+ };
18
+
19
+ const sendQuery = (client, message) => {
20
+ const len = Buffer.alloc(2);
21
+ len.writeUInt16BE(message.length);
22
+ client.write(Buffer.concat([ len, message ]));
23
+ };
24
+
25
+ const protocols = {
26
+ 'tcp:' : (host, port) => tcp.connect({ host, port }),
27
+ 'tls:' : (host, port) => tls.connect({ host, port, servername: host }),
28
+ };
29
+
30
+ const TCPClient = ({ dns, protocol = 'tcp:', port = protocol === 'tls:' ? 853 : 53 } = {}) => {
31
+ if (!protocols[protocol]) {
32
+ throw new Error('Protocol must be tcp: or tls:');
33
+ }
34
+
35
+ return async(name, type, cls, options = {}) => {
36
+ const message = makeQuery({ name, type, cls, ...options });
37
+ const [ host ] = dns.split(':');
38
+ const client = protocols[protocol](host, port);
39
+
40
+ sendQuery(client, message);
28
41
  const data = await Packet.readStream(client);
29
- if (!data.length) {
30
- throw new Error('Empty TCP response');
31
- }
42
+ client.end();
43
+
44
+ if (!data.length) throw new Error('Empty response');
32
45
  return Packet.parse(data);
33
46
  };
34
47
  };
48
+
49
+ module.exports = TCPClient;
package/client/udp.js CHANGED
@@ -1,15 +1,22 @@
1
- const udp = require('dgram');
1
+ const udp = require('node:dgram');
2
+ const net = require('node:net');
3
+ const crypto = require('node:crypto');
2
4
  const Packet = require('../packet');
3
- const { equal } = require('assert');
4
- const { debuglog } = require('util');
5
+ const { debuglog } = require('node:util');
5
6
 
6
7
  const debug = debuglog('dns2');
7
8
 
8
- module.exports = ({ dns = '8.8.8.8', port = 53, socketType = 'udp4' } = {}) => {
9
- return (name, type = 'A', cls = Packet.CLASS.IN, { clientIp, recursive = true } = {}) => {
9
+ module.exports = ({
10
+ dns = '8.8.8.8',
11
+ port = 53,
12
+ socketType = 'udp4',
13
+ timeout = 10000,
14
+ retryOverTCP = true,
15
+ } = {}) => {
16
+ return (name, type = 'A', cls = Packet.CLASS.IN, options = {}) => {
17
+ const { clientIp, recursive = true } = options;
10
18
  const query = new Packet();
11
- query.header.id = (Math.random() * 1e4) | 0;
12
-
19
+ query.header.id = crypto.randomInt(0x10000);
13
20
  // see https://github.com/song940/node-dns/issues/29
14
21
  if (recursive) {
15
22
  query.header.rd = 1;
@@ -25,15 +32,76 @@ module.exports = ({ dns = '8.8.8.8', port = 53, socketType = 'udp4' } = {}) => {
25
32
  type : Packet.TYPE[type],
26
33
  });
27
34
  const client = new udp.Socket(socketType);
35
+ // Only enforce a strict source-address check when `dns` is an IP literal;
36
+ // hostnames would require an extra resolve to compare against.
37
+ const expectedAddress = net.isIP(dns) ? dns : null;
28
38
  return new Promise((resolve, reject) => {
29
- client.once('message', function onMessage(message) {
30
- client.close();
31
- const response = Packet.parse(message);
32
- equal(response.header.id, query.header.id);
39
+ let settled = false;
40
+ let timer;
41
+ const cleanup = () => {
42
+ if (settled) return;
43
+ settled = true;
44
+ if (timer) clearTimeout(timer);
45
+ client.removeListener('message', onMessage);
46
+ client.removeListener('error', onError);
47
+ try { client.close(); } catch (_) { /* already closed */ }
48
+ };
49
+ function onMessage(message, rinfo) {
50
+ // Drop packets that didn't come from the configured resolver.
51
+ if (rinfo.port !== port || (expectedAddress && rinfo.address !== expectedAddress)) {
52
+ debug('udp: dropping packet from unexpected sender %s:%d', rinfo.address, rinfo.port);
53
+ return;
54
+ }
55
+ let response;
56
+ try {
57
+ response = Packet.parse(message);
58
+ } catch (e) {
59
+ debug('udp: dropping unparseable packet: %s', e.message);
60
+ return;
61
+ }
62
+ // Stray / late reply from a reused ephemeral port — keep listening.
63
+ if (response.header.id !== query.header.id) {
64
+ debug('udp: dropping response with mismatched id %d (expected %d)',
65
+ response.header.id, query.header.id);
66
+ return;
67
+ }
68
+ // RFC 1035 §4.2.1: if the TC (truncated) bit is set the upstream had
69
+ // more data than fits in a 512-byte UDP datagram. Retry over TCP so
70
+ // callers always receive a complete answer.
71
+ if (response.header.tc && retryOverTCP) {
72
+ debug('udp: TC bit set — retrying query over TCP');
73
+ cleanup();
74
+ const TCPClient = require('./tcp');
75
+ resolve(TCPClient({ dns, port })(name, type, cls, options));
76
+ return;
77
+ }
78
+ cleanup();
33
79
  resolve(response);
34
- });
80
+ }
81
+ function onError(err) {
82
+ cleanup();
83
+ reject(err);
84
+ }
85
+ client.on('message', onMessage);
86
+ client.on('error', onError);
87
+
88
+ if (timeout > 0) {
89
+ timer = setTimeout(() => {
90
+ cleanup();
91
+ const err = new Error(`DNS query timed out after ${timeout}ms`);
92
+ err.code = 'ETIMEDOUT';
93
+ reject(err);
94
+ }, timeout);
95
+ timer.unref();
96
+ }
97
+
35
98
  debug('send', dns, query.toBuffer());
36
- client.send(query.toBuffer(), port, dns, err => err && reject(err));
99
+ client.send(query.toBuffer(), port, dns, err => {
100
+ if (err) {
101
+ cleanup();
102
+ reject(err);
103
+ }
104
+ });
37
105
  });
38
106
  };
39
107
  };
@@ -1,12 +1,22 @@
1
1
  const { DOHClient } = require('../..');
2
2
 
3
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
3
+ // process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
4
4
 
5
- const resolve = DOHClient({
6
- dns: '1.1.1.1',
7
- });
5
+ // const resolve = DOHClient({
6
+ // dns: '1.1.1.1',
7
+ // });
8
8
 
9
- (async() => {
10
- const response = await resolve('google.com');
11
- console.log(response.answers);
12
- })();
9
+ // (async() => {
10
+ // const response = await resolve('google.com');
11
+ // console.log(response.answers);
12
+ // })();
13
+
14
+ // import DNS2 from 'dns2';
15
+
16
+ // DOHClient({
17
+ // dns: 'h2://ada.openbld.net',
18
+ // })('cdnjs.com', 'NS').then(console.log);
19
+
20
+ DOHClient({
21
+ dns: 'https://1.0.0.1/dns-query',
22
+ })('cdnjs.com', 'NS').then(console.log);
package/index.js CHANGED
@@ -7,7 +7,7 @@ const {
7
7
  createDOHServer,
8
8
  createServer,
9
9
  } = require('./server');
10
- const EventEmitter = require('events');
10
+ const EventEmitter = require('node:events');
11
11
 
12
12
  /**
13
13
  * [DNS description]
@@ -15,13 +15,19 @@ const EventEmitter = require('events');
15
15
  * @docs https://tools.ietf.org/html/rfc1035
16
16
  */
17
17
  class DNS extends EventEmitter {
18
- constructor(options) {
18
+ constructor(options = {}) {
19
19
  super();
20
+ // Accept `dns` as a shorthand alias for `nameServers` so that
21
+ // `new DNS({ dns: '8.8.8.8' })` works as documented and intuited.
22
+ if (options.dns != null && options.nameServers == null) {
23
+ options = Object.assign({}, options, { nameServers: [].concat(options.dns) });
24
+ }
20
25
  Object.assign(this, {
21
26
  port : 53,
22
27
  retries : 3,
23
28
  timeout : 3,
24
29
  recursive : true,
30
+ retryOverTCP : true,
25
31
  resolverProtocol : 'UDP',
26
32
  nameServers : [
27
33
  '8.8.8.8',
@@ -34,27 +40,19 @@ class DNS extends EventEmitter {
34
40
  }, options);
35
41
  }
36
42
 
37
- /**
38
- * query
39
- * @param {*} questions
40
- */
41
- query(name, type, cls, clientIp) {
42
- const { port, nameServers, recursive, resolverProtocol = 'UDP' } = this;
43
- const createResolver = DNS[resolverProtocol + 'Client'];
44
- return Promise.race(nameServers.map(address => {
45
- const resolve = createResolver({ dns: address, port, recursive });
46
- return resolve(name, type, cls, clientIp);
47
- }));
48
- }
49
-
50
43
  /**
51
44
  * resolve
52
45
  * @param {*} domain
53
46
  * @param {*} type
54
47
  * @param {*} cls
55
48
  */
56
- resolve(domain, type = 'ANY', cls = DNS.Packet.CLASS.IN, clientIp = undefined) {
57
- return this.query(domain, type, cls, clientIp);
49
+ resolve(domain, type = 'ANY', cls = DNS.Packet.CLASS.IN, options = {}) {
50
+ const { port, nameServers, resolverProtocol = 'UDP', retryOverTCP } = this;
51
+ const createResolver = DNS[resolverProtocol + 'Client'];
52
+ return Promise.race(nameServers.map(address => {
53
+ const resolve = createResolver({ dns: address, port, retryOverTCP });
54
+ return resolve(domain, type, cls, options);
55
+ }));
58
56
  }
59
57
 
60
58
  resolveA(domain, clientIp) {
@@ -76,6 +74,18 @@ class DNS extends EventEmitter {
76
74
  resolvePTR(domain) {
77
75
  return this.resolve(domain, 'PTR');
78
76
  }
77
+
78
+ resolveDNSKEY(domain) {
79
+ return this.resolve(domain, 'DNSKEY');
80
+ }
81
+
82
+ resolveRRSIG(domain) {
83
+ return this.resolve(domain, 'RRSIG');
84
+ }
85
+
86
+ resolveSOA(domain) {
87
+ return this.resolve(domain, 'SOA');
88
+ }
79
89
  }
80
90
 
81
91
  DNS.TCPServer = TCPServer;
package/lib/reader.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  /**
3
2
  * [Reader description]
4
3
  * @param {[type]} buffer [description]
@@ -19,9 +18,12 @@ function BufferReader(buffer, offset) {
19
18
  */
20
19
  BufferReader.read = function(buffer, offset, length) {
21
20
  let a = [];
22
- let c = Math.ceil(length / 8);
23
21
  let l = Math.floor(offset / 8);
24
22
  const m = offset % 8;
23
+ // Need enough bytes to cover the bit range [offset, offset+length); when the
24
+ // offset isn't byte-aligned, the read can straddle one more byte than
25
+ // ceil(length/8) alone accounts for.
26
+ let c = Math.ceil((length + m) / 8);
25
27
  function t(n) {
26
28
  const r = [ 0, 0, 0, 0, 0, 0, 0, 0 ];
27
29
  for (let i = 7; i >= 0; i--) {
package/lib/writer.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  /**
3
2
  * [Writer description]
4
3
  */