2-2. DataModule procedure 데이터 전송 전에 전송할 데이터를 암호화하고 전송할 데이터의 길이와 "구분자" 한 자리를 더한 값을 앞에 추가한 후 전송한다.
    // 데이터 전송
    function TdmSIMLaz.UF_SIMLazSnd(ACmd: UInt8; var ACont: TIdBytes): Boolean;
    var
      l_Len: Int64;
      l_Data: TIdBytes;
    begin
      Result := False;
      if not IdTCPConn.Connected then
      begin
        MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
        Exit;
      end;

      GP_SIMLazEnc(ACont);
      l_Data := nil;
      l_Len := 1 + Length(ACont);
      AppendBytes(l_Data, ToBytes(l_Len));
      AppendByte(l_Data, ACmd);
      if l_Len > 1 then AppendBytes(l_Data, ACont);
      IdTCPConn.IOHandler.Write(l_Data);
      Result := True;
    end;
데이터 수신 시에는 먼저 수신할 데이터의 길이를 Int64 값으로 받은 후, 그 길이 만큼 TIdBytes에 수신한다. 수신된 첫 자리는 "구분자"로 떼어내고 나머지 부분을 암호 해독처리 한다. 받은 데이터의 "구분자"가 오류관련 "구분자"이면 오류 메세지를 띄우고 전송한 "구분자"와 수신된 "구분자"가 다를 때도 오류 메세지를 띄운다. 정상적으로 수신이 이루어졌으면 True를 반환한다.
    // 데이터 수신
    function TdmSIMLaz.UF_SIMLazRcv(var ACmd: UInt8; out ACont: TIdBytes): Boolean;
    var
      s: string;
      l_Cmd: UInt8;
      l_Len: Int64;
      l_Data: TIdBytes;
    begin
      Result := False;
      if not IdTCPConn.Connected then
      begin
        MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
        Exit;
      end;

      l_Len := IdTCPConn.IOHandler.ReadInt64(False);
      if l_Len <= 0 then
      begin
        MessageDlg(Application.Title, ERR_SIMLAZ301, mtError, [mbOK], 0);
        Exit;
      end;

      l_Data := nil;
      IdTCPConn.IOHandler.ReadBytes(l_Data, l_Len);

      l_Cmd := l_Data[0];
      SetLength(ACont, l_Len - 1);
      Move(l_Data[1], ACont[0], l_Len - 1);
      GP_SIMLazDec(ACont);

      if l_Cmd = smlc_ERROR then
      begin
        s := BytesToString(ACont, u_Encoding, u_Encoding);
        if s = '' then s := ERR_SIMLAZ999;
        ACmd := l_Cmd;
        MessageDlg(Application.Title, s, mtError, [mbOK], 0);
      end
      else if l_Cmd <> ACmd then
      begin
        ShowMessage(Chr(ACmd) + ':' + Chr(l_Cmd));
        ACmd := l_Cmd;
        MessageDlg(Application.Title, ERR_SIMLAZ002, mtError, [mbOK], 0);
      end
      else Result := True;
    end;
로그인에 사용할 암호는 역함수가 필요없으므로 SHA256를 사용한다. Indy에 포함된 "TIdHashSHA256" 객체를 이용해서 SHA256 값을 구한다.
    // SHA256
    function TdmSIMLaz.UF_SIMLazSHA(const AValue: string): string;
    var
      l_hTmp: TIdHash;
    begin
      Result := '';
      if not IdSSLOpenSSLHeaders.Load then Exit;

      l_hTmp := TIdHashSHA256.Create;
      try
        Result := l_hTmp.HashStringAsHex(AValue);
      finally
        FreeAndNil(l_hTmp);
      end;
    end;
사용자 목록은 현재 접속된 클라이언트의 접속 정보를 탭으로 구분된 문자열 레코드 형태로 서버에서 수신받는다.
    // 사용자 목록
    function TdmSIMLaz.UF_SIMLazList: string;
    var
      l_Cmd: UInt8;
      l_Data: TIdBytes;
      l_Stream: TStringStream;
    begin
      Result := '';
      if not IdTCPConn.Connected then
      begin
        MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
        Exit;
      end;

      u_Lock.Enter;
      try
        l_Data := nil;
        l_Cmd := smlc_USERLIST;
        if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

        l_Data := nil;
        if UF_SIMLazRcv(l_Cmd, l_Data) then
        begin
          l_Stream := TStringStream.Create('');
          try
            WriteTIdBytesToStream(l_Stream, l_Data);
            l_Stream.Position := 0;
            Result := l_Stream.DataString;
          finally
            FreeAndNil(l_Stream);
          end;
        end;
      finally
        u_Lock.Leave;
      end;
    end;
