AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Delphi MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?
Thema durchsuchen
Ansicht
Themen-Optionen

MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

Ein Thema von CodeX · begonnen am 23. Mär 2017 · letzter Beitrag vom 24. Mär 2017
Antwort Antwort
Seite 1 von 2  1 2      
CodeX

Registriert seit: 30. Okt 2004
475 Beiträge
 
Delphi 12 Athens
 
#1

MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

  Alt 23. Mär 2017, 21:24
Delphi-Version: XE
Ich möchte in einem Programm gerne den zu einer MAC-Adresse zugehörigen Hersteller anzeigen. Dieser lässt sich ja an den ersten drei Bytes erkennen, da diese offiziell vergeben werden. Nun steht da sonst nicht so viel Logik dahinter und die Zuordnung wird in langen Listen öffentlich dokumentiert. Diese lassen sich in unterschiedlichen Formen beispielsweise hier abrufen:
http://linuxnet.ca/ieee/oui/
Ich habe die Datei mit den Kurzformen hier zusätzlich angehängt, da der Download von deren Server immens langsam ist.
So eine Auflistung hat über 23.000 Einträge.

Mein Problem ist nun, dass ich überhaupt keine Idee habe, wie ich damit in Delphi am besten umgehen sollte. Folgende Kriterien hätte ich damit gerne erfüllt:
  1. Die Liste sollte direkt in der Exe enthalten sein (und nicht als externe Datei eingelesen werden).
  2. Die Liste sollte möglichst kompakt gehalten werden, um das Programm nicht mehr aufzublähen als notwendig. Bspw. wäre es sinnvoll redundante Herstellernamen zusammenzufassen (also HP: 3CD92B, 9C8E99, B499BA, ...)
  3. Die Abfrage sollte so performant wie möglich ablaufen, da mehrere MAC-Adressen hintereinander abgefragt werden könnten.
Wie würdet Ihr das denn machen? Alles mit einem Parser in eine Array-Form bringen und in eine Unit stecken?
Angehängte Dateien
Dateityp: zip nmap-mac-prefixes.zip (205,0 KB, 12x aufgerufen)
Nur Delphi schafft es, einem ein Lächeln zu schenken, wenn man sich beim Schreiben von := vertippt und stattdessen ein :) erscheint.
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

Registriert seit: 29. Mai 2002
37.621 Beiträge
 
Delphi 2006 Professional
 
#2

AW: MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

  Alt 23. Mär 2017, 21:31
Zu 1.: Als Textdatei als Ressource einkompilieren und zur Abfrage temporär entpacken.
Da es wohl kein System gi9bt,bleibt wohl nur eine lange Case-Abfrage übrig.
Michael
Ein Teil meines Codes würde euch verunsichern.
  Mit Zitat antworten Zitat
nahpets
(Gast)

n/a Beiträge
 
#3

AW: MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

  Alt 23. Mär 2017, 22:32
Hab mal einbisserl rumgespielt, dabei ist das rausgekommen:
Delphi-Quellcode:
unit nmap_mac_prefixes;

interface

uses
  Classes;

type
  tNMap_Mac_List = class(TObject)
    fHex : TStringList;
    fName : TStringList;
  private
    procedure Init000;
    procedure Init001;
    procedure Init002;
    procedure Init003;
    procedure Init004;
    procedure Init005;
    procedure Init006;
    procedure Init007;
    procedure Init008;
    procedure Init009;
    procedure Init010;
    procedure Init011;
    procedure Init012;
    procedure Init013;
    procedure Init014;
    procedure Init015;
    procedure Init016;
    procedure Init017;
    procedure Init018;
    procedure Init019;
    procedure Init020;
    procedure Init021;
    procedure Init022;
    procedure Init023;
    procedure Init024;
    procedure Init025;
    procedure Init026;
    procedure Init027;
    procedure Init028;
    procedure Init029;
    procedure Init030;
    procedure Init031;
    procedure Init032;
    procedure Init033;
    procedure Init034;
    procedure Init035;
    procedure Init036;
    procedure Init037;
    procedure Init038;
    procedure Init039;
    procedure Init040;
    procedure Init041;
    procedure Init042;
    procedure Init043;
    procedure Init044;
    procedure Init045;
    procedure Init046;
  public
    constructor Create;
    destructor Destroy; override;
    function GetName(AHex : String) : String;
  end;

implementation

