You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
127 lines
4.4 KiB
TypeScript
127 lines
4.4 KiB
TypeScript
import { from_string, to_string } from 'libsodium-wrappers-sumo';
|
|
import { isString, omit, toNumber } from 'lodash';
|
|
import { EncodeV4OnionRequestInfos, SnodeResponseV4 } from '../apis/snode_api/onions';
|
|
import { concatUInt8Array } from '../crypto';
|
|
|
|
export const encodeV4Request = (requestInfo: EncodeV4OnionRequestInfos): Uint8Array => {
|
|
const { body } = requestInfo;
|
|
|
|
// the body is appended separately to the request (see below)
|
|
const infoWithoutBody = omit(requestInfo, 'body');
|
|
|
|
const requestInfoData = from_string(JSON.stringify(infoWithoutBody));
|
|
const prefixData = from_string(`l${requestInfoData.length}:`);
|
|
const suffixData = from_string('e');
|
|
if (body) {
|
|
const bodyData = body && isString(body) ? from_string(body) : (body as Uint8Array);
|
|
|
|
const bodyCountdata = from_string(`${bodyData.length}:`);
|
|
return concatUInt8Array(prefixData, requestInfoData, bodyCountdata, bodyData, suffixData);
|
|
}
|
|
return concatUInt8Array(prefixData, requestInfoData, suffixData);
|
|
};
|
|
|
|
export type DecodedResponseV4 = {
|
|
metadata: {
|
|
code: number;
|
|
headers?: Record<string, string>;
|
|
};
|
|
body: object | null; // might be object, or binary or maybe some other stuff..
|
|
bodyBinary: Uint8Array;
|
|
bodyContentType: string;
|
|
};
|
|
|
|
/**
|
|
* When we do a batch request, we get a list of bodies in the body of the response. This is the type for those bodies
|
|
*/
|
|
export type DecodedResponseBodiesV4 = Array<any>;
|
|
|
|
/**
|
|
* Nearly identical to request encoding. 2 string bencoded list.
|
|
* Response differs in that the second body part is always present in a response unlike the requests.
|
|
* 1. First part contains response metadata
|
|
* 2. Second part contains the request body.
|
|
*/
|
|
const decodeV4Response = (snodeResponse: SnodeResponseV4): DecodedResponseV4 | undefined => {
|
|
const eAscii = 'e'.charCodeAt(0);
|
|
const lAscii = 'l'.charCodeAt(0);
|
|
const colonAscii = ':'.charCodeAt(0);
|
|
|
|
// json part will have code: containing response code and headers for http headers (always lower case)
|
|
// 1. read first bit till colon to get the length. Substring the next X amount trailing the colon and that's the metadata.
|
|
// 2. grab the number before the next colon. That's the expected length of the body.
|
|
// 3. Use the content type from the metadata header to handle the body.
|
|
|
|
const binary = snodeResponse.bodyBinary;
|
|
|
|
if (
|
|
!(
|
|
binary &&
|
|
binary.byteLength &&
|
|
binary[0] === lAscii &&
|
|
binary[binary.byteLength - 1] === eAscii
|
|
)
|
|
) {
|
|
window?.log?.error(
|
|
'decodeV4Response: response is missing prefix and suffix characters - Dropping response'
|
|
);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const firstDelimitIdx = binary.indexOf(colonAscii);
|
|
|
|
const infoLength = toNumber(to_string(binary.slice(1, firstDelimitIdx)));
|
|
|
|
const infoStringStartIndex = firstDelimitIdx + 1;
|
|
const infoStringEndIndex = infoStringStartIndex + infoLength;
|
|
const infoJson = JSON.parse(to_string(binary.slice(infoStringStartIndex, infoStringEndIndex)));
|
|
|
|
const beforeBodyIndex = binary.indexOf(colonAscii, infoStringEndIndex);
|
|
const bodyLength = toNumber(to_string(binary.slice(infoStringEndIndex, beforeBodyIndex)));
|
|
const bodyBinary = binary.slice(beforeBodyIndex + 1, beforeBodyIndex + (bodyLength + 1));
|
|
|
|
const bodyContentType: string = infoJson?.headers['content-type'];
|
|
let bodyParsed: object | null = null;
|
|
switch (bodyContentType) {
|
|
case 'application/json':
|
|
bodyParsed = JSON.parse(to_string(bodyBinary));
|
|
break;
|
|
case 'text/plain; charset=utf-8':
|
|
bodyParsed = { plainText: to_string(bodyBinary) };
|
|
break;
|
|
case 'application/octet-stream':
|
|
break;
|
|
case 'text/html; charset=utf-8':
|
|
try {
|
|
window?.log?.warn(
|
|
'decodeV4Response - received raw body of type "text/html; charset=utf-8": ',
|
|
to_string(bodyBinary)
|
|
);
|
|
} catch (e) {
|
|
window?.log?.warn(
|
|
'decodeV4Response - received raw body of type "text/html; charset=utf-8" but not a string'
|
|
);
|
|
}
|
|
break;
|
|
default:
|
|
window?.log?.warn(
|
|
'decodeV4Response - No or unknown content-type information for response: ',
|
|
bodyContentType
|
|
);
|
|
}
|
|
|
|
return {
|
|
metadata: infoJson,
|
|
body: bodyParsed,
|
|
bodyContentType,
|
|
bodyBinary,
|
|
};
|
|
} catch (e) {
|
|
window.log.warn('failed to decodeV4Response:', e.message);
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
export const OnionV4 = { decodeV4Response };
|