All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.qcloud.cos.internal.eventstreaming.Message Maven / Gradle / Ivy

/*
 * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.

 * According to cos feature, we modify some class,comment, field name, etc.
 */
package com.qcloud.cos.internal.eventstreaming;

import static java.lang.String.format;

import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CRC32MismatchException;
import com.qcloud.cos.utils.Base64;
import com.qcloud.cos.utils.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.Checksum;

/**
 * An eventstream message.
 */
public class Message {
    private static final int TRAILING_CRC_LENGTH = 4;
    static final int MESSAGE_OVERHEAD = Prelude.LENGTH_WITH_CRC + TRAILING_CRC_LENGTH;

    private final Map headers;
    private final byte[] payload;

    public Message(Map headers, byte[] payload) {
        this.headers = headers;
        this.payload = payload.clone();
    }

    public Map getHeaders() {
        return headers;
    }

    public byte[] getPayload() {
        return payload.clone();
    }

    public static Message decode(ByteBuffer buf) {
        Prelude prelude = Prelude.decode(buf);

        int totalLength = prelude.getTotalLength();
        validateMessageCrc(buf, totalLength);
        buf.position(buf.position() + Prelude.LENGTH_WITH_CRC);

        long headersLength = prelude.getHeadersLength();
        byte[] headerBytes = new byte[Utils.toIntExact(headersLength)];
        buf.get(headerBytes);
        Map headers = decodeHeaders(ByteBuffer.wrap(headerBytes));

        byte[] payload = new byte[Utils.toIntExact(totalLength - MESSAGE_OVERHEAD - headersLength)];
        buf.get(payload);
        buf.getInt(); // skip past the message CRC

        return new Message(headers, payload);
    }

    private static void validateMessageCrc(ByteBuffer buf, int totalLength) {
        Checksum crc = new CRC32();

        Checksums.update(crc, (ByteBuffer) buf.duplicate().limit(buf.position() + totalLength - 4));
        long computedMessageCrc = crc.getValue();

        long wireMessageCrc = Utils.toUnsignedLong(buf.getInt(buf.position() + totalLength - 4));

        if (wireMessageCrc != computedMessageCrc) {
            throw new CosClientException(new CRC32MismatchException(
                    format("Message checksum failure: expected 0x%x, computed 0x%x", wireMessageCrc, computedMessageCrc)));
        }
    }

    static Map decodeHeaders(ByteBuffer buf) {
        Map headers = new HashMap();

        while (buf.hasRemaining()) {
            Header header = Header.decode(buf);
            headers.put(header.getName(), header.getValue());
        }

        return Collections.unmodifiableMap(headers);
    }

    public ByteBuffer toByteBuffer() {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            encode(baos);
            baos.close();
            return ByteBuffer.wrap(baos.toByteArray());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void encode(OutputStream os) {
        try {
            CheckedOutputStream checkedOutputStream = new CheckedOutputStream(os, new CRC32());
            encodeOrThrow(checkedOutputStream);
            long messageCrc = checkedOutputStream.getChecksum().getValue();
            os.write((int) (0xFF & messageCrc >> 24));
            os.write((int) (0xFF & messageCrc >> 16));
            os.write((int) (0xFF & messageCrc >> 8));
            os.write((int) (0xFF & messageCrc));

            os.flush();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    private void encodeOrThrow(OutputStream os) throws IOException {
        ByteArrayOutputStream headersAndPayload = new ByteArrayOutputStream();
        {
            DataOutputStream dos = new DataOutputStream(headersAndPayload);
            for (Entry entry : headers.entrySet()) {
                Header.encode(entry, dos);
            }
            dos.write(payload);
            dos.flush();
        }

        int totalLength = Prelude.LENGTH_WITH_CRC + headersAndPayload.size() + 4;

        {
            byte[] preludeBytes = getPrelude(totalLength);
            Checksum crc = new CRC32();
            crc.update(preludeBytes, 0, preludeBytes.length);

            DataOutputStream dos = new DataOutputStream(os);
            dos.write(preludeBytes);
            long value = crc.getValue();
            int value1 = (int) value;
            dos.writeInt(value1);
            dos.flush();
        }

        headersAndPayload.writeTo(os);
    }

    private byte[] getPrelude(int totalLength) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(8);
        DataOutputStream dos = new DataOutputStream(baos);

        int headerLength = totalLength - Message.MESSAGE_OVERHEAD - payload.length;
        dos.writeInt(totalLength);
        dos.writeInt(headerLength);

        dos.close();
        return baos.toByteArray();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Message message = (Message) o;

        if (!headers.equals(message.headers)) return false;
        return Arrays.equals(payload, message.payload);
    }

    @Override
    public int hashCode() {
        int result = headers.hashCode();
        result = 31 * result + Arrays.hashCode(payload);
        return result;
    }

    @Override
    public String toString() {
        StringBuilder ret = new StringBuilder();

        for (Entry entry : headers.entrySet()) {
            ret.append(entry.getKey());
            ret.append(": ");
            ret.append(entry.getValue().toString());
            ret.append('\n');
        }
        ret.append('\n');

        HeaderValue contentTypeHeader = headers.get("content-type");
        if (contentTypeHeader == null) {
            contentTypeHeader = HeaderValue.fromString("application/octet-stream");
        }

        String contentType = contentTypeHeader.getString();
        if (contentType.contains("json") || contentType.contains("text")) {
            ret.append(new String(payload, StringUtils.UTF8));
        } else {
            ret.append(Base64.encodeAsString(payload));
        }
        ret.append('\n');
        return ret.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy