Skip to content

Commit e2f35b4

Browse files
committed
dev: CLink and SLink classes
1 parent c213b21 commit e2f35b4

File tree

7 files changed

+360
-257
lines changed

7 files changed

+360
-257
lines changed

doc/contributors/comment_prefixes.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,7 @@ This document will be updated on an _as-needed_ basis.
2727
[track-comments.md](../devmeta/track-comments.md)
2828
- `wet:` is usesd to track anything that doesn't adhere
2929
to the DRY principle; the following message should describe
30-
where similar code is
30+
where similar code is
31+
- `compare(<identifier>):` is used to note differences between other
32+
implementations of a similar idea
33+
- `name:` pedantic commentary on the name of something

src/backend/src/modules/broadcast/BroadcastService.js

+14-256
Original file line numberDiff line numberDiff line change
@@ -18,257 +18,8 @@
1818
*/
1919
const { AdvancedBase } = require("@heyputer/puter-js-common");
2020
const BaseService = require("../../services/BaseService");
21-
22-
class KeyPairHelper extends AdvancedBase {
23-
static MODULES = {
24-
tweetnacl: require('tweetnacl'),
25-
};
26-
27-
constructor ({
28-
kpublic,
29-
ksecret,
30-
}) {
31-
super();
32-
this.kpublic = kpublic;
33-
this.ksecret = ksecret;
34-
this.nonce_ = 0;
35-
}
36-
37-
to_nacl_key_ (key) {
38-
console.log('WUT', key);
39-
const full_buffer = Buffer.from(key, 'base64');
40-
41-
// Remove version byte (assumed to be 0x31 and ignored for now)
42-
const buffer = full_buffer.slice(1);
43-
44-
return new Uint8Array(buffer);
45-
}
46-
47-
get naclSecret () {
48-
return this.naclSecret_ ?? (
49-
this.naclSecret_ = this.to_nacl_key_(this.ksecret));
50-
}
51-
get naclPublic () {
52-
return this.naclPublic_ ?? (
53-
this.naclPublic_ = this.to_nacl_key_(this.kpublic));
54-
}
55-
56-
write (text) {
57-
const require = this.require;
58-
const nacl = require('tweetnacl');
59-
60-
const nonce = nacl.randomBytes(nacl.box.nonceLength);
61-
const message = {};
62-
63-
const textUint8 = new Uint8Array(Buffer.from(text, 'utf-8'));
64-
const encryptedText = nacl.box(
65-
textUint8, nonce,
66-
this.naclPublic, this.naclSecret
67-
);
68-
message.text = Buffer.from(encryptedText);
69-
message.nonce = Buffer.from(nonce);
70-
71-
return message;
72-
}
73-
74-
read (message) {
75-
const require = this.require;
76-
const nacl = require('tweetnacl');
77-
78-
const arr = nacl.box.open(
79-
new Uint8Array(message.text),
80-
new Uint8Array(message.nonce),
81-
this.naclPublic,
82-
this.naclSecret,
83-
);
84-
85-
return Buffer.from(arr).toString('utf-8');
86-
}
87-
}
88-
89-
class Peer extends AdvancedBase {
90-
static AUTHENTICATING = Symbol('AUTHENTICATING');
91-
static ONLINE = Symbol('ONLINE');
92-
static OFFLINE = Symbol('OFFLINE');
93-
94-
static MODULES = {
95-
sioclient: require('socket.io-client'),
96-
crypto: require('crypto'),
97-
};
98-
99-
constructor (svc_broadcast, config) {
100-
super();
101-
this.svc_broadcast = svc_broadcast;
102-
this.log = this.svc_broadcast.log;
103-
this.config = config;
104-
}
105-
106-
send (data) {
107-
if ( ! this.socket ) return;
108-
const require = this.require;
109-
const crypto = require('crypto');
110-
const iv = crypto.randomBytes(16);
111-
const cipher = crypto.createCipheriv(
112-
'aes-256-cbc',
113-
this.aesKey,
114-
iv,
115-
);
116-
const jsonified = JSON.stringify(data);
117-
let buffers = [];
118-
buffers.push(cipher.update(Buffer.from(jsonified, 'utf-8')));
119-
buffers.push(cipher.final());
120-
const buffer = Buffer.concat(buffers);
121-
this.socket.send({
122-
iv,
123-
message: buffer,
124-
});
125-
}
126-
127-
get state () {
128-
try {
129-
if ( this.socket?.connected ) return this.constructor.ONLINE;
130-
} catch (e) {
131-
console.error('could not get peer state', e);
132-
}
133-
return this.constructor.OFFLINE;
134-
}
135-
136-
connect () {
137-
const address = this.config.address;
138-
const socket = this.modules.sioclient(address, {
139-
transports: ['websocket'],
140-
path: '/wssinternal',
141-
reconnection: true,
142-
extraHeaders: {
143-
...(this.config.host ? {
144-
Host: this.config.host,
145-
} : {})
146-
}
147-
});
148-
socket.on('connect', () => {
149-
this.log.info(`connected`, {
150-
address: this.config.address
151-
});
152-
153-
const require = this.require;
154-
const crypto = require('crypto');
155-
this.aesKey = crypto.randomBytes(32);
156-
157-
const kp_helper = new KeyPairHelper({
158-
kpublic: this.config.key,
159-
ksecret: this.svc_broadcast.config.keys.secret,
160-
});
161-
socket.send({
162-
$: 'take-my-key',
163-
key: this.svc_broadcast.config.keys.public,
164-
message: kp_helper.write(
165-
this.aesKey.toString('base64')
166-
),
167-
});
168-
});
169-
socket.on('disconnect', () => {
170-
this.log.info(`disconnected`, {
171-
address: this.config.address
172-
});
173-
});
174-
socket.on('connect_error', e => {
175-
this.log.info(`connection error`, {
176-
address: this.config.address,
177-
message: e.message,
178-
});
179-
});
180-
socket.on('error', e => {
181-
this.log.info('error', {
182-
message: e.message,
183-
});
184-
});
185-
186-
this.socket = socket;
187-
}
188-
}
189-
190-
class Connection extends AdvancedBase {
191-
static MODULES = {
192-
crypto: require('crypto'),
193-
}
194-
195-
static AUTHENTICATING = {
196-
on_message (data) {
197-
if ( data.$ !== 'take-my-key' ) {
198-
this.disconnect();
199-
return;
200-
}
201-
202-
const hasKey = this.svc_broadcast.trustedPublicKeys_[data.key];
203-
if ( ! hasKey ) {
204-
this.disconnect();
205-
return;
206-
}
207-
208-
const is_trusted =
209-
this.svc_broadcast.trustedPublicKeys_
210-
.hasOwnProperty(data.key)
211-
if ( ! is_trusted ) {
212-
this.disconnect();
213-
return;
214-
}
215-
216-
const kp_helper = new KeyPairHelper({
217-
kpublic: data.key,
218-
ksecret: this.svc_broadcast.config.keys.secret,
219-
});
220-
221-
const message = kp_helper.read(data.message);
222-
this.aesKey = Buffer.from(message, 'base64');
223-
224-
this.state = this.constructor.ONLINE;
225-
}
226-
}
227-
static ONLINE = {
228-
on_message (data) {
229-
if ( ! this.on_message ) return;
230-
231-
const require = this.require;
232-
const crypto = require('crypto');
233-
const decipher = crypto.createDecipheriv(
234-
'aes-256-cbc',
235-
this.aesKey,
236-
data.iv,
237-
)
238-
const buffers = [];
239-
buffers.push(decipher.update(data.message));
240-
buffers.push(decipher.final());
241-
242-
const rawjson = Buffer.concat(buffers).toString('utf-8');
243-
244-
const output = JSON.parse(rawjson);
245-
246-
this.on_message(output);
247-
}
248-
}
249-
static OFFLINE = {
250-
on_message () {
251-
throw new Error('unexpected message');
252-
}
253-
}
254-
255-
constructor (svc_broadcast, socket) {
256-
super();
257-
this.state = this.constructor.AUTHENTICATING;
258-
this.svc_broadcast = svc_broadcast;
259-
this.log = this.svc_broadcast.log;
260-
this.socket = socket;
261-
262-
socket.on('message', data => {
263-
this.state.on_message.call(this, data);
264-
});
265-
}
266-
267-
disconnect () {
268-
this.socket.disconnect(true);
269-
this.state = this.constructor.OFFLINE;
270-
}
271-
}
21+
const { CLink } = require("./connection/CLink");
22+
const { SLink } = require("./connection/SLink");
27223

