From 99dba2ba2baae430e3a5b18cd89294b6f19acc93 Mon Sep 17 00:00:00 2001 From: Borys_Levytskyi Date: Fri, 23 Apr 2021 20:38:23 +0300 Subject: [PATCH] Add initial implementation of the VPC command --- src/core/components/BinaryString.tsx | 2 +- src/core/formatter.ts | 8 +- src/index.css | 1 + src/networking/components/SubnetView.tsx | 2 +- src/networking/components/VpcView.css | 23 +++++ src/networking/components/VpcView.tsx | 115 +++++++++++++++++++++++ src/networking/ip-parser.test.ts | 9 +- src/networking/ip-parser.ts | 47 ++++++--- src/networking/models.ts | 35 +++++-- src/networking/module.tsx | 12 ++- src/networking/subnet-utils.tsx | 7 +- 11 files changed, 232 insertions(+), 29 deletions(-) create mode 100644 src/networking/components/VpcView.css create mode 100644 src/networking/components/VpcView.tsx diff --git a/src/core/components/BinaryString.tsx b/src/core/components/BinaryString.tsx index 2095a44..61f1c8b 100644 --- a/src/core/components/BinaryString.tsx +++ b/src/core/components/BinaryString.tsx @@ -12,7 +12,7 @@ export type FlipBitEventArg = { index: number; binaryString: string; $event: any; - newBinaryString: string, + newBinaryString: string }; export default class BinaryStringView extends React.Component { diff --git a/src/core/formatter.ts b/src/core/formatter.ts index 85ace3f..f62ecea 100644 --- a/src/core/formatter.ts +++ b/src/core/formatter.ts @@ -33,6 +33,8 @@ function getBase(kind:string) : number { throw new Error("Unsupported kind: " + kind); } -export default formatter; -const emBin = formatter.emBin; -export {emBin}; \ No newline at end of file +const emBin = formatter.emBin.bind(formatter); +const padLeft = formatter.padLeft.bind(formatter); + +export {emBin, padLeft} +export default formatter; \ No newline at end of file diff --git a/src/index.css b/src/index.css index 198e747..fde8741 100644 --- a/src/index.css +++ b/src/index.css @@ -107,6 +107,7 @@ code { font-size: 1.2em; font-weight: bold; } .midnight .zero { color: #85a0ad;} .midnight .prefix { color: #85a0ad} .midnight .other { color: #9FBAC7;} +.midnight .accent-background { background-color: #3b5268;} .midnight .hashLink, .dark .hashLink:visited { color: #85a0ad } .midnight .hashLink:hover { color: #9FBAC7 } .midnight ul.top-links li:hover { background: #132537 } diff --git a/src/networking/components/SubnetView.tsx b/src/networking/components/SubnetView.tsx index 2446ede..7ee7d97 100644 --- a/src/networking/components/SubnetView.tsx +++ b/src/networking/components/SubnetView.tsx @@ -32,7 +32,7 @@ function SubnetView(props : {subnet : SubnetCommand}) { Network Size - {subnet.getAdressSpaceSize()} + {subnet.input.getAdressSpaceSize()} diff --git a/src/networking/components/VpcView.css b/src/networking/components/VpcView.css new file mode 100644 index 0000000..1c04bd5 --- /dev/null +++ b/src/networking/components/VpcView.css @@ -0,0 +1,23 @@ +.vpc-view .description { + vertical-align: middle; + text-align: right; +} + +.vpc-view td { + padding-right: 15px; +} + +.vpc-view { + margin-bottom: 20px; +} + +.vpc-view .part { + border-bottom: solid 1px; +} + + +.vpc-view button { + margin:0 3px; +} + +.vpc-view .ip-address-col { min-width: 8.5em;} \ No newline at end of file diff --git a/src/networking/components/VpcView.tsx b/src/networking/components/VpcView.tsx new file mode 100644 index 0000000..5f01a05 --- /dev/null +++ b/src/networking/components/VpcView.tsx @@ -0,0 +1,115 @@ +import React, { useState } from 'react'; +import BinaryStringView from '../../core/components/BinaryString'; +import './SubnetView.css'; +import { getNetworkAddress, getBroadCastAddress, createSubnetMaskIp, getAddressSpaceSize } from '../subnet-utils'; +import { chunkifyString } from '../../core/utils'; +import IpAddressBinaryString from './IpAddressBinaryString'; +import { IpAddress, IpAddressWithSubnetMask, SubnetCommand, VpcCommand } from '../models'; +import { padLeft } from '../../core/formatter'; + +function SubnetView(props : {vpc : VpcCommand}) { + + const [vpc, setVpc] = useState(VpcModel.create(props.vpc)); + + const subnetMask = vpc.cidr.maskBits + vpc.subnetBits; + const vpcPart = getNetworkAddress(vpc.cidr).toBinaryString(true).substring(0, vpc.cidr.maskBits); + const subnetsPart = padLeft("0", vpc.subnetBits, "0"); + const lastPart = padLeft("0", 32 - (vpc.cidr.maskBits + vpc.subnetBits), "0"); + + const decrSubnet = () => setVpc(vpc.changeSubnetBits(vpc.subnetBits-1)); + const incrSubnet = () => setVpc(vpc.changeSubnetBits(vpc.subnetBits+1)); + const incrVpc = () => setVpc(vpc.changeVpcCidr(new IpAddressWithSubnetMask(vpc.cidr.ipAddress, vpc.cidr.maskBits+1))); + const decrVpc = () => setVpc(vpc.changeVpcCidr(new IpAddressWithSubnetMask(vpc.cidr.ipAddress, vpc.cidr.maskBits-1))); + + return +
+ + + + + + + + + + + + + + + + + + + + + +
+ VPC Address Space + + +
+ VPC Mask: + + + /{vpc.cidr.maskBits} + +
+ Subnet Mask: + + + /{subnetMask} + +
+ Max Subnets: + + {Math.pow(2, vpc.subnetBits)} +
+ Subnet Network Size: + + {getAddressSpaceSize(subnetMask)} +
+
+
; +} + +function VpcRow(props: { ip: IpAddress, descr: string}) { + + const {ip, descr} = props; + + return + {descr} + + {ip.toString()} + + + + + ; +} + +export default SubnetView; + +class VpcModel { + cidr: IpAddressWithSubnetMask; + subnetBits: number; + subnetNum: number; + + constructor(cidr: IpAddressWithSubnetMask, subnetBits: number) { + this.cidr = cidr; + this.subnetBits = subnetBits; + this.subnetNum = 0; + } + + static create(vpc: VpcCommand) { + return new VpcModel(vpc.cidr, vpc.subnetBits); + } + + changeSubnetBits(n: number) { + return new VpcModel(this.cidr, n); + } + + changeVpcCidr(newCidr: IpAddressWithSubnetMask) { + return new VpcModel(newCidr, this.subnetBits); + } +} diff --git a/src/networking/ip-parser.test.ts b/src/networking/ip-parser.test.ts index f39fdb0..cf90100 100644 --- a/src/networking/ip-parser.test.ts +++ b/src/networking/ip-parser.test.ts @@ -1,5 +1,5 @@ import ipAddressParser, { ParsedIpObject, ParsingError } from './ip-parser'; -import { IpAddressWithSubnetMask, IpAddress, SubnetCommand } from "./models"; +import { IpAddressWithSubnetMask, IpAddress, SubnetCommand, VpcCommand } from "./models"; describe('parser tests', () => { @@ -62,4 +62,11 @@ describe('parser tests', () => { const subnet = actual as SubnetCommand; expect(subnet.toString()).toBe('192.168.1.1/23'); }); + + it('parses vpc command', () => { + const actual = ipAddressParser.parse('vpc 192.168.1.1/23'); + expect(actual).toBeInstanceOf(VpcCommand); + const vpc = actual as VpcCommand; + expect(vpc.toString()).toBe('192.168.1.1/23'); + }); }); \ No newline at end of file diff --git a/src/networking/ip-parser.ts b/src/networking/ip-parser.ts index 86a7f27..ae9e947 100644 --- a/src/networking/ip-parser.ts +++ b/src/networking/ip-parser.ts @@ -1,10 +1,18 @@ -import { IpAddress, IpAddressWithSubnetMask, SubnetCommand } from './models'; +import { IpAddress, IpAddressWithSubnetMask, SubnetCommand, VpcCommand } from './models'; export type ParsedIpObject = IpAddress | IpAddressWithSubnetMask; +export type ParsingResult = ParsedIpObject[] | + SubnetCommand | + ParsingError | + VpcCommand | + null; + +const CMD_SUBNET = 'subnet'; +const CMD_VPC = 'vpc'; const ipAddressParser = { - parse: function(input: string) : ParsedIpObject[] | SubnetCommand | ParsingError | null { + parse: function(input: string) : ParsingResult { const result = this.parseCommand(input); @@ -27,12 +35,12 @@ const ipAddressParser = { } if(result.command != null) { - const result = this.createSubnetDefinition(parsedObjects as ParsedIpObject[]); + const cmd = + result.command == CMD_SUBNET + ? this.createSubnetDefinition(parsedObjects as ParsedIpObject[]) + : this.createVpcDefinition(parsedObjects as ParsedIpObject[]); - if(result instanceof ParsingError) - return result; - - return result; + return cmd; } return parsedObjects as ParsedIpObject[]; @@ -40,9 +48,14 @@ const ipAddressParser = { parseCommand(input : string) : { command: null | string, nextInput: string } { - const command = 'subnet'; - if(input.startsWith(command)) - return { command, nextInput: input.substring(command.length)} + + + if(input.startsWith(CMD_SUBNET)) + return { command: CMD_SUBNET, nextInput: input.substring(CMD_SUBNET.length)}; + + if(input.startsWith(CMD_VPC)) { + return {command: CMD_VPC, nextInput: input.substring(CMD_VPC.length)}; + } return { command: null, nextInput: input }; }, @@ -101,6 +114,19 @@ const ipAddressParser = { } return new ParsingError("Network definition requires subnet mask"); + }, + + createVpcDefinition(items: ParsedIpObject[]) : VpcCommand | ParsingError { + + if(items.length != 1) + return new ParsingError("Incorrect VPC definition"); + + const first = items[0]; + if(first instanceof IpAddressWithSubnetMask) { + return new VpcCommand(first); + } + + return new ParsingError("VPC definition requires subnet mask"); } } @@ -111,5 +137,4 @@ export class ParsingError { } } - export default ipAddressParser; \ No newline at end of file diff --git a/src/networking/models.ts b/src/networking/models.ts index 646efad..02ab9de 100644 --- a/src/networking/models.ts +++ b/src/networking/models.ts @@ -1,4 +1,6 @@ import {emBin} from "../core/formatter"; +import { numberParser } from "../expression/numberParser"; +import { getAddressSpaceSize } from "./subnet-utils"; export type OctetNumber = 1 | 2 | 3 | 4; export type NetworkClass = 'a' | 'b' | 'c' | 'd' | 'e'; @@ -12,6 +14,11 @@ export class IpAddressWithSubnetMask { this.maskBits = maskBits; } + getAdressSpaceSize(): number { + const spaceLengthInBits = 32 - this.maskBits; + return getAddressSpaceSize(spaceLengthInBits); + } + toString() { return `${this.ipAddress.toString()}/${this.maskBits}`; } @@ -35,9 +42,12 @@ export class IpAddress { return `${this.firstByte}.${this.secondByte}.${this.thirdByte}.${this.fourthByte}`; } - toBinaryString() { + toBinaryString(skipDots?: boolean) { - return `${emBin(this.firstByte)}).${emBin(this.secondByte)}.${emBin(this.thirdByte)}.${emBin(this.fourthByte)}`; + if(!skipDots) + return `${emBin(this.firstByte)}.${emBin(this.secondByte)}.${emBin(this.thirdByte)}.${emBin(this.fourthByte)}`; + else + return `${emBin(this.firstByte)}${emBin(this.secondByte)}${emBin(this.thirdByte)}${emBin(this.fourthByte)}`; } clone(): IpAddress { @@ -63,18 +73,25 @@ export class IpAddress { } export class SubnetCommand { - input: IpAddressWithSubnetMask; + input: IpAddressWithSubnetMask; // TODO: rename to cidr constructor(definition: IpAddressWithSubnetMask) { this.input = definition; } - - getAdressSpaceSize(): number { - const spaceLengthInBits = 32 - this.input.maskBits; - return Math.pow(2, spaceLengthInBits) - 2; // 0 - network address, 1 - multicast address - } - + toString() { return this.input.toString(); } } +export class VpcCommand { + cidr: IpAddressWithSubnetMask; + subnetBits: number; + constructor(cidr: IpAddressWithSubnetMask) { + this.cidr = cidr; + this.subnetBits = 3; + } + + toString() { + return this.cidr.toString(); + } +} diff --git a/src/networking/module.tsx b/src/networking/module.tsx index 783fe21..8db74b0 100644 --- a/src/networking/module.tsx +++ b/src/networking/module.tsx @@ -4,11 +4,13 @@ import { CmdShell, CommandInput, CommandOptions } from '../shell/cmd'; import ErrorResultView from '../shell/components/ErrorResultView'; import IpAddressView from './components/IpAddressView'; import ipAddressParser, {ParsingError, ParsedIpObject} from './ip-parser'; -import { IpAddress, IpAddressWithSubnetMask, SubnetCommand } from "./models"; +import { IpAddress, IpAddressWithSubnetMask, SubnetCommand, VpcCommand } from "./models"; import log from 'loglevel'; import SubnetView from './components/SubnetView'; import { createSubnetMaskIp } from './subnet-utils'; import {sendAnalyticsEvent} from '../shell/analytics'; +import TextResultView from '../shell/components/TextResultView'; +import VpcView from './components/VpcView'; const networkingAppModule = { setup: function(appState: AppState, cmd: CmdShell) { @@ -33,6 +35,12 @@ const networkingAppModule = { return; } + if(result instanceof VpcCommand) { + appState.addCommandResult(c.input, ); + trackCommand('VpcCommand', c.options); + return; + } + const ipAddresses : IpAddress[] = []; (result as ParsedIpObject[]).forEach(r => { @@ -43,7 +51,7 @@ const networkingAppModule = { } else if(r instanceof IpAddress) { ipAddresses.push(r); - } + } }); trackCommand("IpAddressesInput", c.options); diff --git a/src/networking/subnet-utils.tsx b/src/networking/subnet-utils.tsx index 36a769b..e1ff663 100644 --- a/src/networking/subnet-utils.tsx +++ b/src/networking/subnet-utils.tsx @@ -29,6 +29,11 @@ function getBroadCastAddress(ipm: IpAddressWithSubnetMask) : IpAddress { return flipSubnetMaskBits(ipm, flipBitsToOne, 255); } +function getAddressSpaceSize(maskSize: number) { + const spaceLengthInBits = 32 - maskSize; + return Math.pow(2, spaceLengthInBits) - 2; // 0 - network address, 1 - multicast address +} + function flipSubnetMaskBits(ipm: IpAddressWithSubnetMask, flipper : FlipFunction, fullByte: number) { // Cannot treat ip address as a single number operation because 244 << 24 results in a negative number in JS const flip = (maskBits: number, byte: number) => flipper(byte, 8 - maskBits); @@ -80,4 +85,4 @@ function getNetworkClass (ipAddress: IpAddress) : NetworkClass { type FlipFunction = (byte: number, numberOfBits: number) => number; -export {createSubnetMaskIp, getBroadCastAddress, getNetworkAddress, getNetworkClass}; \ No newline at end of file +export {createSubnetMaskIp, getBroadCastAddress, getNetworkAddress, getNetworkClass, getAddressSpaceSize}; \ No newline at end of file