Bug 1316507

Summary: [C++ client] Message::setContent("") does not work
Product: Red Hat Enterprise MRG Reporter: Pavel Moravec <pmoravec>
Component: qpid-cppAssignee: messaging-bugs <messaging-bugs>
Status: CLOSED ERRATA QA Contact: Messaging QE <messaging-qe-bugs>
Severity: high Docs Contact:
Priority: high    
Version: 3.2CC: gsim, jross, mcressma, tkratky, zkraus
Target Milestone: 3.2.2   
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: qpid-cpp-0.34-10 Doc Type: Bug Fix
Doc Text:
Cause: The message implementation can hold two views of message content: the raw bytes or a decode object form. These operate independently, i.e. changes to the raw bytes do not alter the object content. Consequence: When a message is automatically decoded into object form on receipt, then updated by setting the raw bytes in order to resend, the resent message used the original object content rather than the bytes. Fix: On setting the raw bytes, any previously held object content view is reset (i.e. invalidated). (Additionally, a setting was added to allow the automatic decoding of messages into object form to be disabled by environment variable or config file). Result: The message content reflects the applications actions.
Story Points: ---
Clone Of: Environment:
Last Closed: 2016-10-11 07:36:14 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description Pavel Moravec 2016-03-10 11:21:52 UTC
Description of problem:
Message::setContent internally updates just "bytes" property of the Message object but not the "content" object. That brings problems when trying to reset content to empty one - original content is still stored in "content" object property and e.g. an attempt to send this "empty" message sends the message with original content.


Version-Release number of selected component (if applicable):
qpid-cpp 0.34-5


How reproducible:
100%


Steps to Reproduce:
1. Have trivial program:

#include <iostream>
#include <qpid/messaging/Message.h>

using namespace std;
using namespace qpid::messaging;

int main(int argc, char* argv[])
{
    qpid::types::Variant content("some content");
    Message m1(content);
    cout << "Message 1: initial content set to \"" << m1.getContent() << "\", contentSize = " << m1.getContentSize() << endl;
    m1.setContent("message 1");
    cout << "Message 1: after content set to \"" << m1.getContent() << "\", contentSize = " << m1.getContentSize() << endl;
    m1.setContent(std::string());
    cout << "Message 1: after content set to an empty string, the content is still \"" << m1.getContent() << "\" and contentSize = " << m1.getContentSize() << endl;

    return 0;
}

