Skip to content

Commit 4609f95

Browse files
committed
Implement Best Effort Application Transport (BEAT)
Change-Id: I7d800955194acc5c28b013b32421fdbf795abfd9
1 parent f147776 commit 4609f95

File tree

9 files changed

+946
-2
lines changed

9 files changed

+946
-2
lines changed

src/Link.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Transport, { TransportConstructor } from './transports';
66
import { SocketLike } from './Socket';
77

88
jest.mock('./Interface');
9+
jest.mock('./transports/BestEffortTransport');
910

1011
let intf: Interface;
1112
let link: Link;

src/Link.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import EventEmitter from 'events';
22
import Interface from './Interface';
3-
import Transport, { TransportConstructor } from './transports';
3+
import Transport, {
4+
BestEffortTransport,
5+
TransportConstructor,
6+
} from './transports';
47
import { SocketLike } from './Socket';
58

69
export default class Link extends EventEmitter {
710
private static availableTransports: { [name: string]: TransportConstructor } =
8-
{};
11+
{
12+
bestEffort: BestEffortTransport,
13+
};
914

1015
public closed = false;
1116
private transports: { [name: string]: Transport } = {};
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import PulseControlMessageProtocol from './PulseControlMessageProtocol';
2+
import Transport from '../transports';
3+
4+
import { encode } from '../encodingUtil';
5+
6+
let transport: Transport;
7+
let pcmp: PulseControlMessageProtocol;
8+
9+
beforeEach(() => {
10+
jest.useFakeTimers();
11+
transport = {
12+
unregisterSocket: jest.fn(),
13+
send: jest.fn(),
14+
openSocket: jest.fn(),
15+
down: jest.fn(),
16+
mtu: 0,
17+
};
18+
pcmp = new PulseControlMessageProtocol(transport);
19+
});
20+
21+
afterEach(() => {
22+
jest.useRealTimers();
23+
});
24+
25+
describe('close', () => {
26+
beforeEach(() => {
27+
pcmp.close();
28+
});
29+
30+
it('unregisters the socket', () => {
31+
expect(transport.unregisterSocket).toBeCalledTimes(1);
32+
});
33+
34+
it('is idempotent', () => {
35+
pcmp.close();
36+
expect(transport.unregisterSocket).toBeCalledTimes(1);
37+
});
38+
});
39+
40+
describe('ping', () => {
41+
describe('resolves', () => {
42+
it('simple', async () => {
43+
const pingPromise = pcmp.ping();
44+
pcmp.onReceive(encode('\x02'));
45+
await expect(pingPromise).resolves.toBeUndefined();
46+
expect(jest.getTimerCount()).toEqual(0);
47+
});
48+
49+
it('succeeds after single retry', async () => {
50+
const attempts = 2;
51+
const timeout = 1000;
52+
const pingPromise = pcmp.ping(attempts, timeout);
53+
jest.advanceTimersByTime(timeout);
54+
pcmp.onReceive(encode('\x02'));
55+
await expect(pingPromise).resolves.toBeUndefined();
56+
expect(jest.getTimerCount()).toEqual(0);
57+
});
58+
59+
it('succeeds after multiple retries', async () => {
60+
const attempts = 3;
61+
const timeout = 1000;
62+
const pingPromise = pcmp.ping(attempts, timeout);
63+
expect(transport.send).toBeCalledTimes(1);
64+
jest.advanceTimersByTime(timeout);
65+
expect(transport.send).toBeCalledTimes(2);
66+
jest.advanceTimersByTime(timeout);
67+
expect(transport.send).toBeCalledTimes(attempts);
68+
pcmp.onReceive(encode('\x02'));
69+
await expect(pingPromise).resolves.toBeUndefined();
70+
expect(jest.getTimerCount()).toEqual(0);
71+
});
72+
});
73+
74+
describe('rejects', () => {
75+
it('on timeout after single attempt', async () => {
76+
const attempts = 1;
77+
const timeout = 1000;
78+
const pingPromise = pcmp.ping(attempts, timeout);
79+
jest.advanceTimersByTime(timeout);
80+
await expect(pingPromise).rejects.toThrow('Ping timed out');
81+
expect(jest.getTimerCount()).toEqual(0);
82+
});
83+
84+
it('on timeout after multiple attempts', async () => {
85+
const attempts = 3;
86+
const timeout = 1000;
87+
const pingPromise = pcmp.ping(attempts, timeout);
88+
expect(transport.send).toBeCalledTimes(1);
89+
jest.advanceTimersByTime(timeout);
90+
expect(transport.send).toBeCalledTimes(2);
91+
jest.advanceTimersByTime(timeout);
92+
expect(transport.send).toBeCalledTimes(attempts);
93+
jest.advanceTimersByTime(timeout);
94+
await expect(pingPromise).rejects.toThrow('Ping timed out');
95+
expect(jest.getTimerCount()).toEqual(0);
96+
});
97+
98+
it('if socket is closed', async () => {
99+
const pingPromise = pcmp.ping();
100+
pcmp.close();
101+
await expect(pingPromise).rejects.toThrow('Ping failed: socket closed');
102+
expect(jest.getTimerCount()).toEqual(0);
103+
});
104+
105+
it('when attempts=0', () => {
106+
return expect(pcmp.ping(0)).rejects.toThrowError(
107+
'attempts must be positive',
108+
);
109+
});
110+
111+
it('when attempts=-1', () => {
112+
return expect(pcmp.ping(-1)).rejects.toThrowError(
113+
'attempts must be positive',
114+
);
115+
});
116+
117+
it('when timeout=0', () => {
118+
return expect(pcmp.ping(1, 0)).rejects.toThrowError(
119+
'timeout must be positive',
120+
);
121+
});
122+
123+
it('when timeout=-1', () => {
124+
return expect(pcmp.ping(1, -1)).rejects.toThrowError(
125+
'timeout must be positive',
126+
);
127+
});
128+
129+
it('ping already in progress', () => {
130+
void pcmp.ping();
131+
return expect(pcmp.ping()).rejects.toThrowError(
132+
'Another ping is currently in progress',
133+
);
134+
});
135+
});
136+
});
137+
138+
it('send unknown code', () => {
139+
// eslint-disable-next-line
140+
(pcmp as any).sendUnknownCode(42);
141+
expect(transport.send).toBeCalledWith(1, encode('\x82\x2a'));
142+
});
143+
144+
it('send echo request', () => {
145+
// eslint-disable-next-line
146+
(pcmp as any).sendEchoRequest(encode('abcdefg'));
147+
expect(transport.send).toBeCalledWith(1, encode('\x01abcdefg'));
148+
});
149+
150+
it('send echo reply', () => {
151+
// eslint-disable-next-line
152+
(pcmp as any).sendEchoReply(encode('abcdefg'));
153+
expect(transport.send).toBeCalledWith(1, encode('\x02abcdefg'));
154+
});
155+
156+
it('receieve an empty packet', () => {
157+
pcmp.onReceive(Buffer.alloc(0));
158+
expect(transport.send).not.toBeCalled();
159+
});
160+
161+
it('recieve a message with an unknown code', () => {
162+
pcmp.onReceive(encode('\x00'));
163+
expect(transport.send).toBeCalledWith(1, encode('\x82\x00'));
164+
});
165+
166+
it('recieve a message with malformed unknown code', () => {
167+
pcmp.onReceive(encode('\x82'));
168+
expect(transport.send).not.toBeCalled();
169+
});
170+
171+
it('recieve a message with malformed unknown code', () => {
172+
pcmp.onReceive(encode('\x82\x00\x01'));
173+
expect(transport.send).not.toBeCalled();
174+
});
175+
176+
it('receive a discard request', () => {
177+
pcmp.onReceive(encode('\x03'));
178+
expect(transport.send).not.toBeCalled();
179+
});
180+
181+
it('receive a discard request with data', () => {
182+
pcmp.onReceive(encode('\x03asdfasdfasdf'));
183+
expect(transport.send).not.toBeCalled();
184+
});
185+
186+
it('receieve an echo request', () => {
187+
pcmp.onReceive(encode('\x01'));
188+
expect(transport.send).toBeCalledWith(1, encode('\x02'));
189+
});
190+
191+
it('receieve an echo request with data', () => {
192+
pcmp.onReceive(encode('\x01a'));
193+
expect(transport.send).toBeCalledWith(1, encode('\x02a'));
194+
});
195+
196+
it('receive an echo reply', () => {
197+
pcmp.onReceive(encode('\x02'));
198+
expect(transport.send).not.toBeCalled();
199+
});
200+
201+
it('receive an echo reply with data', () => {
202+
pcmp.onReceive(encode('\x02abc'));
203+
expect(transport.send).not.toBeCalled();
204+
});
205+
206+
it('receive port closed without handler', () => {
207+
pcmp.onReceive(encode('\x81\xab\xcd'));
208+
expect(transport.send).not.toBeCalled();
209+
});
210+
211+
it('receive port closed with handler', () => {
212+
const closedHandler = jest.fn();
213+
pcmp.on('portClosed', closedHandler);
214+
pcmp.onReceive(encode('\x81\xab\xcd'));
215+
expect(closedHandler).toBeCalledWith(0xabcd);
216+
});
217+
218+
it('receive malformed port closed', () => {
219+
const closedHandler = jest.fn();
220+
pcmp.on('portClosed', closedHandler);
221+
pcmp.onReceive(encode('\x81\xab'));
222+
expect(closedHandler).not.toBeCalled();
223+
});
224+
225+
it('receive malformed port closed', () => {
226+
const closedHandler = jest.fn();
227+
pcmp.on('portClosed', closedHandler);
228+
pcmp.onReceive(encode('\x81\xab\xcd\xef'));
229+
expect(closedHandler).not.toBeCalled();
230+
});
231+
232+
it('cannot be used as a socket', () => {
233+
expect(() => pcmp.send(Buffer.alloc(1))).toThrowError(
234+
'PCMP cannot be used as a socket',
235+
);
236+
});
237+
238+
it('ignores a received malformed UnknownCode packet', () => {
239+
expect(() => pcmp.onReceive(encode('\x82'))).not.toThrowError();
240+
});
241+
242+
it('ignores a received UnknownCode packet', () => {
243+
expect(() => pcmp.onReceive(encode('\x82\xff'))).not.toThrowError();
244+
});

0 commit comments

Comments
 (0)