mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2026-01-29 07:04:41 +01:00
Refactor towards modular structure
This commit is contained in:
1
src/networking/components/IpAddressView.css
Normal file
1
src/networking/components/IpAddressView.css
Normal file
@@ -0,0 +1 @@
|
||||
.ip-address-info { padding-top: 1em; font-size: 0.85em; vertical-align: middle; display: none;}
|
||||
82
src/networking/components/IpAddressView.tsx
Normal file
82
src/networking/components/IpAddressView.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import { IpAddress, OctetNumber, getNetworkClass } from '../ip';
|
||||
import formatter from '../../core/formatter'
|
||||
import BinaryStringView from '../../core/components/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 className="label"><strong>{ip.toString()}</strong></td>
|
||||
<td className="bin">
|
||||
{this.bin(ip.firstByte, 1, ip)}<span className="soft">.</span>
|
||||
{this.bin(ip.secondByte, 2, ip)}<span className="soft">.</span>
|
||||
{this.bin(ip.thirdByte, 3, ip)}<span className="soft">.</span>
|
||||
{this.bin(ip.fourthByte, 4, ip)}
|
||||
</td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
renderSingleIp(ip: IpAddress) {
|
||||
return <table className="expression">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='first-decimal'>{ip.firstByte}</th>
|
||||
<th className='second-decimal'>{ip.secondByte}</th>
|
||||
<th className='third-decimal'>{ip.thirdByte}</th>
|
||||
<th className='fourth-decimal'>{ip.fourthByte}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className='first-bin'>{this.bin(ip.firstByte, 1, ip)}</td>
|
||||
<td className='second-bin'>{this.bin(ip.secondByte, 2, ip)}</td>
|
||||
<td className='third-bin'>{this.bin(ip.thirdByte, 3, ip)}</td>
|
||||
<td className='fourth-bin'>{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}
|
||||
className={`octet-${octetNumber}`}
|
||||
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;
|
||||
104
src/networking/ip.test.ts
Normal file
104
src/networking/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/networking/ip.ts
Normal file
183
src/networking/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};
|
||||
47
src/networking/module.tsx
Normal file
47
src/networking/module.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import AppState from '../shell/AppState';
|
||||
import { CmdShell, CommandInput } from '../shell/cmd';
|
||||
import ErrorResultView from '../shell/components/ErrorResultView';
|
||||
import IpAddressView from './components/IpAddressView';
|
||||
import { ipAddressParser, ParsingError, IpAddress, ParsedIpObject, IpAddressWithSubnetMask } from './ip';
|
||||
import log from 'loglevel';
|
||||
|
||||
const networkingAppModule = {
|
||||
setup: function(appState: AppState, cmd: CmdShell) {
|
||||
|
||||
// Add Ip Address commands
|
||||
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(c.input, <ErrorResultView errorMessage={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(c.input, <IpAddressView ipAddresses={ipAddresses} />);
|
||||
}
|
||||
});
|
||||
|
||||
log.debug();
|
||||
}
|
||||
}
|
||||
|
||||
export default networkingAppModule;
|
||||
Reference in New Issue
Block a user