Function parses the RAW AS2 message; decrypts, verifies and
        decompresses it and extracts the payload.

        :param raw_content:
            A byte string of the received HTTP headers followed by the body.

        :param find_org_cb:
            A callback the returns an Organization object if exists. The
            as2-to header value is passed as an argument to it.

        :param find_partner_cb:
            A callback the returns an Partner object if exists. The
            as2-from header value is passed as an argument to it.

        :param find_message_cb:
            An optional callback the returns an Message object if exists in
            order to check for duplicates. The message id and partner id is
            passed as arguments to it.

        :return:
            A three element tuple containing (status, (exception, traceback)
            , mdn). The status is a string indicating the status of the
            transaction. The exception is populated with any exception raised
            during processing and the mdn is an MDN object or None in case
            the partner did not request it.

def parse(self, raw_content, find_org_cb, find_partner_cb,
              find_message_cb=None):
        """Function parses the RAW AS2 message; decrypts, verifies and
        decompresses it and extracts the payload.

        :param raw_content:
            A byte string of the received HTTP headers followed by the body.

        :param find_org_cb:
            A callback the returns an Organization object if exists. The
            as2-to header value is passed as an argument to it.

        :param find_partner_cb:
            A callback the returns an Partner object if exists. The
            as2-from header value is passed as an argument to it.

        :param find_message_cb:
            An optional callback the returns an Message object if exists in
            order to check for duplicates. The message id and partner id is
            passed as arguments to it.

        :return:
            A three element tuple containing (status, (exception, traceback)
            , mdn). The status is a string indicating the status of the
            transaction. The exception is populated with any exception raised
            during processing and the mdn is an MDN object or None in case
            the partner did not request it.
        """

        # Parse the raw MIME message and extract its content and headers
        status, detailed_status, exception, mdn = \
            'processed', None, (None, None), None
        self.payload = parse_mime(raw_content)
        as2_headers = {}
        for k, v in self.payload.items():
            k = k.lower()
            if k == 'message-id':
                self.message_id = v.lstrip('<').rstrip('>')
            as2_headers[k] = v

        try:
            # Get the organization and partner for this transmission
            org_id = unquote_as2name(as2_headers['as2-to'])
            self.receiver = find_org_cb(org_id)
            if not self.receiver:
                raise PartnerNotFound(
                    'Unknown AS2 organization with id {}'.format(org_id))

            partner_id = unquote_as2name(as2_headers['as2-from'])
            self.sender = find_partner_cb(partner_id)
            if not self.sender:
                raise PartnerNotFound(
                    'Unknown AS2 partner with id {}'.format(partner_id))

            if find_message_cb and \
                    find_message_cb(self.message_id, partner_id):
                raise DuplicateDocument(
                    'Duplicate message received, message with this ID '
                    'already processed.')

            if self.sender.encrypt and \
                    self.payload.get_content_type() != 'application/pkcs7-mime':
                raise InsufficientSecurityError(
                    'Incoming messages from partner {} are must be encrypted'
                    ' but encrypted message not found.'.format(partner_id))

            if self.payload.get_content_type() == 'application/pkcs7-mime' \
                    and self.payload.get_param('smime-type') == 'enveloped-data':
                encrypted_data = self.payload.get_payload(decode=True)
                # logger.debug(
                    # 'Decrypting the payload :\n%s' % self.payload.as_string())

                self.encrypted = True
                self.enc_alg, decrypted_content = decrypt_message(
                    encrypted_data,
                    self.receiver.decrypt_key
                )
                raw_content = decrypted_content
                self.payload = parse_mime(decrypted_content)

                if self.payload.get_content_type() == 'text/plain':
                    self.payload = email_message.Message()
                    self.payload.set_payload(decrypted_content)
                    self.payload.set_type('application/edi-consent')

            # Check for compressed data here
            self.compressed, self.payload = self.decompress_data(self.payload)

            if self.sender.sign and \
                    self.payload.get_content_type() != 'multipart/signed':
                raise InsufficientSecurityError(
                    'Incoming messages from partner {} are must be signed '
                    'but signed message not found.'.format(partner_id))

            if self.payload.get_content_type() == 'multipart/signed':
                # logger.debug(b'Verifying the signed payload:\n{0:s}'.format(
                #     self.payload.as_string()))
                self.signed = True
                signature = None
                message_boundary = (
                        '--' + self.payload.get_boundary()).encode('utf-8')
                for part in self.payload.walk():
                    if part.get_content_type() == "application/pkcs7-signature":
                        signature = part.get_payload(decode=True)
                    else:
                        self.payload = part

                # Verify the message, first using raw message and if it fails
                # then convert to canonical form and try again
                mic_content = canonicalize(self.payload)
                verify_cert = self.sender.load_verify_cert()
                try:
                    self.digest_alg = verify_message(
                        mic_content, signature, verify_cert)
                except IntegrityError:
                    mic_content = raw_content.split(message_boundary)[
                        1].replace(b'\n', b'\r\n')
                    self.digest_alg = verify_message(
                        mic_content, signature, verify_cert)

                # Calculate the MIC Hash of the message to be verified
                digest_func = hashlib.new(self.digest_alg)
                digest_func.update(mic_content)
                self.mic = binascii.b2a_base64(digest_func.digest()).strip()

            # Check for compressed data here
            if not self.compressed:
                self.compressed, self.payload = self.decompress_data(self.payload)

        except Exception as e:
            status = getattr(e, 'disposition_type', 'processed/Error')
            detailed_status = getattr(
                e, 'disposition_modifier', 'unexpected-processing-error')
            print(traceback.format_exc())
            exception = (e, traceback.format_exc())
        finally:
            # Update the payload headers with the original headers
            for k, v in as2_headers.items():
                if self.payload.get(k) and k.lower() != 'content-disposition':
                    del self.payload[k]
                self.payload.add_header(k, v)

            if as2_headers.get('disposition-notification-to'):
                mdn_mode = SYNCHRONOUS_MDN

                mdn_url = as2_headers.get('receipt-delivery-option')
                if mdn_url:
                    mdn_mode = ASYNCHRONOUS_MDN

                digest_alg = as2_headers.get('disposition-notification-options')
                if digest_alg:
                    digest_alg = digest_alg.split(';')[-1].split(',')[
                        -1].strip()
                mdn = Mdn(
                    mdn_mode=mdn_mode, mdn_url=mdn_url, digest_alg=digest_alg)
                mdn.build(message=self,
                          status=status,
                          detailed_status=detailed_status)

            return status, exception, mdn