{$mode objfpc}
{$H+}
unit macuuid;

Interface

uses SysUtils;

Function CreateMacGUID(Out GUID : TGUID) : Integer;


Implementation

uses unixtype, sockets, baseunix, unix;

Const 
  MAX_ADJUSTMENT = 10;
  IPPROTO_IP     = 0;
//  AF_INET        = 2;
//  SOCK_DGRAM     = 2; 
  IF_NAMESIZE    = 16;
  SIOCGIFCONF    = $8912;
  SIOCGIFHWADDR  = $8927;
  
Type
  {$packrecords c}
  tifr_ifrn = record
    case integer of
      0 : (ifrn_name: array [0..IF_NAMESIZE-1] of char);
  end;
  tifmap = record
    mem_start : culong;
    mem_end   : culong;
    base_addr : cushort;
    irq       : cuchar;
    dma       : cuchar;
    port      : cuchar;
  end;
  PIFrec = ^TIFrec;
  TIFrec = record
    ifr_ifrn : tifr_ifrn;
    case integer of
      0 : (ifru_addr      : TSockAddr);
      1 : (ifru_dstaddr   : TSockAddr);
      2 : (ifru_broadaddr : TSockAddr);
      3 : (ifru_netmask   : TSockAddr);
      4 : (ifru_hwaddr    : TSockAddr);
      5 : (ifru_flags     : cshort);
      6 : (ifru_ivalue    : cint);
      7 : (ifru_mtu       : cint);
      8 : (ifru_map       : tifmap);
      9 : (ifru_slave     : Array[0..IF_NAMESIZE-1] of char);
      10 : (ifru_newname  : Array[0..IF_NAMESIZE-1] of char);
      11 : (ifru_data     : pointer);
  end; 
  TIFConf = record
    ifc_len : cint;
    case integer of
      0 : (ifcu_buf : pointer);
      1 : (ifcu_req : ^tifrec);
  end;

  tuuid = record 
    time_low : cardinal;
    time_mid : Word;
    time_hi_and_version : Word;
    clock_seq : Word;
    node : Array[0..5] of byte;
  end;

Var
  MacAddr      : Packed Array[1..6] of byte = (0,0,0,0,0,0);
  MacAddrTried : Byte = 0 ;
  Last   : TTimeVal = (tv_sec:0;tv_usec:0);
  ClockSeq   : Word = 0;
  AdjustMent : Integer = 0;

Procedure GetRandomBytes(Var Buf; NBytes : Integer);

Var
  I : Integer;
  P : PByte;

begin
  P:=@Buf;
  Randomize;
  For I:=0 to NBytes-1 do
    P[i]:=Random(256);
end;
  
Function GetMacAddr : Boolean;

var
  i,j,n,Sd : Integer;
  buf : Array[0..1023] of byte;
  ifc : TIfConf;
  ifr : TIFRec;
  ifp : PIFRec;
  p   : PChar;
