Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/jupyter/zmq/message.ts
1447 views
1
/*
2
This is from https://github.com/n-riesco/jmp but rewritten in typescript.
3
4
The original and all modifications in CoCalc of the code in THIS DIRECTORY
5
are: * BSD 3-Clause License *
6
7
*/
8
9
/*
10
* BSD 3-Clause License
11
*
12
* Copyright (c) 2015, Nicolas Riesco and others as credited in the AUTHORS file
13
* All rights reserved.
14
*
15
* Redistribution and use in source and binary forms, with or without
16
* modification, are permitted provided that the following conditions are met:
17
*
18
* 1. Redistributions of source code must retain the above copyright notice,
19
* this list of conditions and the following disclaimer.
20
*
21
* 2. Redistributions in binary form must reproduce the above copyright notice,
22
* this list of conditions and the following disclaimer in the documentation
23
* and/or other materials provided with the distribution.
24
*
25
* 3. Neither the name of the copyright holder nor the names of its contributors
26
* may be used to endorse or promote products derived from this software without
27
* specific prior written permission.
28
*
29
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
30
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
33
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
34
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
37
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39
* POSSIBILITY OF SUCH DAMAGE.
40
*
41
*/
42
43
import crypto from "crypto";
44
import { v4 as uuid } from "uuid";
45
import { Dealer } from "zeromq";
46
47
const DEBUG = (global as any).DEBUG || false;
48
49
let log: (...args: any[]) => void;
50
if (DEBUG) {
51
// eslint-disable-next-line @typescript-eslint/no-var-requires
52
const console = require("console");
53
log = (...args) => {
54
process.stderr.write("JMP: ");
55
console.error(...args);
56
};
57
} else {
58
try {
59
// eslint-disable-next-line @typescript-eslint/no-var-requires
60
log = require("debug")("JMP:");
61
} catch {
62
log = () => {};
63
}
64
}
65
66
export const DELIMITER = "<IDS|MSG>";
67
68
export interface JupyterHeader {
69
msg_id?: string;
70
username?: string;
71
session?: string;
72
msg_type?: string;
73
version?: string;
74
[key: string]: any;
75
}
76
77
export interface MessageProps {
78
idents?: Buffer[];
79
header?: JupyterHeader;
80
parent_header?: JupyterHeader;
81
metadata?: any;
82
content?: any;
83
buffers?;
84
}
85
86
export class Message {
87
idents: Buffer[];
88
header: JupyterHeader;
89
parent_header: JupyterHeader;
90
metadata: { [key: string]: any };
91
content: { [key: string]: any };
92
buffers: Buffer[];
93
94
constructor(properties?: MessageProps) {
95
this.idents = properties?.idents ?? [];
96
this.header = properties?.header ?? {};
97
this.parent_header = properties?.parent_header ?? {};
98
this.metadata = properties?.metadata ?? {};
99
this.content = properties?.content ?? {};
100
this.buffers = properties?.buffers ?? [];
101
}
102
103
respond(
104
socket: Dealer,
105
messageType: string,
106
content?: object,
107
metadata?: object,
108
protocolVersion?: string,
109
): Message {
110
const response = new Message();
111
response.idents = this.idents.slice();
112
response.header = {
113
msg_id: uuid(),
114
username: this.header.username,
115
session: this.header.session,
116
msg_type: messageType,
117
};
118
if (this.header?.version) {
119
response.header.version = this.header.version;
120
}
121
if (protocolVersion) {
122
response.header.version = protocolVersion;
123
}
124
response.parent_header = { ...this.header };
125
response.content = content ?? {};
126
response.metadata = metadata ?? {};
127
socket.send(response as any);
128
return response;
129
}
130
131
static _decode(
132
messageFrames: Buffer[] | IArguments,
133
scheme = "sha256",
134
key = "",
135
): Message | null {
136
try {
137
return _decode(messageFrames, scheme, key);
138
} catch (err) {
139
log("MESSAGE: DECODE: Error:", err);
140
return null;
141
}
142
}
143
144
_encode(scheme = "sha256", key = ""): (Buffer | string)[] {
145
const idents = this.idents;
146
147
const header = JSON.stringify(this.header);
148
const parent_header = JSON.stringify(this.parent_header);
149
const metadata = JSON.stringify(this.metadata);
150
const content = JSON.stringify(this.content);
151
152
let signature = "";
153
if (key) {
154
const hmac = crypto.createHmac(scheme, key);
155
const encoding = "utf8";
156
hmac.update(Buffer.from(header, encoding));
157
hmac.update(Buffer.from(parent_header, encoding));
158
hmac.update(Buffer.from(metadata, encoding));
159
hmac.update(Buffer.from(content, encoding));
160
signature = hmac.digest("hex");
161
}
162
163
return [
164
...idents,
165
DELIMITER,
166
signature,
167
header,
168
parent_header,
169
metadata,
170
content,
171
...this.buffers,
172
];
173
}
174
}
175
176
// Helper decode
177
function _decode(
178
messageFrames: Buffer[] | IArguments,
179
scheme: string,
180
key: string,
181
): Message | null {
182
// Could be an arguments object, convert to array if so
183
const frames = Array.isArray(messageFrames)
184
? messageFrames
185
: Array.prototype.slice.call(messageFrames);
186
187
let i = 0;
188
const idents: Buffer[] = [];
189
for (; i < frames.length; i++) {
190
const frame = frames[i];
191
if (frame.toString() === DELIMITER) break;
192
idents.push(frame);
193
}
194
195
if (frames.length - i < 5) {
196
log("MESSAGE: DECODE: Not enough message frames", frames);
197
return null;
198
}
199
200
if (frames[i].toString() !== DELIMITER) {
201
log("MESSAGE: DECODE: Missing delimiter", frames);
202
return null;
203
}
204
205
if (key) {
206
const obtainedSignature = frames[i + 1].toString();
207
const hmac = crypto.createHmac(scheme, key);
208
hmac.update(frames[i + 2]);
209
hmac.update(frames[i + 3]);
210
hmac.update(frames[i + 4]);
211
hmac.update(frames[i + 5]);
212
const expectedSignature = hmac.digest("hex");
213
214
if (expectedSignature !== obtainedSignature) {
215
log(
216
"MESSAGE: DECODE: Incorrect message signature:",
217
"Obtained =",
218
obtainedSignature,
219
"Expected =",
220
expectedSignature,
221
);
222
return null;
223
}
224
}
225
226
return new Message({
227
idents: idents,
228
header: JSON.parse(frames[i + 2].toString()),
229
parent_header: JSON.parse(frames[i + 3].toString()),
230
metadata: JSON.parse(frames[i + 4].toString()),
231
content: JSON.parse(frames[i + 5].toString()),
232
buffers: frames.slice(i + 6),
233
});
234
}
235
236