c++amazon-lexaws-sdk-cpp

Using AWS Lex v2 streaming with C++: Signature does not match


I am trying to make the C++ application on my PC talk to AWS Lex v2 using streaming APIs. But there is absolutely no sample code anywhere, forcing me to guess the flow all the way. I managed to stitch something together, but is not working. I get the following error:

In Request handler: CRC Mismatch. prelude_crc was 0x0865223A22, but computed 0x08A98FE9C5 In Response handler: The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.

There is nothing wrong with the secret access key since I am able call the same bot from AWS CLI from the same PC, using aws lexv2-runtime recognize-text command.

Is there a need to seed the streaming with a signature first?

The AWS log says the following:

[ERROR] 2021 - 08 - 19 18:08 : 19.427 AWSClient[24956] HTTP response code : -1
Resolved remote host IP address :
Request ID :
Exception name :
Error message : Encountered network error when sending http request
0 response headers :
[WARN] 2021 - 08 - 19 18:08 : 19.427 AWSClient[24956] If the signature check failed.This could be because of a time skew.Attempting to adjust the signer.
[WARN] 2021 - 08 - 19 18:08 : 19.427 AWSClient[24956] Request failed, now waiting 50 ms before attempting again.
[WARN] 2021 - 08 - 19 18:08 : 19.489 WinHttpSyncHttpClient[24956] Failed setting TCP keep - alive interval with error code : 12018
[WARN] 2021 - 08 - 19 18:08 : 19.717 AWSErrorMarshaller[24956] Encountered AWSError 'InvalidSignatureException' : The request signature we calculated does not match the signature you provided.Check your AWS Secret Access Key and signing method.Consult the service documentation for details.
[ERROR] 2021 - 08 - 19 18:08 : 19.717 AWSClient[24956] HTTP response code : 403

My entire test code is as follows, what am I missing? Since I can confirm key and secret are fine, how to ensure it is signed correctly? The BOT region is us-east-1, but I am making the request from another region. Does that matter?

#include <aws/core/Aws.h>
#include <aws/core/client/AsyncCallerContext.h>
#include <aws/core/utils/Outcome.h>
#include <aws/core/auth/AWSCredentialsProvider.h>
#include <aws/lexv2-runtime/LexRuntimeV2Client.h>
#include <aws/lexv2-runtime/model/AudioInputEvent.h>
#include <aws/lexv2-runtime/model/RecognizeUtteranceRequest.h>
#include <aws/lexv2-runtime/model/RecognizeUtteranceResult.h>
#include <aws/lexv2-runtime/model/StartConversationHandler.h>
#include <aws/lexv2-runtime/model/StartConversationRequest.h>
#include <aws/lexv2-runtime/model/StartConversationRequestEventStream.h>
#include <aws/lex/LexRuntimeServiceClient.h>
#include <aws/lex/LexRuntimeServiceRequest.h>
#include <aws/lex/model/PostContentRequest.h>
#include <aws/lex/model/PostContentResult.h>
#include <iterator>
#include <nlohmann_json.hpp>
#include <fstream>
#include <chrono>

using namespace Aws::LexRuntimeV2;
int SessionCount = 0;

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        std::cout << "Usage: LexV2Test pcm_audio.raw bot_name_cred.json" << std::endl;
        return -1;
    }

    // read json file and populate the fields
    std::ifstream jsonFile(argv[1]);
    if (!jsonFile)
    {
        std::cout << "Cannot open license file " << argv[1] << std::endl;
        return -1;
    }

    nlohmann::json jsonObj = nlohmann::json::parse(std::istreambuf_iterator<char>(jsonFile), std::istreambuf_iterator<char>());
    jsonFile.close();

    std::string lexKey, lexSecret;
    std::string botId, botAliasId, localeId, sessionId, regionId;

    botId = jsonObj["bot_id"];
    botAliasId = jsonObj["bot_alias_id"];
    lexKey = jsonObj["lex_key"];
    lexSecret = jsonObj["lex_secret"];
    localeId = jsonObj["lex_locale"];

    Aws::SDKOptions options;
    options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Info;
    Aws::InitAPI(options);
    Aws::Client::ClientConfiguration config;
    config.region = jsonObj["lex_region"];

    auto lexClient = Aws::MakeUnique<LexRuntimeV2Client>("MyClient", Aws::Auth::AWSCredentials(lexKey.c_str(), lexSecret.c_str()), config);
    Model::StartConversationRequest LexRequest;
    Model::StartConversationHandler requestHandler;

    requestHandler.SetTranscriptEventCallback([](const Model::TranscriptEvent& ev)
        {
            std::cout << ev.GetTranscript() << std::endl;
        });
    requestHandler.SetOnErrorCallback([](const Aws::Client::AWSError<LexRuntimeV2Errors>& error)
        {
            std::cout << "Request handler: " << error.GetMessage() << std::endl;
        });


    LexRequest.WithLocaleId(localeId).WithBotId(botId.c_str()).WithBotAliasId(botAliasId.c_str()).WithSessionId("Blah")
        .WithConversationMode(Model::ConversationMode::AUDIO).WithEventStreamHandler(requestHandler);

    Aws::Utils::Threading::Semaphore signaling(0 /*initialCount*/, 1 /*maxCount*/);
    auto OnResponseCallback = [&signaling](const LexRuntimeV2Client*,const Model::StartConversationRequest&,
        const Model::StartConversationOutcome& outcome, const std::shared_ptr<const Aws::Client::AsyncCallerContext>&) 
    { 
        std::cout << "Response handler: " << outcome.GetError().GetMessage();
        signaling.Release(); 
    };

    Model::StartConversationRequestEventStream* pStream = nullptr;
    Aws::Utils::Threading::Semaphore starting(0 /*initialCount*/, 1 /*maxCount*/);
    auto OnStreamReady = [&starting,&pStream](Model::StartConversationRequestEventStream& stream)
    {
        pStream = &stream;
        pStream->SetSignatureSeed("blah");
        starting.Release();
    };

    lexClient->StartConversationAsync(LexRequest, OnStreamReady, OnResponseCallback, nullptr);
    starting.WaitOne();
    std::ifstream audioFile(argv[2], std::ios_base::binary);
    if (!audioFile)
    {
        std::cout << "Cannot open audio file " << argv[2] << std::endl;
        return 0;
    }

    while (!audioFile.eof())
    {
        unsigned char buf[320 + 1];
        audioFile.read((char*)buf, 320);
        Aws::Utils::ByteBuffer bytes(buf, audioFile.gcount());
        Model::AudioInputEvent input;
        auto millisec_since_epoch = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock().now().time_since_epoch()).count();
        input.SetClientTimestampMillis(millisec_since_epoch);
        input.SetAudioChunk(bytes);
        input.SetContentType("audio/lpcm; sample-rate=8000; sample-size-bits=16; channel-count=1; is-big-endian=false");
        pStream->WriteAudioInputEvent(input);
        _sleep(20);
    }
    signaling.WaitOne(); // prevent the application from exiting until we're done

    Aws::ShutdownAPI(options);
    return 0;
}

Solution

  • Turns out this is an limitation with WinHttp/WinINet on Windows. It doesn't fully support HTTP/2 multiplexing yet. So I switched to CURL, and it worked.

    Since AWS streaming lex/transcribe feature needs HTTP/2 multiplexing, always use it with CURL.

    Aws::Client::ClientConfiguration config;
    config.httpLibOverride = Aws::Http::TransferLibType::CURL_CLIENT;