begin
  Result:=MacAddrTried>0;
  If Result then
    Result:=MacAddrTried>1
  else  
    begin
    MacAddrTried:=1;
    sd:=fpSocket(AF_INET,SOCK_DGRAM,IPPROTO_IP);
    if (sd<0) then 
      exit;
    Try
      ifc.ifc_len:=Sizeof(Buf);
      ifc.ifcu_buf:=@buf;
      if fpioctl(sd, SIOCGIFCONF, @ifc)<0 then
        Exit;
      n:= ifc.ifc_len;  
      i:=0;
      While (Not Result) and (I<N) do
        begin
        ifp:=PIFRec(PByte(ifc.ifcu_buf)+i);
        move(ifp^.ifr_ifrn.ifrn_name,ifr.ifr_ifrn.ifrn_name,IF_NAMESIZE);
        if (fpioctl(sd, SIOCGIFHWADDR, @ifr) >= 0) then
          begin
          P:=Pchar(@ifr.ifru_hwaddr.sa_data);
          Result:=(p[0]<>#0) or (p[1]<>#0) or (p[2]<>#0) 
                  or (p[3]<>#0) or (p[4]<>#0) or (p[5]<>#0);
          If Result Then
            begin
            Move(P^,MacAddr,SizeOf(MacAddr));  
            MacAddrTried:=2;
            // DumpMacAddr;
            end;
          end;
        I:=I+sizeof(tifrec);
        end;
    Finally  
      fileClose(sd);
    end;
    end;
end;

  
Function GetClock(Var ClockHigh,ClockLow : Cardinal; Var RetClockSeq : Word) : boolean;

Var
  TV       : TTImeVal;
  ClockReg : QWord;  
  OK       : Boolean; 

begin
  OK:=True;
  Repeat
    FPGetTimeOfDay(@Tv,Nil);
    If (Last.tv_sec=0) and (last.tv_sec=0) then
      begin
      GetRandomBytes(ClockSeq,SizeOf(ClockSeq));
      ClockSeq:=ClockSeq and $1FFF;
      last:=TV;
      Dec(last.tv_sec);
      end;
    if (tv.tv_sec<last.tv_sec) or 
        ((tv.tv_sec=last.tv_sec) and (tv.tv_usec<last.tv_usec)) then
      begin
      ClockSeq:=(ClockSeq+1) and $1FFF;
      Adjustment:=0;
      Last:=Tv;
      end
    else if (tv.tv_sec=last.tv_sec) and (tv.tv_usec=last.tv_usec) then
      begin
      If Adjustment>=MAX_ADJUSTMENT then
        OK:=False
      else  
        inc(AdjustMent);
      end
    else
      begin
      AdjustMent:=0;
      Last:=tv;
      end;
  Until OK;  
  ClockReg:=tv.tv_usec*10+adjustment;
  Inc(ClockReg,tv.tv_sec*10000000);
  Inc(ClockReg,($01B21DD2 shl 32) + $13814000);
  ClockHigh   :=Hi(ClockReg);
  ClockLow    :=Lo(ClockReg);
  RetClockSeq :=ClockSeq;
  Result      :=True;                  
end;

Procedure UUIDPack(Const UU : TUUID; Var GUID : TGUID);

Var
  tmp : Cardinal;
  P   : PByte;
  
begin
  P:=PByte(@GUID);
  
  tmp:=uu.time_low;
  P[3]:=tmp and $FF;
  tmp:=tmp shr 8;
  P[2]:=tmp and $FF;
  tmp:=tmp shr 8;
  P[1]:=tmp and $FF;
  tmp:=tmp shr 8;
  P[0]:=tmp and $FF;
  
  tmp:=uu.time_mid;
  P[5]:=tmp and $FF;
  tmp:=tmp shr 8;
  P[4]:=tmp and $FF;
  
  tmp:=uu.time_hi_and_version;
  P[7]:=tmp and $FF;
  tmp:=tmp shr 8;
  P[6]:=tmp and $FF;
  
  tmp:=uu.clock_seq;
  P[9]:=tmp and $FF;
  tmp:=tmp shr 8;
  P[8]:=tmp and $FF;
  
  Move(uu.node,P[10],6);
end;

Procedure DumpMacAddr;

var
  I : Integer;
begin
  Write('Mac Addr: ');
  For i:=1 to 6 do
    write(hexstr(MacAddr[i],2),':');
end;

Function CreateMacGUID(Out GUID : TGUID) : Integer;

Var
  UU       : TUUId;
  ClockMid : Cardinal;

begin
  Result:=Ord(not GetMacAddr);
  If (Result=0) then
    begin
    // DumpMacAddr;
    // Writeln;
    GetClock(ClockMid,uu.time_low,uu.clock_seq);
    uu.Clock_seq:=uu.Clock_seq or $8000;
    uu.time_mid:=lo(clockMid);
    uu.time_hi_and_version:=hi(ClockMid) or $1000;
    move(MacAddr,uu.node,sizeof(MacAddr));
    UUIDPack(UU,GUID);
    end;
end;

initialization
  OnCreateGUID:=@CreateMacGUID;
end.
