mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2025-12-10 06:52:05 +01:00
Add support of IP addresses (#15)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -21,3 +21,5 @@
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
|
debug.log
|
||||||
|
|||||||
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "pwa-chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Chrome against localhost",
|
||||||
|
"url": "http://localhost:3000",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import HelpResult from './models/HelpResult';
|
|||||||
import AboutResult from './models/AboutResult';
|
import AboutResult from './models/AboutResult';
|
||||||
import UnknownCommandResult from './models/UnknownCommandResult';
|
import UnknownCommandResult from './models/UnknownCommandResult';
|
||||||
import ExpressionResult from './models/ExpressionResult';
|
import ExpressionResult from './models/ExpressionResult';
|
||||||
import ErrorResult from './models/ErrorResult';
|
import {UnhandledErrorResult, ErrorResult} from './models/ErrorResults';
|
||||||
import WahtsnewResult from './models/WhatsnewResult';
|
import WahtsnewResult from './models/WhatsnewResult';
|
||||||
import StringResult from './models/StringResult';
|
import StringResult from './models/StringResult';
|
||||||
import * as expression from './expression/expression';
|
import * as expression from './expression/expression';
|
||||||
@@ -10,6 +10,9 @@ import uuid from 'uuid/v4';
|
|||||||
import { CommandInput, CmdShell } from './core/cmd';
|
import { CommandInput, CmdShell } from './core/cmd';
|
||||||
import { ExpressionInput } from './expression/expression-interfaces';
|
import { ExpressionInput } from './expression/expression-interfaces';
|
||||||
import AppState from './core/AppState';
|
import AppState from './core/AppState';
|
||||||
|
import {ParsingError, IpAddress, ipAddressParser, IpAddressWithSubnetMask, ParsedIpObject} from './ipaddress/ip'
|
||||||
|
import IpAddressResult from './models/IpAddressResult';
|
||||||
|
import { isGetAccessor, isPrefixUnaryExpression } from 'typescript';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
initialize (cmd: CmdShell, appState: AppState) {
|
initialize (cmd: CmdShell, appState: AppState) {
|
||||||
@@ -32,11 +35,43 @@ export default {
|
|||||||
appState.addCommandResult(new StringResult(c.input, `Debug Mode: ${appState.debugMode}`))
|
appState.addCommandResult(new StringResult(c.input, `Debug Mode: ${appState.debugMode}`))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Ip Addresses
|
||||||
|
cmd.command({
|
||||||
|
canHandle: (input:string) => ipAddressParser.parse(input) != null,
|
||||||
|
handle: function(c: CommandInput) {
|
||||||
|
var result = ipAddressParser.parse(c.input);
|
||||||
|
|
||||||
|
if(result == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(result instanceof ParsingError) {
|
||||||
|
appState.addCommandResult(new ErrorResult(c.input, result.errorMessage));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipAddresses : IpAddress[] = [];
|
||||||
|
|
||||||
|
(result as ParsedIpObject[]).forEach(r => {
|
||||||
|
if(r instanceof IpAddressWithSubnetMask)
|
||||||
|
{
|
||||||
|
ipAddresses.push(r.ipAddress);
|
||||||
|
ipAddresses.push(r.createSubnetMaskIp());
|
||||||
|
}
|
||||||
|
else if(r instanceof IpAddress) {
|
||||||
|
ipAddresses.push(r);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appState.addCommandResult(new IpAddressResult(c.input, ipAddresses));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bitwise Expressions
|
||||||
cmd.command({
|
cmd.command({
|
||||||
canHandle: (input:string) => expression.parser.canParse(input),
|
canHandle: (input:string) => expression.parser.canParse(input),
|
||||||
handle: function(c: CommandInput) {
|
handle: function(c: CommandInput) {
|
||||||
var expr = expression.parser.parse(c.input);
|
var expr = expression.parser.parse(c.input);
|
||||||
appState.toggleDebugMode();
|
|
||||||
appState.addCommandResult(new ExpressionResult(c.input, expr as ExpressionInput));
|
appState.addCommandResult(new ExpressionResult(c.input, expr as ExpressionInput));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -47,6 +82,6 @@ export default {
|
|||||||
handle: (c: CommandInput) => appState.addCommandResult(new UnknownCommandResult(c.input))
|
handle: (c: CommandInput) => appState.addCommandResult(new UnknownCommandResult(c.input))
|
||||||
});
|
});
|
||||||
|
|
||||||
cmd.onError((input: string, err: Error) => appState.addCommandResult(new ErrorResult(input, err)));
|
cmd.onError((input: string, err: Error) => appState.addCommandResult(new UnhandledErrorResult(input, err)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,11 +7,13 @@ import ExpressionResult from '../models/ExpressionResult';
|
|||||||
import BitwiseOperationExpressionView from './results/expressions/BitwiseOperationExpressionView';
|
import BitwiseOperationExpressionView from './results/expressions/BitwiseOperationExpressionView';
|
||||||
import WhatsnewResult from '../models/WhatsnewResult';
|
import WhatsnewResult from '../models/WhatsnewResult';
|
||||||
import WhatsnewResultView from './results/WhatsNewResultView';
|
import WhatsnewResultView from './results/WhatsNewResultView';
|
||||||
import ErrorResult from '../models/ErrorResult';
|
import {UnhandledErrorResult, ErrorResult} from '../models/ErrorResults';
|
||||||
import StringResult from '../models/StringResult';
|
import StringResult from '../models/StringResult';
|
||||||
|
import IpAddressView from './results/IpAddressView';
|
||||||
|
|
||||||
import CommandResult from '../models/CommandResult';
|
import CommandResult from '../models/CommandResult';
|
||||||
import AppState from '../core/AppState';
|
import AppState from '../core/AppState';
|
||||||
|
import IpAddressResult from '../models/IpAddressResult';
|
||||||
|
|
||||||
type DisplayResultProps = {
|
type DisplayResultProps = {
|
||||||
content : CommandResult,
|
content : CommandResult,
|
||||||
@@ -54,12 +56,24 @@ export default class DisplayResult extends React.Component<DisplayResultProps> {
|
|||||||
return <p>{result.value}</p>
|
return <p>{result.value}</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result instanceof ErrorResult) {
|
if (result instanceof UnhandledErrorResult) {
|
||||||
return <div className="result">
|
return <div className="result">
|
||||||
<div className="error">(X_X) Ooops.. Something ain' right: <strong>{result.error.message}</strong></div>
|
<div className="error">(X_X) Ooops.. Something ain' right: <strong>{result.error.message}</strong></div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result instanceof ErrorResult) {
|
||||||
|
return <div className="result">
|
||||||
|
<div className="error">{result.errorMessage}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if(result instanceof IpAddressResult) {
|
||||||
|
const ipResult = result as IpAddressResult;
|
||||||
|
|
||||||
|
return <IpAddressView ipAddresses={ipResult.ipAddresses} />
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="result">
|
return <div className="result">
|
||||||
<div className="error">¯\_(ツ)_/¯ Sorry, i don′t know what <strong>{this.props.content.input}</strong> is</div>
|
<div className="error">¯\_(ツ)_/¯ Sorry, i don′t know what <strong>{this.props.content.input}</strong> is</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
export type BinaryStringViewProps = {
|
export type BinaryStringViewProps = {
|
||||||
allowFlipBits: boolean;
|
allowFlipBits: boolean;
|
||||||
binaryString: string;
|
binaryString: string;
|
||||||
onFlipBit: (input: FlipBitEventArg) => void;
|
onFlipBit?: (input: FlipBitEventArg) => void;
|
||||||
emphasizeBytes: boolean;
|
emphasizeBytes: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ export type FlipBitEventArg = {
|
|||||||
index: number;
|
index: number;
|
||||||
binaryString: string;
|
binaryString: string;
|
||||||
$event: any;
|
$event: any;
|
||||||
|
newBinaryString: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class BinaryStringView extends React.Component<BinaryStringViewProps> {
|
export default class BinaryStringView extends React.Component<BinaryStringViewProps> {
|
||||||
@@ -19,13 +20,19 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
onBitClick(index: number, e : any) {
|
onBitClick(index: number, e : any) {
|
||||||
if(!this.props.allowFlipBits) {
|
if(!this.props.allowFlipBits || !this.props.onFlipBit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.props.onFlipBit) {
|
if(!this.props.onFlipBit) {
|
||||||
this.props.onFlipBit({ index: index, binaryString: this.props.binaryString, $event: e });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const arr = this.props.binaryString.split('');
|
||||||
|
arr[index] = arr[index] == '0' ? '1' : '0';
|
||||||
|
const newBinaryString = arr.join('');
|
||||||
|
|
||||||
|
this.props.onFlipBit({ index: index, binaryString: this.props.binaryString, $event: e, newBinaryString });
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildren() {
|
getChildren() {
|
||||||
|
|||||||
1
src/components/results/IpAddressView.css
Normal file
1
src/components/results/IpAddressView.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.ip-address-info { padding-top: 1em; font-size: 0.85em; vertical-align: middle;}
|
||||||
78
src/components/results/IpAddressView.tsx
Normal file
78
src/components/results/IpAddressView.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IpAddress, OctetNumber, getNetworkClass } from '../../ipaddress/ip';
|
||||||
|
import formatter from '../../core/formatter'
|
||||||
|
import BinaryStringView from './BinaryString';
|
||||||
|
import './IpAddressView.css';
|
||||||
|
type IpAddressViewProps = {
|
||||||
|
ipAddresses: IpAddress[]
|
||||||
|
};
|
||||||
|
|
||||||
|
export class IpAddressView extends React.Component<IpAddressViewProps>
|
||||||
|
{
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if(this.props.ipAddresses.length === 1)
|
||||||
|
return this.renderSingleIp(this.props.ipAddresses[0]);
|
||||||
|
|
||||||
|
return this.renderMultipleIps();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMultipleIps() {
|
||||||
|
return <table className="expression">
|
||||||
|
<tbody>
|
||||||
|
{this.props.ipAddresses.map((ip, i) => <tr key={i}>
|
||||||
|
<td>{ip.toString()}</td>
|
||||||
|
<td>
|
||||||
|
{this.bin(ip.firstByte, 1, ip)}.{this.bin(ip.secondByte, 2, ip)}.{this.bin(ip.thirdByte, 3, ip)}.{this.bin(ip.fourthByte, 4, ip)}
|
||||||
|
</td>
|
||||||
|
</tr>)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSingleIp(ip: IpAddress) {
|
||||||
|
return <table className="expression">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{ip.firstByte}</th>
|
||||||
|
<th>{ip.secondByte}</th>
|
||||||
|
<th>{ip.thirdByte}</th>
|
||||||
|
<th>{ip.fourthByte}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{this.bin(ip.firstByte, 1, ip)}</td>
|
||||||
|
<td>{this.bin(ip.secondByte, 2, ip)}</td>
|
||||||
|
<td>{this.bin(ip.thirdByte, 3, ip)}</td>
|
||||||
|
<td>{this.bin(ip.fourthByte, 4, ip)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colSpan={2} className="ip-address-info">
|
||||||
|
<a href="https://www.wikiwand.com/en/Classful_network" target="_blank">Network Class: {getNetworkClass(ip).toUpperCase()}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>;
|
||||||
|
}
|
||||||
|
|
||||||
|
bin(value: number, octetNumber: OctetNumber, ip: IpAddress) {
|
||||||
|
return <BinaryStringView
|
||||||
|
binaryString={fmt(value)}
|
||||||
|
key={octetNumber}
|
||||||
|
emphasizeBytes={false}
|
||||||
|
allowFlipBits={true}
|
||||||
|
onFlipBit={e => this.onFlippedBit(e.newBinaryString, octetNumber, ip)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFlippedBit(binaryString: string, number: OctetNumber, ip : IpAddress) {
|
||||||
|
ip.setOctet(number, parseInt(binaryString, 2));
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function fmt(num: number) : string {
|
||||||
|
return formatter.padLeft(formatter.formatString(num, 'bin'), 8, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IpAddressView;
|
||||||
@@ -25,17 +25,17 @@ code { font-size: 1.2em; font-weight: bold; }
|
|||||||
|
|
||||||
.expression .label { font-weight: bold; padding-right: 5px; text-align: right; }
|
.expression .label { font-weight: bold; padding-right: 5px; text-align: right; }
|
||||||
.expression .bin { letter-spacing: 3px; }
|
.expression .bin { letter-spacing: 3px; }
|
||||||
.expression .flipable { cursor: pointer; opacity: 1 }
|
|
||||||
.expression .flipable:hover { opacity: 0.8 }
|
|
||||||
.expression .byte { margin: 0 3px; }
|
.expression .byte { margin: 0 3px; }
|
||||||
.expression .flipable { cursor: pointer; opacity: 1 }
|
|
||||||
.expression .flipable:hover { opacity: 0.8 }
|
|
||||||
.expression-result td { border-top: dotted 1px gray; }
|
.expression-result td { border-top: dotted 1px gray; }
|
||||||
.expression { font-size: 1.5em; font-family: monospace }
|
.expression { font-size: 1.5em; font-family: monospace }
|
||||||
.expression .prefix { font-weight: normal; display: none; font-size: 0.9em }
|
.expression .prefix { font-weight: normal; display: none; font-size: 0.9em }
|
||||||
.expression .other { font-size: 0.9em}
|
.expression .other { font-size: 0.9em}
|
||||||
.expression .sign { text-align: right}
|
.expression .sign { text-align: right}
|
||||||
|
|
||||||
|
.flipable { cursor: pointer; opacity: 1 }
|
||||||
|
.flipable { cursor: pointer; opacity: 1 }
|
||||||
|
.flipable:hover { opacity: 0.8 }
|
||||||
|
|
||||||
.hex .prefix { display: inline; }
|
.hex .prefix { display: inline; }
|
||||||
|
|
||||||
.help { padding: 10px; }
|
.help { padding: 10px; }
|
||||||
|
|||||||
104
src/ipaddress/ip.test.ts
Normal file
104
src/ipaddress/ip.test.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import {IpAddress, ipAddressParser, getNetworkClass, ParsingError, IpAddressWithSubnetMask, ParsedIpObject} from './ip';
|
||||||
|
|
||||||
|
|
||||||
|
describe('parser tests', () => {
|
||||||
|
it('can parse correct ip address', () => {
|
||||||
|
const actual = ipAddressParser.parse('127.1.2.3');
|
||||||
|
expect(actual).not.toBe(null);
|
||||||
|
expect(actual).not.toBeInstanceOf(ParsingError);
|
||||||
|
|
||||||
|
const obj = (actual as ParsedIpObject[])[0];
|
||||||
|
const expected = new IpAddress(127, 1, 2, 3);
|
||||||
|
expect(obj).not.toBe(null);
|
||||||
|
expect(obj.toString()).toBe(expected.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot parse incorrect ip address', () => {
|
||||||
|
expect(ipAddressParser.parse('abc')).toBe(null);
|
||||||
|
expect(ipAddressParser.parse('')).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse invalid ip address', () => {
|
||||||
|
expect(ipAddressParser.parse('256.0.0.0')).toBeInstanceOf(ParsingError);
|
||||||
|
expect(ipAddressParser.parse('0.256.0.0')).toBeInstanceOf(ParsingError);
|
||||||
|
expect(ipAddressParser.parse('0.0.256.0')).toBeInstanceOf(ParsingError);
|
||||||
|
expect(ipAddressParser.parse('0.0.0.256')).toBeInstanceOf(ParsingError);
|
||||||
|
expect(ipAddressParser.parse('0.0.0.255 asd')).toBeInstanceOf(ParsingError);
|
||||||
|
expect(ipAddressParser.parse('0.0.0.255/99')).toBeInstanceOf(ParsingError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses correct ip and subnet mask', () => {
|
||||||
|
const actual = ipAddressParser.parse('127.0.0.1/24');
|
||||||
|
|
||||||
|
expect(actual).not.toBe(null);
|
||||||
|
expect(actual).not.toBeInstanceOf(ParsingError);
|
||||||
|
|
||||||
|
const obj = (actual as ParsedIpObject[])[0];
|
||||||
|
expect(obj).toBeInstanceOf(IpAddressWithSubnetMask);
|
||||||
|
expect(obj!.toString()).toBe('127.0.0.1/24');
|
||||||
|
expect((obj as IpAddressWithSubnetMask).maskBits).toBe(24);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses list of ip addresses', () => {
|
||||||
|
const actual = ipAddressParser.parse('127.0.0.1/24 255.255.1.1');
|
||||||
|
|
||||||
|
expect(actual).not.toBe(null);
|
||||||
|
expect(actual).not.toBeInstanceOf(ParsingError);
|
||||||
|
|
||||||
|
const first = (actual as ParsedIpObject[])[0];
|
||||||
|
expect(first).toBeInstanceOf(IpAddressWithSubnetMask);
|
||||||
|
expect(first!.toString()).toBe('127.0.0.1/24');
|
||||||
|
expect((first as IpAddressWithSubnetMask).maskBits).toBe(24);
|
||||||
|
|
||||||
|
const second = (actual as ParsedIpObject[])[1];
|
||||||
|
expect(second).toBeInstanceOf(IpAddress);
|
||||||
|
expect(second!.toString()).toBe('255.255.1.1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getNetworkClass tests', () => {
|
||||||
|
it('detects class a', () => {
|
||||||
|
expect(getNetworkClass(new IpAddress(1, 0, 0, 0))).toBe('a');
|
||||||
|
expect(getNetworkClass(new IpAddress(55, 0, 0, 0))).toBe('a');
|
||||||
|
expect(getNetworkClass(new IpAddress(97, 0, 0, 0))).toBe('a');
|
||||||
|
expect(getNetworkClass(new IpAddress(127, 0, 0, 0))).toBe('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects class b', () => {
|
||||||
|
expect(getNetworkClass(new IpAddress(128, 0, 0, 0))).toBe('b');
|
||||||
|
expect(getNetworkClass(new IpAddress(134, 0, 0, 0))).toBe('b');
|
||||||
|
expect(getNetworkClass(new IpAddress(180, 0, 0, 0))).toBe('b');
|
||||||
|
expect(getNetworkClass(new IpAddress(191, 0, 0, 0))).toBe('b');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects class c', () => {
|
||||||
|
expect(getNetworkClass(new IpAddress(192, 0, 0, 0))).toBe('c');
|
||||||
|
expect(getNetworkClass(new IpAddress(218, 0, 0, 0))).toBe('c');
|
||||||
|
expect(getNetworkClass(new IpAddress(223, 0, 0, 0))).toBe('c');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects class d', () => {
|
||||||
|
expect(getNetworkClass(new IpAddress(224, 0, 0, 0))).toBe('d');
|
||||||
|
expect(getNetworkClass(new IpAddress(234, 0, 0, 0))).toBe('d');
|
||||||
|
expect(getNetworkClass(new IpAddress(239, 0, 0, 0))).toBe('d');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects class e', () => {
|
||||||
|
expect(getNetworkClass(new IpAddress(240, 0, 0, 0))).toBe('e');
|
||||||
|
expect(getNetworkClass(new IpAddress(241, 0, 0, 0))).toBe('e');
|
||||||
|
expect(getNetworkClass(new IpAddress(255, 0, 0, 0))).toBe('e');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('IpAddressWithSubnetMask tests', () => {
|
||||||
|
|
||||||
|
it('creates subnetmask ip', () => {
|
||||||
|
const ip = new IpAddress(127, 0, 0, 1);
|
||||||
|
expect(new IpAddressWithSubnetMask(ip, 1).createSubnetMaskIp().toString()).toBe('128.0.0.0');
|
||||||
|
expect(new IpAddressWithSubnetMask(ip, 8).createSubnetMaskIp().toString()).toBe('255.0.0.0');
|
||||||
|
expect(new IpAddressWithSubnetMask(ip, 10).createSubnetMaskIp().toString()).toBe('255.192.0.0');
|
||||||
|
expect(new IpAddressWithSubnetMask(ip, 20).createSubnetMaskIp().toString()).toBe('255.255.240.0');
|
||||||
|
expect(new IpAddressWithSubnetMask(ip, 30).createSubnetMaskIp().toString()).toBe('255.255.255.252');
|
||||||
|
expect(new IpAddressWithSubnetMask(ip, 32).createSubnetMaskIp().toString()).toBe('255.255.255.255');
|
||||||
|
});
|
||||||
|
});
|
||||||
183
src/ipaddress/ip.ts
Normal file
183
src/ipaddress/ip.ts
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
import formatter from '../core/formatter';
|
||||||
|
|
||||||
|
|
||||||
|
export type OctetNumber = 1 | 2 | 3 | 4;
|
||||||
|
export type NetworkClass = 'a' | 'b' | 'c' | 'd' | 'e';
|
||||||
|
export type ParsedIpObject = IpAddress | IpAddressWithSubnetMask;
|
||||||
|
|
||||||
|
const ipAddressParser = {
|
||||||
|
parse: function(input: string) : ParsedIpObject[] | ParsingError | null {
|
||||||
|
|
||||||
|
const matches = this.getMaches(input);
|
||||||
|
const correctInputs = matches.filter(m => m.matches != null);
|
||||||
|
const incorrectInputs = matches.filter(m => m.matches == null);
|
||||||
|
|
||||||
|
if(correctInputs.length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if(incorrectInputs.length > 0) {
|
||||||
|
return new ParsingError(`Value(s) ${incorrectInputs.map(v => v.input).join(',')} was not recognized as valid ip address or ip address with a subnet mask`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedObjects = matches.map(m => this.parseSingle(m.matches!, m.input));
|
||||||
|
const parsingErrors = parsedObjects.filter(p => p instanceof ParsingError);
|
||||||
|
|
||||||
|
if(parsingErrors.length > 0) {
|
||||||
|
return parsingErrors[0] as ParsingError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedObjects as ParsedIpObject[];
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
getMaches(input : string) : { matches: RegExpExecArray | null, input: string }[] {
|
||||||
|
|
||||||
|
return input.
|
||||||
|
replace(/[\t\s]+/g, ' ')
|
||||||
|
.split(' ')
|
||||||
|
.filter(s => s.length>0)
|
||||||
|
.map(s => {
|
||||||
|
const ipV4Regex = /^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})(\/\d+)?$/;
|
||||||
|
const matches = ipV4Regex.exec(s);
|
||||||
|
|
||||||
|
if(matches == null || matches.length === 0)
|
||||||
|
return {matches: null, input: s};
|
||||||
|
|
||||||
|
return {matches, input: s};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
parseSingle(matches : RegExpExecArray, input: string) : ParsedIpObject | ParsingError {
|
||||||
|
const invalid = (n: number) => n < 0 || n > 255;
|
||||||
|
|
||||||
|
const first = parseInt(matches[1]);
|
||||||
|
const second = parseInt(matches[2]);
|
||||||
|
const third = parseInt(matches[3]);
|
||||||
|
const fourth = parseInt(matches[4]);
|
||||||
|
|
||||||
|
if(invalid(first) || invalid(second) || invalid(third) || invalid(fourth))
|
||||||
|
return new ParsingError(`${input} value doesn't fall within the valid range of the IP address space`);
|
||||||
|
|
||||||
|
const ipAddress = new IpAddress(first, second, third, fourth);
|
||||||
|
|
||||||
|
if(matches[5]) {
|
||||||
|
const maskPart = matches[5].substr(1);
|
||||||
|
const maskBits = parseInt(maskPart);
|
||||||
|
|
||||||
|
if(maskBits > 32) {
|
||||||
|
return new ParsingError(`Subnet mask value in ${input} is out of range`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new IpAddressWithSubnetMask(ipAddress, maskBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ParsingError {
|
||||||
|
errorMessage: string;
|
||||||
|
constructor(message: string) {
|
||||||
|
this.errorMessage = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IpAddressWithSubnetMask {
|
||||||
|
maskBits: number;
|
||||||
|
ipAddress: IpAddress;
|
||||||
|
|
||||||
|
constructor(ipAddress : IpAddress, maskBits : number) {
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
this.maskBits = maskBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.ipAddress.toString()}/${this.maskBits}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
createSubnetMaskIp() : IpAddress {
|
||||||
|
|
||||||
|
const mask = (bits: number) => 255<<(8-bits)&255;
|
||||||
|
|
||||||
|
if(this.maskBits <= 8) {
|
||||||
|
return new IpAddress(mask(this.maskBits), 0, 0, 0);
|
||||||
|
}
|
||||||
|
else if(this.maskBits <= 16) {
|
||||||
|
return new IpAddress(255, mask(this.maskBits-8), 0, 0);
|
||||||
|
}
|
||||||
|
else if(this.maskBits <= 24) {
|
||||||
|
return new IpAddress(255, 255, mask(this.maskBits-16), 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new IpAddress(255, 255, 255, mask(this.maskBits-24));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IpAddress {
|
||||||
|
|
||||||
|
firstByte : number;
|
||||||
|
secondByte: number;
|
||||||
|
thirdByte : number;
|
||||||
|
fourthByte: number
|
||||||
|
|
||||||
|
constructor(firstByte : number, secondByte: number, thirdByte : number, fourthByte: number) {
|
||||||
|
this.firstByte = firstByte;
|
||||||
|
this.secondByte = secondByte;
|
||||||
|
this.thirdByte = thirdByte;
|
||||||
|
this.fourthByte = fourthByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() : string {
|
||||||
|
return `${this.firstByte}.${this.secondByte}.${this.thirdByte}.${this.fourthByte}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOctet(octet: OctetNumber, value : number) {
|
||||||
|
switch(octet) {
|
||||||
|
case 1:
|
||||||
|
this.firstByte = value;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this.secondByte = value;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
this.thirdByte = value;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this.fourthByte = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const getNetworkClass = function (ipAddress: IpAddress) : NetworkClass {
|
||||||
|
const byte = ipAddress.firstByte;
|
||||||
|
const bineryRep = formatter.formatString(ipAddress.firstByte, 'bin');
|
||||||
|
|
||||||
|
const firstBitOne = (byte & 128) === 128;
|
||||||
|
const firstBitZero = (byte & 128) === 0;
|
||||||
|
const secondBitOne = (byte & 64) === 64;
|
||||||
|
|
||||||
|
const thirdBitOne = (byte & 32) === 32;
|
||||||
|
const thirdBitZero = (byte & 32) === 0;
|
||||||
|
|
||||||
|
const forthBitZero = (byte & 16) === 0;
|
||||||
|
const forthBitOne = (byte & 16) === 16;
|
||||||
|
|
||||||
|
// e: 1111
|
||||||
|
|
||||||
|
if(firstBitOne && secondBitOne && thirdBitOne && forthBitOne)
|
||||||
|
return 'e';
|
||||||
|
|
||||||
|
if(firstBitOne && secondBitOne && thirdBitOne && forthBitZero) // Start bits: 1110;
|
||||||
|
return 'd';
|
||||||
|
|
||||||
|
if(firstBitOne && secondBitOne && thirdBitZero) // Start bits: 110;
|
||||||
|
return 'c';
|
||||||
|
|
||||||
|
return firstBitOne ? 'b' : 'a';
|
||||||
|
};
|
||||||
|
|
||||||
|
export {ipAddressParser, getNetworkClass};
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import CommandResult from './CommandResult';
|
|
||||||
|
|
||||||
export default class ErrorResult extends CommandResult {
|
|
||||||
error: Error;
|
|
||||||
constructor(input: string, error : Error) {
|
|
||||||
super(input);
|
|
||||||
this.error = error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
src/models/ErrorResults.ts
Normal file
17
src/models/ErrorResults.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import CommandResult from './CommandResult';
|
||||||
|
|
||||||
|
export class UnhandledErrorResult extends CommandResult {
|
||||||
|
error: Error;
|
||||||
|
constructor(input: string, error : Error) {
|
||||||
|
super(input);
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorResult extends CommandResult {
|
||||||
|
errorMessage: string;
|
||||||
|
constructor(input: string, errorMessage : string) {
|
||||||
|
super(input);
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/models/IpAddressResult.ts
Normal file
10
src/models/IpAddressResult.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { IpAddress, ipAddressParser, IpAddressWithSubnetMask } from '../ipaddress/ip';
|
||||||
|
import CommandResult from './CommandResult';
|
||||||
|
|
||||||
|
export default class IpAddressResult extends CommandResult {
|
||||||
|
ipAddresses: IpAddress[];
|
||||||
|
constructor(input: string, ipAddresses: IpAddress[]) {
|
||||||
|
super(input);
|
||||||
|
this.ipAddresses = ipAddresses;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user