Pull request 734: + client: 1778 Add ip sort function, write unit tests
Close #1778 Squashed commit of the following: commit ba63e147311799b17deaa97d7a12c2e0ec44a2ed Merge: 143ba427705a9d90
Author: ArtemBaskal <a.baskal@adguard.com> Date: Thu Aug 13 12:00:10 2020 +0300 Merge branch 'master' into feature/1778 commit 143ba42707da3d7eece9f3e137a8b245f7f7888f Merge: 483d2ff997df1989
Author: ArtemBaskal <a.baskal@adguard.com> Date: Thu Aug 13 11:16:46 2020 +0300 Merge branch 'master' into feature/1778 commit 483d2ff9fa3de706d94a647701f1d26a3bcbb217 Author: ArtemBaskal <a.baskal@adguard.com> Date: Wed Aug 12 13:34:21 2020 +0300 Always put ipv4 before ipv6 in sort, add invalid input unit tests commit 26eb41b313785fe7ffaf98a7573cc5eef0dca14f Author: ArtemBaskal <a.baskal@adguard.com> Date: Wed Aug 12 11:27:46 2020 +0300 Rewrite tests: replace ipv4-mapped adresses commit 4ecf287fd46945effe9ff11cfc9ae49217b9c796 Author: ArtemBaskal <a.baskal@adguard.com> Date: Tue Aug 11 19:05:15 2020 +0300 Minor fix commit 3e5e2a6bb1f2f166619da54e5ade0904fe22a20d Author: ArtemBaskal <a.baskal@adguard.com> Date: Tue Aug 11 19:01:26 2020 +0300 + client: 1778 Add ip sort function, write unit tests
This commit is contained in:
parent
705a9d909d
commit
2f8e34e73b
|
@ -1,4 +1,4 @@
|
|||
import { getIpMatchListStatus } from '../helpers/helpers';
|
||||
import { getIpMatchListStatus, sortIp } from '../helpers/helpers';
|
||||
import { IP_MATCH_LIST_STATUS } from '../helpers/constants';
|
||||
|
||||
describe('getIpMatchListStatus', () => {
|
||||
|
@ -129,3 +129,356 @@ describe('getIpMatchListStatus', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortIp', () => {
|
||||
describe('ipv4', () => {
|
||||
test('one octet differ', () => {
|
||||
const arr = [
|
||||
'127.0.2.0',
|
||||
'127.0.3.0',
|
||||
'127.0.1.0',
|
||||
];
|
||||
const sortedArr = [
|
||||
'127.0.1.0',
|
||||
'127.0.2.0',
|
||||
'127.0.3.0',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('few octets differ', () => {
|
||||
const arr = [
|
||||
'192.168.11.10',
|
||||
'192.168.10.0',
|
||||
'192.168.11.11',
|
||||
'192.168.10.10',
|
||||
'192.168.1.10',
|
||||
'192.168.0.1',
|
||||
'192.168.1.0',
|
||||
'192.168.1.1',
|
||||
'192.168.11.0',
|
||||
'192.168.0.10',
|
||||
'192.168.10.11',
|
||||
'192.168.0.11',
|
||||
'192.168.1.11',
|
||||
'192.168.0.0',
|
||||
'192.168.10.1',
|
||||
'192.168.11.1',
|
||||
];
|
||||
const sortedArr = [
|
||||
'192.168.0.0',
|
||||
'192.168.0.1',
|
||||
'192.168.0.10',
|
||||
'192.168.0.11',
|
||||
'192.168.1.0',
|
||||
'192.168.1.1',
|
||||
'192.168.1.10',
|
||||
'192.168.1.11',
|
||||
'192.168.10.0',
|
||||
'192.168.10.1',
|
||||
'192.168.10.10',
|
||||
'192.168.10.11',
|
||||
'192.168.11.0',
|
||||
'192.168.11.1',
|
||||
'192.168.11.10',
|
||||
'192.168.11.11',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
|
||||
// Example from issue https://github.com/AdguardTeam/AdGuardHome/issues/1778#issuecomment-640937599
|
||||
const arr2 = [
|
||||
'192.168.2.11',
|
||||
'192.168.3.1',
|
||||
'192.168.2.100',
|
||||
'192.168.2.2',
|
||||
'192.168.2.1',
|
||||
'192.168.2.10',
|
||||
'192.168.2.99',
|
||||
'192.168.2.200',
|
||||
'192.168.2.199',
|
||||
];
|
||||
const sortedArr2 = [
|
||||
'192.168.2.1',
|
||||
'192.168.2.2',
|
||||
'192.168.2.10',
|
||||
'192.168.2.11',
|
||||
'192.168.2.99',
|
||||
'192.168.2.100',
|
||||
'192.168.2.199',
|
||||
'192.168.2.200',
|
||||
'192.168.3.1',
|
||||
];
|
||||
expect(arr2.sort(sortIp)).toStrictEqual(sortedArr2);
|
||||
});
|
||||
});
|
||||
describe('ipv6', () => {
|
||||
test('only long form', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'2001:db8:11a3:9d7:0:0:0:3',
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
];
|
||||
const sortedArr = [
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'2001:db8:11a3:9d7:0:0:0:3',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('only short form', () => {
|
||||
const arr = [
|
||||
'2001:db8::',
|
||||
'2001:db7::',
|
||||
'2001:db9::',
|
||||
];
|
||||
const sortedArr = [
|
||||
'2001:db7::',
|
||||
'2001:db8::',
|
||||
'2001:db9::',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('long and short forms', () => {
|
||||
const arr = [
|
||||
'2001:db8::',
|
||||
'2001:db7:11a3:9d7:0:0:0:2',
|
||||
'2001:db6:11a3:9d7:0:0:0:1',
|
||||
'2001:db6::',
|
||||
'2001:db7:11a3:9d7:0:0:0:1',
|
||||
'2001:db7::',
|
||||
];
|
||||
const sortedArr = [
|
||||
'2001:db6::',
|
||||
'2001:db6:11a3:9d7:0:0:0:1',
|
||||
'2001:db7::',
|
||||
'2001:db7:11a3:9d7:0:0:0:1',
|
||||
'2001:db7:11a3:9d7:0:0:0:2',
|
||||
'2001:db8::',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
});
|
||||
describe('ipv4 and ipv6', () => {
|
||||
test('ipv6 long form', () => {
|
||||
const arr = [
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
'2001:db8:11a3:9d7:0:0:0:3',
|
||||
'127.0.0.1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'127.0.0.2',
|
||||
];
|
||||
const sortedArr = [
|
||||
'127.0.0.1',
|
||||
'127.0.0.2',
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'2001:db8:11a3:9d7:0:0:0:3',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('ipv6 short form', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7::3',
|
||||
'127.0.0.1',
|
||||
'2001:db8:11a3:9d7::2',
|
||||
'127.0.0.2',
|
||||
];
|
||||
const sortedArr = [
|
||||
'127.0.0.1',
|
||||
'127.0.0.2',
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'2001:db8:11a3:9d7::2',
|
||||
'2001:db8:11a3:9d7::3',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('ipv6 long and short forms', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'127.0.0.1',
|
||||
'2001:db8:11a3:9d7::3',
|
||||
'127.0.0.2',
|
||||
];
|
||||
const sortedArr = [
|
||||
'127.0.0.1',
|
||||
'127.0.0.2',
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'2001:db8:11a3:9d7::3',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('always put ipv4 before ipv6', () => {
|
||||
const arr = [
|
||||
'::1',
|
||||
'0.0.0.2',
|
||||
'127.0.0.1',
|
||||
'::2',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'0.0.0.1',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
];
|
||||
const sortedArr = [
|
||||
'0.0.0.1',
|
||||
'0.0.0.2',
|
||||
'127.0.0.1',
|
||||
'::1',
|
||||
'::2',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
});
|
||||
describe('cidr', () => {
|
||||
test('only ipv4 cidr', () => {
|
||||
const arr = [
|
||||
'192.168.0.1/9',
|
||||
'192.168.0.1/7',
|
||||
'192.168.0.1/8',
|
||||
];
|
||||
const sortedArr = [
|
||||
'192.168.0.1/7',
|
||||
'192.168.0.1/8',
|
||||
'192.168.0.1/9',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('ipv4 and cidr ipv4', () => {
|
||||
const arr = [
|
||||
'192.168.0.1/9',
|
||||
'192.168.0.1',
|
||||
'192.168.0.1/32',
|
||||
'192.168.0.1/7',
|
||||
'192.168.0.1/8',
|
||||
];
|
||||
const sortedArr = [
|
||||
'192.168.0.1/7',
|
||||
'192.168.0.1/8',
|
||||
'192.168.0.1/9',
|
||||
'192.168.0.1/32',
|
||||
'192.168.0.1',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('only ipv6 cidr', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7::1/32',
|
||||
'2001:db8:11a3:9d7::1/64',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
];
|
||||
const sortedArr = [
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
'2001:db8:11a3:9d7::1/32',
|
||||
'2001:db8:11a3:9d7::1/64',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('ipv6 and cidr ipv6', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7::1/32',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'2001:db8:11a3:9d7::1/64',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
];
|
||||
const sortedArr = [
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
'2001:db8:11a3:9d7::1/32',
|
||||
'2001:db8:11a3:9d7::1/64',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
});
|
||||
describe('invalid input', () => {
|
||||
const originalError = console.error;
|
||||
|
||||
beforeEach(() => {
|
||||
console.error = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
expect(console.error).toHaveBeenCalled();
|
||||
console.error = originalError;
|
||||
});
|
||||
|
||||
test('invalid strings', () => {
|
||||
const arr = ['invalid ip', 'invalid cidr'];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||
});
|
||||
test('invalid ip', () => {
|
||||
const arr = ['127.0.0.2.', '.127.0.0.1.', '.2001:db8:11a3:9d7:0:0:0:0'];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||
});
|
||||
test('invalid cidr', () => {
|
||||
const arr = ['127.0.0.2/33', '2001:db8:11a3:9d7:0:0:0:0/129'];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||
});
|
||||
test('valid and invalid ip', () => {
|
||||
const arr = ['127.0.0.4.', '127.0.0.1', '.127.0.0.3', '127.0.0.2'];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||
});
|
||||
});
|
||||
describe('mixed', () => {
|
||||
test('ipv4, ipv6 in short and long forms and cidr', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7:0:0:0:1/32',
|
||||
'192.168.1.2',
|
||||
'127.0.0.2',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
'127.0.0.1/12',
|
||||
'192.168.1.1',
|
||||
'2001:db8::/32',
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
'192.168.1.2/12',
|
||||
'2001:db7::/32',
|
||||
'127.0.0.1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'192.168.1.1/24',
|
||||
'2001:db7::/64',
|
||||
'2001:db7::',
|
||||
'2001:db8::',
|
||||
'2001:db8:11a3:9d7:0:0:0:1/128',
|
||||
'192.168.1.1/12',
|
||||
'127.0.0.1/32',
|
||||
'::1',
|
||||
];
|
||||
const sortedArr = [
|
||||
'127.0.0.1/12',
|
||||
'127.0.0.1/32',
|
||||
'127.0.0.1',
|
||||
'127.0.0.2',
|
||||
'192.168.1.1/12',
|
||||
'192.168.1.1/24',
|
||||
'192.168.1.1',
|
||||
'192.168.1.2/12',
|
||||
'192.168.1.2',
|
||||
'::1',
|
||||
'2001:db7::/32',
|
||||
'2001:db7::/64',
|
||||
'2001:db7::',
|
||||
'2001:db8::/32',
|
||||
'2001:db8::',
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
'2001:db8:11a3:9d7:0:0:0:1/32',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
'2001:db8:11a3:9d7:0:0:0:1/128',
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Trans, withTranslation } from 'react-i18next';
|
|||
import Card from '../ui/Card';
|
||||
import Cell from '../ui/Cell';
|
||||
|
||||
import { getPercent, getIpMatchListStatus } from '../../helpers/helpers';
|
||||
import { getPercent, getIpMatchListStatus, sortIp } from '../../helpers/helpers';
|
||||
import { IP_MATCH_LIST_STATUS, STATUS_COLORS } from '../../helpers/constants';
|
||||
import { formatClientCell } from '../../helpers/formatClientCell';
|
||||
|
||||
|
@ -99,7 +99,7 @@ const Clients = ({
|
|||
{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
|
||||
sortMethod: sortIp,
|
||||
Cell: clientCell(t, toggleClientStatus, processingAccessSet, disallowedClients),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -8,6 +8,7 @@ import CellWrap from '../../ui/CellWrap';
|
|||
|
||||
import whoisCell from './whoisCell';
|
||||
import LogsSearchLink from '../../ui/LogsSearchLink';
|
||||
import { sortIp } from '../../../helpers/helpers';
|
||||
|
||||
const COLUMN_MIN_WIDTH = 200;
|
||||
|
||||
|
@ -18,6 +19,7 @@ class AutoClients extends Component {
|
|||
accessor: 'ip',
|
||||
minWidth: COLUMN_MIN_WIDTH,
|
||||
Cell: CellWrap,
|
||||
sortMethod: sortIp,
|
||||
},
|
||||
{
|
||||
Header: this.props.t('table_name'),
|
||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import ReactTable from 'react-table';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../helpers/constants';
|
||||
import { sortIp } from '../../../helpers/helpers';
|
||||
|
||||
class Leases extends Component {
|
||||
cellWrap = ({ value }) => (
|
||||
|
@ -27,6 +28,7 @@ class Leases extends Component {
|
|||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
Cell: this.cellWrap,
|
||||
sortMethod: sortIp,
|
||||
}, {
|
||||
Header: <Trans>dhcp_table_hostname</Trans>,
|
||||
accessor: 'hostname',
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import ReactTable from 'react-table';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../../helpers/constants';
|
||||
|
||||
import { sortIp } from '../../../../helpers/helpers';
|
||||
import Modal from './Modal';
|
||||
|
||||
class StaticLeases extends Component {
|
||||
|
@ -49,6 +49,7 @@ class StaticLeases extends Component {
|
|||
{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
sortMethod: sortIp,
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -681,3 +681,68 @@ export const processContent = (content) => (Array.isArray(content)
|
|||
export const getObjectKeysSorted = (object, sortKey) => Object.entries(object)
|
||||
.sort(([, { [sortKey]: order1 }], [, { [sortKey]: order2 }]) => order1 - order2)
|
||||
.map(([key]) => key);
|
||||
|
||||
/**
|
||||
* @param ip
|
||||
* @returns {[IPv4|IPv6, 33|129]}
|
||||
*/
|
||||
const getParsedIpWithPrefixLength = (ip) => {
|
||||
const MAX_PREFIX_LENGTH_V4 = 32;
|
||||
const MAX_PREFIX_LENGTH_V6 = 128;
|
||||
|
||||
const parsedIp = ipaddr.parse(ip);
|
||||
const prefixLength = parsedIp.kind() === 'ipv4' ? MAX_PREFIX_LENGTH_V4 : MAX_PREFIX_LENGTH_V6;
|
||||
|
||||
// Increment prefix length to always put IP after CIDR, e.g. 127.0.0.1/32, 127.0.0.1
|
||||
return [parsedIp, prefixLength + 1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for IP and CIDR comparison (supports both v4 and v6)
|
||||
* @param item - ip or cidr
|
||||
* @returns {number[]}
|
||||
*/
|
||||
const getAddressesComparisonBytes = (item) => {
|
||||
// Sort ipv4 before ipv6
|
||||
const IP_V4_COMPARISON_CODE = 0;
|
||||
const IP_V6_COMPARISON_CODE = 1;
|
||||
|
||||
const [parsedIp, cidr] = ipaddr.isValid(item)
|
||||
? getParsedIpWithPrefixLength(item)
|
||||
: ipaddr.parseCIDR(item);
|
||||
|
||||
const [normalizedBytes, ipVersionComparisonCode] = parsedIp.kind() === 'ipv4'
|
||||
? [parsedIp.toIPv4MappedAddress().parts, IP_V4_COMPARISON_CODE]
|
||||
: [parsedIp.parts, IP_V6_COMPARISON_CODE];
|
||||
|
||||
return [ipVersionComparisonCode, ...normalizedBytes, cidr];
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare function for IP and CIDR sort in ascending order (supports both v4 and v6)
|
||||
* @param a
|
||||
* @param b
|
||||
* @returns {number} -1 | 0 | 1
|
||||
*/
|
||||
export const sortIp = (a, b) => {
|
||||
try {
|
||||
const comparisonBytesA = getAddressesComparisonBytes(a);
|
||||
const comparisonBytesB = getAddressesComparisonBytes(b);
|
||||
|
||||
for (let i = 0; i < comparisonBytesA.length; i += 1) {
|
||||
const byteA = comparisonBytesA[i];
|
||||
const byteB = comparisonBytesB[i];
|
||||
|
||||
if (byteA === byteB) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
return byteA > byteB ? 1 : -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue