smoldot 1.0.11 → 1.0.13

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.
@@ -120,12 +120,11 @@ export interface ConnectionConfig {
120
120
  type: 'single-stream';
121
121
  handshake: 'multistream-select-noise-yamux';
122
122
  initialWritableBytes: number;
123
- writeClosable: boolean;
124
123
  } | {
125
124
  type: 'multi-stream';
126
125
  handshake: 'webrtc';
127
- localTlsCertificateMultihash: Uint8Array;
128
- remoteTlsCertificateMultihash: Uint8Array;
126
+ localTlsCertificateSha256: Uint8Array;
127
+ remoteTlsCertificateSha256: Uint8Array;
129
128
  }) => void;
130
129
  /**
131
130
  * Callback called when the connection transitions to the `Reset` state.
@@ -287,8 +287,11 @@ function start(options, wasmModule, platformBindings) {
287
287
  if (jsonRpcMaxSubscriptions > 0xffffffff) {
288
288
  jsonRpcMaxSubscriptions = 0xffffffff;
289
289
  }
290
+ // Sanitize `databaseContent`.
291
+ if (options.databaseContent !== undefined && typeof options.databaseContent !== 'string')
292
+ throw new public_types_js_1.AddChainError("`databaseContent` is not a string");
290
293
  const promise = new Promise((resolve) => state.addChainResults.push(resolve));
291
- state.instance.instance.addChain(options.chainSpec, typeof options.databaseContent === 'string' ? options.databaseContent : "", potentialRelayChainsIds, !!options.disableJsonRpc, jsonRpcMaxPendingRequests, jsonRpcMaxSubscriptions);
294
+ state.instance.instance.addChain(options.chainSpec, options.databaseContent || "", potentialRelayChainsIds, !!options.disableJsonRpc, jsonRpcMaxPendingRequests, jsonRpcMaxSubscriptions);
292
295
  const outcome = yield promise;
293
296
  if (!outcome.success)
294
297
  throw new public_types_js_1.AddChainError(outcome.error);
@@ -84,10 +84,10 @@ export type ParsedMultiaddr = {
84
84
  url: string;
85
85
  } | {
86
86
  ty: "webrtc";
87
- targetPort: string;
87
+ targetPort: number;
88
88
  ipVersion: string;
89
89
  targetIp: string;
90
- remoteCertMultibase: string;
90
+ remoteTlsCertificateSha256: Uint8Array;
91
91
  };
92
92
  export interface Instance {
93
93
  request: (request: string, chainId: number) => number;
@@ -107,12 +107,11 @@ export interface Instance {
107
107
  type: 'single-stream';
108
108
  handshake: 'multistream-select-noise-yamux';
109
109
  initialWritableBytes: number;
110
- writeClosable: boolean;
111
110
  } | {
112
111
  type: 'multi-stream';
113
112
  handshake: 'webrtc';
114
- localTlsCertificateMultihash: Uint8Array;
115
- remoteTlsCertificateMultihash: Uint8Array;
113
+ localTlsCertificateSha256: Uint8Array;
114
+ remoteTlsCertificateSha256: Uint8Array;
116
115
  }) => void;
117
116
  connectionReset: (connectionId: number, message: string) => void;
118
117
  streamWritableBytes: (connectionId: number, numExtra: number, streamId?: number) => void;
@@ -133,10 +132,3 @@ export interface Instance {
133
132
  * isn't sanitized. In other words, you know what you're doing.
134
133
  */
135
134
  export declare function startLocalInstance(config: Config, wasmModule: WebAssembly.Module, eventCallback: (event: Event) => void): Promise<Instance>;
136
- export declare function parseMultiaddr(address: string, forbidTcp: boolean, forbidWs: boolean, forbidNonLocalWs: boolean, forbidWss: boolean, forbidWebRtc: boolean): {
137
- success: true;
138
- address: ParsedMultiaddr;
139
- } | {
140
- success: false;
141
- error: string;
142
- };
@@ -12,7 +12,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
12
12
  });
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.parseMultiaddr = exports.startLocalInstance = void 0;
15
+ exports.startLocalInstance = void 0;
16
16
  // This program is free software: you can redistribute it and/or modify