constructor tNMap_Mac_List.Create;
begin
  fHex := TStringList.Create;
  fName := TStringList.Create;
  Init000;
  Init001;
  Init002;
  Init003;
  Init004;
  Init005;
  Init006;
  Init007;
  Init008;
  Init009;
  Init010;
  Init011;
  Init012;
  Init013;
  Init014;
  Init015;
  Init016;
  Init017;
  Init018;
  Init019;
  Init020;
  Init021;
  Init022;
  Init023;
  Init024;
  Init025;
  Init026;
  Init027;
  Init028;
  Init029;
  Init030;
  Init031;
  Init032;
  Init033;
  Init034;
  Init035;
  Init036;
  Init037;
  Init038;
  Init039;
  Init040;
  Init041;
  Init042;
  Init043;
  Init044;
  Init045;
  Init046;
end;

destructor tNMap_Mac_List.Destroy;
begin
  fName.Free;
  fHex.Free;
end;

function tNMap_Mac_List.GetName(AHex : String) : String;
var
         iIndex : Integer;
begin
  iIndex := fHex.IndexOf(AHex);
  if iIndex > -1 then Result := fName[iIndex] else Result := '<unbekannt>';
end;

procedure tNMap_Mac_List.Init000;
begin
  fHex.Add('000000'); fName.Add('Xerox');
  fHex.Add('000001'); fName.Add('Xerox');
  fHex.Add('000002'); fName.Add('Xerox');
  fHex.Add('000003'); fName.Add('Xerox');
  fHex.Add('000004'); fName.Add('Xerox');
  fHex.Add('000005'); fName.Add('Xerox');
  fHex.Add('000006'); fName.Add('Xerox');
  fHex.Add('000007'); fName.Add('Xerox');
  fHex.Add('000008'); fName.Add('Xerox');
  fHex.Add('000009'); fName.Add('Xerox');
  fHex.Add('00000A'); fName.Add('Omron Tateisi');

... und viele weitere Zeilen dieser Art ...
... verteilt auf 46 Prozeduren, da ansonsten zuviele Konstanten in einer Prozedur enthalten sind ...
... vollständig im Anhang ...

  fHex.Add('FCF8B7'); fName.Add('TRONTEQ');
  fHex.Add('FCFAF7'); fName.Add('Shanghai Baud Data');
  fHex.Add('FCFBFB'); fName.Add('Cisco');
  fHex.Add('FCFC48'); fName.Add('Apple');
  fHex.Add('FCFE77'); fName.Add('Hitachi Reftechno');
  fHex.Add('FCFEC2'); fName.Add('Invensys Controls UK');
  fHex.Add('FCFFAA'); fName.Add('IEEE Registration Authority');
end;

end.
zu benutzen in z. B. der Form:
Delphi-Quellcode:
var
procedure Irgendwas(AMac : String);
 x : tNMap_Mac_List;
begin
  x := tNMap_Mac_List.Create;
  ShowMessage(x.GetName(AMac));
  x.Free;
end;
Ob das jetzt unbedingt eine sinnvolle Lösung ist, weiß ich nicht, aber es läßt sich kompilieren und ist damit alles in der EXE drin

Geändert von nahpets (21. Nov 2017 um 17:41 Uhr)
  Mit Zitat antworten Zitat
CodeX

Registriert seit: 30. Okt 2004
475 Beiträge
 
Delphi 12 Athens
 
#4

AW: MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

  Alt 23. Mär 2017, 22:57
Danke für den ersten Ansatz.

Ich weiß nicht, ob das so geschickt ist, die Bytes als String zu verwenden. Wenn man sie als das, was sie ja eigentlich sind, nehmen würde, sollte das eigentlich performanter und vermutlich auch platzsparender sein.

Auf jeden Fall aber übergeht Dein Ansatz die Anforderung, die Redundanz zu entfernen.
Denn statt einer platzsparenden Zuordnung von Name zu mehreren MAC-Bytes, muss für jeden 3Byte-Eintrag ein Herstellername stehen. Klar, die könnte man auch noch mühevoll durch Konstanten ersetzen und die Konstanten referenzieren, aber dann wäre eswahrscheinlich gleich besser, das andersherum zu machen.
Nur Delphi schafft es, einem ein Lächeln zu schenken, wenn man sich beim Schreiben von := vertippt und stattdessen ein :) erscheint.
  Mit Zitat antworten Zitat
nahpets
(Gast)

n/a Beiträge
 
#5

AW: MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

  Alt 23. Mär 2017, 23:56
Mann, Du bist aber anspruchsvoll

Ok: Luckies Caseansatz könnte dann so aussehen:
Delphi-Quellcode:
unit nmap_mac_prefixes;

interface

  function GetMac(AMac : Integer) : String;

