amqplib 2.2.0 → 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/test/data.test.js DELETED
@@ -1,33 +0,0 @@
1
- const { forAll } = require('claire');
2
- const { Octet, ShortStr, LongStr, UShort, ULong, ULongLong, Short, Long, LongLong, Bit, Double, Float, Decimal, Timestamp, FieldTable, FieldArray } = require('./lib/data');
3
-
4
- const domainProps = [
5
- [Octet, (n) => n >= 0 && n < 256],
6
- [ShortStr, (s) => typeof s === 'string' && s.length < 256],
7
- [LongStr, (s) => Buffer.isBuffer(s)],
8
- [UShort, (n) => n >= 0 && n <= 0xffff],
9
- [ULong, (n) => n >= 0 && n <= 0xffffffff],
10
- [ULongLong, (n) => n >= 0 && n <= 0xffffffffffffffff],
11
- [Short, (n) => n >= -0x8000 && n <= 0x8000],
12
- [Long, (n) => n >= -0x80000000 && n < 0x80000000],
13
- [LongLong, (n) => n >= Number.MIN_SAFE_INTEGER && n <= Number.MAX_SAFE_INTEGER],
14
- [Bit, (b) => typeof b === 'boolean'],
15
- [Double, (f) => !Number.isNaN(f) && Number.isFinite(f)],
16
- [Float, (f) => !Number.isNaN(f) && Number.isFinite(f) && Math.log(Math.abs(f)) * Math.LOG10E < 309],
17
- [Decimal, (d) => d['!'] === 'decimal' && d.value['places'] <= 255 && d.value['digits'] <= 0xffffffff],
18
- [Timestamp, (t) => t['!'] === 'timestamp'],
19
- [FieldTable, (t) => typeof t === 'object'],
20
- [FieldArray, (a) => Array.isArray(a)],
21
- ];
22
-
23
- if (global.describe) {
24
- describe('Domains', () => {
25
- domainProps.forEach((p) => {
26
- console.log(p[0].toString())
27
- it(`${p[0]} domain`, forAll(p[0]).satisfy((v) => {
28
- console.log({ v });
29
- return p[1](v)
30
- }).asTest({ times: 1 }));
31
- });
32
- });
33
- }
@@ -1,180 +0,0 @@
1
- const { describe, it } = require('node:test');
2
- const assert = require('node:assert');
3
- const connection = require('../lib/connection');
4
- const Frames = connection.Connection;
5
- const HEARTBEAT = require('../lib/frame').HEARTBEAT;
6
- const Stream = require('node:stream');
7
- const PassThrough = Stream.PassThrough;
8
- const { choice, forAll, repeat, label, sequence, transform, sized } = require('claire');
9
- const amqp = require('./lib/data');
10
- const assertEqualModuloDefaults = require('./lib/util').assertEqualModuloDefaults;
11
- const defs = require('../lib/defs');
12
-
13
- // We'll need to supply a stream which we manipulate ourselves
14
- function inputs() {
15
- // don't coalesce buffers, since that could mess up properties (e.g., encoded frame size)
16
- return new PassThrough({ objectMode: true });
17
- }
18
-
19
- const HB = Buffer.from([
20
- defs.constants.FRAME_HEARTBEAT,
21
- 0,
22
- 0, // channel 0
23
- 0,
24
- 0,
25
- 0,
26
- 0, // zero size
27
- defs.constants.FRAME_END,
28
- ]);
29
-
30
- describe('Frame', () => {
31
-
32
- describe('Explicit parsing', () => {
33
- it('Parse heartbeat', () => {
34
- const input = inputs();
35
- const frames = new Frames(input);
36
- input.write(HB);
37
- assert.ok(frames.recvFrame() === HEARTBEAT);
38
- assert.ok(!frames.recvFrame());
39
- });
40
-
41
- it('Parse partitioned', () => {
42
- const input = inputs();
43
- const frames = new Frames(input);
44
- input.write(HB.subarray(0, 3));
45
- assert.ok(!frames.recvFrame());
46
- input.write(HB.subarray(3));
47
- assert.ok(frames.recvFrame() === HEARTBEAT);
48
- assert.ok(!frames.recvFrame());
49
- });
50
-
51
- function testBogusFrame(name, bytes) {
52
- it(name, (_t, done) => {
53
- const input = inputs();
54
- const frames = new Frames(input);
55
- frames.frameMax = 5; //for the max frame test
56
- input.write(Buffer.from(bytes));
57
- frames.step((err, _frame) => {
58
- if (err != null) done();
59
- else assert.fail('Was a bogus frame!');
60
- });
61
- });
62
- }
63
-
64
- testBogusFrame('Wrong sized frame', [
65
- defs.constants.FRAME_BODY,
66
- 0,
67
- 0,
68
- 0,
69
- 0,
70
- 0,
71
- 0, // zero length
72
- 65, // but a byte!
73
- defs.constants.FRAME_END,
74
- ]);
75
-
76
- testBogusFrame('Unknown method frame', [
77
- defs.constants.FRAME_METHOD,
78
- 0,
79
- 0,
80
- 0,
81
- 0,
82
- 0,
83
- 4,
84
- 0,
85
- 0,
86
- 0,
87
- 0, // garbage ID
88
- defs.constants.FRAME_END,
89
- ]);
90
- });
91
-
92
- const Trace = label('frame trace', repeat(choice.apply(choice, amqp.methods)));
93
-
94
- describe('Parsing', () => {
95
- function testPartitioning(partition) {
96
- return forAll(Trace)
97
- .satisfy((t) => {
98
- const bufs = [];
99
- const input = inputs();
100
- const frames = new Frames(input);
101
- let i = 0;
102
- let ex;
103
- frames.accept = (f) => {
104
- // A minor hack to make sure we get the assertion exception;
105
- // otherwise, it's just a test that we reached the line
106
- // incrementing `i` for each frame.
107
- try {
108
- assertEqualModuloDefaults(t[i], f.fields);
109
- } catch (e) {
110
- ex = e;
111
- }
112
- i++;
113
- };
114
-
115
- t.forEach((f) => {
116
- f.channel = 0;
117
- bufs.push(defs.encodeMethod(f.id, 0, f.fields));
118
- });
119
-
120
- partition(bufs).forEach((chunk) => input.write(chunk));
121
- frames.acceptLoop();
122
- if (ex) throw ex;
123
- return i === t.length;
124
- })
125
- .asTest({ times: 20 });
126
- }
127
-
128
- it('Parse trace of methods', testPartitioning((bufs) => bufs));
129
-
130
- it("Parse concat'd methods", testPartitioning((bufs) => [Buffer.concat(bufs)]));
131
-
132
- it('Parse partitioned methods', testPartitioning((bufs) => {
133
- const full = Buffer.concat(bufs);
134
- const onethird = Math.floor(full.length / 3);
135
- const twothirds = 2 * onethird;
136
- return [full.subarray(0, onethird), full.subarray(onethird, twothirds), full.subarray(twothirds)];
137
- }));
138
- });
139
-
140
- const FRAME_MAX_MAX = 4096 * 4;
141
- const FRAME_MAX_MIN = 4096;
142
- const FrameMax = amqp.rangeInt('frame max', FRAME_MAX_MIN, FRAME_MAX_MAX);
143
- const Body = sized((_n) => Math.floor(Math.random() * FRAME_MAX_MAX), repeat(amqp.Octet));
144
-
145
- const Content = transform(
146
- (args) => ({
147
- method: args[0].fields,
148
- header: args[1].fields,
149
- body: Buffer.from(args[2]),
150
- }),
151
- sequence(amqp.methods['BasicDeliver'], amqp.properties['BasicProperties'], Body),
152
- );
153
-
154
- describe('Content framing', () => {
155
- it('Adhere to frame max', forAll(Content, FrameMax)
156
- .satisfy((content, max) => {
157
- const input = inputs();
158
- const frames = new Frames(input);
159
- frames.frameMax = max;
160
- frames.sendMessage(0, defs.BasicDeliver, content.method, defs.BasicProperties, content.header, content.body);
161
- let _i = 0;
162
- let largest = 0;
163
- let frame = input.read();
164
- while (frame) {
165
- _i++;
166
- if (frame.length > largest) largest = frame.length;
167
- if (frame.length > max) {
168
- return false;
169
- }
170
- frame = input.read();
171
- }
172
- // The ratio of frames to 'contents' should always be >= 2
173
- // (one properties frame and at least one content frame); > 2
174
- // indicates fragmentation. The largest is always, of course <= frame max
175
- //console.log('Frames: %d; frames per message: %d; largest frame %d', _i, _i / t.length, largest);
176
- return true;
177
- })
178
- .asTest());
179
- });
180
- });
package/test/lib/data.js DELETED
@@ -1,249 +0,0 @@
1
- const { data: arb, transform, repeat, label, sequence, asGenerator, sized, recursive, choice, Undefined } = require('claire');
2
- const defs = require('../../lib/defs');
3
-
4
- // These aren't exported in claire/index. so I could have to reproduce them I guess.
5
- function choose(a, b) {
6
- return Math.random() * (b - a) + a;
7
- }
8
-
9
- function chooseInt(a, b) {
10
- return Math.floor(choose(a, b));
11
- }
12
-
13
- function rangeInt(name, a, b) {
14
- return label(name, asGenerator((_) => chooseInt(a, b)));
15
- }
16
-
17
- function toFloat32(i) {
18
- const b = Buffer.alloc(4);
19
- b.writeFloatBE(i, 0);
20
- return b.readFloatBE(0);
21
- }
22
-
23
- function floatChooser(maxExp) {
24
- return () => {
25
- let n = Number.NaN;
26
- while (Number.isNaN(n)) {
27
- const mantissa = Math.random() * 2 - 1;
28
- const exponent = chooseInt(0, maxExp);
29
- n = mantissa ** exponent;
30
- }
31
- return n;
32
- };
33
- }
34
-
35
- function explicitType(t, underlying) {
36
- return label(
37
- t,
38
- transform((n) => ({ '!': t, value: n }), underlying),
39
- );
40
- }
41
-
42
- // FIXME null, byte array, others?
43
-
44
- const Octet = rangeInt('octet', 0, 255);
45
- const ShortStr = label('shortstr', transform((s) => s.substr(0, 255), arb.Str));
46
- const LongStr = label('longstr', transform((bytes) => Buffer.from(bytes), repeat(Octet)));
47
- const UShort = rangeInt('short-uint', 0, 0xffff);
48
- const ULong = rangeInt('long-uint', 0, 0xffffffff);
49
- const ULongLong = rangeInt('longlong-uint', 0, Number.MAX_SAFE_INTEGER);
50
- const Short = rangeInt('short-int', -0x8000, 0x7fff);
51
- const Long = rangeInt('long-int', -0x80000000, 0x7fffffff);
52
- const LongLong = rangeInt('longlong-int', Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
53
- const Bit = label('bit', arb.Bool);
54
- const Double = label('double', asGenerator(floatChooser(308)));
55
- const Float = label('float', transform(toFloat32, floatChooser(38)));
56
- const Timestamp = label('timestamp', transform((n) => ({ '!': 'timestamp', value: n }), ULongLong));
57
- const Decimal = label('decimal', transform((args) => ({ '!': 'decimal', value: { places: args[1], digits: args[0] } }), sequence(arb.UInt, Octet)));
58
- const UnsignedByte = label('unsignedbyte', transform((n) => ({ '!': 'unsignedbyte', value: n }), Octet));
59
- const UnsignedShort = label('unsignedshort', transform((n) => ({ '!': 'unsignedshort', value: n }), UShort));
60
- const UnsignedInt = label('unsignedint', transform((n) => ({ '!': 'unsignedint', value: n }), ULong));
61
- const Byte = rangeInt('byte', -128, 127);
62
-
63
- // Explicitly typed values
64
- const ExByte = explicitType('byte', Byte);
65
- const ExInt8 = explicitType('int8', Byte);
66
- const ExShort = explicitType('short', Short);
67
- const ExInt16 = explicitType('int16', Short);
68
- const ExInt = explicitType('int', Long);
69
- const ExInt32 = explicitType('int32', Long);
70
- const ExLong = explicitType('long', LongLong);
71
- const ExInt64 = explicitType('int64', LongLong);
72
-
73
- const FieldArray = label('field-array',
74
- recursive(() =>
75
- arb.Array(
76
- arb.Null,
77
- LongStr,
78
- ShortStr,
79
- Octet,
80
- UShort,
81
- ULong,
82
- ULongLong,
83
- Byte,
84
- Short,
85
- Long,
86
- LongLong,
87
- ExByte,
88
- ExInt8,
89
- ExShort,
90
- ExInt16,
91
- ExInt,
92
- ExInt32,
93
- ExLong,
94
- ExInt64,
95
- Bit,
96
- Float,
97
- Double,
98
- FieldTable,
99
- FieldArray,
100
- ),
101
- ),
102
- );
103
-
104
- const FieldTable = label('field-table',
105
- recursive(() =>
106
- sized(
107
- () => 5,
108
- arb.Object(
109
- arb.Null,
110
- LongStr,
111
- ShortStr,
112
- Octet,
113
- UShort,
114
- ULong,
115
- ULongLong,
116
- Byte,
117
- Short,
118
- Long,
119
- LongLong,
120
- ExByte,
121
- ExInt8,
122
- ExShort,
123
- ExInt16,
124
- ExInt,
125
- ExInt32,
126
- ExLong,
127
- ExInt64,
128
- Bit,
129
- Float,
130
- Double,
131
- FieldArray,
132
- FieldTable,
133
- ),
134
- ),
135
- ),
136
- );
137
-
138
- // For methods and properties (as opposed to field table values) it's
139
- // easier just to accept and produce numbers for timestamps.
140
- const ArgTimestamp = label('timestamp', ULongLong);
141
-
142
- // These are the domains used in method arguments
143
- const ARG_TYPES = {
144
- octet: Octet,
145
- shortstr: ShortStr,
146
- longstr: LongStr,
147
- short: UShort,
148
- long: ULong,
149
- longlong: ULongLong,
150
- bit: Bit,
151
- table: FieldTable,
152
- timestamp: ArgTimestamp,
153
- };
154
-
155
- function argtype(thing) {
156
- if (thing.default === undefined) {
157
- return ARG_TYPES[thing.type];
158
- } else {
159
- return choice(ARG_TYPES[thing.type], Undefined);
160
- }
161
- }
162
-
163
- function zipObject(vals, names) {
164
- const obj = {};
165
- vals.forEach((v, i) => {
166
- obj[names[i]] = v;
167
- });
168
- return obj;
169
- }
170
-
171
- function name(arg) {
172
- return arg.name;
173
- }
174
-
175
- function method(info) {
176
- const domain = sequence.apply(null, info.args.map(argtype));
177
- const names = info.args.map(name);
178
- return label(
179
- info.name,
180
- transform((fieldVals) => ({ id: info.id, fields: zipObject(fieldVals, names) }), domain),
181
- );
182
- }
183
-
184
- function properties(info) {
185
- const types = info.args.map(argtype);
186
- types.unshift(ULongLong); // size
187
- const domain = sequence.apply(null, types);
188
- const names = info.args.map(name);
189
- return label(info.name,
190
- transform((fieldVals) => ({ id: info.id, size: fieldVals[0], fields: zipObject(fieldVals.slice(1), names) }), domain),
191
- );
192
- }
193
-
194
- const methods = [];
195
- const propertieses = [];
196
-
197
- for (const k in defs) {
198
- if (k.substr(0, 10) === 'methodInfo') {
199
- methods.push(method(defs[k]));
200
- methods[defs[k].name] = method(defs[k]);
201
- } else if (k.substr(0, 14) === 'propertiesInfo') {
202
- propertieses.push(properties(defs[k]));
203
- propertieses[defs[k].name] = properties(defs[k]);
204
- }
205
- }
206
-
207
- const OPEN_OPTS = {
208
- // start-ok
209
- clientProperties: {},
210
- mechanism: 'PLAIN',
211
- response: Buffer.from(['', 'guest', 'guest'].join(String.fromCharCode(0))),
212
- locale: 'en_US',
213
-
214
- // tune-ok
215
- channelMax: 0,
216
- frameMax: 0,
217
- heartbeat: null,
218
-
219
- // open
220
- virtualHost: '/',
221
- capabilities: '',
222
- insist: 0,
223
- };
224
-
225
- module.exports = {
226
- Octet,
227
- ShortStr,
228
- LongStr,
229
- UShort,
230
- ULong,
231
- ULongLong,
232
- Short,
233
- Long,
234
- LongLong,
235
- Bit,
236
- Double,
237
- Float,
238
- Timestamp,
239
- Decimal,
240
- UnsignedByte,
241
- UnsignedShort,
242
- UnsignedInt,
243
- FieldArray,
244
- FieldTable,
245
- methods,
246
- properties: propertieses,
247
- rangeInt,
248
- OPEN_OPTS,
249
- };
package/test/lib/util.js DELETED
@@ -1,223 +0,0 @@
1
- const crypto = require('node:crypto');
2
- const PassThrough = require('node:stream').PassThrough;
3
- const Connection = require('../../lib/connection').Connection;
4
- const defs = require('../../lib/defs');
5
- const assert = require('node:assert');
6
-
7
- const schedule = typeof setImmediate === 'function' ? setImmediate : process.nextTick;
8
-
9
- function randomString() {
10
- const hash = crypto.createHash('sha1');
11
- hash.update(crypto.randomBytes(64));
12
- return hash.digest('base64');
13
- }
14
-
15
- // Set up a socket pair {client, server}, such that writes to the
16
- // client are readable from the server, and writes to the server are
17
- // readable at the client.
18
- //
19
- // +---+ +---+
20
- // | C | | S |
21
- // --write->| l |----->| e |--read-->
22
- // | i | | r |
23
- // <--read--| e |<-----| v |<-write--
24
- // | n | | e |
25
- // | t | | r |
26
- // +---+ +---+
27
- //
28
- // I also need to make sure that end called on either socket affects
29
- // the other.
30
-
31
- function socketPair() {
32
- const server = new PassThrough();
33
- const client = new PassThrough();
34
- server.write = client.push.bind(client);
35
- client.write = server.push.bind(server);
36
- function end(chunk, encoding) {
37
- if (chunk) this.push(chunk, encoding);
38
- this.push(null);
39
- }
40
- server.end = end.bind(client);
41
- client.end = end.bind(server);
42
-
43
- return { client: client, server: server };
44
- }
45
-
46
- function runServer(socket, run) {
47
- const frames = new Connection(socket);
48
- // We will be closing the socket without doing a closing handshake,
49
- // so cheat
50
- frames.expectSocketClose = true;
51
- // We also need to create some channel buffers, again a cheat
52
- frames.freshChannel(null);
53
- frames.freshChannel(null);
54
- frames.freshChannel(null);
55
-
56
- function send(id, fields, channel, content) {
57
- channel = channel || 0;
58
- if (content) {
59
- schedule(() => {
60
- frames.sendMessage(channel, id, fields, defs.BasicProperties, fields, content);
61
- });
62
- } else {
63
- schedule(() => {
64
- frames.sendMethod(channel, id, fields);
65
- });
66
- }
67
- }
68
-
69
- function wait(method) {
70
- return () =>
71
- new Promise((resolve, reject) => {
72
- if (method) {
73
- frames.step((e, f) => {
74
- if (e !== null) return reject(e);
75
- if (f.id === method) resolve(f);
76
- else reject(new Error(`Expected method: ${method}, got ${f.id}`));
77
- });
78
- } else {
79
- frames.step((e, f) => {
80
- if (e !== null) return reject(e);
81
- else resolve(f);
82
- });
83
- }
84
- });
85
- }
86
- run(send, wait);
87
- return frames;
88
- }
89
-
90
- // Create a function that will call done once it's been called itself
91
- // `count` times. If it's called with an error value, it will
92
- // immediately call done with that error value.
93
- function latch(count, done) {
94
- let awaiting = count;
95
- let alive = true;
96
- return (err) => {
97
- if (err instanceof Error && alive) {
98
- alive = false;
99
- done(err);
100
- } else {
101
- awaiting--;
102
- if (awaiting === 0 && alive) {
103
- alive = false;
104
- done();
105
- }
106
- }
107
- };
108
- }
109
-
110
- // Call a thunk with a continuation that will be called with an error
111
- // if the thunk throws one, or nothing if it runs to completion.
112
- function completes(thunk, done) {
113
- try {
114
- thunk();
115
- done();
116
- } catch (e) {
117
- done(e);
118
- }
119
- }
120
-
121
-
122
- // When encoding, you can supply explicitly-typed fields like `{'!':
123
- // int32, 50}`. Most of these do not appear in the decoded values, so
124
- // to compare like-to-like we have to remove them from the input.
125
- function removeExplicitTypes(input) {
126
- switch (typeof input) {
127
- case 'object':
128
- if (input == null) {
129
- return null;
130
- }
131
- if (Array.isArray(input)) {
132
- const newArr = [];
133
- for (let i = 0; i < input.length; i++) {
134
- newArr[i] = removeExplicitTypes(input[i]);
135
- }
136
- return newArr;
137
- }
138
- if (Buffer.isBuffer(input)) {
139
- return input;
140
- }
141
- switch (input['!']) {
142
- case 'timestamp':
143
- case 'decimal':
144
- case 'float':
145
- return input;
146
- case undefined: {
147
- const newObj = {};
148
- for (const k in input) {
149
- newObj[k] = removeExplicitTypes(input[k]);
150
- }
151
- return newObj;
152
- }
153
- default:
154
- return input.value;
155
- }
156
-
157
- default:
158
- return input;
159
- }
160
- }
161
-
162
- // Asserts that the decoded fields are equal to the original fields,
163
- // or equal to a default where absent in the original. The defaults
164
- // depend on the type of method or properties.
165
- //
166
- // This works slightly different for methods and properties: for
167
- // methods, each field must have a value, so the default is
168
- // substituted for undefined values when encoding; for properties,
169
- // fields may be absent in the encoded value, so a default is
170
- // substituted for missing fields when decoding. The effect is the
171
- // same so far as these tests are concerned.
172
- function assertEqualModuloDefaults(original, decodedFields) {
173
- const args = defs.info(original.id).args;
174
- for (let i = 0; i < args.length; i++) {
175
- const arg = args[i];
176
- const originalValue = original.fields[arg.name];
177
- const decodedValue = decodedFields[arg.name];
178
- try {
179
- if (originalValue === undefined) {
180
- // longstr gets special treatment here, since the defaults are
181
- // given as strings rather than buffers, but the decoded values
182
- // will be buffers.
183
- assert.deepEqual(arg.type === 'longstr' ? Buffer.from(arg.default) : arg.default, decodedValue);
184
- } else {
185
- assert.deepEqual(removeExplicitTypes(originalValue), decodedValue);
186
- }
187
- } catch (assertionErr) {
188
- const methodOrProps = defs.info(original.id).name;
189
- assertionErr.message += ` (frame ${methodOrProps} field ${arg.name})`;
190
- throw assertionErr;
191
- }
192
- }
193
- // %%% TODO make sure there's no surplus fields
194
- return true;
195
- }
196
-
197
- function handshake(send, wait) {
198
- // kick it off
199
- send(defs.ConnectionStart, {
200
- versionMajor: 0,
201
- versionMinor: 9,
202
- serverProperties: {},
203
- mechanisms: Buffer.from('PLAIN'),
204
- locales: Buffer.from('en_US'),
205
- });
206
- return wait(defs.ConnectionStartOk)()
207
- .then(() => send(defs.ConnectionTune, { channelMax: 0, heartbeat: 0, frameMax: 0 }))
208
- .then(wait(defs.ConnectionTuneOk))
209
- .then(wait(defs.ConnectionOpen))
210
- .then(() => send(defs.ConnectionOpenOk, { knownHosts: '' }));
211
- }
212
-
213
- module.exports = {
214
- socketPair,
215
- runServer,
216
- latch,
217
- completes,
218
- schedule,
219
- randomString,
220
- removeExplicitTypes,
221
- assertEqualModuloDefaults,
222
- handshake,
223
- };