27324
class BroadcastService extends BaseService {
27425
static MODULES = {
@@ -286,7 +37,11 @@ class BroadcastService extends BaseService {
28637
const peers = this.config.peers ?? [];
28738
for ( const peer_config of peers ) {
28839
this.trustedPublicKeys_[peer_config.key] = true;
289-
const peer = new Peer(this, peer_config);
40+
const peer = new CLink({
41+
keys: this.config.keys,
42+
config: peer_config,
43+
log: this.log,
44+
});
29045
this.peers_.push(peer);
29146
peer.connect();
29247
}
@@ -301,7 +56,6 @@ class BroadcastService extends BaseService {
30156
if ( meta.from_outside ) return;
30257

30358
for ( const peer of this.peers_ ) {
304-
if ( peer.state !== Peer.ONLINE ) continue;
30559
peer.send({ key, data, meta });
30660
}
30761
}
@@ -318,10 +72,14 @@ class BroadcastService extends BaseService {
31872
});
31973

32074
io.on('connection', async socket => {
321-
const conn = new Connection(this, socket);
75+
const conn = new SLink({
76+
keys: this.config.keys,
77+
trustedKeys: this.trustedPublicKeys_,
78+
socket,
79+
});
32280
this.connections_.push(conn);
32381

324-
conn.on_message = ({ key, data, meta }) => {
82+
conn.channels.message.on(({ key, data, meta }) => {
32583
if ( meta.from_outside ) {
32684
this.log.noticeme('possible over-sending');
32785
return;
@@ -335,7 +93,7 @@ class BroadcastService extends BaseService {
33593

33694
meta.from_outside = true;
33795
svc_event.emit(key, data, meta);
338-
};
96+
});
33997
});
34098

34199

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const { AdvancedBase } = require("@heyputer/puter-js-common");
2+
const { ChannelFeature } = require("../../../traits/ChannelFeature");
3+
4+
class BaseLink extends AdvancedBase {
5+
static FEATURES = [
6+
new ChannelFeature(),
7+
];
8+
static CHANNELS = ['message'];
9+
10+
static MODULES = {
11+
crypto: require('crypto'),
12+
};
13+
14+
static AUTHENTICATING = {};
15+
static ONLINE = {};
16+
static OFFLINE = {};
17+
18+
send (data) {
19+
if ( this.state !== this.constructor.ONLINE ) {
20+
return false;
21+
}
22+
23+
return this._send(data);
24+
}
25+
26+
constructor () {
27+
super();
28+
this.state = this.constructor.AUTHENTICATING;
29+
}
30+
}
31+
32+
module.exports = {
33+
BaseLink,
34+
};

0 commit comments

Comments
 (0)