17
17
  // it under the terms of the GNU General Public License as published by
18
18
  // the Free Software Foundation, either version 3 of the License, or
@@ -127,26 +127,90 @@ function startLocalInstance(config, wasmModule, eventCallback) {
127
127
  }, ms);
128
128
  }
129
129
  },
130
+ // Must indicate whether the given connection type is supported.
131
+ connection_type_supported: (ty) => {
132
+ // TODO: consider extracting config options so user can't change the fields dynamically
133
+ switch (ty) {
134
+ case 0:
135
+ case 1:
136
+ case 2: {
137
+ return config.forbidTcp ? 0 : 1;
138
+ }
139
+ case 4:
140
+ case 5:
141
+ case 6: {
142
+ return config.forbidNonLocalWs ? 0 : 1;
143
+ }
144
+ case 7: {
145
+ return config.forbidWs ? 0 : 1;
146
+ }
147
+ case 14: {
148
+ return config.forbidWss ? 0 : 1;
149
+ }
150
+ case 16:
151
+ case 17: {
152
+ return config.forbidWebRtc ? 0 : 1;
153
+ }
154
+ default:
155
+ // Indicates a bug somewhere.
156
+ throw new Error("Invalid connection type passed to `connection_type_supported`");
157
+ }
158
+ },
130
159
  // Must create a new connection object. This implementation stores the created object in
131
160
  // `connections`.
132
- connection_new: (connectionId, addrPtr, addrLen, errorBufferIndexPtr) => {
161
+ connection_new: (connectionId, addrPtr, addrLen) => {
133
162
  const instance = state.instance;
163
+ const mem = new Uint8Array(instance.exports.memory.buffer);
134
164
  addrPtr >>>= 0;
135
165
  addrLen >>>= 0;
136
- errorBufferIndexPtr >>>= 0;
137
- const address = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), addrPtr, addrLen);
138
- // TODO: consider extracting config options so user can't change the fields dynamically
139
- const result = parseMultiaddr(address, config.forbidTcp, config.forbidWs, config.forbidNonLocalWs, config.forbidWss, config.forbidWebRtc);
140
- if (result.success) {
141
- eventCallback({ ty: "new-connection", connectionId, address: result.address });
142
- return 0;
143
- }
144
- else {
145
- const mem = new Uint8Array(instance.exports.memory.buffer);
146
- state.bufferIndices[0] = new TextEncoder().encode(result.error);
147
- buffer.writeUInt32LE(mem, errorBufferIndexPtr, 0);
148
- return 1;
166
+ let address;
167
+ switch (buffer.readUInt8(mem, addrPtr)) {
168
+ case 0:
169
+ case 1:
170
+ case 2: {
171
+ const port = buffer.readUInt16BE(mem, addrPtr + 1);
172
+ const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3);
173
+ address = { ty: "tcp", port, hostname };
174
+ break;
175
+ }
176
+ case 4:
177
+ case 6: {
178
+ const port = buffer.readUInt16BE(mem, addrPtr + 1);
179
+ const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3);
180
+ address = { ty: "websocket", url: "ws://" + hostname + ":" + port };
181
+ break;
182
+ }
183
+ case 5: {
184
+ const port = buffer.readUInt16BE(mem, addrPtr + 1);
185
+ const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3);
186
+ address = { ty: "websocket", url: "ws://[" + hostname + "]:" + port };
187
+ break;
188
+ }
189
+ case 14: {
190
+ const port = buffer.readUInt16BE(mem, addrPtr + 1);
191
+ const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3);
192
+ address = { ty: "websocket", url: "wss://" + hostname + ":" + port };
193
+ break;
194
+ }
195
+ case 16: {
196
+ const targetPort = buffer.readUInt16BE(mem, addrPtr + 1);
197
+ const remoteTlsCertificateSha256 = mem.slice(addrPtr + 3, addrPtr + 35);
198
+ const targetIp = buffer.utf8BytesToString(mem, addrPtr + 35, addrLen - 3);
199
+ address = { ty: "webrtc", ipVersion: '4', remoteTlsCertificateSha256, targetIp, targetPort };
200
+ break;
201
+ }
202
+ case 17: {
203
+ const targetPort = buffer.readUInt16BE(mem, addrPtr + 1);
204
+ const remoteTlsCertificateSha256 = mem.slice(addrPtr + 3, addrPtr + 35);
205
+ const targetIp = buffer.utf8BytesToString(mem, addrPtr + 35, addrLen - 3);
206
+ address = { ty: "webrtc", ipVersion: '6', remoteTlsCertificateSha256, targetIp, targetPort };
207
+ break;
208
+ }
209
+ default:
210
+ // Indicates a bug somewhere.
211
+ throw new Error("Invalid encoded address passed to `connection_new`");
149
212
  }
