-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bugfix around message attributes on Publish(SNS), ReceiveMessages(SQS) #339
Bugfix around message attributes on Publish(SNS), ReceiveMessages(SQS) #339
Conversation
binaryValue := values.Get(fmt.Sprintf("Entries.%d.MessageAttributes.%d.Value.BinaryValue", entryIndex, attributeIndex)) | ||
encodedBinaryValue := values.Get(fmt.Sprintf("Entries.%d.MessageAttributes.%d.Value.BinaryValue", entryIndex, attributeIndex)) | ||
|
||
if r.Entries[entryIndex].MessageAttributes == nil { | ||
r.Entries[entryIndex].MessageAttributes = make(map[string]MessageAttribute) | ||
} | ||
|
||
binaryValue, err := base64.StdEncoding.DecodeString(encodedBinaryValue) | ||
if err != nil { | ||
log.Warnf("Failed to base64 decode. %s may not have been base64 encoded.", encodedBinaryValue) | ||
continue | ||
} | ||
|
||
r.Entries[entryIndex].MessageAttributes[name] = MessageAttribute{ | ||
DataType: dataType, | ||
StringValue: stringValue, | ||
BinaryValue: []byte(binaryValue), | ||
BinaryValue: binaryValue, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same fix for bug 1.
binaryValue := values.Get(fmt.Sprintf("MessageAttributes.entry.%d.Value.BinaryValue", i)) | ||
encodedBinaryValue := values.Get(fmt.Sprintf("MessageAttributes.entry.%d.Value.BinaryValue", i)) | ||
binaryValue, err := base64.StdEncoding.DecodeString(encodedBinaryValue) | ||
if err != nil { | ||
log.Warnf("Failed to base64 decode. %s may not have been base64 encoded.", encodedBinaryValue) | ||
continue | ||
} | ||
|
||
if r.MessageAttributes == nil { | ||
r.MessageAttributes = make(map[string]MessageAttribute) | ||
} | ||
|
||
attributes[name] = MessageAttribute{ | ||
DataType: caser.String(dataType), // capitalize | ||
DataType: cases.Title(language.AmericanEnglish).String(dataType), // capitalize | ||
StringValue: stringValue, | ||
BinaryValue: []byte(binaryValue), | ||
BinaryValue: binaryValue, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same fix for bug 1.
binaryValue := values.Get(fmt.Sprintf("MessageAttribute.%d.Value.BinaryValue", i)) | ||
encodedBinaryValue := values.Get(fmt.Sprintf("MessageAttribute.%d.Value.BinaryValue", i)) | ||
|
||
binaryValue, err := base64.StdEncoding.DecodeString(encodedBinaryValue) | ||
if err != nil { | ||
log.Warnf("Failed to base64 decode. %s may not have been base64 encoded.", encodedBinaryValue) | ||
continue | ||
} | ||
|
||
r.MessageAttributes[name] = MessageAttribute{ | ||
DataType: dataType, | ||
StringValue: stringValue, | ||
BinaryValue: []byte(binaryValue), | ||
BinaryValue: binaryValue, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes Bug 1 "Received Message Attribute via XML is not decoded from base 64".
JSON requests has decoded from base 64 on JSON marshaller, but for XML, here SetAttributesFromForm
does not have decoding step. So internal BinaryValue
was still base 64 string (as binary) value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really love this. I know it's more true to life, since that input is supposed to be encoded, but I'd prefer to make the decoding optional. It's a testing tool after all, and we're doing our best to cover the needs of all. It's not like we're throwing errors for invalid attributes or anything.
So maybe instead of your continue, the log is fine, but take the value as is and stuff it in the resulting attribute?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK! I will update it as the below 👍
- Try decoding
- Success: store it as the decoded raw value
- Failure: log and store it as original requested value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry @kojisaikiAtSony - I'm looking at this again, and more of my previous thoughts are coming back to me. Looks to me like this change is decoding it, storing it internally, and then upon receiving the message, encoding it, and returning it. Double check me, is that what you're doing here?
If so, why do this at all? That's why I undid all this decoding/encoding business not long ago. It didn't seem like there was a point. Are you having trouble with the checksum or something because it's encoded? Where are you seeing the "Received Message Attribute via XML is not decoded from base 64" error, and how are you recreating it?
assert.Equal(t, []uint8("VmFsdWUy"), attr2.BinaryValue) | ||
assert.Equal(t, []uint8("Value2"), attr2.BinaryValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Internal value should be raw (not base 64 encoded) value.
if value.DataType == "Binary" { | ||
value.BinaryValue = []byte(base64.StdEncoding.EncodeToString(value.BinaryValue)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the related fix for Bug 1.
In the AWS query protocol, blobs are sent and received after being base64 encoded , so the binary value should be base64 encoded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know about this. I understand that's part of the query protocol, but that whole thing is deprecated in AWS. Based on my tests this works, as is, for the existing sdks I've tried. What exactly isn't working for you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you said this part is for the query protocol. The latest version of AWS SDK for Java also works without this part.
I thought that future versions of GOAWS still keep support the query protocol, so I added it.
Sorry, I don't remember clearly, but Is the policy that "users who need to use the query protocol should use older versions of GOAWS"? (It also makes sense)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, no, that's not the policy per say. I hadn't given it that much thought if I'm honest. But if you think about it like this - this is a tool for folks to run their own apps against instead AWS directly. So my main goal is to make it work in a performant/lightweight way and replicate as much of the AWS SQS/SNS "contract" as I can.
So with that in mind - we don't NEED to match what AWS does, as long as we respond the same way that it would. We just need to match their contracts right? At any rate we can't know what they're doing all the times as their docs are generally garbage in my opinion.
So, I don't care about things like security, and base64 encoding much, because, it's really up to the user here to worry about their input and output - because it's an app running where a user puts it. "Do what you will with it." so to speak, as long as an SDK doesn't fail on it - "garbage in garbage" out.
Here's what I need:
So all that said - this change is doing a couple of things. I need to know why we need them. The way I had it lately was working for me, based on the contract I had researched. What I'd like to see is what you're failing with, how you're recreating it, and why we think these changes will fix them.
Sorry - this is a lot for me to juggle long term and I need a little grooming/refining (in agile terms), before I let it in. Otherwise I'll be adrift if someone else complains about it or something similar later.
As always thank you for giving it so much work and thought. I really appreciate it and without you I wouldn't be able to keep this tool as current and viable as it is.
entry = "<MessageAttribute><Name>attr2</Name><Value><BinaryValue>binary-value</BinaryValue><DataType>Binary</DataType></Value></MessageAttribute>" | ||
entry = "<MessageAttribute><Name>attr2</Name><Value><BinaryValue>YmluYXJ5LXZhbHVl</BinaryValue><DataType>Binary</DataType></Value></MessageAttribute>" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assertion update for blobs are sent and received after being base64 encoded.
|
||
snsClient.SetSubscriptionAttributes(context.TODO(), &sns.SetSubscriptionAttributesInput{ | ||
SubscriptionArn: subscribeResult.SubscriptionArn, | ||
AttributeName: aws.String("RawMessageDelivery"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new test pattern is for "RawMessageDelivery".
@@ -268,7 +268,7 @@ func Test_ReceiveMessageV1_xml_with_attributes(t *testing.T) { | |||
assert.Equal(t, 1, len(receiveMessageResponse.Result.Messages)) | |||
assert.Equal(t, "MyTestMessage", receiveMessageResponse.Result.Messages[0].Body) | |||
assert.Equal(t, "ad4883a84ad41c79aa3a373698c0d4e9", receiveMessageResponse.Result.Messages[0].MD5OfBody) | |||
assert.Equal(t, "", receiveMessageResponse.Result.Messages[0].MD5OfMessageAttributes) | |||
assert.Equal(t, "ae8770938aee44bc548cf65ac377e3bf", receiveMessageResponse.Result.Messages[0].MD5OfMessageAttributes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
xml/json both should have hash for message attribute if the message attributes is not empty.
@@ -370,7 +372,7 @@ func TestSendMessageBatchV1_Xml_Success_including_attributes(t *testing.T) { | |||
WithFormField("Entries.1.MessageBody", messageBody2). | |||
WithFormField("Entries.1.MessageAttributes.1.Name", binaryAttributeKey). | |||
WithFormField("Entries.1.MessageAttributes.1.Value.DataType", binaryType). | |||
WithFormField("Entries.1.MessageAttributes.1.Value.BinaryValue", binaryValue). | |||
WithFormField("Entries.1.MessageAttributes.1.Value.BinaryValue", binaryValueEncodeString). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is also for blobs are sent and received after being base64 encoded.
BinaryValue in XML should be base 64 encoded.
DataType: caser.String(dataType), // capitalize | ||
DataType: cases.Title(language.AmericanEnglish).String(dataType), // capitalize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the fix for Bug 2 "DataType value was broken by caser
unexpectedly".
As I wrote in the description, I don't know the exact conditions under which this occurs, but since there was no need to make it a global variable, I changed it to a local definition and the problem was resolved. If you have any ideas, I would appreciate your comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to understand more about why you want to change this. If you don't know the exact conditions does that mean you can't recreate it? If so, what are we fixing, and beyond that how can we create a test that makes sure the issue is fixed?
In the short term, I like the pattern of it being global. If I need to uppercase anything else, I want to use the same instance of caser
instead of creating new ones all over the place. That's why I made it global.
Can you provide a little more information before we move forward with this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I thought you said so 😅. It's a little creepy when the behavior isn't clear. Let's dive into the caser code and take a closer look 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need some more information on this one before we move forward. Having just done some significant work on this, I want to understand more about what's causing you problems before we change it. Also, I think there are a couple opportunities to be the "path of least resistance" here, and I think that's helpful. See my individualized comments for the specifics.
Finally - #338 @toastwaffle did some good work on combining the logic around parsing incoming MessageAttributes. It'll definitely affect your change, since you'd only need to do it once. Want to take a look at what they've got first? I think there's is pretty close and I think it makes sense to get that in before this, so we can just fix the bugs in 1 area, if you're ok with that.
Thanks for looking at this, as always!
binaryValue := values.Get(fmt.Sprintf("MessageAttribute.%d.Value.BinaryValue", i)) | ||
encodedBinaryValue := values.Get(fmt.Sprintf("MessageAttribute.%d.Value.BinaryValue", i)) | ||
|
||
binaryValue, err := base64.StdEncoding.DecodeString(encodedBinaryValue) | ||
if err != nil { | ||
log.Warnf("Failed to base64 decode. %s may not have been base64 encoded.", encodedBinaryValue) | ||
continue | ||
} | ||
|
||
r.MessageAttributes[name] = MessageAttribute{ | ||
DataType: dataType, | ||
StringValue: stringValue, | ||
BinaryValue: []byte(binaryValue), | ||
BinaryValue: binaryValue, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really love this. I know it's more true to life, since that input is supposed to be encoded, but I'd prefer to make the decoding optional. It's a testing tool after all, and we're doing our best to cover the needs of all. It's not like we're throwing errors for invalid attributes or anything.
So maybe instead of your continue, the log is fine, but take the value as is and stuff it in the resulting attribute?
if value.DataType == "Binary" { | ||
value.BinaryValue = []byte(base64.StdEncoding.EncodeToString(value.BinaryValue)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know about this. I understand that's part of the query protocol, but that whole thing is deprecated in AWS. Based on my tests this works, as is, for the existing sdks I've tried. What exactly isn't working for you?
if r.MessageAttributes != nil { | ||
e.EncodeElement(r.MD5OfMessageAttributes, xml.StartElement{Name: xml.Name{Local: "MD5OfMessageAttributes"}}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why I left this out. I think there was a reason though. A lot of this had to do with the raw vs regular message handling. Can you tell me more about what your issues are on this?
The PR is very helpful! We'll rebase this once it is merged 👍 |
@Admiral-Piett |
We found 3 bugs around message attributes, as shown below:
caser
unexpectedly.String
becomes a string like\u0000\u0000\u0000\u0000\u0000\u0000
due to thecaser
.MD5OfMessageAttributes
is missing on the message with message attributes from SQS-ReceiveMessages.