{
  Joachim Deckers
  Anlage zur 2. Staatsexamensarbeit / 28.5.96

  Diese Unit Protokoll wird zuerst im Abschnitt "7.3.5.1 Einfache Protokollsprachen"
  benötigt.

  Simuliert wird eine einfache Datenübertragung zwischen zwei Rechnern bzw. zwei
  Rechneranwendungen, wobei anstelle einer Netzwerkkarte eine Datei zur Pufferung
  der Daten verwendet wird.

  Ggf. sollte die Konstante dateiname verändert werden!
}


UNIT protokoll;


INTERFACE



PROCEDURE Sende(zeichenkette: string);
  { Sendet den String zeichenkette }
PROCEDURE SendeChars(anzahl: Word; feld: PChar);
  { Sendet anzahl Zeichen aus dem Speicher ab der Position,
      auf die feld zeigt (ohne Größenkontrolle des Feldes).}
PROCEDURE Empfange(anzahl: Word; var zeichenkette: string);
  { Wenn noch anzahl gesendete Zeichen zur Verfügung stehen,
      werden anzahl Zeichen empfangen und in zeichenkette abgelegt.
    Sonst wird ein String der Länge 0 in zeichenkette abgelegt. }
PROCEDURE EmpfangeChars(var anzahl: Word; feld: PChar);
  { Wenn noch anzahl gesendete Zeichen zur Verfügung stehen,
      werden anzahl Zeichen ampfangen und ab der Position im Speicher
      abgelegt, auf die feld zeigt (ohne Kontrolle, wie groß das Feld ist).}
PROCEDURE SetzeZurueck;
  { Löscht alle bisher gesendeten Zeichen.
      Kein Empfänger kann danach noch Zeichen empfangen. }  

PROCEDURE Uebertragungsfehler(simulation: boolean);
  { Schaltet die Simulation von Sende- und Empfangsfehlern ein (true)
      oder aus (false). Durch die Simulation wird die Laufzeit merklich erhöht. }
PROCEDURE SetzeFehlerrate(rate: real);
  { Setzt die Fehlerrate für Sendefehler auf rate und
      verhindert die Simulation von Empfangsfehlern. }
PROCEDURE SetzeFehlerraten(srate: real; erate: real);
  { Setzt die Fehlerraten für Sendefehler und für Empfangsfehler }
PROCEDURE SendeFehler(simulation: boolean);
  { Schaltet die Simulation von Sendefehlern ein (true) oder aus (false).} 
PROCEDURE EmpfangsFehler(simulation:boolean);
  { Schaltet die Simulation von Empfangsfehlern ein (true) oder
      aus (false).}


PROCEDURE Warte(centisec: longint);
  { Wartet centisec Hundertstel Sekunden mit der weiteren Programmausführung.
      Die Prozeßkontrolle wird währenddessen immer wieder an Windows zurückgegeben.}

PROCEDURE PCharInString(anzahl: Byte; von: PChar; var zu:string);
  { Kopiert Zeichen aus einem Feld von Chars in einen String.
    Näheres siehe unten. }


IMPLEMENTATION

uses   windos,    { Zeitabfrage }
       wincrt,    { Standard-I/O }
       winprocs,  { Dateioperationen (shared), Meldungsfenster, Taskverwaltung }
       wintypes,  { PChar }
       strings;   { StrPCopy }

const  dateiname   = 'PROTOKOL.DAT'; { für die Sende-/Empfangsdaten } 

       { Durch den exklusiven Zugriff bei Schreiboperationen kann immer
         nur ein Prozeß zu einer Zeit schreiben. Daher müssen andere
         Prozesse, die zur gleichen Zeit Schreibzugriff benötigen,
         eine (nicht immer gleichlange!) Zeit warten und es dann erneut
         versuchen. }
       maxversuche =   5; { Anzahl Versuche bei mißlungenen Dateioperationen      }    
       minwartezeit=  10; { Obere und untere Schranken für Wartezeit vor erneutem }
       maxwartezeit= 200; { Versuch nach mißlungener Dateioperation (öffnen)      } 

       outofmemory = 'Nicht genügend Speicher vorhanden!';
       teststring  : string[4] = 'test'; { siehe Hauptprogramm }

var    lesepos           : Longint; { Momentane Position des Lesezeigers }
       f                 : Integer; { Dateihandle (unter Windows) }
       sendefehlersim,
       empfangsfehlersim : Boolean; { Fehlersimulation durchführen? }
       sendefehlerrate,
       empfangsfehlerrate: Real;    { Fehlerraten setzen }


function DateiExistiert: Boolean;
  { Prüft, ob die Datei für Sende-/Empfangsdaten existiert. }
begin
  f:=_lopen(dateiname,of_Share_Deny_None);
  if (f=-1) then
    DateiExistiert:=false
  else begin
    DateiExistiert:=true;
    _lclose(f)
  end;
end;  { datei_existiert }

procedure Fehler(titel, meldung: string; fatal: boolean);
  { Gibt eine Fehlermeldung in einer MessageBox aus.
    Falls fatal, wird die Programmausführung abgebrochen. }
var buffer: array[1..2,0..255] of char; 
begin
  StrPCopy(@buffer[1],titel);
  StrPCopy(@buffer[2],meldung);
  MessageBox(0,@buffer[2],@buffer[1],mb_IconExclamation or mb_Ok);
  if fatal then halt(100);
end;

procedure verzoegere;
  { Verzögert die Programmausführung nach mißlungenem Dateizugriff }
begin
  Warte(Round(minwartezeit+(maxwartezeit-minwartezeit)*random))
end;

procedure SendeChars; { s. o. }
var fzaehl, zaehl, zaehl2: Word;    
    offset               : Longint; 
    maske                : Byte;    { fuer Bitoperationen bei Fehlersimulation }
begin
  fzaehl:=0; { Zähler für Fehlversuche bei Öffnen der Datei }
  repeat     { Schleife für einzelne Versuche } 
    { Datei öffnen bzw. erzeugen }
    if not DateiExistiert then f:=_lcreat(dateiname,0)
    else f:=_lopen(dateiname,of_Share_Deny_Write or of_Write);
                             { Andere dürfen zugleich höchstens lesen }
    if f=-1 then begin
      inc(fzaehl);                          { Fehlerzähler inkrementieren        }
      if fzaehl<maxversuche then verzoegere { und ggf. warten fuer neuen Versuch }
    end
    else begin
      offset:=_llseek(f,0,2);      { Ans Ende der Datei positionieren }
      if (offset=-1) then
        Fehler('Positionierungsfehler','Die Botschaft wurde nicht gesendet.',false)
      else begin
        if sendefehlersim and (sendefehlerrate>0) then { Fehler simulieren? }
          for zaehl:=0 to anzahl-1 do
          begin
            maske:=1;
            for zaehl2:=1 to 8 do begin { Bitweise Fehler simulieren }
              if random<sendefehlerrate then
                feld[zaehl]:=char((byte(feld[zaehl]) and ($ff-maske))  { Bit löschen }
                      or ((byte(feld[zaehl]) and maske) xor maske));    { Bit kippen  }
              maske:=maske*2;
            end;
          end; { Ende der Fehlersimulation }

        if _lwrite(f,PChar(feld),anzahl)<>anzahl then { feld in die Datei schreiben }
          Fehler('Schreibfehler','Die Botschaft wurde nicht erfolgreich gesendet.',false);
      end; { else }
      _lclose(f)
    end { else } 
  until (f<>-1) or (fzaehl=maxversuche);
  if fzaehl=maxversuche then { Bei Sendefehler erfolgt Programmabbruch.
                               Er kann ja auch durch ReadOnly-Flags erzeugt sein! }
    Fehler('Sendefehler','Die Botschaftendatei konnte nicht geöffnet werden.',true);
  Keypressed;
  Yield;
end;

procedure Sende; { s. o. }
var feld: PChar; { Temporär angelegtes feld (für SendeChars) }
    l   : Word;  { Länge des Feldes }
begin
  l:=Length(zeichenkette)+1; {+1 wegen StrPCopy }
  if MaxAvail< l then 
    Fehler('Speicherfehler',outofmemory,true)
  else begin
    GetMem(feld, l);
    StrPCopy(feld,zeichenkette);
    SendeChars(l-1,feld);
    FreeMem(feld, l)
  end
