// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {BytesLib} from "./BytesLib.sol";
import {IHandler} from "../../interfaces/IHandler.sol";

/// @dev A 1-byte constant '0xbb' indicative of a message. This NEVER
///         changes in future versions.
uint8 constant MAGIC_BYTE = 0xbb;

/// @dev A 1-byte version identifier. Deployments MUST increment this
///         version number.
uint8 constant MESSAGE_VERSION = 0x0;

/// @dev Details for the Wormhole leading byte.
uint256 constant MAGIC_BYTE_OFFSET = 0;
uint256 constant MAGIC_BYTE_SIZE = 1;

/// @dev Details for the version offset.
uint256 constant VERSION_OFFSET = MAGIC_BYTE_OFFSET + MAGIC_BYTE_SIZE;
uint256 constant VERSION_SIZE = 1;

/// @dev Details for the message type.
uint256 constant MESSAGE_TYPE_OFFSET = VERSION_OFFSET + VERSION_SIZE;
uint256 constant MESSAGE_TYPE_SIZE = 1;

/// @notice Got a mismatch for the leading byte during message decoding.
///         Expected the value of `MAGIC_BYTE` but got `unexpected`.
/// @dev Selector 0x981b4e39.
error MagicByteMismatch(uint8 unexpected);

/// @notice Got a version mismatch during message decoding. Expected the
///         value of `MESSAGE_VERSION` but got `unexpected`.
/// @dev Selector 0xc6147f41.
error VersionMismatch(uint8 unexpected);

/// @notice Got a message type mismatch during message decoding. Expected the
///         value of `expected` but got `unexpected`.
/// @dev Selector 0x6cf016d3.
error TypeMismatch(uint8 expected, uint8 unexpected);

/// @notice An error indicating that the targetAddress is too large
/// to encode OR the address is empty.
/// @dev Selector 0x2a2a06b2.
error AddressLength();

/// @notice An error indicating that the sender address is too large to
/// encode.
/// @dev Selector 0x496d8313.
error SenderLength();

/// @notice An error indicating that the message contents are too large to
/// encode.
/// @dev Selector 0xbe3cbd10.
error ContentsLength();

/// @notice Failed message decode because there were not enough bytes in
///         the input.
/// @dev Selector 0xb8d8f0f4.
error Overrun();

/// @notice Failed message decode because there were leftover bytes in the
///         input after decodig.
/// @dev Selector 0x94701070.
error LeftoverBytes();

/// @notice Target address is not a valid EVM address. The app that
///         emitted the message may have been misconfigured or
///         misimplemented.
/// @dev Selector 0x3e95d7cb.
error InvalidTargetAddress(bytes targetAddress);

/// @notice Encode the message header.
/// @dev The header follows a simple specification:
///      HEADER
///      - magic - 1 byte
///      - version - 1 byte
///      - messageType - 1 byte
/// @param messageType The message type. 0 for Message, 1 for MessageWithTokens.
/// @return header The encoded header.
function encodeHeader(uint8 messageType) pure returns (bytes memory header) {
    header = abi.encodePacked(MAGIC_BYTE, MESSAGE_VERSION, messageType);
}

/// @notice Check that an encoded message has the expected header.
/// @dev Checks the leading byte, version, and message type.
/// @custom:reverts MagicByteMismatch() If the leading byte is not
///                 "0xbb"
/// @custom:reverts VersionMistmatch() If the version is not expected.
///                 MessageLib supports any equal-or-lower version.
/// @custom:reverts TypeMismatch() If the message type is not expected.
///                 Type supported varies by decoding function.
function expectHeader(uint8 messageType, bytes memory encoded) pure {
    uint8 magic = BytesLib.toUint8(encoded, MAGIC_BYTE_OFFSET);
    if (magic != MAGIC_BYTE) revert MagicByteMismatch(magic);
    // 1 byte at position 1
    uint8 version = BytesLib.toUint8(encoded, VERSION_OFFSET);
    if (version > MESSAGE_VERSION) revert VersionMismatch(version);
    // 1 byte at position 2
    uint8 ty = BytesLib.toUint8(encoded, MESSAGE_TYPE_OFFSET);
    if (ty != messageType) revert TypeMismatch(messageType, ty);
}

/// @notice Convert a 20-byte array to an EVM address.
/// @dev This function loads the 20 bytes of address data from the
///      provided array, and converts it to an EVM address by shifting
///      it right by 96 bits.
/// @dev We chose not to check extcode here, as that will be handled
///      by the compiler when the address is used to make a call.
/// @param encoded The 20-byte array containing the address data.
/// @return decoded An IHandler interface at the decoded EVM address.
/// @custom:reverts InvalidTargetAddress(encoded) if the encoded array is
///                 not exactly 20-bytes long.
function extractAddress(bytes memory encoded) pure returns (IHandler decoded) {
    if (encoded.length != 20) revert InvalidTargetAddress(encoded);
    uint256 word;
    assembly ("memory-safe") {
        // This assemply loads 1 word from the body of the encoded array.
        // This word is known to consist of 20 bytes of address data,
        // followed by 12 bytes of anything. `bytes` are 0-padded to the
        // next word by Solidity convention, but the contents must be
        // shifted out regardless.
        word := mload(add(encoded, 0x20))
    }
    // We shift the word right by 96 bits, to remove the 12 low bytes
    word = word >> 96;
    decoded = IHandler(address(uint160(word)));
}

/// @notice Calculates a hash of a message used to check for replay.
/// @param emitterChainId emitterChainId The chain id of the remote Outbox.
/// @param emitterAddress The address of the remote Outbox.
/// @param index The index of the message in the remote Outbox.
/// @return A protocol-specific unique identifier of the message
function calculateReplayHash(uint16 emitterChainId, bytes32 emitterAddress, uint64 index) pure returns (bytes32) {
    return keccak256(abi.encodePacked(emitterChainId, emitterAddress, index));
}

/// @notice Read a length-prefixed bytearray from the encoded message. The
///         bytearray is prefixed with a uint16 that specifies its length.
/// @dev First reads the length prefix, and then reads that many bytes
///      after the prefix. Performs bounds checking before each read.
/// @param encoded The encoded message as a bytearray.
/// @param offset The offset in the encoded messages to start reading from.
/// @return contents The contents of the byte array (without the length
///         prefix).
/// @custom:reverts Overrun() If the encoded message is not long enough to
///                 read the length-prefixed bytearray.
function readU16PrefixedBytes(bytes memory encoded, uint256 offset) pure returns (bytes memory contents) {
    // ensure that the input is long enough to read a length-prefix from
    if (encoded.length < offset + 2) revert Overrun();
    uint16 len = BytesLib.toUint16(encoded, offset);
    uint256 contentsOffset = offset + 2;
    // ensure that the input is long enough to read from
    if (encoded.length < contentsOffset + len) revert Overrun();
    contents = BytesLib.slice(encoded, contentsOffset, len);
}
[{"inputs":[{"internalType":"address","name":"attestor","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ATTESTOR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"core","type":"address"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"deploy","outputs":[],"stateMutability":"nonpayable","type":"function"}]