213
+ eventCallback({ ty: "new-connection", connectionId, address });
150
214
  },
151
215
  // Must close and destroy the connection object.
152
216
  reset_connection: (connectionId) => {
@@ -476,14 +540,14 @@ function startLocalInstance(config, wasmModule, eventCallback) {
476
540
  return;
477
541
  switch (info.type) {
478
542
  case 'single-stream': {
479
- state.instance.exports.connection_open_single_stream(connectionId, 0, info.initialWritableBytes, info.writeClosable ? 1 : 0);
543
+ state.instance.exports.connection_open_single_stream(connectionId, info.initialWritableBytes);
480
544
  break;
481
545
  }
482
546
  case 'multi-stream': {
483
- const handshakeTy = new Uint8Array(1 + info.localTlsCertificateMultihash.length + info.remoteTlsCertificateMultihash.length);
547
+ const handshakeTy = new Uint8Array(1 + info.localTlsCertificateSha256.length + info.remoteTlsCertificateSha256.length);
484
548
  buffer.writeUInt8(handshakeTy, 0, 0);
485
- handshakeTy.set(info.localTlsCertificateMultihash, 1);
486
- handshakeTy.set(info.remoteTlsCertificateMultihash, 1 + info.localTlsCertificateMultihash.length);
549
+ handshakeTy.set(info.localTlsCertificateSha256, 1);
550
+ handshakeTy.set(info.remoteTlsCertificateSha256, 1 + info.localTlsCertificateSha256.length);
487
551
  state.bufferIndices[0] = handshakeTy;
488
552
  state.instance.exports.connection_open_multi_stream(connectionId, 0);
489
553
  delete state.bufferIndices[0];
@@ -524,49 +588,3 @@ function startLocalInstance(config, wasmModule, eventCallback) {
524
588
  });
525
589
  }
526
590
  exports.startLocalInstance = startLocalInstance;
527
- // TODO: consider moving this function somewhere else or something
528
- function parseMultiaddr(address, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRtc) {
529
- const tcpParsed = address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)$/);
530
- // TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940)
531
- const wsParsed = address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)\/(ws|wss|tls\/ws)$/);
532
- const webRTCParsed = address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc-direct\/certhash\/(.*?)$/);
533
- if (wsParsed != null) {
534
- const proto = (wsParsed[4] == 'ws') ? 'ws' : 'wss';
535
- if (proto == 'ws' && forbidWs) {
536
- return { success: false, error: 'WebSocket connections not available' };
537
- }
538
- if (proto == 'wss' && forbidWss) {
539
- return { success: false, error: 'WebSocket secure connections not available' };
540
- }
541
- if (proto == 'ws' && wsParsed[2] != 'localhost' && wsParsed[2] != '127.0.0.1' && forbidNonLocalWs) {
542
- return { success: false, error: 'Non-local WebSocket connections not available' };
543
- }
544
- const url = (wsParsed[1] == 'ip6') ?
545
- (proto + "://[" + wsParsed[2] + "]:" + wsParsed[3]) :
546
- (proto + "://" + wsParsed[2] + ":" + wsParsed[3]);
547
- return { success: true, address: { ty: "websocket", url } };
548
- }
549
- else if (tcpParsed != null) {
550
- if (forbidTcp) {
551
- return { success: false, error: 'TCP connections not available' };
552
- }
553
- return { success: true, address: { ty: "tcp", hostname: tcpParsed[2], port: parseInt(tcpParsed[3]) } };
554
- }
555
- else if (webRTCParsed != null) {
556
- const targetPort = webRTCParsed[3];
557
- if (forbidWebRtc) {
558
- return { success: false, error: 'WebRTC connections not available' };
559
- }
560
- if (targetPort === '0') {
561
- return { success: false, error: 'Invalid WebRTC target port' };
562
- }
563
- const ipVersion = webRTCParsed[1] == 'ip4' ? '4' : '6';
564
- const targetIp = webRTCParsed[2];
565
- const remoteCertMultibase = webRTCParsed[4];
566
- return { success: true, address: { ty: "webrtc", targetPort, ipVersion, targetIp, remoteCertMultibase } };
567
- }
568
- else {
569
- return { success: false, error: 'Unrecognized multiaddr format' };
570
- }
571
- }
572
- exports.parseMultiaddr = parseMultiaddr;
@@ -14,7 +14,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.startWithBytecode = exports.QueueFullError = exports.MalformedJsonRpcError = exports.JsonRpcDisabledError = exports.CrashError = exports.AlreadyDestroyedError = exports.AddChainError = void 0;
16
16
  const client_js_1 = require("./internals/client.js");
