{ $Project$ $Workfile$ $Revision$ $DateUTC$ $Id$ This file is part of the Indy (Internet Direct) project, and is offered under the dual-licensing agreement described on the Indy website. (http://www.indyproject.org/) Copyright: (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. } { $Log$ } { Rev 1.4 10/26/2004 10:33:46 PM JPMugaas Updated refs. Rev 1.3 2004.02.03 5:44:08 PM czhower Name changes Rev 1.2 24/01/2004 19:27:30 CCostelloe Cleaned up warnings Rev 1.1 1/21/2004 2:20:26 PM JPMugaas InitComponent Rev 1.0 11/13/2002 07:57:46 AM JPMugaas } unit IdNetworkCalculator; interface {$i IdCompilerDefines.inc} uses Classes, IdGlobal, IdBaseComponent, IdStruct; type TNetworkClass = ( ID_NET_CLASS_A, ID_NET_CLASS_B, ID_NET_CLASS_C, ID_NET_CLASS_D, ID_NET_CLASS_E ); const ID_NC_MASK_LENGTH = 32; ID_NETWORKCLASS = ID_NET_CLASS_A; type TIdIPAddressType = (IPLocalHost, IPLocalNetwork, IPReserved, IPInternetHost, IPPrivateNetwork, IPLoopback, IPMulticast, IPFutureUse, IPGlobalBroadcast); TIpProperty = class(TPersistent) protected FReadOnly: Boolean; FBitArray: array[0..31] of Boolean; FValue: array[0..3] of Byte; FOnChange: TNotifyEvent; function GetAddressType: TIdIPAddressType; function GetAsBinaryString: String; function GetAsDoubleWord: UInt32; function GetAsString: String; function GetBit(Index: Byte): Boolean; function GetByte(Index: Integer): Byte; procedure SetAsBinaryString(const Value: String); procedure SetAsDoubleWord(const Value: UInt32); procedure SetAsString(const Value: String); procedure SetBit(Index: Byte; const Value: Boolean); procedure SetByte(Index: Integer; const Value: Byte); // property IsReadOnly: Boolean read FReadOnly write FReadOnly default False; public constructor Create; virtual; destructor Destroy; override; // procedure SetAll(One, Two, Three, Four: Byte); virtual; procedure Assign(Source: TPersistent); override; // property Bits[Index: Byte]: Boolean read GetBit write SetBit; property AddressType: TIdIPAddressType read GetAddressType; property OnChange: TNotifyEvent read FOnChange write FOnChange; published property Byte1: Byte index 0 read GetByte write SetByte stored False; property Byte2: Byte index 1 read GetByte write SetByte stored False; property Byte3: Byte index 2 read GetByte write SetByte stored False; property Byte4: Byte index 3 read GetByte write SetByte stored False; property AsDoubleWord: UInt32 read GetAsDoubleWord write SetAsDoubleWord stored False; property AsBinaryString: String read GetAsBinaryString write SetAsBinaryString stored False; property AsString: String read GetAsString write SetAsString; end; TIdNetworkCalculator = class(TIdBaseComponent) protected FListIP: TStrings; FNetworkMaskLength: UInt32; FNetworkMask: TIpProperty; FNetworkAddress: TIpProperty; FNetworkClass: TNetworkClass; FOnChange: TNotifyEvent; FOnGenIPList: TNotifyEvent; procedure FillIPList; function GetNetworkClassAsString: String; function GetIsAddressRoutable: Boolean; function GetListIP: TStrings; procedure SetNetworkAddress(const Value: TIpProperty); procedure SetNetworkMask(const Value: TIpProperty); procedure SetNetworkMaskLength(const Value: UInt32); procedure NetMaskChanged(Sender: TObject); procedure NetAddressChanged(Sender: TObject); procedure InitComponent; override; public destructor Destroy; override; function IsAddressInNetwork(const Address: String): Boolean; function NumIP: UInt32; function StartIP: String; function EndIP: String; // property ListIP: TStrings read GetListIP; property NetworkClass: TNetworkClass read FNetworkClass; property NetworkClassAsString: String read GetNetworkClassAsString; property IsAddressRoutable: Boolean read GetIsAddressRoutable; published property NetworkAddress: TIpProperty read FNetworkAddress write SetNetworkAddress; property NetworkMask: TIpProperty read FNetworkMask write SetNetworkMask; property NetworkMaskLength: UInt32 read FNetworkMaskLength write SetNetworkMaskLength default ID_NC_MASK_LENGTH; property OnGenIPList: TNotifyEvent read FOnGenIPList write FOnGenIPList; property OnChange: TNotifyEvent read FOnChange write FOnChange; end; implementation uses IdException, IdGlobalProtocols, IdResourceStringsProtocols, IdStack, SysUtils; function MakeLongWordIP(const One, Two, Three, Four: Byte): UInt32; begin Result := (UInt32(One) shl 24) or (UInt32(Two) shl 16) or (UInt32(Three) shl 8) or UInt32(Four); end; procedure BreakupLongWordIP(const Value: UInt32; var One, Two, Three, Four: Byte); begin One := Byte((Value and $FF000000) shr 24); Two := Byte((Value and $00FF0000) shr 16); Three := Byte((Value and $0000FF00) shr 8); Four := Byte(Value and $000000FF); end; function StrToIP(const Value: string): UInt32; var strBuffers: Array [0..3] of String; cardBuffers: Array[0..3] of UInt32; StrWork: String; I: Integer; begin StrWork := Value; // Separate the strings strBuffers[0] := Fetch(StrWork, '.', True); {Do not Localize} strBuffers[1] := Fetch(StrWork, '.', True); {Do not Localize} strBuffers[2] := Fetch(StrWork, '.', True); {Do not Localize} strBuffers[3] := StrWork; try for I := 0 to 3 do begin cardBuffers[I] := IndyStrToInt(strBuffers[I]); end; except IndyRaiseOuterException(EIdException.CreateFmt(RSNETCALInvalidIPString, [Value])); end; // range check for I := 0 to 3 do begin if not (cardBuffers[I] in [0..255]) then begin raise EIdException.CreateFmt(RSNETCALInvalidIPString, [Value]); end; end; Result := MakeLongWordIP(cardBuffers[0], cardBuffers[1], cardBuffers[2], cardBuffers[3]); end; { TIdNetworkCalculator } procedure TIdNetworkCalculator.InitComponent; begin inherited InitComponent; FNetworkMask := TIpProperty.Create; FNetworkMask.OnChange := NetMaskChanged; FNetworkAddress := TIpProperty.Create; FNetworkAddress.OnChange := NetAddressChanged; FListIP := TStringList.Create; FNetworkClass := ID_NETWORKCLASS; NetworkMaskLength := ID_NC_MASK_LENGTH; end; destructor TIdNetworkCalculator.Destroy; begin FreeAndNil(FNetworkMask); FreeAndNil(FNetworkAddress); FreeAndNil(FListIP); inherited Destroy; end; procedure TIdNetworkCalculator.FillIPList; var i: UInt32; BaseIP: UInt32; LByte1, LByte2, LByte3, LByte4: Byte; begin if FListIP.Count = 0 then begin // prevent to start a long loop in the IDE (will lock delphi) if IsDesignTime and (NumIP > 1024) then begin FListIP.text := IndyFormat(RSNETCALConfirmLongIPList, [NumIP]); end else begin BaseIP := NetworkAddress.AsDoubleWord and NetworkMask.AsDoubleWord; // Lock the list so we won't be "repainting" the whole time... {Do not Localize} FListIP.BeginUpdate; try FListIP.Capacity := NumIP; for i := 1 to (NumIP - 1) do begin Inc(BaseIP); BreakupLongWordIP(BaseIP, LByte1, LByte2, LByte3, LByte4); FListIP.Append(IndyFormat('%d.%d.%d.%d', [LByte1, LByte2, LByte3, LByte4])); {Do not Localize} end; finally FListIP.EndUpdate; end; end; end; end; function TIdNetworkCalculator.GetListIP: TStrings; begin FillIPList; Result := FListIP; end; function TIdNetworkCalculator.IsAddressInNetwork(const Address: String): Boolean; begin Result := (StrToIP(Address) and NetworkMask.AsDoubleWord) = (NetworkAddress.AsDoubleWord and NetworkMask.AsDoubleWord); end; procedure TIdNetworkCalculator.NetAddressChanged(Sender: TObject); var sBuffer: String; begin FListIP.Clear; sBuffer := NetworkAddress.AsBinaryString; // RFC 1365 if TextStartsWith(sBuffer, '0') then begin {Do not Localize} fNetworkClass := ID_NET_CLASS_A; end else if TextStartsWith(sBuffer, '10') then begin {Do not Localize} fNetworkClass := ID_NET_CLASS_B; end else if TextStartsWith(sBuffer, '110') then begin {Do not Localize} fNetworkClass := ID_NET_CLASS_C; end // Network class D is reserved for multicast else if TextStartsWith(sBuffer, '1110') then begin {Do not Localize} fNetworkClass := ID_NET_CLASS_D; end // network class E is reserved and shouldn't be used {Do not Localize} else {if TextStartsWith(sBuffer, '1111') then} begin {Do not Localize} fNetworkClass := ID_NET_CLASS_E; end; if Assigned(FOnChange) then begin FOnChange(Self); end; end; procedure TIdNetworkCalculator.NetMaskChanged(Sender: TObject); var sBuffer: string; InitialMaskLength: UInt32; begin FListIP.Clear; InitialMaskLength := FNetworkMaskLength; // A network mask MUST NOT contains holes. sBuffer := FNetworkMask.AsBinaryString; while TextStartsWith(sBuffer, '1') do begin {Do not Localize} Delete(sBuffer, 1, 1); end; if IndyPos('1', sBuffer) > 0 then {Do not Localize} begin NetworkMaskLength := InitialMaskLength; raise EIdException.Create(RSNETCALCInvalidNetworkMask); // 'Invalid network mask' {Do not Localize} end; // set the net mask length NetworkMaskLength := 32 - Length(sBuffer); if Assigned(FOnChange) then begin FOnChange(Self); end; end; procedure TIdNetworkCalculator.SetNetworkAddress(const Value: TIpProperty); begin FNetworkAddress.Assign(Value); end; procedure TIdNetworkCalculator.SetNetworkMask(const Value: TIpProperty); begin FNetworkMask.Assign(Value); end; procedure TIdNetworkCalculator.SetNetworkMaskLength(const Value: UInt32); var LBuffer, LValue: UInt32; begin if Value <= 32 then begin LValue := Value; end else begin LValue := 32; end; if FNetworkMaskLength <> LValue then begin FNetworkMaskLength := LValue; if Value > 0 then begin LBuffer := High(UInt32) shl (32 - LValue); end else begin LBuffer := 0; end; FNetworkMask.AsDoubleWord := LBuffer; end; end; function TIdNetworkCalculator.GetNetworkClassAsString: String; const sClasses: array[TNetworkClass] of String = ('A', 'B', 'C', 'D','E'); {Do not Localize} begin Result := sClasses[FNetworkClass]; end; function TIdNetworkCalculator.GetIsAddressRoutable: Boolean; begin // RFC 1918 Result := not ( (FNetworkAddress.Byte1 = 10) or ((FNetworkAddress.Byte1 = 172) and (FNetworkAddress.Byte2 in [16..31])) or ((FNetworkAddress.Byte1 = 192) and (FNetworkAddress.Byte2 = 168)) ); end; function TIdNetworkCalculator.EndIP: String; var IP: UInt32; LByte1, LByte2, LByte3, LByte4: Byte; begin IP := (NetworkAddress.AsDoubleWord and NetworkMask.AsDoubleWord) + (NumIP - 1); BreakupLongWordIP(IP, LByte1, LByte2, LByte3, LByte4); Result := IndyFormat('%d.%d.%d.%d', [LByte1, LByte2, LByte3, LByte4]); {Do not Localize} end; function TIdNetworkCalculator.NumIP: UInt32; begin Result := 1 shl (32 - NetworkMaskLength); end; function TIdNetworkCalculator.StartIP: String; var IP: UInt32; LByte1, LByte2, LByte3, LByte4: Byte; begin IP := NetworkAddress.AsDoubleWord and NetworkMask.AsDoubleWord; BreakupLongWordIP(IP, LByte1, LByte2, LByte3, LByte4); Result := IndyFormat('%d.%d.%d.%d', [LByte1, LByte2, LByte3, LByte4]); {Do not Localize} end; { TIpProperty } constructor TIpProperty.Create; begin inherited Create; FValue[0] := $0; FValue[1] := $0; FValue[2] := $0; FValue[3] := $0; end; destructor TIpProperty.Destroy; begin inherited Destroy; end; procedure TIpProperty.Assign(Source: TPersistent); var LSource: TIpProperty; begin if Source is TIpProperty then begin LSource := TIpProperty(Source); SetAll(LSource.Byte1, LSource.Byte2, LSource.Byte3, LSource.Byte4); end else begin inherited Assign(Source); end; end; function TIpProperty.GetBit(Index: Byte): boolean; begin Result := FBitArray[index]; end; procedure TIpProperty.SetAll(One, Two, Three, Four: Byte); var i, j: Integer; begin if (FValue[0] <> One) or (FValue[1] <> Two) or (FValue[2] <> Three) or (FValue[3] <> Four) then begin FValue[0] := One; FValue[1] := Two; FValue[2] := Three; FValue[3] := Four; // set the binary array for i := 0 to 3 do begin for j := 0 to 7 do begin FBitArray[(8*i)+j] := (FValue[i] and (1 shl (7-j))) <> 0; end; end; if Assigned(FOnChange) then begin FOnChange(Self); end; end; end; function TIpProperty.GetAsBinaryString: String; var i : Integer; {$IFDEF STRING_IS_IMMUTABLE} LSB: TIdStringBuilder; {$ENDIF} begin // get the binary string {$IFDEF STRING_IS_IMMUTABLE} LSB := TIdStringBuilder.Create(32); {$ELSE} SetLength(Result, 32); {$ENDIF} for i := 1 to 32 do begin if FBitArray[i-1] then begin {$IFDEF STRING_IS_IMMUTABLE} LSB.Append(Char('1')); {Do not Localize} {$ELSE} Result[i] := '1'; {Do not Localize} {$ENDIF} end else begin {$IFDEF STRING_IS_IMMUTABLE} LSB.Append(Char('0')); {Do not Localize} {$ELSE} Result[i] := '0'; {Do not Localize} {$ENDIF} end; end; {$IFDEF STRING_IS_IMMUTABLE} Result := LSB.ToString; {$ENDIF} end; function TIpProperty.GetAsDoubleWord: UInt32; begin Result := MakeLongWordIP(FValue[0], FValue[1], FValue[2], FValue[3]); end; function TIpProperty.GetAsString: String; begin // Set the string Result := IndyFormat('%d.%d.%d.%d', [FValue[0], FValue[1], FValue[2], FValue[3]]); {Do not Localize} end; procedure TIpProperty.SetAsBinaryString(const Value: String); var i: Integer; NewIP: UInt32; begin if IsReadOnly then begin Exit; end; if Length(Value) <> 32 then begin raise EIdException.Create(RSNETCALCInvalidValueLength) // 'Invalid value length: Should be 32.' {Do not Localize} end; if not TextIsSame(Value, AsBinaryString) then begin NewIP := 0; for i := 1 to 32 do begin if Value[i] <> '0' then begin {Do not Localize} NewIP := NewIP or (1 shl (32 - i)); end; end; SetAsDoubleWord(NewIP); end; end; function TIpProperty.GetByte(Index: Integer): Byte; begin Result := FValue[Index]; end; procedure TIpProperty.SetAsDoubleWord(const Value: UInt32); var LByte1, LByte2, LByte3, LByte4: Byte; begin if not IsReadOnly then begin BreakupLongWordIP(Value, LByte1, LByte2, LByte3, LByte4); SetAll(LByte1, LByte2, LByte3, LByte4); end; end; procedure TIpProperty.SetAsString(const Value: String); begin SetAsDoubleWord(StrToIP(Value)); end; procedure TIpProperty.SetBit(Index: Byte; const Value: Boolean); var ByteIndex: Integer; BitValue: Byte; begin if (not IsReadOnly) and (FBitArray[Index] <> Value) then begin FBitArray[Index] := Value; ByteIndex := Index div 8; BitValue := Byte(1 shl (7-(Index mod 8))); if Value then begin FValue[ByteIndex] := FValue[ByteIndex] or BitValue; end else begin FValue[ByteIndex] := FValue[ByteIndex] and not BitValue; end; if Assigned(OnChange) then begin OnChange(Self); end; end; end; procedure TIpProperty.SetByte(Index: Integer; const Value: Byte); begin if (not IsReadOnly) and (GetByte(Index) <> Value) then begin case Index of 0: SetAll(Value, Byte2, Byte3, Byte4); 1: SetAll(Byte1, Value, Byte3, Byte4); 2: SetAll(Byte1, Byte2, Value, Byte4); 3: SetAll(Byte1, Byte2, Byte3, Value); end; end; end; function TIpProperty.GetAddressType: TIdIPAddressType; // based on http://www.ora.com/reference/dictionary/terms/I/IP_Address.htm begin Result := IPInternetHost; case Byte1 of {localhost or local network} 0 : if AsDoubleWord = 0 then begin Result := IPLocalHost; end else begin Result := IPLocalNetwork; end; {Private network allocations} 10 : Result := IPPrivateNetwork; 172 : if Byte2 = 16 then begin Result := IPPrivateNetwork; end; 192 : if Byte2 = 168 then begin Result := IPPrivateNetwork; end else if (Byte2 = 0) and (Byte3 = 0) then begin Result := IPReserved; end; {loopback} 127 : Result := IPLoopback; 255 : if AsDoubleWord = $FFFFFFFF then begin Result := IPGlobalBroadcast; end else begin Result := IPFutureUse; end; {Reserved} 128 : if Byte2 = 0 then begin Result := IPReserved; end; 191 : if (Byte2 = 255) and (Byte3 = 255) then begin Result := IPReserved; end; 223 : if (Byte2 = 255) and (Byte3 = 255) then begin Result := IPReserved; end; {Multicast} 224..239: Result := IPMulticast; {Future Use} 240..254: Result := IPFutureUse; end; end; end.