프로그램 ID에 해당되는 최신 버전의 정보를 서버에서 가져와서 사용 중인 파일과 다를 경우 최신 버전의 파일을 가져와 교체한다. 이때 실행 파일뿐만 아니라 각종 이미지나 설정파일, dll 등등을 zip 파일에 넣어서 처리할 수 있다. 버전 유치 저리는 하나의 실행파일에서 할 수도 있겠지만 본 예제에서는 버전 유지용 프로그램과 업무용 클라이언트를 분리해서 관리한다.
    // 버전 정보
    function TdmSIMLaz.UF_SIMLazVer(AID: string): string;
    var
      l_Cmd: UInt8;
      l_Data: TIdBytes;
    begin
      Result := '';
      if not IdTCPConn.Connected then
      begin
        MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
        Exit;
      end;

      u_Lock.Enter;
      try
        l_Data := nil;
        l_Cmd := smlc_VERSION;
        AppendString(l_Data, AID, -1, u_Encoding, u_Encoding);
        if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

        l_Data := nil;
        if UF_SIMLazRcv(l_Cmd, l_Data) then
          if Length(l_Data) > 0 thenResult := BytesToString(l_Data, u_Encoding, u_Encoding);
      finally
        u_Lock.Leave;
      end;
    end;

    // 파일 가져오기
    procedure TdmSIMLaz.UP_SIMLazFile(AID, AFileName: string);
    var
      l_Cmd: UInt8;
      l_Data: TIdBytes;
      l_Stream: TMemoryStream;
    begin
      if not IdTCPConn.Connected then
      begin
        MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
        Exit;
      end;

      u_Lock.Enter;
      l_Stream := TMemoryStream.Create;
      try
        l_Data := nil;
        l_Cmd := smlc_GETFILE;
        AppendString(l_Data, AID, -1, u_Encoding, u_Encoding);
        if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

        l_Data := nil;
        if UF_SIMLazRcv(l_Cmd, l_Data) then
        begin
          WriteTIdBytesToStream(l_Stream, l_Data);
          l_Stream.Position := 0;
          if FileExists(AFileName) then DeleteFile(AFileName);
          l_Stream.SaveToFile(AFileName);
        end;
      finally
        FreeAndNil(l_Stream);
        u_Lock.Leave;
      end;
    end;
로그인시 암호는 SHA256으로 인코딩된 값이어야한다. 로그인에 성공하면 사용자 ID와 사용자명, 관리자 여부를 DataModule의 public 변수에 저장한다.
    // 로그인
    function TdmSIMLaz.UF_SIMLazLogin(AID, APasswd: string): Boolean;
    var
      s: string;
      l_Cmd: UInt8;
      l_Data: TIdBytes;
    begin
      Result := False;
      if not IdTCPConn.Connected then
      begin
        MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
        Exit;
      end;

      u_Lock.Enter;
      try
        l_Data := nil;
        l_Cmd := smlc_LOGIN;
        s := AID + SIMLAZ_EOF + APasswd;
        AppendString(l_Data, s, -1, u_Encoding, u_Encoding);
        if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

        l_Data := nil;
        if UF_SIMLazRcv(l_Cmd, l_Data) then
        begin
          if Length(l_Data) <= 0 then Exit;
          u_uid := AID;
          u_admin_flag := Chr(l_Data[0]) = '1';
          u_uname := BytesToString(l_Data, 2, -1, u_Encoding, u_Encoding);
          Result := True;
        end;
      finally
        u_Lock.Leave;
      end;
    end;
Query 실행 함수에서는 데이터 조작용 SQL 구문을 서버로 보내서 실행시키고 적용된 레코드 수를 반환 받는다.
    // Query 실행
    function TdmSIMLaz.UF_SIMLazExec(AQuery: string): Int32;
    var
      l_Cmd: UInt8;
      l_Data: TIdBytes;
    begin
      Result := 0;
      if not IdTCPConn.Connected then
      begin
        MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
        Exit;
      end;

      u_Lock.Enter;
      try
        l_Data := nil;
        l_Cmd := smlc_EXECQUERY;
        AppendString(l_Data, AQuery, -1, u_Encoding, u_Encoding);
        if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

        l_Data := nil;
        if UF_SIMLazRcv(l_Cmd, l_Data) then Result := StrToInt(BytesToString(l_Data));
      finally
        u_Lock.Leave;
      end;
    end;
데이터 조회 시 서버로부터 받은 XML을 Stream으로 복사한 후 TBufDataset에 적용시킨다. 이를 위해서 uses에 "XMLDatapacketReader"를 추가해야한다. ALoadFields가 True일 때는 참조된 TCustomBufDataset의 Field를 모두 초기화하고 XML에 지정된 Field로 재구성한다. False인 경우에는 Field 구조를 그대로 유지한 채 DataSet의 내용만 갱신한다. 일반적으로 내용이 고정된 프로그램에서는 Field 구조가 유지되어야하지만 하나의 TBufDataset을 이용해서 가변적인 데이터를 조회하는 경우에는 Field 구조를 매번 삭제하고 새로 생성해야한다.
    // 데이터 가져오기
    procedure TdmSIMLaz.UP_SIMLazOpen(ADataSet: TCustomBufDataset;
      AQuery: string; const ALoadFields: Boolean);
    var
      l_Cmd: UInt8;
      l_Data: TIdBytes;
      l_Stream: TMemoryStream;
    begin
      if not IdTCPConn.Connected then
      begin
        MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
        Exit;
      end;

      u_Lock.Enter;
      try
        l_Data := nil;
        l_Cmd := smlc_OPENQUERY;
        AppendString(l_Data, AQuery, -1, u_Encoding, u_Encoding);
        if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

        if UF_SIMLazRcv(l_Cmd, l_Data) then
        begin
          if ADataSet.Active then ADataSet.Close;
          if ALoadFields then ADataSet.Clear;
          l_Stream := TMemoryStream.Create;
          try
            WriteTIdBytesToStream(l_Stream, l_Data);
            l_Stream.Position := 0;
            ADataSet.LoadFromStream(l_Stream, dfXML);
          finally
            l_Stream.Free;
          end;
        end;
      finally
        u_Lock.Leave;
      end;
    end;