'The MD5 of all attributes is calculated by first generating a
utf-8 string from each attribute and MD5-ing the concatenation
of them all. Each attribute is encoded with some bytes that
describe the length of each part and the type of attribute.
Not yet implemented:
List types (https://github.com/aws/aws-sdk-java/blob/7844c64cf248aed889811bf2e871ad6b276a89ca/aws-java-sdk-sqs/src/main/java/com/amazonaws/services/sqs/MessageMD5ChecksumHandler.java#L58k)'

@property
def attribute_md5(self):

    def utf8(str):
        if isinstance(str, six.string_types):
            return str.encode(u'utf-8')
        return str
    md5 = hashlib.md5()
    for name in sorted(self.message_attributes.keys()):
        attr = self.message_attributes[name]
        data_type = attr[u'data_type']
        encoded = utf8(u'')
        encoded += (struct.pack(u'!I', len(utf8(name))) + utf8(name))
        encoded += (struct.pack(u'!I', len(data_type)) + utf8(data_type))
        encoded += TRANSPORT_TYPE_ENCODINGS[data_type]
        if ((data_type == u'String') or (data_type == u'Number')):
            value = attr[u'string_value']
        elif (data_type == u'Binary'):
            print (data_type, attr[u'binary_value'], type(attr[u'binary_value']))
            value = base64.b64decode(attr[u'binary_value'])
        else:
            print u"Moto    hasn't    implemented    MD5    hashing    for    {}    attributes".format(data_type)
            return u'deadbeefdeadbeefdeadbeefdeadbeef'
        encoded += (struct.pack(u'!I', len(utf8(value))) + utf8(value))
        md5.update(encoded)
    return md5.hexdigest()