implementation

function GetMac(AMac : Integer) : String;
begin
  case AMac of
    $30F33A: Result := '+plugg srl';
    $700258: Result := '01DB-Metravib';
    $C49313: Result := '100fio networks';
    $080024: Result := '10NET/DCA';
    $000B10: Result := '11wave Technonlogy';
    $A85EE4: Result := '12Sided';
    $005029: Result := '1394 Printer Working Group';
    $00A02D: Result := '1394 Trade Association';
    $001974: Result := '16063';
    $003070: Result := '1Net';
    $54369B: Result := '1Verge Internet';
    $443719: Result := '2 Save Energy';
    $0011B2: Result := '2001';
    $0025C3: Result := '21168';
    $EC9681: Result := '2276427 Ontario';
    $001387: Result := '27M';
    $000761: Result := '29530';
    $28F358: Result := '2C - Trifonov';
    $3C3F51: Result := '2CRSI';
    $0016A9: Result := '2EI';
    $B8B7D7: Result := '2GIG';
    $001B8A: Result := '2M';
    $001929: Result := '2M2B Montadora de Maquinas Bahia Brasil';
    $7C1EB3: Result := '2N TELEKOMUNIKACE a.s';
    $F82C18, $28162E, $383BC8, $94C150, $001288, $002456, $60FE20, $982CBE,
    $0022A4, $002650, $34EF44, $00183F, $002351, $00D09E, $001FB3, $DC7FA4,
    $749DDC, $00253C, $0019E4, $001EC7, $F81897, $640F28, $C0830A, $000D72,
    $3CEA4F, $00217C, $60C397, $001D5A, $B8E625, $B0E754, $14EDBB, $001AC4,
    $001B5B, $001495: Result := '2Wire';

... und viele weitere Zeilen ...
... vollständig im Anhang ...

    $000689: Result := 'yLez';
    $8CC7D0: Result := 'zhejiang ebang';
    $30F31D, $28FF3E, $78312B, $DC028E, $4C09B4, $601466, $84742A, $E47723,
    $30D386, $2C26C5, $601888, $B805AB, $8C7967, $A8A668, $744AA4, $901D27,
    $F4B8A7, $C864C7, $4C16F1, $688AF0, $709F2D, $789682, $4CAC0A, $D0154A,
    $4CCBF5, $48282F, $FCC897, $B4B362, $CC1AFA, $540955, $300C23, $48A74E,
    $B49842, $346987, $004A77, $344DEA, $F41F88, $343759, $344B50, $F084C9,
    $001E73, $88D274, $B075D5, $986CF5, $C87B5B, $F46DE2, $702E22, $AC6462,
    $981333, $CC7B35, $2C957F, $C4A366, $681AB2, $34DE34, $D855A3, $A0EC80,
    $208986, $D437D7, $64136C, $FC2D5E, $0C1262, $083FBC, $94A7B7, $EC1D7F,
    $002293, $D87495, $38D82F, $749781, $08181A, $D476EA, $1844E6, $9CA9E4,
    $146080, $F8DFA8, $9CD24B, $18686A, $002512, $D4C1C8, $E07C13, $F8A34F,
    $A091C8, $10D0AB, $143EBF, $3CDA2A, $0015EB, $74A78E, $98F537, $98F428,
    $8CE081, $78C1A7, $5422F8, $54BE53, $6C8B2F, $8CE117, $0026ED, $44F436,
    $6CA75F, $EC237B, $F4E4AD, $34E0CF, $0019C6, $EC8A4C, $384608, $6073BC,
    $D071C4, $90C7D8, $90D8F3, $E0C3F3, $78E8B6, $74B57E, $D0608C, $D05BA8,
    $D058A8, $24C44A, $689FF0, $A47E39, $049573: Result := 'zte';
  else
    Result := '<unbekannt>';
  end;
end;

end.
Das ist allerdings nicht kompilierbar, wegen doppelter Case-Label. So ist z. B. 080030 nicht eindeutig, sondern wird von drei Firmen genutzt: Cern, Network und Royal Melbourne Inst Of. Da wirst Du Dir dann wohl noch eine andere Lösung suchen müssen

Allerdings halten sich die Dubletten im Rahmen, folgende hab' ich finden können:
Code:
0001C8 Conrad und Thomas Conrad
080030 Cern, Network und Royal Melbourne Inst Of
Dafür wäre der Aufruf aber deutlich einfacher:ShowMessage(GetMac($000019));

Geändert von nahpets (21. Nov 2017 um 17:41 Uhr)
  Mit Zitat antworten Zitat
