// This test validates that the construction and parsing of the message integrity attribute in a stun message works as expected
// The test also validates both short term and long term credential modes with or without the presence of a fingerprint attribute
HRESULT CTestIntegrity::TestMessageIntegrity(bool fWithFingerprint, bool fLongCredentials)
{
    HRESULT hr = S_OK;
    
    const char* pszUserName = "******";
    const char* pszRealm = "stunrealm";
    const char* pszPassword = "******";
    
    CStunMessageBuilder builder;
    CStunMessageReader reader;
    uint8_t *pMsg = NULL;
    size_t sizeMsg = 0;
    CStunMessageReader::ReaderParseState state;
    CRefCountedBuffer spBuffer;
    
    builder.AddBindingRequestHeader();
    builder.AddRandomTransactionId(NULL);
    builder.AddUserName(pszUserName);
    builder.AddRealm(pszRealm);
    
    
    if (fLongCredentials == false)
    {
        Chk(builder.AddMessageIntegrityShortTerm(pszPassword));
    }
    else
    {
        Chk(builder.AddMessageIntegrityLongTerm(pszUserName, pszRealm, pszPassword));
    }
    
    if (fWithFingerprint)
    {
        builder.AddFingerprintAttribute();
    }
    
    Chk(builder.GetResult(&spBuffer));
    
    pMsg = spBuffer->GetData();
    sizeMsg = spBuffer->GetSize();
    
    state = reader.AddBytes(pMsg, sizeMsg);
    
    ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL);
    
    ChkIfA(reader.HasMessageIntegrityAttribute()==false, E_FAIL);
    
    if (fLongCredentials == false)
    {
        ChkA(reader.ValidateMessageIntegrityShort(pszPassword));
    }
    else
    {
        ChkA(reader.ValidateMessageIntegrityLong(pszUserName, pszRealm, pszPassword));
    }
    
Cleanup:
    return hr;
}
HRESULT CStunThreadMessageHandler::ProcessBindingRequest(CStunMessageReader& reader)
{

    HRESULT hrTmp;
    bool fRequestHasPaddingAttribute = false;
    SocketRole socketOutput = _message.localSocket;
    StunChangeRequestAttribute changerequest = {};
    bool fSendOtherAddress = false;
    bool fSendOriginAddress = false;
    SocketRole socketOther;
    CSocketAddress addrOrigin;
    CSocketAddress addrOther;
    CStunMessageBuilder builder;
    uint16_t paddingSize = 0;
    bool fLegacyFormat = false; // set to true if the client appears to be rfc3489 based instead of based on rfc 5789


    _spResponseBuffer->SetSize(0);
    builder.GetStream().Attach(_spResponseBuffer, true);
    
    fLegacyFormat = reader.IsMessageLegacyFormat();

    // check for an alternate response port
    // check for padding attribute (todo - figure out how to inject padding into the response)
    // check for a change request and validate we can do it.  If so, set _socketOutput.  If not, fill out _error and return.
    // determine if we have an "other" address to notify the caller about


    // did the request come with a padding request
    if (SUCCEEDED(reader.GetPaddingAttributeSize(&paddingSize)))
    {
        // todo - figure out how we're going to get the MTU size of the outgoing interface
        fRequestHasPaddingAttribute = true;
    }

    // as per 5780, section 6.1, If the Request contained a PADDING attribute...
    // "If the Request also contains the RESPONSE-PORT attribute the server MUST return an error response of type 400."
    if (_fRequestHasResponsePort && fRequestHasPaddingAttribute)
    {
        _error.errorcode = STUN_ERROR_BADREQUEST;
        return E_FAIL;
    }

    // handle change request logic and figure out what "other-address" attribute is going to be
    if (SUCCEEDED(reader.GetChangeRequest(&changerequest)))
    {
        if (changerequest.fChangeIP)
        {
            socketOutput = SocketRoleSwapIP(socketOutput);
        }
        if(changerequest.fChangePort)
        {
            socketOutput = SocketRoleSwapPort(socketOutput);
        }

        // IsValidSocketRole just validates the enum, not whether or not we can send on it
        ASSERT(IsValidSocketRole(socketOutput));

        // now, make sure we have the ability to send from another socket
        if (_spStunResponder->HasAddress(socketOutput) == false)
        {
            // send back an error.  We're being asked to respond using another address that we don't have a socket for
            _error.errorcode = STUN_ERROR_BADREQUEST;
            return E_FAIL;
        }
    }

    // If we're only working one socket, then that's ok, we just don't send back an "other address" unless we have all four sockets confgiured

    // now here's a problem.  If we binded to "INADDR_ANY", all of the sockets will have "0.0.0.0" for an address (same for IPV6)
    // So we effectively can't send back "other address" if don't really know our own IP address
    // Fortunately, recvfromex and the ioctls on the socket allow address discovery a bit better

    fSendOtherAddress = (_spStunResponder->HasAddress(RolePP) && _spStunResponder->HasAddress(RolePA) && _spStunResponder->HasAddress(RoleAP) && _spStunResponder->HasAddress(RoleAA));

    if (fSendOtherAddress)
    {
        socketOther = SocketRoleSwapIP(SocketRoleSwapPort(_message.localSocket));

        hrTmp = _spStunResponder->GetSocketAddressForRole(socketOther, &addrOther);
        ASSERT(SUCCEEDED(hrTmp));

        // so if our ip address is "0.0.0.0", disable this attribute
        fSendOtherAddress = (SUCCEEDED(hrTmp) && (addrOther.IsIPAddressZero()==false));
    }

    // What's our address origin?
    VERIFY(SUCCEEDED(_spStunResponder->GetSocketAddressForRole(socketOutput, &addrOrigin)));
    if (addrOrigin.IsIPAddressZero())
    {
        // Since we're sending back from the IP address we received on, we can just use the address the message came in on
        // Otherwise, we don't actually know it
        if (socketOutput == _message.localSocket)
        {
            addrOrigin = _message.localAddr;
        }
    }
    fSendOriginAddress = (false == addrOrigin.IsIPAddressZero());

    // Success - we're all clear to build the response

    _socketOutput = socketOutput;

    _spResponseBuffer->SetSize(0);
    builder.GetStream().Attach(_spResponseBuffer, true);

    builder.AddHeader(StunMsgTypeBinding, StunMsgClassSuccessResponse);
    builder.AddTransactionId(_transid);
    builder.AddMappedAddress(_message.remoteAddr);

    if (fLegacyFormat == false)
    {
        builder.AddXorMappedAddress(_message.remoteAddr);
    }

    if (fSendOriginAddress)
    {
        builder.AddResponseOriginAddress(addrOrigin);
    }

    if (fSendOtherAddress)
    {
        builder.AddOtherAddress(addrOther, fLegacyFormat); // pass true to send back CHANGED-ADDRESS, otherwise, pass false to send back OTHER-ADDRESS
    }
    
    // finally - if we're supposed to have a message integrity attribute as a result of authorization, add it at the very end
    if (_integrity.fSendWithIntegrity)
    {
        if (_integrity.fUseLongTerm == false)
        {
            builder.AddMessageIntegrityShortTerm(_integrity.szPassword);
        }
        else
        {
            builder.AddMessageIntegrityLongTerm(_integrity.szUser, _integrity.szRealm, _integrity.szPassword);
        }
    }

    builder.FixLengthField();

    return S_OK;
}