2. (optionally, get the message from the broker and send it back to ensure proper behaviour; in this case test both AMQP 0-10 (should be broken) and AMQP 1.0 (should work even now).

3. run the program


Actual results:
Message 1: initial content set to "some content", contentSize = 12
Message 1: after content set to "message 1", contentSize = 9
Message 1: after content set to an empty string, the content is still "some content" and contentSize = 12


Expected results:


Message 1: initial content set to "some content", contentSize = 12
Message 1: after content set to "message 1", contentSize = 9
Message 1: after content set to an empty string, the content is still "" and contentSize = 0



Additional info:
See "some content" of size 12 returned after an attempt to empty the message content. See it is the original content before first setContent called.

Comment 1 Gordon Sim 2016-03-10 21:27:43 UTC
A workaround is to setContentObject(std::string()), or getContentObject().reset().

Comment 2 Gordon Sim 2016-03-10 22:56:08 UTC
Fixed upstream: https://svn.apache.org/r1734476

Comment 4 Mike McCune 2016-03-28 23:04:30 UTC
This bug was accidentally moved from POST to MODIFIED via an error in automation, please see mmccune with any questions

Comment 5 Pavel Moravec 2016-03-29 09:20:06 UTC
The fix does not work and some more generic problem identified (let split to 2nd bug it if you feel a separate BZ needed for it).

Reproducer: use amqp/map content type, copy such message, update content (to "" or to some non-empty string, shorter than original one), send to qpidd, receive - the received message will have the _original_ content, still

Reproducer source code:

#include <iostream>
#include <string>

#include <unistd.h>

#include <qpid/messaging/Address.h>
#include <qpid/messaging/Connection.h>
#include <qpid/messaging/Session.h>
#include <qpid/messaging/Message.h>
#include <qpid/messaging/Sender.h>
#include <qpid/messaging/Receiver.h>
#include <qpid/types/Variant.h>

using namespace std;
using namespace qpid::messaging;
using namespace qpid::types;


const int fragSize = 102;
const int msgSize = 107;
int 
main( int argc, char *argv[])
{
//enable it to test the content type amqp/map
#if 1 
   Message message;
   qpid::types::Variant::Map params;
   params["payload"] = std::string(msgSize, 'a');
   encode( params, message);
   message.setCorrelationId("123");
   cout << "message content: " << message.getContent() << endl;
#endif

//enable it to test the content type empty
#if 0
   
   qpid::types::Variant::Map params;
   std::string content;
   for (int i = 0; i < msgSize; i++)
    content+="A"; 
   Message message(content);
   cout << "message content: " << message.getContent() << endl;
#endif
   
   Connection connection = Connection("localhost:5672");
   connection.open();
   Session session = connection.createSession();
   Sender sender = session.createSender("amq.direct");
   message.setSubject("IN");

//   message.setContentType("amqp/map"); // already set
   cout << "message sent:\n size : " <<  message.getContentSize() << " type : " << message.getContentType() << " cID : " << message.getCorrelationId() << std::endl;
   sender.send(message);

   Message c_message;
   Connection c_connection("localhost:5672");
   c_connection.open();
   Session c_session = c_connection.createSession();
   Receiver receiver = c_session.createReceiver("testQueueIn");
   receiver.fetch(c_message, Duration::FOREVER);
   cout << "message received:\n size : " <<  c_message.getContentSize() << " type : " << c_message.getContentType() << " cID: " << c_message.getCorrelationId() << std::endl;
   c_connection.close();

   Message fragTemplate(c_message);
   for( int sequenceNumber = 1; (sequenceNumber-1) * fragSize <= (signed int)c_message.getContentSize() ; sequenceNumber++)
   {
     Message fragment(fragTemplate);
     fragment.setContent(c_message.getContent().substr((sequenceNumber-1) * fragSize, fragSize));
     
     if( sequenceNumber == 1 ) 
       fragment.setCorrelationId( "1");
     else
       fragment.setCorrelationId ("2");
      
     fragment.setSubject("OUT");
//     fragment.setContentType("amqp/map"); // already set
     cout << "fragment sent:\n size: " << fragment.getContentSize() << " type: " << fragment.getContentType() <<  " cID : " << fragment.getCorrelationId() << endl;
     cout << "fragment content: " << fragment.getContent() << endl;
     sender.send(fragment);
     session.sync();
   }   

  for( int i = 0; i < 2 ; ++i) { 
   Message r_message;
   Connection r_connection("localhost:5672");
   r_connection.open();
   Session r_session = r_connection.createSession();
   Receiver r_receiver = r_session.createReceiver("testQueueOut");
   r_receiver.fetch(r_message, Duration::IMMEDIATE);
   r_session.acknowledge(true);
   cout << "message received:\n size : " <<  r_message.getContentSize() << " type : " << r_message.getContentType() << " cID : " << r_message.getCorrelationId() << std::endl;
   cout << "message content: " << r_message.getContent() << endl;
   r_connection.close();
  }

  return 0;
}


Setup to reproduce it:
qpid-config add queue testQueueIn
qpid-config add queue testQueueOut
qpid-config bind amq.direct testQueueIn IN
qpid-config bind amq.direct testQueueOut OUT

Running the reproducer on 0.34-8:
message content: zpayload�kaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
message sent:
 size : 126 type : amqp/map cID : 123
message received:
 size : 126 type : amqp/map cID: 123
fragment sent:
 size: 102 type: amqp/map cID : 1
fragment content: zpayload�kaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
fragment sent:
 size: 24 type: amqp/map cID : 2
fragment content: aaaaaaaaaaaaaaaaaaaaaaaa
message received:
 size : 126 type : amqp/map cID : 1
message content: zpayload�kaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
message received:
 size : 126 type : amqp/map cID : 2
message content: zpayload�kaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

See the "fragment content" is OK, but even sending it to the broker, the original content is used (run tcpdump to confirm).


Making the scenario worse:
having line 68:

fragment.setContent("");

the program coredumps:
message content: zpayload�kaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
message sent:
 size : 126 type : amqp/map cID : 123
message received:
 size : 126 type : amqp/map cID: 123
fragment sent:
 size: 0 type: amqp/map cID : 1
fragment content: 
fragment sent:
 size: 0 type: amqp/map cID : 2
fragment content: 
terminate called after throwing an instance of 'qpid::messaging::EncodingException'
  what():  illegal-argument: Not enough data for field table. (/builddir/build/BUILD/qpid-cpp-0.34/src/qpid/framing/FieldTable.cpp:295)
Aborted (core dumped)

Comment 6 Pavel Moravec 2016-03-29 12:13:36 UTC
FYI the reproducer works fine in upstream, so a difference between upstream (r1737000) and qpid-cpp 0.34-8 causes the problems.

Comment 7 Gordon Sim 2016-03-29 20:39:28 UTC
I see the same behaviour upstream as well as on 0.34-mrg, namely that the bytes actually sent are the original full map, not the fragments as would be expected.

This is because the map is decoded in the receiver (in order for the behaviour between 0-10 and 1.0 to be uniform). There is a workaround, which is to set a connection option on c_connection and r_connection of "disable_auto_decode" to true.

The fix is to remove the condition from the original fix, i.e. reset the content-object whenever the bytes are changed.

However with that fix in place, if the auto-decode is not disabled then the fetch will fail as the message is considered malformed (it has a content-type that indicates it is a map, but the content itself is not a valid map). The fetch throws an exception, which if caught will prevent the core dump.

So to summarise, there was a new behaviour introduced that will auto-decode maps/lists for AMQP 0-10. This can be disabled via a connection option. If disabled, it works around the bug exposed by the latest reproducer. Even if the bug is fixed, the new auto-decoding behaviour would cause problems if messages are ever sent with content that does not match the content type.

One possibility would be to add a way to alter the default value for disable_auto_decode, so that could be set in an env var or conf file and would apply to all connections without modifying any code.

Comment 8 Gordon Sim 2016-03-30 10:35:01 UTC
Fixed[1] the bug that prevents updated bytes resetting the content-object (this is really an extension to the first fix made for this issue to cover all cases). Also added the ability to set the disable-auto-decode option via an environment variable or client config file[2].

[1] https://svn.apache.org/r1737093
[2] https://svn.apache.org/r1737094

Comment 12 errata-xmlrpc 2016-10-11 07:36:14 UTC
Since the problem described in this bug report should be
resolved in a recent advisory, it has been closed with a
resolution of ERRATA.

For information on the advisory, and where to find the updated
files, follow the link below.

If the solution does not work for you, open a new bug report.

https://rhn.redhat.com/errata/RHBA-2016-2049.html