ipv4.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /* eslint-disable no-param-reassign */
  2. import * as common from './common';
  3. import * as constants from './v4/constants';
  4. import { AddressError } from './address-error';
  5. import { BigInteger } from 'jsbn';
  6. import { sprintf } from 'sprintf-js';
  7. /**
  8. * Represents an IPv4 address
  9. * @class Address4
  10. * @param {string} address - An IPv4 address string
  11. */
  12. export class Address4 {
  13. address: string;
  14. addressMinusSuffix?: string;
  15. groups: number = constants.GROUPS;
  16. parsedAddress: string[] = [];
  17. parsedSubnet: string = '';
  18. subnet: string = '/32';
  19. subnetMask: number = 32;
  20. v4: boolean = true;
  21. constructor(address: string) {
  22. this.address = address;
  23. const subnet = constants.RE_SUBNET_STRING.exec(address);
  24. if (subnet) {
  25. this.parsedSubnet = subnet[0].replace('/', '');
  26. this.subnetMask = parseInt(this.parsedSubnet, 10);
  27. this.subnet = `/${this.subnetMask}`;
  28. if (this.subnetMask < 0 || this.subnetMask > constants.BITS) {
  29. throw new AddressError('Invalid subnet mask.');
  30. }
  31. address = address.replace(constants.RE_SUBNET_STRING, '');
  32. }
  33. this.addressMinusSuffix = address;
  34. this.parsedAddress = this.parse(address);
  35. }
  36. static isValid(address: string): boolean {
  37. try {
  38. // eslint-disable-next-line no-new
  39. new Address4(address);
  40. return true;
  41. } catch (e) {
  42. return false;
  43. }
  44. }
  45. /*
  46. * Parses a v4 address
  47. */
  48. parse(address: string) {
  49. const groups = address.split('.');
  50. if (!address.match(constants.RE_ADDRESS)) {
  51. throw new AddressError('Invalid IPv4 address.');
  52. }
  53. return groups;
  54. }
  55. /**
  56. * Returns the correct form of an address
  57. * @memberof Address4
  58. * @instance
  59. * @returns {String}
  60. */
  61. correctForm(): string {
  62. return this.parsedAddress.map((part) => parseInt(part, 10)).join('.');
  63. }
  64. /**
  65. * Returns true if the address is correct, false otherwise
  66. * @memberof Address4
  67. * @instance
  68. * @returns {Boolean}
  69. */
  70. isCorrect = common.isCorrect(constants.BITS);
  71. /**
  72. * Converts a hex string to an IPv4 address object
  73. * @memberof Address4
  74. * @static
  75. * @param {string} hex - a hex string to convert
  76. * @returns {Address4}
  77. */
  78. static fromHex(hex: string): Address4 {
  79. const padded = hex.replace(/:/g, '').padStart(8, '0');
  80. const groups = [];
  81. let i;
  82. for (i = 0; i < 8; i += 2) {
  83. const h = padded.slice(i, i + 2);
  84. groups.push(parseInt(h, 16));
  85. }
  86. return new Address4(groups.join('.'));
  87. }
  88. /**
  89. * Converts an integer into a IPv4 address object
  90. * @memberof Address4
  91. * @static
  92. * @param {integer} integer - a number to convert
  93. * @returns {Address4}
  94. */
  95. static fromInteger(integer: number): Address4 {
  96. return Address4.fromHex(integer.toString(16));
  97. }
  98. /**
  99. * Return an address from in-addr.arpa form
  100. * @memberof Address4
  101. * @static
  102. * @param {string} arpaFormAddress - an 'in-addr.arpa' form ipv4 address
  103. * @returns {Adress4}
  104. * @example
  105. * var address = Address4.fromArpa(42.2.0.192.in-addr.arpa.)
  106. * address.correctForm(); // '192.0.2.42'
  107. */
  108. static fromArpa(arpaFormAddress: string): Address4 {
  109. // remove ending ".in-addr.arpa." or just "."
  110. const leader = arpaFormAddress.replace(/(\.in-addr\.arpa)?\.$/, '');
  111. const address = leader.split('.').reverse().join('.');
  112. return new Address4(address);
  113. }
  114. /**
  115. * Converts an IPv4 address object to a hex string
  116. * @memberof Address4
  117. * @instance
  118. * @returns {String}
  119. */
  120. toHex(): string {
  121. return this.parsedAddress.map((part) => sprintf('%02x', parseInt(part, 10))).join(':');
  122. }
  123. /**
  124. * Converts an IPv4 address object to an array of bytes
  125. * @memberof Address4
  126. * @instance
  127. * @returns {Array}
  128. */
  129. toArray(): number[] {
  130. return this.parsedAddress.map((part) => parseInt(part, 10));
  131. }
  132. /**
  133. * Converts an IPv4 address object to an IPv6 address group
  134. * @memberof Address4
  135. * @instance
  136. * @returns {String}
  137. */
  138. toGroup6(): string {
  139. const output = [];
  140. let i;
  141. for (i = 0; i < constants.GROUPS; i += 2) {
  142. const hex = sprintf(
  143. '%02x%02x',
  144. parseInt(this.parsedAddress[i], 10),
  145. parseInt(this.parsedAddress[i + 1], 10)
  146. );
  147. output.push(sprintf('%x', parseInt(hex, 16)));
  148. }
  149. return output.join(':');
  150. }
  151. /**
  152. * Returns the address as a BigInteger
  153. * @memberof Address4
  154. * @instance
  155. * @returns {BigInteger}
  156. */
  157. bigInteger(): BigInteger {
  158. return new BigInteger(
  159. this.parsedAddress.map((n) => sprintf('%02x', parseInt(n, 10))).join(''),
  160. 16
  161. );
  162. }
  163. /**
  164. * Helper function getting start address.
  165. * @memberof Address4
  166. * @instance
  167. * @returns {BigInteger}
  168. */
  169. _startAddress(): BigInteger {
  170. return new BigInteger(this.mask() + '0'.repeat(constants.BITS - this.subnetMask), 2);
  171. }
  172. /**
  173. * The first address in the range given by this address' subnet.
  174. * Often referred to as the Network Address.
  175. * @memberof Address4
  176. * @instance
  177. * @returns {Address4}
  178. */
  179. startAddress(): Address4 {
  180. return Address4.fromBigInteger(this._startAddress());
  181. }
  182. /**
  183. * The first host address in the range given by this address's subnet ie
  184. * the first address after the Network Address
  185. * @memberof Address4
  186. * @instance
  187. * @returns {Address4}
  188. */
  189. startAddressExclusive(): Address4 {
  190. const adjust = new BigInteger('1');
  191. return Address4.fromBigInteger(this._startAddress().add(adjust));
  192. }
  193. /**
  194. * Helper function getting end address.
  195. * @memberof Address4
  196. * @instance
  197. * @returns {BigInteger}
  198. */
  199. _endAddress(): BigInteger {
  200. return new BigInteger(this.mask() + '1'.repeat(constants.BITS - this.subnetMask), 2);
  201. }
  202. /**
  203. * The last address in the range given by this address' subnet
  204. * Often referred to as the Broadcast
  205. * @memberof Address4
  206. * @instance
  207. * @returns {Address4}
  208. */
  209. endAddress(): Address4 {
  210. return Address4.fromBigInteger(this._endAddress());
  211. }
  212. /**
  213. * The last host address in the range given by this address's subnet ie
  214. * the last address prior to the Broadcast Address
  215. * @memberof Address4
  216. * @instance
  217. * @returns {Address4}
  218. */
  219. endAddressExclusive(): Address4 {
  220. const adjust = new BigInteger('1');
  221. return Address4.fromBigInteger(this._endAddress().subtract(adjust));
  222. }
  223. /**
  224. * Converts a BigInteger to a v4 address object
  225. * @memberof Address4
  226. * @static
  227. * @param {BigInteger} bigInteger - a BigInteger to convert
  228. * @returns {Address4}
  229. */
  230. static fromBigInteger(bigInteger: BigInteger): Address4 {
  231. return Address4.fromInteger(parseInt(bigInteger.toString(), 10));
  232. }
  233. /**
  234. * Returns the first n bits of the address, defaulting to the
  235. * subnet mask
  236. * @memberof Address4
  237. * @instance
  238. * @returns {String}
  239. */
  240. mask(mask?: number): string {
  241. if (mask === undefined) {
  242. mask = this.subnetMask;
  243. }
  244. return this.getBitsBase2(0, mask);
  245. }
  246. /**
  247. * Returns the bits in the given range as a base-2 string
  248. * @memberof Address4
  249. * @instance
  250. * @returns {string}
  251. */
  252. getBitsBase2(start: number, end: number): string {
  253. return this.binaryZeroPad().slice(start, end);
  254. }
  255. /**
  256. * Return the reversed ip6.arpa form of the address
  257. * @memberof Address4
  258. * @param {Object} options
  259. * @param {boolean} options.omitSuffix - omit the "in-addr.arpa" suffix
  260. * @instance
  261. * @returns {String}
  262. */
  263. reverseForm(options?: common.ReverseFormOptions): string {
  264. if (!options) {
  265. options = {};
  266. }
  267. const reversed = this.correctForm().split('.').reverse().join('.');
  268. if (options.omitSuffix) {
  269. return reversed;
  270. }
  271. return sprintf('%s.in-addr.arpa.', reversed);
  272. }
  273. /**
  274. * Returns true if the given address is in the subnet of the current address
  275. * @memberof Address4
  276. * @instance
  277. * @returns {boolean}
  278. */
  279. isInSubnet = common.isInSubnet;
  280. /**
  281. * Returns true if the given address is a multicast address
  282. * @memberof Address4
  283. * @instance
  284. * @returns {boolean}
  285. */
  286. isMulticast(): boolean {
  287. return this.isInSubnet(new Address4('224.0.0.0/4'));
  288. }
  289. /**
  290. * Returns a zero-padded base-2 string representation of the address
  291. * @memberof Address4
  292. * @instance
  293. * @returns {string}
  294. */
  295. binaryZeroPad(): string {
  296. return this.bigInteger().toString(2).padStart(constants.BITS, '0');
  297. }
  298. /**
  299. * Groups an IPv4 address for inclusion at the end of an IPv6 address
  300. * @returns {String}
  301. */
  302. groupForV6(): string {
  303. const segments = this.parsedAddress;
  304. return this.address.replace(
  305. constants.RE_ADDRESS,
  306. sprintf(
  307. '<span class="hover-group group-v4 group-6">%s</span>.<span class="hover-group group-v4 group-7">%s</span>',
  308. segments.slice(0, 2).join('.'),
  309. segments.slice(2, 4).join('.')
  310. )
  311. );
  312. }
  313. }