17
- const base64_js_1 = require("./internals/base64.js");
18
17
  var public_types_js_1 = require("./public-types.js");
19
18
  Object.defineProperty(exports, "AddChainError", { enumerable: true, get: function () { return public_types_js_1.AddChainError; } });
20
19
  Object.defineProperty(exports, "AlreadyDestroyedError", { enumerable: true, get: function () { return public_types_js_1.AlreadyDestroyedError; } });
@@ -107,7 +106,7 @@ function connect(config) {
107
106
  connection.onopen = () => {
108
107
  config.onOpen({
109
108
  type: 'single-stream', handshake: 'multistream-select-noise-yamux',
110
- initialWritableBytes: 1024 * 1024, writeClosable: false,
109
+ initialWritableBytes: 1024 * 1024
111
110
  });
112
111
  };
113
112
  connection.onclose = (event) => {
@@ -159,13 +158,7 @@ function connect(config) {
159
158
  };
160
159
  }
161
160
  else if (config.address.ty === "webrtc") {
162
- const { targetPort, ipVersion, targetIp, remoteCertMultibase } = config.address;
163
- // The payload of `/certhash` is the hash of the self-generated certificate that the
164
- // server presents.
165
- // This function throws an exception if the certhash isn't correct. For this reason, this call
166
- // is performed as part of the parsing of the multiaddr.
167
- const remoteCertMultihash = (0, base64_js_1.multibaseBase64Decode)(remoteCertMultibase);
168
- const remoteCertSha256Hash = multihashToSha256(remoteCertMultihash);
161
+ const { targetPort, ipVersion, targetIp, remoteTlsCertificateSha256 } = config.address;
169
162
  // TODO: detect localhost for Firefox? https://bugzilla.mozilla.org/show_bug.cgi?id=1659672
170
163
  // Note that `pc` can be the connection, but also null or undefined.
171
164
  // `undefined` means "certificate generation in progress", while `null` means "opening must
@@ -179,10 +172,10 @@ function connect(config) {
179
172
  // to smoldot. This data channel is stored in this variable. Once it is reported to smoldot,
180
173
  // it is inserted in `dataChannels`.
181
174
  let handshakeDataChannel;
182
- // Multihash-encoded DTLS certificate of the local node. Unknown as long as it hasn't been
175
+ // SHA256 hash of the DTLS certificate of the local node. Unknown as long as it hasn't been
183
176
  // generated.
184
177
  // TODO: could be merged with `pc` in one variable, and maybe even the other fields as well
185
- let localTlsCertificateMultihash;
178
+ let localTlsCertificateSha256;
186
179
  // Kills all the JavaScript objects (the connection and all its substreams), ensuring that no
187
180
  // callback will be called again. Doesn't report anything to smoldot, as this should be done
188
181
  // by the caller.
@@ -232,8 +225,8 @@ function connect(config) {
232
225
  handshake: 'webrtc',
233
226
  // `addChannel` can never be called before the local certificate is generated, so this
234
227
  // value is always defined.
235
- localTlsCertificateMultihash: localTlsCertificateMultihash,
236
- remoteTlsCertificateMultihash: remoteCertMultihash
228
+ localTlsCertificateSha256: localTlsCertificateSha256,
229
+ remoteTlsCertificateSha256,
237
230
  });
238
231
  }
239
232
  else {
@@ -338,9 +331,8 @@ function connect(config) {
338
331
  config.onConnectionReset('Failed to obtain the browser certificate fingerprint');
339
332
  return;
340
333
  }
341
- localTlsCertificateMultihash = new Uint8Array(34);
342
- localTlsCertificateMultihash.set([0x12, 32], 0);
343
- localTlsCertificateMultihash.set(localTlsCertificateHex.split(':').map((s) => parseInt(s, 16)), 2);
334
+ localTlsCertificateSha256 = new Uint8Array(32);
335
+ localTlsCertificateSha256.set(localTlsCertificateHex.split(':').map((s) => parseInt(s, 16)), 0);
344
336
  // `onconnectionstatechange` is used to detect when the connection has closed or has failed
345
337
  // to open.
346
338
  // Note that smoldot will think that the connection is open even when it is still opening.
@@ -376,7 +368,7 @@ function connect(config) {
376
368
  sdpOffer = sdpOffer.replace(/^a=ice-pwd.*$/m, 'a=ice-pwd:' + ufragPwd);
377
369
  yield pc.setLocalDescription({ type: 'offer', sdp: sdpOffer });
378
370
  // Transform certificate hash into fingerprint (upper-hex; each byte separated by ":").
379
- const fingerprint = Array.from(remoteCertSha256Hash).map((n) => ("0" + n.toString(16)).slice(-2).toUpperCase()).join(':');
371
+ const fingerprint = Array.from(remoteTlsCertificateSha256).map((n) => ("0" + n.toString(16)).slice(-2).toUpperCase()).join(':');
380
372
  // Note that the trailing line feed is important, as otherwise Chrome
381
373
  // fails to parse the payload.
382
374
  const remoteSdp =
@@ -403,7 +395,7 @@ function connect(config) {
403
395
  // The `<fmt>` component must always be `webrtc-datachannel` for WebRTC.
404
396
  // The rest of the SDP payload adds attributes to this specific media stream.
405
397
  // RFCs: 8839, 8866, 8841
406
- "m=application " + targetPort + " " + "UDP/DTLS/SCTP webrtc-datachannel" + "\n" +
398
+ "m=application " + String(targetPort) + " " + "UDP/DTLS/SCTP webrtc-datachannel" + "\n" +
407
399
  // Indicates the IP address of the remote.
408
400
  // Note that "IN" means "Internet" (and not "input").
409
401
  "c=IN IP" + ipVersion + " " + targetIp + "\n" +
@@ -434,7 +426,7 @@ function connect(config) {
434
426
  "a=max-message-size:16384" + "\n" +
435
427
  // A transport address for a candidate that can be used for connectivity
436
428
  // checks (RFC8839).
437
- "a=candidate:1 1 UDP 1 " + targetIp + " " + targetPort + " typ host" + "\n";
429
+ "a=candidate:1 1 UDP 1 " + targetIp + " " + String(targetPort) + " typ host" + "\n";
438
430
  yield pc.setRemoteDescription({ type: "answer", sdp: remoteSdp });
439
431
  });
440
432
  pc.ondatachannel = ({ channel }) => {
@@ -506,12 +498,3 @@ function connect(config) {
506
498
  throw new Error();
507
499
  }
508
500
  }
509
- /// Parses a multihash-multibase-encoded string into a SHA256 hash.
510
- ///
511
- /// Throws an exception if the multihash algorithm isn't SHA256.
512
- const multihashToSha256 = (certMultihash) => {
513
- if (certMultihash.length != 34 || certMultihash[0] != 0x12 || certMultihash[1] != 32) {
514
- throw new Error('Certificate multihash is not SHA-256');
515
- }
516
- return new Uint8Array(certMultihash.slice(2));
517
- };
@@ -80,7 +80,7 @@ function connect(config) {
80
80
  config.onWritableBytes(wasSent);
81
81
  };
82
82
  socket.onopen = () => {
83
- config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false });
83
+ config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024 });
84
84
  };
