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 };
 |