command.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import {AddressType, CommandReplyType, CommandType} from "./constants.js";
  2. import ipaddr from 'ipaddr.js';
  3. function parseAddress(chunk: Buffer):[string, number] {
  4. let address = [], port;
  5. const addressType = chunk.readUint8(3);
  6. switch (addressType) {
  7. case AddressType.IPv4:
  8. address.push(chunk.readUint8(4).toString());
  9. address.push(chunk.readUint8(5).toString());
  10. address.push(chunk.readUint8(6).toString());
  11. address.push(chunk.readUint8(7).toString());
  12. port = chunk.readUInt16BE(8);
  13. return [address.join('.'), port];
  14. case AddressType.Domain:
  15. const domainLength: number = chunk.readUint8(4);
  16. let i:number;
  17. for(i = 5; i < 5 + domainLength; i++) {
  18. address.push(String.fromCharCode(chunk.readUint8(i)));
  19. }
  20. port = chunk.readUInt16BE(i);
  21. return [address.join(''), port];
  22. case AddressType.IPv6:
  23. let byteArray: number[] = [];
  24. for(let i = 4; i < 20; i++) {
  25. byteArray.push(chunk.readUint8(i));
  26. }
  27. const ipv6Addr = ipaddr.fromByteArray(byteArray);
  28. port = chunk.readUInt16BE(20);
  29. return [ipv6Addr.toString(), port];
  30. default:
  31. break;
  32. }
  33. return ['', 0];
  34. }
  35. function host2Buffer(chunk: Buffer, host: string): number {
  36. let nextOffset: number = -1;
  37. let offset = 4; // all command host started at index of 4
  38. const addressType = chunk.readUint8(3);
  39. switch (addressType) {
  40. case AddressType.IPv4:
  41. host.split('.').forEach((seg: string) => {
  42. chunk.writeUint8(Number.parseInt(seg), offset++);
  43. });
  44. nextOffset = 8;
  45. break;
  46. case AddressType.Domain:
  47. chunk.writeUint8(host.length, 4);
  48. offset += 1;
  49. [...host].forEach((c, i) => {
  50. chunk.writeUint8(host.charCodeAt(i), offset++);
  51. });
  52. nextOffset = 4 + 1 + host.length;
  53. break;
  54. case AddressType.IPv6:
  55. const bytes = ipaddr.parse(host).toByteArray();
  56. bytes.map((byte) => {
  57. chunk.writeUint8(byte, offset++);
  58. });
  59. nextOffset = 20;
  60. break;
  61. }
  62. return nextOffset;
  63. }
  64. export class Command {
  65. public version: number;
  66. public commandType: CommandType;
  67. public addressType: AddressType;
  68. public host: string;
  69. public port: number;
  70. constructor(chunk: Buffer) {
  71. // console.log(inspect(chunk));
  72. this.version = chunk.readUint8(0);
  73. this.commandType = chunk.readUint8(1) as CommandType;
  74. this.addressType = chunk.readUint8(3) as AddressType;
  75. [this.host, this.port] = parseAddress(chunk);
  76. }
  77. }
  78. export class UdpAssociateCommand {
  79. public static parseCommand(chunk: Buffer): [boolean, AddressType, string, number, Buffer] {
  80. // TODO check if data is valid
  81. const rsv: number = chunk.readUInt16BE(0);
  82. const frag: number = chunk.readUint8(2);
  83. const addressType: AddressType = chunk.readUint8(3) as AddressType;
  84. const [host, port] = parseAddress(chunk);
  85. const dataLength = chunk.length - 6 -
  86. (addressType == AddressType.IPv4 ? 4 : (AddressType.IPv6 ? 16 : host.length));
  87. const dataStartIndex: number = addressType == AddressType.IPv4 ? 10 : (AddressType.IPv6 ? 22 : (6 + host.length));
  88. let data = Buffer.alloc(dataLength);
  89. chunk.copy(data, 0, dataStartIndex);
  90. return [true, addressType, host, port, data];
  91. }
  92. public static generateCommandReplyHeader(addressType: AddressType, address: string, port: number): Buffer {
  93. const replyBufferLength = 6
  94. + (addressType == AddressType.IPv4 ? 4 : (AddressType.IPv6 ? 16 : address.length));
  95. let reply = Buffer.alloc(replyBufferLength);
  96. reply.writeUInt16BE(0, 0);
  97. reply.writeUint8(0, 2);
  98. reply.writeUint8(addressType, 3);
  99. const nextOffset: number = host2Buffer(reply, address);
  100. reply.writeUInt16BE(port, nextOffset);
  101. return reply;
  102. }
  103. }
  104. export class CommandReply {
  105. private bufferLength:number = -1;
  106. constructor(
  107. private commandReplyType: CommandReplyType,
  108. private addressType: AddressType,
  109. private host: string,
  110. private port: number
  111. ) {
  112. switch (this.addressType) {
  113. case AddressType.IPv4:
  114. this.bufferLength = 10;
  115. break;
  116. case AddressType.Domain:
  117. this.bufferLength = 7 + this.host.length;
  118. break;
  119. case AddressType.IPv6:
  120. this.bufferLength = 22;
  121. break;
  122. }
  123. }
  124. public generateBuffer(): Buffer {
  125. const chunk = Buffer.alloc(this.bufferLength);
  126. chunk.writeUint8(5, 0);
  127. chunk.writeUint8(this.commandReplyType.valueOf(), 1);
  128. chunk.writeUint8(0, 2);
  129. chunk.writeUint8(this.addressType.valueOf(), 3);
  130. const nextOffset: number = host2Buffer(chunk, this.host);
  131. chunk.writeUInt16BE(this.port, nextOffset);
  132. return chunk;
  133. }
  134. }