CodeX

Registriert seit: 30. Okt 2004
475 Beiträge
 
Delphi 12 Athens
 
#6

AW: MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

  Alt 24. Mär 2017, 00:05
Oh wow, ja das ist super!
Irgendwie habe ich mich gedanklich in andere Richtungen verrannt. DANKE!!
Die Dubletten sind nicht schlimm. Die werden dann eben zusammengefasst. Kann ja nichts dafür, dass die so vergeben wurden.

Hast Du Dir da extra einen Parser dafür geschrieben?
Nur Delphi schafft es, einem ein Lächeln zu schenken, wenn man sich beim Schreiben von := vertippt und stattdessen ein :) erscheint.

Geändert von CodeX (24. Mär 2017 um 00:31 Uhr)
  Mit Zitat antworten Zitat
nahpets
(Gast)

n/a Beiträge
 
#7

AW: MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

  Alt 24. Mär 2017, 00:25
Parser wäre da wohl deutlich übertrieben

Mein Editor unterstützt die Nutzung von Pascal-Script.

Und dafür hab' ich mir halt folgenden Quelltext zusammengedaddelt:
Delphi-Quellcode:
program Test;
var
        i : Integer;
        k : Integer;
        s : String;
        sCase : String;

procedure Dubletten;
begin
  GetSelText;
  s := '';
  for i := 0 to SelText.Count - 1 do begin
    if s = Copy(SelText[i],1,6) then begin
       WriteLn(Format('Dublette %s',[Copy(SelText[i],1,6)]));
       WriteLn(Format('Dublette %s',[Copy(SelText[i - 1],8,4096)]));
       WriteLn(Format('Dublette %s',[Copy(SelText[i],8,4096)]));
    end;
    s := Copy(SelText[i],1,6);
  end;
end;