end;

procedure EmpfangeChars; { s. o. }
var offset,
    groesse: Longint; { Größe der datei }
    zaehl,
    fzaehl,
    zaehl2 : Integer;
    maske  : Byte;    { für Bitoperationen bei Fehlersimulation }

begin
  fzaehl:=0; { Zähler für Fehlversuche bei Öffnen der Datei }
  if anzahl>0 then { Bei anzahl=0 ist nichts zu empfangen. }
    repeat
      f:=_lopen(dateiname,of_Share_Deny_None or of_Read);
                          { Andere dürfen zugleich lesen und schreiben } 
      if (f=-1) then begin
        inc(fzaehl);
        if fzaehl<maxversuche then verzoegere
      end
      else begin
        groesse:=_llseek(f,0,2)+1;
        if lesepos>groesse then { Wurde die Datei zwischendurch zurückgesetzt? }
          lesepos:=0;
        if anzahl+lesepos<groesse then { Sind genügend Zeichen gesendet worden? }
        begin
          offset:=_llseek(f,lesepos,0); { Lesezeiger in der Datei positionieren }
          if offset=-1 then
            Fehler('Positionierungsfehler','Es wurde keine Botschaft empfangen.',false);

          if _lread(f,feld,anzahl)<>anzahl then
            { Darf nicht passieren - groesse kann hoechstens wachsen }
            Fehler('Lesefehler','Unit Protokoll überprüfen!',true);

          if empfangsfehlersim and (empfangsfehlerrate>0) then { Fehler simulieren? }
            for zaehl:=0 to anzahl-1 do
            begin
              maske:=1;
              for zaehl2:=1 to 8 do begin { Bitweise Fehler simulieren }
                if random<empfangsfehlerrate then
                  feld[zaehl]:=char((byte(feld[zaehl]) and ($ff-maske)){ Bit löschen }
                    or ((byte(feld[zaehl]) and maske) xor maske));   { Bit kippen }
                maske:=maske*2;
              end;
            end; { Ende der Fehlersimulation }

          inc(lesepos,anzahl);
        end
        else
          anzahl:=0;
        _lclose(f);
      end;
      Keypressed;
      Yield;
    until (f<>-1) or (fzaehl=maxversuche);
  if fzaehl=maxversuche then
     Fehler('Empfangsfehler','Die Botschaftendatei kann nicht geöffnet werden.',true)
  { Alternative: (wenn kein ProgrAbbruch erfolgen soll)
  if fzaehl=maxversuche then begin
     Fehler('Empfangsfehler','Die Botschaftendatei kann nicht geöffnet werden.',false)
     anzahl:=0
  end }
end;