85
85
  socket.onclose = (event) => {
86
86
  const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
@@ -135,7 +135,7 @@ function connect(config) {
135
135
  if (socket.destroyed)
136
136
  return established;
137
137
  established === null || established === void 0 ? void 0 : established.setNoDelay();
138
- config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: true });
138
+ config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024 });
139
139
  // Spawns an asynchronous task that continuously reads from the socket.
140
140
  // Every time data is read, the task re-executes itself in order to continue reading.
141
141
  // The task ends automatically if an EOF or error is detected, which should also happen
@@ -76,7 +76,7 @@ function connect(config) {
76
76
  config.onWritableBytes(wasSent);
77
77
  };
78
78
  socket.onopen = () => {
79
- config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false });
79
+ config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024 });
80
80
  };
81
81
  socket.onclose = (event) => {
82
82
  const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
@@ -131,7 +131,7 @@ function connect(config) {
131
131
  return;
132
132
  config.onOpen({
133
133
  type: 'single-stream', handshake: 'multistream-select-noise-yamux',
134
- initialWritableBytes: socket.writableHighWaterMark, writeClosable: true
134
+ initialWritableBytes: socket.writableHighWaterMark
135
135
  });
136
136
  });
137
137
  socket.on('close', (hasError) => {
@@ -1,4 +1,6 @@
1
1
  export declare function utf8BytesToString(buffer: Uint8Array, offset: number, length: number): string;
2
+ export declare function readUInt8(buffer: Uint8Array, offset: number): number;
3
+ export declare function readUInt16BE(buffer: Uint8Array, offset: number): number;
2
4
  export declare function readUInt32LE(buffer: Uint8Array, offset: number): number;
3
5
  /**
4
6
  * Sets the value of a given byte in the buffer.
@@ -17,6 +17,14 @@ export function utf8BytesToString(buffer, offset, length) {
17
17
  // <https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder>
18
18
  return new TextDecoder().decode(buffer.slice(offset, offset + length));
19
19
  }
20
+ export function readUInt8(buffer, offset) {
21
+ checkRange(buffer, offset, 1);
22
+ return buffer[offset];
23
+ }
24
+ export function readUInt16BE(buffer, offset) {
25
+ checkRange(buffer, offset, 2);
26
+ return ((buffer[offset] << 8) | buffer[offset + 1]);
27
+ }
20
28
  export function readUInt32LE(buffer, offset) {
21
29
  checkRange(buffer, offset, 4);
22
30
  return (buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16)) + (buffer[offset + 3] * 0x1000000);