Onvif (SOAP) - Error 503 przy GetStreamUri, GetNetworkInterfaces.

0

Problem bardziej ogólny bo dotyczy bardziej komunikacji soap'owej niż samego Delphi ale aktualnie problem tkwi w Delphi.

Komunikacja z DVR/Kamerami po protokole ONVIF - działa wszystko (GetCapabilities[device], GetDeviceInfo[device], GetVideoSources[media], GetProfiles[media]) za wyjątkiem dwóch akcji: GetStreamUri i GetNetworkInterfaces, które zwracają
"HTTP/1.0 503 Service Unavailable"

Nie jest to problem samego DVR gdyż program OnvifManager łączy się z tym samym DVR i poprawnie wysyła tę kopertę otrzymując prawidłową odpowiedź.
Porównałem obie koperty (z obu programów) i są takie same:

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" s:mustUnderstand="1">
      <UsernameToken>
        <Username>Admin</Username>
        <Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">NUY2RkRFNTFEMDY5ODU1NDU0MEVGNTY1RDNGM0FEQzQwMkYxQzY0NQ==</Password>
        <Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">bmV4dDEyMw==</Nonce>
        <Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2016-05-19T06:43:42.689Z</Created>
      </UsernameToken>
    </Security>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <GetStreamUri xmlns="http://www.onvif.org/ver10/media/wsdl">
      <StreamSetup>
        <Stream xmlns="http://www.onvif.org/ver10/schema">RTP-Unicast</Stream>
        <Transport xmlns="http://www.onvif.org/ver10/schema">
          <Protocol>HTTP</Protocol>
        </Transport>
      </StreamSetup>
      <ProfileToken>Profile1</ProfileToken>
    </GetStreamUri>
  </s:Body>
</s:Envelope>

Natomiast w moim programie ta akcja zwraca powyższy błąd.

Sposób wysyłki kopert jest taki sam dla każdej z akcji:

  try
    oMsgStr := TStringStream.Create(FMsgContent);
    try
      oMsgStr.Position := 0;
      FMsgResponse := FIdHTTP.Post(sAddr, oMsgStr);

      prProcessResponse(FMsgResponse);

      Result := True;
    finally
      oMsgStr.Free;
    end;
  except
    on eExc: Exception do begin
      if (Assigned(ErrorLog)) then
        ErrorLog.Add(eExc.Message);
    end;
  end;

Adres dla serwisu media oczywiście pobrany z GetCapabilities ale jest taki sam jak główny adres.
http://ADRES_IP:83/onvif/device_service

Reasumując... skąd stały problem z tymi dwoma ramkami? I jak sobie z nim poradzić?

0

Jak Indy to wołam @kAzek

0

Aktualnie podejrzenia padają na sposób generowania Nonce i PasswordDigest.

var
  dTime: TDateTime;
  sCreated, sNonce, sPassword: String;
  ii: byte;
....
    sNonce := '';
    for ii := 0 to 19 do
      sNonce := sNonce + Chr(Random(255));
                                       
    dTime := NowUTC();
    sCreated := FormatDateTime('yyyy-mm-dd',dTime) + 'T' + FormatDateTime('hh:nn:ss.zzz', dTime) + 'Z';
    //'2016-05-17T09:02:01.003Z';
    sPassword := '1234';
    sPassword := StrToBase64(HashSHA1(sNonce+sCreated+sPassword));
0

Śledziłeś czy może jest jakiś Redirect? bo na oko wszystko wygląda ok sprawdź w OnRedirect czy czasem nie ma jakiegoś dziwnego przekierowania.

0

Problem jest na 100% w procedurze wyliczającej PasswordDigest. Podłączyłem kamerę innego producenta (AXIS) i ta zwraca powyższy błąd dla każdej ramki. Wygląda na to że DynaColor pozwala na komunikację bez autoryzacji w niektórych ramkach.
Zrobiłem procedurę wyliczającą w C# jako DLL dla Delphi z następującym kodem:

 
        [DllExport("GenerateDigest")]
        [return: MarshalAs(UnmanagedType.LPStr)]
        public static string GenerateDigest(
            [MarshalAs(UnmanagedType.LPStr)] string APassword, 
            [MarshalAs(UnmanagedType.LPStr)] string ACreated, 
            [MarshalAs(UnmanagedType.LPStr)] out string ANonce64)
        {
            byte[] _nonce = new byte[16];

            RandomNumberGenerator rndGenerator = new RNGCryptoServiceProvider();
            rndGenerator.GetBytes(_nonce);

            byte[] time = Encoding.UTF8.GetBytes(ACreated);
            byte[] pwd = Encoding.UTF8.GetBytes(APassword);

            byte[] operand = new byte[_nonce.Length + time.Length + pwd.Length];
            Array.Copy(_nonce, operand, _nonce.Length);
            Array.Copy(time, 0, operand, _nonce.Length, time.Length);
            Array.Copy(pwd, 0, operand, _nonce.Length + time.Length, pwd.Length);

            SHA1 sha1 = SHA1.Create();

            LastNonceUsed = Convert.ToBase64String(_nonce);
            ANonce64 = LastNonceUsed;

            return Convert.ToBase64String(sha1.ComputeHash(operand));
        }

Metoda zwraca poprawny PasswordDigest.. jest to tymczasowy środek zaradczy żeby klient mógł już zacząć korzystać, teraz tylko w wolnej chwili muszę zamienić to na Delphi.. pierwsze próby spełzły na niczym ;)

0

Nie wiem czy się bawiłeś AxisMediaControl ale ten ich activeX jest na tyle sprytnie napisany, że ja spokojnie korzystam z niego dla wszystkich kamer IP nie tylko axisa. Sprawdzałem na kamerkach axisa, edimaxa, lg oraz urządzeniach firmy Marshall.

2

Dzięki. Na przyszłość cenna uwaga.. ale już komponenty sam napisałem zarówno do ONVIF jak i RTSP w pełni funkcjonalne więc nie ma sensu się wracać na początek..

Jest sporo postów na necie z takim samym problemem jak mój ale nikt nie raczył się podzielić rozwiązaniem więc robię to ja dla potomnych. Do sha wykorzystuję paczkę komponentów DCPcrypt.

Metoda:
fnHashSHA1 zwraca hash SHA1.
fnMakeNonce - generuje tzw NONCE do autoryzacji SOAP
fnGenerateDigestNonce - generuje password digest na podstawie hasła, daty utworzenia nonce i samego nonce (w formie zbase'owanej)

oraz dla przypomnienia format daty dla pola CREATED: 2016-05-17T0901.003Z

function fnHashSHA1(AStr: string):string;
var
  oDcp: TDCP_sha1;
  digest:array [0..19] of byte;
  res:string;
  ii: byte;
begin
  oDcp := TDCP_sha1.Create(nil);
  try
    oDcp.Init;
    oDcp.UpdateStr(AStr);
    oDcp.Final(digest);
  finally
    oDcp.Free;
  end;

  res := '';
  for ii := 0 to High(digest) do
    res := res + Char(digest[ii]);
  result := res;
end;

function fnGenerateDigestNonce(APassword: string; ACreated: string; ANonce: String): String;
begin
  Result := StrToBase64(HashSHA1(Base64ToStr(ANonce) + ACreated + APassword));
end;

function fnMakeNonce(): String;
var
  ii: Integer;
begin
  Result := '';
  for ii := 0 to 15 do
    Result := Result + Char(Random(254));

  Result := StrToBase64(Result);
end;

1 użytkowników online, w tym zalogowanych: 0, gości: 1