mirror of
https://github.com/tlsnotary/PageSigner.git
synced 2026-01-08 22:27:57 -05:00
dont perform OT on the same inputs of c4 and c6 use fetch() built-in download progress monitor query the notary about fetch() upload progress renumber circuits (we have new circuit 4) traverse circuit faster with wasm
259 lines
8.5 KiB
JavaScript
259 lines
8.5 KiB
JavaScript
/* global chrome*/
|
|
|
|
// The only way to determine if the server is done sending data is to check
|
|
// that the receiving buffer has nothing but complete TLS records i.e. that
|
|
// there are no incomplete TLS records. However it was observed that in cases
|
|
// when getting e.g. zip files, some servers first send HTTP header as one TLS
|
|
// record followed by the body as another record(s). That's why after
|
|
// receiving a complete TLS record we wait to get some more data.This extra
|
|
// waiting must NOT be done for the handshake messages to avoid causing a
|
|
// handshake timeout by the server.
|
|
// We do not communicate directly with the server but we send messages to the
|
|
// helper app. It is the helper app who opens a TCP socket and sends/receives
|
|
// data.
|
|
|
|
import {globals} from './globals.js';
|
|
import {ba2str, b64decode, concatTA, b64encode, str2ba, ba2int} from './utils.js';
|
|
|
|
export class Socket {
|
|
constructor(name, port){
|
|
this.name = name;
|
|
this.port = port;
|
|
this.uid = Math.random().toString(36).slice(-10);
|
|
|
|
this.buffer = new Uint8Array();
|
|
// connect will throw if we couldnt establish a connection to the server for this long
|
|
this.connectTimeout = 5 * 1000;
|
|
// recv() will reject if no data was seen for this long
|
|
this.noDataTimeout = 5 * 1000;
|
|
// close the socket after this time, even if it is in the middle of receiving data
|
|
this.lifeTime = 40 * 1000;
|
|
// delay after which we make a final check of the receicing buffer and if there was no data,
|
|
// from the server, the we consider the data transmission finished
|
|
this.delayBeforeFinalIteration = 500;
|
|
this.wasClosed = false;
|
|
this.backendPort = 20022;
|
|
}
|
|
|
|
async connect() {
|
|
const that = this;
|
|
let timer;
|
|
// eslint-disable-next-line no-async-promise-executor
|
|
const response = await new Promise(async function(resolve, reject) {
|
|
// dont wait for connect for too long
|
|
timer = setTimeout(function() {
|
|
reject('Unable to connect to the webserver. Please check your internet connection.');
|
|
return;
|
|
}, that.connectTimeout);
|
|
|
|
const msg = {'command': 'connect', 'args': {'name': that.name, 'port': that.port}, 'uid': that.uid};
|
|
if (globals.usePythonBackend){
|
|
const url = 'http://127.0.0.1:' + that.backendPort;
|
|
const payload = JSON.stringify(msg);
|
|
try{
|
|
var req = await fetch (url, {method:'POST', body: str2ba(payload).buffer, cache: 'no-store'});
|
|
}
|
|
catch (error) {
|
|
reject('connection error');
|
|
return;
|
|
}
|
|
const text = new Uint8Array (await req.arrayBuffer());
|
|
const response = ba2str(text);
|
|
resolve(JSON.parse(response));
|
|
return;
|
|
}
|
|
else {
|
|
chrome.runtime.sendMessage(globals.appId, msg, function(response) {resolve(response);});
|
|
}
|
|
})
|
|
.catch(function(e){
|
|
throw(e);
|
|
});
|
|
|
|
// we need to access runtime.lastError to prevent Chrome from complaining
|
|
// about unchecked error
|
|
chrome.runtime.lastError;
|
|
clearTimeout(timer);
|
|
if (response == undefined){
|
|
throw (undefined);
|
|
}
|
|
if (response.retval != 'success') {
|
|
// throw(response.retval)
|
|
}
|
|
// else if (response.retval == 'success') {
|
|
setTimeout(function() {
|
|
if (! that.wasClosed)
|
|
that.close();
|
|
}, that.lifeTime);
|
|
// endless data fetching loop for the lifetime of this Socket
|
|
that.fetchLoop();
|
|
return 'ready';
|
|
}
|
|
|
|
async send(data_in) {
|
|
var msg = {'command': 'send', 'args': {'data': Array.from(data_in)}, 'uid': this.uid};
|
|
if (globals.usePythonBackend){
|
|
msg.args.data = Array.from(b64encode(msg.args.data));
|
|
await fetch('http://127.0.0.1:20022', {method:'POST', body: JSON.stringify(msg),
|
|
cache: 'no-store'});
|
|
}
|
|
else{
|
|
chrome.runtime.sendMessage(globals.appId, msg);
|
|
}
|
|
}
|
|
|
|
// poll the backend for more data
|
|
async fetchLoop() {
|
|
if (this.wasClosed) {
|
|
return;
|
|
}
|
|
var that = this;
|
|
// eslint-disable-next-line no-async-promise-executor
|
|
var response = await new Promise(async function(resolve){
|
|
var msg = {'command': 'recv', 'uid': that.uid};
|
|
if (globals.usePythonBackend){
|
|
var req = await fetch('http://127.0.0.1:20022', {method:'POST', body: JSON.stringify(msg),
|
|
cache: 'no-store'});
|
|
var text = new Uint8Array(await req.arrayBuffer());
|
|
var response = JSON.parse(ba2str(text));
|
|
if (response.data.length > 0){
|
|
response.data = Array.from(b64decode(response.data));
|
|
}
|
|
resolve(response);
|
|
}
|
|
else {
|
|
chrome.runtime.sendMessage(globals.appId, msg, function(response) {resolve(response);});
|
|
}
|
|
});
|
|
if (response.data.length > 0){
|
|
console.log('fetched some data', response.data.length, that.uid);
|
|
that.buffer = concatTA(that.buffer, new Uint8Array(response.data));
|
|
}
|
|
setTimeout(function() {
|
|
that.fetchLoop();
|
|
}, 100);
|
|
}
|
|
|
|
// fetchLoop has built up the recv buffer. Check if there are complete
|
|
// records in the buffer, return them if yes or wait some more if no.
|
|
recv (is_handshake = false) {
|
|
const that = this;
|
|
return new Promise(function(resolve, reject) {
|
|
var dataLastSeen = new Date().getTime();
|
|
var complete_records = new Uint8Array();
|
|
var buf = new Uint8Array();
|
|
var resolved = false;
|
|
var lastIteration = false;
|
|
|
|
function finished_receiving() {
|
|
console.log('recv promise resolving', that.uid);
|
|
resolved = true;
|
|
resolve(complete_records);
|
|
}
|
|
|
|
var check = function() {
|
|
var now = new Date().getTime();
|
|
if ((now - dataLastSeen) > that.noDataTimeout){
|
|
reject('recv: no data timeout');
|
|
return;
|
|
}
|
|
// console.log('check()ing for more data', uid);
|
|
if (resolved) {
|
|
console.log('returning because resolved');
|
|
return;
|
|
}
|
|
if (that.buffer.length === 0) {
|
|
if (lastIteration){
|
|
finished_receiving();
|
|
return;
|
|
}
|
|
setTimeout(function() {check();}, 100);
|
|
return;
|
|
}
|
|
// else got new data
|
|
if (lastIteration){
|
|
console.log('more data received on last iteration', that.uid);
|
|
lastIteration = false;
|
|
}
|
|
console.log('new data in check', that.buffer.length);
|
|
dataLastSeen = now;
|
|
buf = concatTA(buf, that.buffer);
|
|
that.buffer = new Uint8Array();
|
|
const rv = that.check_complete_records(buf);
|
|
complete_records = concatTA(complete_records, rv.comprecs);
|
|
if (!rv.is_complete) {
|
|
console.log('waiting for complete records...', that.uid);
|
|
buf = rv.incomprecs;
|
|
setTimeout(function() {check();}, 100);
|
|
return;
|
|
}
|
|
else {
|
|
console.log('got complete records', that.uid);
|
|
if (is_handshake) {
|
|
finished_receiving();
|
|
return;
|
|
}
|
|
else {
|
|
console.log('in recv waiting for an extra second', that.uid);
|
|
buf = new Uint8Array();
|
|
// give the server another second to send more data
|
|
lastIteration = true;
|
|
setTimeout(function() {check();}, that.delayBeforeFinalIteration);
|
|
}
|
|
}
|
|
};
|
|
check();
|
|
})
|
|
.catch(function(error){
|
|
throw(error);
|
|
});
|
|
}
|
|
|
|
async close() {
|
|
this.wasClosed = true;
|
|
var msg = {'command': 'close', 'uid': this.uid};
|
|
console.log('closing socket', this.uid);
|
|
if (globals.usePythonBackend){
|
|
await fetch('http://127.0.0.1:20022', {method:'POST', body: JSON.stringify(msg),
|
|
cache: 'no-store'});
|
|
}
|
|
else {
|
|
chrome.runtime.sendMessage(globals.appId, msg);
|
|
}
|
|
}
|
|
|
|
check_complete_records(d) {
|
|
/* '''Given a response d from a server,
|
|
we want to know if its contents represents
|
|
a complete set of records, however many.'''
|
|
*/
|
|
let complete_records = new Uint8Array();
|
|
|
|
while (d) {
|
|
if (d.length < 5) {
|
|
return {
|
|
'is_complete': false,
|
|
'comprecs': complete_records,
|
|
'incomprecs': d
|
|
};
|
|
}
|
|
var l = ba2int(d.slice(3, 5));
|
|
if (d.length < (l + 5)) {
|
|
return {
|
|
'is_complete': false,
|
|
'comprecs': complete_records,
|
|
'incomprecs': d
|
|
};
|
|
} else if (d.length === (l + 5)) {
|
|
return {
|
|
'is_complete': true,
|
|
'comprecs': concatTA(complete_records, d)
|
|
};
|
|
} else {
|
|
complete_records = concatTA(complete_records, d.slice(0, l + 5));
|
|
d = d.slice(l + 5);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
} |