procedure CaseErstellen;
begin
  GetSelText;
  k := 0;
  s := AnsiReplaceText(Copy(SelText[0],8,4096),'''',''''' ');
  s := '';
  sCase := '';
  for i := 0 to SelText.Count - 1 do begin
    if s <> AnsiReplaceText(Copy(SelText[i],8,4096),'''',''''' ') then begin
      WriteLn(Format(' %s: Result := ''%s'';',[sCase,s]));
      s := AnsiReplaceText(Copy(SelText[i],8,4096),'''',''''' ');
      sCase := '$' + Copy(SelText[i],1,6);
    end else begin
      if Length(sCase) = 70 then begin
        WriteLn(Format(' %s,',[sCase]));
        sCase := '';
      end;
      if sCase = 'then sCase := '$' + Copy(SelText[i],1,6) else sCase := sCase + ', $' + Copy(SelText[i],1,6);
    end;
  end;
  WriteLn(Format(' %s: Result := ''%s'';',[sCase,s]));
end;

procedure InitErstellen;
begin
  GetSelText;
  k := 0;
  for i := 0 to SelText.Count - 1 do begin
    if (i mod 500 = 0) then begin
      if i > 0 then begin
        WriteLn('end;');
        WriteLn('');
      end;
      Output.Add(Format(' procedure Init%0.3d;',[k]));
      WriteLn(Format('procedure tNMap_Mac_List.Init%0.3d;',[k]));
      WriteLn('begin');
      k := k + 1;
    end;
    WriteLn(Format(' fHex.Add(''%s''); fName.Add(''%s'');',[Copy(SelText[i],1,6),AnsiReplaceText(Copy(SelText[i],8,4096),'''',''''' ')]));
  end;
  WriteLn('end;');
end;

begin
  // InitErstellen;
  // CaseErstellen;
  Dubletten;
end.
GetSelText holt den im Editor markierten Text, SelText ist 'ne Stringliste mit eben diesem Text und WriteLn schreibt in ein Ausgabefenster (ein TMemo).

Der Rest ist "handelsübliches" Delphi und einbisserl Schreibarbeit

Wenn Du das auf Delphi umbauen möchtest, musst Du halt noch zwei Stringlisten machen.

Delphi-Quellcode:
SelText := TStringList.Create;
Ausgabe := TStringList.Create;
SelText.LoadFromFile('nmap-mac-prefixes.txt');
// Statt WriteLn(...); halt Ausgabe.Add(...);
Ausgabe.SaveToFile('nmap_mac_prefixes.inc');
Ausgabe.Free;
SelText.Free;
Und damit dürfte es mit marginaler Nacharbeit möglich sein, ggfls. aus 'ner neuen nmap-mac-prefixes.txt den Case-Teil zu generieren.

Für die Dublettenprüfung muss die Datei nmap-mac-prefixes.txt ab der 1. Spalte sortiert sein, für das Erstellen des Case-Teiles und die erste Stringlistenvariante ab der 8. Spalte.
  Mit Zitat antworten Zitat
Benutzerbild von p80286
p80286

Registriert seit: 28. Apr 2008
Ort: Stolberg (Rhl)
6.659 Beiträge
 
FreePascal / Lazarus
 
#8

AW: MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

  Alt 24. Mär 2017, 08:08
Das ist doch eine typische Datenbankanwendung.
Die Daten normalisieren, sprich zwei Tabellen erstellen und dann mit binärer Suche dadurch hüpfen.

Gruß
K-H
Programme gehorchen nicht Deinen Absichten sondern Deinen Anweisungen
R.E.D retired error detector
  Mit Zitat antworten Zitat
Benutzerbild von haentschman
haentschman

Registriert seit: 24. Okt 2006
Ort: Seifhennersdorf / Sachsen
5.388 Beiträge
 
Delphi 12 Athens
 
#9

AW: MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

  Alt 24. Mär 2017, 08:30
Moin...

Wenn es keine DB hat, dann würde ich die Textdatei als Ressource kompilieren. Nach dem Programmstart würde ich die Ressource einlesen. Für die Lagerung der Informationen käme ein TDictionary<string, string> in Frage. Beim Einlesen wird aus jeder Zeile aus den ersten 6 Zeichen der Key generiert in der Rest ist das Value. Die Dupletten kannst du schon beim Einlesen handeln. Wenn der Key existiert dann den Value an den bestehenden Value hängen.
...fertsch.

Vorteil:

1. Liste.LoadFromFile('nmap-mac-prefixes.txt'); ist nicht notwendig da die Textdatei zur Laufzeit nicht beigelegt werden muß...wird beim Erzeugen einkompiliert.
2. Keine Änderung des Quelltextes bei Inhaltsänderungen der TXT...einfach neu kompilieren.
3. Ein Dictionary ist dafür da was aus einer "Liste" herauszusuchen...deutlich schneller als TStringlist.

Delphi-Quellcode:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Variants, System.Classes, System.Generics.Collections, System.Generics.Defaults,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FDictionary: TDictionary<string, string>;
    procedure ReadFile;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  FDictionary := TDictionary<string, string>.Create;
  ReadFile;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FDictionary.Free;
end;

procedure TForm1.ReadFile;
var
  I: Integer;
  Content: TStringList;
  TextStream: TResourceStream;
  CurrentRowKey: string;
  CurrentRowValue: string;
begin
  FDictionary.Clear;
  Content := TStringList.Create;
  try
    TextStream := TResourceStream.Create(HInstance, 'Content', PWideChar('ContentFile'));
    try
      Content.LoadFromStream(TextStream);
      for I := 0 to Content.Count - 1 do
      begin
        CurrentRowKey := Copy(Content[I], 1, 6);
        if not FDictionary.TryGetValue(CurrentRowKey, CurrentRowValue) then
        begin // einfügen
          CurrentRowValue := Copy(Content[I], 8, Length(Content[I]) + 8);
          FDictionary.Add(CurrentRowKey, CurrentRowValue);
        end
        else
        begin // Key zusammensetzen bei Duplikaten
          CurrentRowValue := Copy(Content[I], 8, Length(Content[I]) + 8);
          FDictionary.AddOrSetValue(CurrentRowKey, FDictionary.Items[CurrentRowKey] + '; ' + CurrentRowValue);
        end;
      end;
    finally
      TextStream.Free;
    end;
  finally
    Content.Free;
  end;
end;

end.
RC Datei Inhalt:
Zitat:
Content ContentFile "D:\Blubb\nmap-mac-prefixes.txt"
... als RC Datei ins Projekt. (Pfadangaben anpassen)
Miniaturansicht angehängter Grafiken
resccource.png  
Angehängte Dateien
Dateityp: zip Muster.zip (462,7 KB, 4x aufgerufen)

Geändert von haentschman (24. Mär 2017 um 12:21 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

Registriert seit: 29. Mai 2002
37.621 Beiträge
 
Delphi 2006 Professional
 
#10

AW: MAC-Hersteller-Zuordnung bzw. Wie mit einem sehr großen Datensatz umgehen?

  Alt 24. Mär 2017, 08:31
War auch meine erste Idee. Nur er will alles in einer Datei weiter geben ohne (Installation.
Michael
Ein Teil meines Codes würde euch verunsichern.
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 06:51 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz