2016-08-25 52 views
0

我試圖對Adobe Analytics進行REST API調用,但我無法使用當前代碼進行連接,但無法找出原因。我知道我到達服務器和報頭格式是否正確,因爲我發現了以下錯誤:使用VBA(PHP中的原始代碼)調用Adobe Analytics REST API

{"error":"Bad Request","error_description":"Unable to validate authentication.","error_uri":null} 

此API明確要求幾個不同的加密組件這是我認爲這個問題是。 (做我的SHA1和Base64編碼的功能看起來是正確的下面?)該請求的頭看起來是這樣的:

X-WSSE: UsernameToken Username="will.smith:Google", PasswordDigest="QOmCMlIR4mVPTaiqmuSzM5eKZn0=", Nonce="MTRlYjY2YTM3NmNjMTVlZDk0NDkzZWFj", Created="2016-08-24T23:51:08Z" 

的一些注意事項你閱讀的代碼之前:

  • Adob​​e建議使用MD5(RAND() )來生成Nonce變量,但是我找不到適用於VBA的良好MD5庫。我選擇了根據我讀過的一些文檔生成我自己的隨機32位字母數字字符串。
  • 我檢查過我的用戶名,密碼和端點都是正確的幾次,所以我相當確定這個問題是在SHA1或Base64轉換。

在PHP他們的示例代碼是這樣的:

include_once("SimpleRestClient.class.php"); 

$username = '%%YOUR-USERNAME%%'; 
$secret = '%%YOUR-SECRET%%'; 
$nonce = md5(uniqid(php_uname('n'), true)); 
$nonce_ts = date('c'); 

$digest = base64_encode(sha1($nonce.$nonce_ts.$secret)); 

$server = "https://api.omniture.com"; 
$path = "/admin/1.3/rest/"; 

$rc=new SimpleRestClient(); 
$rc->setOption(CURLOPT_HTTPHEADER, array("X-WSSE: UsernameToken  Username=\"$username\", PasswordDigest=\"$digest\", Nonce=\"$nonce\", Created=\"$nonce_ts\"")); 

$query="?method=Company.GetTokenUsage"; 

$rc->getWebRequest($server.$path.$query); 

if ($rc->getStatusCode()==200) { 
    $response=$rc->getWebResponse(); 
    var_dump($response); 
} else { 
    echo "something went wrong\n"; 
    var_dump($rc->getInfo()); 
} 

這是我的解釋VBA:

Sub GetPromoData() 
    Dim objHTTP As New WinHttp.WinHttpRequest 
    Dim Send As String 

    Dim Username As String 
    Dim Secret As String 
    Dim EndPoint As String 

    Dim Time As String 
    Dim nonce As String 
    Dim Timestamp As String 
    Dim digest As String 
    Dim Header As String 

    Time = DateAdd("h", 7, Now()) 
    'Time = Now() 
    Username = "Redacted" 
    Secret = "Redacted" 

    'Randomize 
    Timestamp = generateTimestamp(Time) 
    nonce = generateNonce() 
    digest = generateDigest(nonce & Timestamp & Secret) 

    Debug.Print Timestamp 
    Debug.Print nonce 
    Debug.Print digest 


    Header = "UsernameToken Username=""" & Username & """, PasswordDigest=""" & digest & """, Nonce=""" & nonce & """, Created=""" & Timestamp & """" 

    Debug.Print Header 

    Send = Worksheets("Promo Code Data").Range("A1").Value 

    URL = "https://api.omniture.com/admin/1.4/rest/?method=Report.Queue" 
    objHTTP.Open "POST", URL, False 
    objHTTP.SetRequestHeader "X-WSSE", Header 
    objHTTP.SetRequestHeader "Content-Type", "application/x-www-form-urlencoded" 
    objHTTP.Send (Send) 
    Debug.Print objHTTP.Status 
    Debug.Print objHTTP.ResponseText 

End Sub 

Public Function generateTimestamp(Timestamp As String) 

'Debug.Print Application.WorksheetFunction.Text(TimeStamp, "yyyy-MM-ddTHH:mm:ssZ"); 
generateTimestamp = Application.WorksheetFunction.Text(Timestamp, "yyyy-MM-ddTHH:mm:ssZ") 

End Function 

Public Function generateNonce() 

Dim nonce As String 

Dim alphaNumeric As Variant 
alphaNumeric = Array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z") 

Randomize 

For I = 1 To 32 
    nonce = nonce & alphaNumeric(61 * Rnd) 
Next 

generateNonce = nonce 

End Function 


Public Function generateDigest(Values As String) 

'Debug.Print SHA1Base64(Values) 
generateDigest = SHA1Base64(Values) 

End Function 

Public Function SHA1Base64(ByVal sTextToHash As String) 

    Dim asc As Object, enc As Object 
    Dim TextToHash() As Byte 
    Set asc = CreateObject("System.Text.UTF8Encoding") 
    Set enc = CreateObject("System.Security.Cryptography.SHA1CryptoServiceProvider") 
    TextToHash = asc.Getbytes_4(sTextToHash) 
    Dim bytes() As Byte 
    bytes = enc.ComputeHash_2((TextToHash)) 
    SHA1Base64 = EncodeBase64(bytes) 
    Set asc = Nothing 
    Set enc = Nothing 

End Function 

Private Function EncodeBase64(ByRef arrData() As Byte) As String 

    Dim objXML As MSXML2.DOMDocument 
    Dim objNode As MSXML2.IXMLDOMElement 

    Set objXML = New MSXML2.DOMDocument 

    ' byte array to base64 
    Set objNode = objXML.createElement("b64") 
    objNode.DataType = "bin.base64" 
    objNode.nodeTypedValue = arrData 
    EncodeBase64 = objNode.Text 

    Set objNode = Nothing 
    Set objXML = Nothing 

End Function 

增加實際的HTTP請求,淨度:

{ 
    ""reportDescription"":{ 
    ""reportSuiteID"":""Redacted"", 
    ""date"":""2016-8-23"", 
    ""metrics"":[ 
     { 
      ""id"":""Orders"" 
     } 
    ], 
    ""sortBy"":""Orders"", 
    ""elements"":[ 
     { 
      ""id"":""evar4"", 
      ""top"":""10"", 
      ""startingWith"":""1"" 
     } 
    ] 
    } 
} 
+0

您可以張貼實際HTTPS請求到Adobe(與你的creds屏蔽) –

+0

還你檢查,以確保您的憑證,一般有效嗎?您可以訪問https://marketing.adobe.com/developer/api-explorer來測試API功能 –

+0

我將HTTP請求添加到了bost的底部 - 憑據絕對有效。我在你連接的API瀏覽器中測試了這個確切的請求,它工作得很好。 – Fubudis

回答

1

我解決了這個問題。我發現的SHA1和Base64編碼器不準確。 Send變量必須使用正確的有效負載進行更新,並且URL變量也需要使用正確的方法進行更新。

這裏有一個完整版的工作代碼:

Sub CallAPI() 
Dim objHTTP As New WinHttp.WinHttpRequest 

Dim Send As String 

Dim Username As String 
Dim Secret As String 
Dim EndPoint As String 

Dim Time As String 
Dim Nonce As String 
Dim Timestamp As String 
Dim digest As String 
Dim Header As String 

Time = DateAdd("h", 7, Now()) 
'Time = Now() 
Username = "USERNAME HERE" 
Secret = "SECRETHERE" 

Timestamp = generateTimestamp(Time) 
Nonce = generateNonce() 
digest = generateDigest(Nonce, Timestamp, Secret) 

Debug.Print Timestamp 
Debug.Print Nonce 
Debug.Print digest 


Header = "UsernameToken Username=""" & Username & """, PasswordDigest=""" & digest & """, Nonce=""" & Nonce & """, Created=""" & Timestamp & """" 

Debug.Print Header 

Send = Worksheets("Promo Code Data").Range("A1").Value 

URL = "https://api.omniture.com/admin/1.4/rest/?method=Report.Queue" 
objHTTP.Open "POST", URL, False 
objHTTP.SetRequestHeader "X-WSSE", Header 
objHTTP.SetRequestHeader "Content-Type", "application/x-www-form-urlencoded" 
objHTTP.Send (Send) 
Debug.Print objHTTP.Status 
Debug.Print objHTTP.ResponseText 

End Sub 

Public Function generateTimestamp(Timestamp As String) 

'Debug.Print Application.WorksheetFunction.Text(TimeStamp, "yyyy-MM-ddTHH:mm:ssZ"); 
generateTimestamp = Application.WorksheetFunction.Text(Timestamp, "yyyy-MM-ddTHH:mm:ssZ") 

End Function 

Public Function generateNonce() 

Dim Nonce As String 


Dim alphaNumeric As Variant 
alphaNumeric = Array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z") 

Randomize 

For i = 1 To 32 
    Nonce = Nonce & alphaNumeric(61 * Rnd) 
Next 

generateNonce = Nonce 

End Function 


Public Function generateDigest(Nonce, Timestamp, Secret) 

generateDigest = Base64EncodeString(SHA1HASH(Nonce & Timestamp & Secret)) 

End Function 


' Based on: http://vb.wikia.com/wiki/SHA-1.bas 
Option Explicit 

Private Type FourBytes 
    a As Byte 
    b As Byte 
    c As Byte 
    d As Byte 
End Type 
Private Type OneLong 
    L As Long 
End Type 

Function HexDefaultSHA1(message() As Byte) As String 
Dim H1 As Long, H2 As Long, H3 As Long, H4 As Long, H5 As Long 
DefaultSHA1 message, H1, H2, H3, H4, H5 
HexDefaultSHA1 = DecToHex5(H1, H2, H3, H4, H5) 
End Function 

Function HexSHA1(message() As Byte, ByVal Key1 As Long, ByVal Key2 As Long, ByVal Key3 As Long, ByVal Key4 As Long) As String 
Dim H1 As Long, H2 As Long, H3 As Long, H4 As Long, H5 As Long 
xSHA1 message, Key1, Key2, Key3, Key4, H1, H2, H3, H4, H5 
HexSHA1 = DecToHex5(H1, H2, H3, H4, H5) 
End Function 

Sub DefaultSHA1(message() As Byte, H1 As Long, H2 As Long, H3 As Long, H4 As Long, H5 As Long) 
xSHA1 message, &H5A827999, &H6ED9EBA1, &H8F1BBCDC, &HCA62C1D6, H1, H2, H3, H4, H5 
End Sub 

Sub xSHA1(message() As Byte, ByVal Key1 As Long, ByVal Key2 As Long, ByVal Key3 As Long, ByVal Key4 As Long, H1 As Long, H2 As Long, H3 As Long, H4 As Long, H5 As Long) 
'CA62C1D68F1BBCDC6ED9EBA15A827999 + "abc" = "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D" 
'"abc" = "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D" 

Dim U As Long, P As Long 
Dim FB As FourBytes, OL As OneLong 
Dim i As Integer 
Dim w(80) As Long 
Dim a As Long, b As Long, c As Long, d As Long, e As Long 
Dim t As Long 

H1 = &H67452301: H2 = &HEFCDAB89: H3 = &H98BADCFE: H4 = &H10325476: H5 = &HC3D2E1F0 

U = UBound(message) + 1: OL.L = U32ShiftLeft3(U): a = U \ &H20000000: LSet FB = OL 'U32ShiftRight29(U) 

ReDim Preserve message(0 To (U + 8 And -64) + 63) 
message(U) = 128 

U = UBound(message) 
message(U - 4) = a 
message(U - 3) = FB.d 
message(U - 2) = FB.c 
message(U - 1) = FB.b 
message(U) = FB.a 

While P < U 
    For i = 0 To 15 
     FB.d = message(P) 
     FB.c = message(P + 1) 
     FB.b = message(P + 2) 
     FB.a = message(P + 3) 
     LSet OL = FB 
     w(i) = OL.L 
     P = P + 4 
    Next i 

    For i = 16 To 79 
     w(i) = U32RotateLeft1(w(i - 3) Xor w(i - 8) Xor w(i - 14) Xor w(i - 16)) 
    Next i 

    a = H1: b = H2: c = H3: d = H4: e = H5 

    For i = 0 To 19 
     t = U32Add(U32Add(U32Add(U32Add(U32RotateLeft5(a), e), w(i)), Key1), ((b And c) Or ((Not b) And d))) 
     e = d: d = c: c = U32RotateLeft30(b): b = a: a = t 
    Next i 
    For i = 20 To 39 
     t = U32Add(U32Add(U32Add(U32Add(U32RotateLeft5(a), e), w(i)), Key2), (b Xor c Xor d)) 
     e = d: d = c: c = U32RotateLeft30(b): b = a: a = t 
    Next i 
    For i = 40 To 59 
     t = U32Add(U32Add(U32Add(U32Add(U32RotateLeft5(a), e), w(i)), Key3), ((b And c) Or (b And d) Or (c And d))) 
     e = d: d = c: c = U32RotateLeft30(b): b = a: a = t 
    Next i 
    For i = 60 To 79 
     t = U32Add(U32Add(U32Add(U32Add(U32RotateLeft5(a), e), w(i)), Key4), (b Xor c Xor d)) 
     e = d: d = c: c = U32RotateLeft30(b): b = a: a = t 
    Next i 

    H1 = U32Add(H1, a): H2 = U32Add(H2, b): H3 = U32Add(H3, c): H4 = U32Add(H4, d): H5 = U32Add(H5, e) 
Wend 
End Sub 

Function U32Add(ByVal a As Long, ByVal b As Long) As Long 
If (a Xor b) < 0 Then 
    U32Add = a + b 
Else 
    U32Add = (a Xor &H80000000) + b Xor &H80000000 
End If 
End Function 

Function U32ShiftLeft3(ByVal a As Long) As Long 
U32ShiftLeft3 = (a And &HFFFFFFF) * 8 
If a And &H10000000 Then U32ShiftLeft3 = U32ShiftLeft3 Or &H80000000 
End Function 

Function U32ShiftRight29(ByVal a As Long) As Long 
U32ShiftRight29 = (a And &HE0000000) \ &H20000000 And 7 
End Function 

Function U32RotateLeft1(ByVal a As Long) As Long 
U32RotateLeft1 = (a And &H3FFFFFFF) * 2 
If a And &H40000000 Then U32RotateLeft1 = U32RotateLeft1 Or &H80000000 
If a And &H80000000 Then U32RotateLeft1 = U32RotateLeft1 Or 1 
End Function 
Function U32RotateLeft5(ByVal a As Long) As Long 
U32RotateLeft5 = (a And &H3FFFFFF) * 32 Or (a And &HF8000000) \ &H8000000 And 31 
If a And &H4000000 Then U32RotateLeft5 = U32RotateLeft5 Or &H80000000 
End Function 
Function U32RotateLeft30(ByVal a As Long) As Long 
U32RotateLeft30 = (a And 1) * &H40000000 Or (a And &HFFFC) \ 4 And &H3FFFFFFF 
If a And 2 Then U32RotateLeft30 = U32RotateLeft30 Or &H80000000 
End Function 

Function DecToHex5(ByVal H1 As Long, ByVal H2 As Long, ByVal H3 As Long, ByVal H4 As Long, ByVal H5 As Long) As String 
Dim H As String, L As Long 
DecToHex5 = "00000000 00000000 00000000 00000000 00000000" 
H = Hex(H1): L = Len(H): Mid(DecToHex5, 9 - L, L) = H 
H = Hex(H2): L = Len(H): Mid(DecToHex5, 18 - L, L) = H 
H = Hex(H3): L = Len(H): Mid(DecToHex5, 27 - L, L) = H 
H = Hex(H4): L = Len(H): Mid(DecToHex5, 36 - L, L) = H 
H = Hex(H5): L = Len(H): Mid(DecToHex5, 45 - L, L) = H 
End Function 

' Convert the string into bytes so we can use the above functions 
' From Chris Hulbert: http://splinter.com.au/blog 

Public Function SHA1HASH(str) 
    Dim i As Integer 
    Dim arr() As Byte 
    ReDim arr(0 To Len(str) - 1) As Byte 
    For i = 0 To Len(str) - 1 
    arr(i) = asc(Mid(str, i + 1, 1)) 
    Next i 
    SHA1HASH = Replace(LCase(HexDefaultSHA1(arr)), " ", "") 
End Function 


' A Base64 Encoder/Decoder. 
' 
' This module is used to encode and decode data in Base64 format as described in RFC 1521. 
' 
' Home page: www.source-code.biz. 
' License: GNU/LGPL (www.gnu.org/licenses/lgpl.html). 
' Copyright 2007: Christian d'Heureuse, Inventec Informatik AG, Switzerland. 
' This module is provided "as is" without warranty of any kind. 

Option Explicit 

Private InitDone As Boolean 
Private Map1(0 To 63) As Byte 
Private Map2(0 To 127) As Byte 

' Encodes a string into Base64 format. 
' No blanks or line breaks are inserted. 
' Parameters: 
' S   a String to be encoded. 
' Returns: a String with the Base64 encoded data. 
Public Function Base64EncodeString(ByVal s As String) As String 
    Base64EncodeString = Base64Encode(ConvertStringToBytes(s)) 
    End Function 

' Encodes a byte array into Base64 format. 
' No blanks or line breaks are inserted. 
' Parameters: 
' InData an array containing the data bytes to be encoded. 
' Returns: a string with the Base64 encoded data. 
Public Function Base64Encode(InData() As Byte) 
    Base64Encode = Base64Encode2(InData, UBound(InData) - LBound(InData) + 1) 
    End Function 

' Encodes a byte array into Base64 format. 
' No blanks or line breaks are inserted. 
' Parameters: 
' InData an array containing the data bytes to be encoded. 
' InLen  number of bytes to process in InData. 
' Returns: a string with the Base64 encoded data. 
Public Function Base64Encode2(InData() As Byte, ByVal InLen As Long) As String 
    If Not InitDone Then Init 
    If InLen = 0 Then Base64Encode2 = "": Exit Function 
    Dim ODataLen As Long: ODataLen = (InLen * 4 + 2) \ 3  ' output length without padding 
    Dim OLen As Long: OLen = ((InLen + 2) \ 3) * 4   ' output length including padding 
    Dim Out() As Byte 
    ReDim Out(0 To OLen - 1) As Byte 
    Dim ip0 As Long: ip0 = LBound(InData) 
    Dim ip As Long 
    Dim op As Long 
    Do While ip < InLen 
     Dim i0 As Byte: i0 = InData(ip0 + ip): ip = ip + 1 
     Dim i1 As Byte: If ip < InLen Then i1 = InData(ip0 + ip): ip = ip + 1 Else i1 = 0 
     Dim i2 As Byte: If ip < InLen Then i2 = InData(ip0 + ip): ip = ip + 1 Else i2 = 0 
     Dim o0 As Byte: o0 = i0 \ 4 
     Dim o1 As Byte: o1 = ((i0 And 3) * &H10) Or (i1 \ &H10) 
     Dim o2 As Byte: o2 = ((i1 And &HF) * 4) Or (i2 \ &H40) 
     Dim o3 As Byte: o3 = i2 And &H3F 
     Out(op) = Map1(o0): op = op + 1 
     Out(op) = Map1(o1): op = op + 1 
     Out(op) = IIf(op < ODataLen, Map1(o2), asc("=")): op = op + 1 
     Out(op) = IIf(op < ODataLen, Map1(o3), asc("=")): op = op + 1 
     Loop 
    Base64Encode2 = ConvertBytesToString(Out) 
    End Function 

' Decodes a string from Base64 format. 
' Parameters: 
' s  a Base64 String to be decoded. 
' Returns  a String containing the decoded data. 
Public Function Base64DecodeString(ByVal s As String) As String 
    If s = "" Then Base64DecodeString = "": Exit Function 
    Base64DecodeString = ConvertBytesToString(Base64Decode(s)) 
    End Function 

' Decodes a byte array from Base64 format. 
' Parameters 
' s   a Base64 String to be decoded. 
' Returns: an array containing the decoded data bytes. 
Public Function Base64Decode(ByVal s As String) As Byte() 
    If Not InitDone Then Init 
    Dim IBuf() As Byte: IBuf = ConvertStringToBytes(s) 
    Dim ILen As Long: ILen = UBound(IBuf) + 1 
    If ILen Mod 4 <> 0 Then Err.Raise vbObjectError, , "Length of Base64 encoded input string is not a multiple of 4." 
    Do While ILen > 0 
     If IBuf(ILen - 1) <> asc("=") Then Exit Do 
     ILen = ILen - 1 
     Loop 
    Dim OLen As Long: OLen = (ILen * 3) \ 4 
    Dim Out() As Byte 
    ReDim Out(0 To OLen - 1) As Byte 
    Dim ip As Long 
    Dim op As Long 
    Do While ip < ILen 
     Dim i0 As Byte: i0 = IBuf(ip): ip = ip + 1 
     Dim i1 As Byte: i1 = IBuf(ip): ip = ip + 1 
     Dim i2 As Byte: If ip < ILen Then i2 = IBuf(ip): ip = ip + 1 Else i2 = asc("A") 
     Dim i3 As Byte: If ip < ILen Then i3 = IBuf(ip): ip = ip + 1 Else i3 = asc("A") 
     If i0 > 127 Or i1 > 127 Or i2 > 127 Or i3 > 127 Then _ 
     Err.Raise vbObjectError, , "Illegal character in Base64 encoded data." 
     Dim b0 As Byte: b0 = Map2(i0) 
     Dim b1 As Byte: b1 = Map2(i1) 
     Dim b2 As Byte: b2 = Map2(i2) 
     Dim b3 As Byte: b3 = Map2(i3) 
     If b0 > 63 Or b1 > 63 Or b2 > 63 Or b3 > 63 Then _ 
     Err.Raise vbObjectError, , "Illegal character in Base64 encoded data." 
     Dim o0 As Byte: o0 = (b0 * 4) Or (b1 \ &H10) 
     Dim o1 As Byte: o1 = ((b1 And &HF) * &H10) Or (b2 \ 4) 
     Dim o2 As Byte: o2 = ((b2 And 3) * &H40) Or b3 
     Out(op) = o0: op = op + 1 
     If op < OLen Then Out(op) = o1: op = op + 1 
     If op < OLen Then Out(op) = o2: op = op + 1 
     Loop 
    Base64Decode = Out 
    End Function 

Private Sub Init() 
    Dim c As Integer, i As Integer 
    ' set Map1 
    i = 0 
    For c = asc("A") To asc("Z"): Map1(i) = c: i = i + 1: Next 
    For c = asc("a") To asc("z"): Map1(i) = c: i = i + 1: Next 
    For c = asc("0") To asc("9"): Map1(i) = c: i = i + 1: Next 
    Map1(i) = asc("+"): i = i + 1 
    Map1(i) = asc("/"): i = i + 1 
    ' set Map2 
    For i = 0 To 127: Map2(i) = 255: Next 
    For i = 0 To 63: Map2(Map1(i)) = i: Next 
    InitDone = True 
    End Sub 

Private Function ConvertStringToBytes(ByVal s As String) As Byte() 
    Dim b1() As Byte: b1 = s 
    Dim L As Long: L = (UBound(b1) + 1) \ 2 
    If L = 0 Then ConvertStringToBytes = b1: Exit Function 
    Dim b2() As Byte 
    ReDim b2(0 To L - 1) As Byte 
    Dim P As Long 
    For P = 0 To L - 1 
     Dim c As Long: c = b1(2 * P) + 256 * CLng(b1(2 * P + 1)) 
     If c >= 256 Then c = asc("?") 
     b2(P) = c 
     Next 
    ConvertStringToBytes = b2 
    End Function 

Private Function ConvertBytesToString(b() As Byte) As String 
    Dim L As Long: L = UBound(b) - LBound(b) + 1 
    Dim b2() As Byte 
    ReDim b2(0 To (2 * L) - 1) As Byte 
    Dim p0 As Long: p0 = LBound(b) 
    Dim P As Long 
    For P = 0 To L - 1: b2(2 * P) = b(p0 + P): Next 
    Dim s As String: s = b2 
    ConvertBytesToString = s 
    End Function