procedure PCharInString(anzahl: Byte; von: PChar; var zu:string);
  { Wegen der Längenbegrenzung von Strings auf 255 Zeichen unter allen
    mir bekannten PASCAL-Compilern, werden Bytefelder verwendet, auf die mit 
    einem Zeiger vom Typ PChar zugegriffen wird (Längenbegrenzung:
    65535 Zeichen). Da aber auch Zeichenketten, die #0 enthalten, verarbeitet
    werden sollen, darf die Routine StrPas aus der Unit Strings (Borland
    Turbo Pascal für Windows 1.5) nicht verwendet werden sondern muß´
    durch eine eigene Routine PCharInString ersetzt werden. Dafür muß kontrolliert
    werden, ob bei der jeweils verwendeten Compiler-Version Strings noch
    immer den bisher üblichen Aufbau haben (Pos. 0 enthält die Länge,
    Pos. 1 - Pos. Length(..) die Zeichenkette. Dieses geschieht im
    Hauptprogramm der Unit. }
begin
  zu[0]:=char(anzahl);
  while (anzahl>0) do begin
    zu[anzahl]:=von[anzahl-1];
    dec(anzahl);
  end
end;

procedure Empfange; {s. o.}
var feld: PChar; { Temporär angelegtes Feld für EmpfangeChars }
    l   : Word;  { Größe des Feldes }
begin
  if MaxAvail< anzahl then
    Fehler('Speicherfehler',outofmemory,true)
  else if anzahl>0 then 
    begin
      GetMem(feld, anzahl);
      EmpfangeChars(anzahl,feld);
      PCharInString(anzahl,feld,zeichenkette);
      FreeMem(feld, anzahl)
    end
    else zeichenkette:='';
end;

procedure SetzeZurueck; {s. o.}
begin 
  f:=_lcreat(dateiname,0);
  _lclose(f);
end;


procedure ZeigeAn;
  { Zeigt die Datei als Hexdump auf die Standardausgabe aus.
    NUR FUER TESTZWECKE !!! 
    Ggf. Prozedurkopf in den INTERFACE-Teil aufnehmen! }
var i, k   : Integer;
    c      : array [1..9] of byte;
    groesse: Longint;
begin
  Write('Anzeige der Datei ',dateiname);
  f:=_lopen(dateiname,of_Share_Deny_None or of_Read);
  if (f=-1) then
    Writeln('--- Datei konnte nicht geöffnet werden!')
  else begin   
    groesse:=_llseek(f,0,2)+1;
    Writeln(' (',groesse,' Bytes):');
    _llseek(f,0,0);
    for i:=0 to (groesse-1) div 8 do begin
      k:=_lread(f,PChar(@c),8);
      for k:=1 to 8 do
        if (i*8+k<groesse) then
          write(' ',c[k]:3)
        else write('    ');
      write(' : ');
      for k:=1 to 8 do
        if (i*8+k<groesse) then
          if IsCharAlphaNumeric(char(c[k])) then
            write(char(c[k]):1)
          else write('.');
      writeln;
    end;
  end;
end;

 procedure SendeFehler; {s. o.}
 begin
   sendefehlersim:=simulation
 end;

 procedure EmpfangsFehler; {s. o.}
 begin
   empfangsfehlersim:=simulation
 end;

 procedure Uebertragungsfehler; {s. o.}
 begin
   SendeFehler(simulation);
   EmpfangsFehler(simulation);
 end;

 procedure SetzeFehlerraten; {s. o.}
 begin
   sendefehlerrate:=srate;
   empfangsfehlerrate:=erate;
   if (erate*srate<>0.0) then
     if ((srate<=1.526e-5) or (erate<1.526e-5)) then
       Fehler('Warnung','Fehlerraten zwischen 0 und 0.00001526 sind nicht möglich.',false)
       { Na ja, genauer gesagt: Die kleinstmögliche Fehlerrate liegt bei 2^(-16) }
     else
     if (srate<1e-4) or (erate<1e-4) then
       Fehler('Warnung','Bei Fehlerraten unter 0.0001 wird die Simulation sehr ungenau!',false);
 end;

 procedure SetzeFehlerrate; {s. o.}
 begin
   sendefehlerrate:=rate;
   empfangsfehlerrate:=0;
 end;

 procedure Warte; {s. o.}
 var t1, t2          : longint;
     std,min,sec,s100: word;
 begin
   gettime(std,min,sec,s100);
   t1:=s100+100*(sec+60*(min+60*std));
   repeat
     gettime(std,min,sec,s100);
     t2:=s100+100*(sec+60*(min+60*std));
     KeyPressed;
     { Keypressed wird bei Verwendung von Yield und WinCRT benötigt,
       damit Programmabbrüche möglich sind. }
     Yield;
   until (t2-t1>=centisec) or (t2<t1) { falls ein neuer Tag anbricht... }
 end;

begin { Hauptprogramm zur Initialisierung der Unit-Variablen }
  if (Length(teststring)<>ord(teststring[0])) or (SizeOf(teststring)<>Length(teststring)+1) then
    { Vgl. Kommentar bei Procedure PCharInString }
    Fehler('Fataler Fehler','Der Compiler verarbeitet Strings nicht wie vorgesehen!',true);   
  lesepos           :=0;
  sendefehlersim    :=false;
  empfangsfehlersim :=false;
  sendefehlerrate   :=1E-4;
  empfangsfehlerrate:=0;
  CheckBreak        :=TRUE;
  randomize
end.

