"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.assertSignatureIsValid = exports.validateStreamMessage = void 0;
const protocol_1 = require("@streamr/protocol");
const trackerless_network_1 = require("@streamr/trackerless-network");
const utils_1 = require("@streamr/utils");
const signature_1 = require("../signature");
const validateStreamMessage = async (msg, streamRegistry) => {
    await doValidate(msg, streamRegistry).catch((err) => {
        // all StreamMessageError already have this streamMessage, maybe this is 
        // here if e.g. contract call fails? TODO is this really needed as
        // the onError callback in messagePipeline knows which message
        // it is handling?
        if (!err.streamMessage) {
            err.streamMessage = msg;
        }
        throw err;
    });
};
exports.validateStreamMessage = validateStreamMessage;
/**
 * Checks that the given StreamMessage satisfies the requirements of the protocol.
 * This includes checking permissions as well as signature. The method supports all
 * message types defined by the protocol.
 *
 * Resolves the promise if the message is valid, rejects otherwise.
 *
 * @param streamMessage the StreamMessage to validate.
 */
const doValidate = (streamMessage, streamRegistry) => {
    (0, exports.assertSignatureIsValid)(streamMessage);
    switch (streamMessage.messageType) {
        case protocol_1.StreamMessageType.MESSAGE:
            return validateMessage(streamMessage, streamRegistry);
        case protocol_1.StreamMessageType.GROUP_KEY_REQUEST:
            return validateGroupKeyMessage(streamMessage, (0, trackerless_network_1.convertBytesToGroupKeyRequest)(streamMessage.content).recipient, streamMessage.getPublisherId(), streamRegistry);
        case protocol_1.StreamMessageType.GROUP_KEY_RESPONSE:
            return validateGroupKeyMessage(streamMessage, streamMessage.getPublisherId(), (0, trackerless_network_1.convertBytesToGroupKeyResponse)(streamMessage.content).recipient, streamRegistry);
        default:
            throw new protocol_1.StreamMessageError(`Unknown message type: ${streamMessage.messageType}!`, streamMessage);
    }
};
/**
 * Checks that the signature in the given StreamMessage is cryptographically valid.
 * Resolves if valid, rejects otherwise.
 *
 * @param streamMessage the StreamMessage to validate.
 */
const assertSignatureIsValid = (streamMessage) => {
    const payload = (0, signature_1.createSignaturePayload)({
        messageId: streamMessage.messageId,
        messageType: streamMessage.messageType,
        content: streamMessage.content,
        signatureType: streamMessage.signatureType,
        encryptionType: streamMessage.encryptionType,
        prevMsgRef: streamMessage.prevMsgRef ?? undefined,
        newGroupKey: streamMessage.newGroupKey ?? undefined
    });
    let success;
    try {
        success = (0, utils_1.verifySignature)(streamMessage.getPublisherId(), payload, streamMessage.signature);
    }
    catch (err) {
        throw new protocol_1.StreamMessageError(`An error occurred during address recovery from signature: ${err}`, streamMessage);
    }
    if (!success) {
        throw new protocol_1.StreamMessageError('Signature validation failed', streamMessage);
    }
};
exports.assertSignatureIsValid = assertSignatureIsValid;
const validateMessage = async (streamMessage, streamRegistry) => {
    const streamId = streamMessage.getStreamId();
    const stream = await streamRegistry.getStream(streamId);
    const partitionCount = stream.getMetadata().partitions;
    if (streamMessage.getStreamPartition() < 0 || streamMessage.getStreamPartition() >= partitionCount) {
        throw new protocol_1.StreamMessageError(`Partition ${streamMessage.getStreamPartition()} is out of range (0..${partitionCount - 1})`, streamMessage);
    }
    const sender = streamMessage.getPublisherId();
    const isPublisher = await streamRegistry.isStreamPublisher(streamId, sender);
    if (!isPublisher) {
        throw new protocol_1.StreamMessageError(`${sender} is not a publisher on stream ${streamId}`, streamMessage);
    }
};
const validateGroupKeyMessage = async (streamMessage, expectedPublisher, expectedSubscriber, streamRegistry) => {
    const streamId = streamMessage.getStreamId();
    const isPublisher = await streamRegistry.isStreamPublisher(streamId, expectedPublisher);
    if (!isPublisher) {
        throw new protocol_1.StreamMessageError(`${expectedPublisher} is not a publisher on stream ${streamId}`, streamMessage);
    }
    const isSubscriber = await streamRegistry.isStreamSubscriber(streamId, expectedSubscriber);
    if (!isSubscriber) {
        throw new protocol_1.StreamMessageError(`${expectedSubscriber} is not a subscriber on stream ${streamId}`, streamMessage);
    }
};
//# sourceMappingURL=validateStreamMessage.js.map