"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.downloadSingleOwnerChunk = exports.uploadSingleOwnerChunkData = exports.uploadSingleOwnerChunk = exports.makeSingleOwnerChunk = exports.makeSOCAddress = exports.makeSingleOwnerChunkFromData = void 0;
const bytes_1 = require("../utils/bytes");
const bmt_1 = require("./bmt");
const signer_1 = require("./signer");
const hash_1 = require("../utils/hash");
const span_1 = require("./span");
const serialize_1 = require("./serialize");
const error_1 = require("../utils/error");
const cac_1 = require("./cac");
const hex_1 = require("../utils/hex");
const socAPI = __importStar(require("../modules/soc"));
const chunkAPI = __importStar(require("../modules/chunk"));
const type_1 = require("../utils/type");
const IDENTIFIER_SIZE = 32;
const SIGNATURE_SIZE = 65;
const SOC_IDENTIFIER_OFFSET = 0;
const SOC_SIGNATURE_OFFSET = SOC_IDENTIFIER_OFFSET + IDENTIFIER_SIZE;
const SOC_SPAN_OFFSET = SOC_SIGNATURE_OFFSET + SIGNATURE_SIZE;
const SOC_PAYLOAD_OFFSET = SOC_SPAN_OFFSET + span_1.SPAN_SIZE;
function recoverChunkOwner(data) {
    const cacData = data.slice(SOC_SPAN_OFFSET);
    const chunkAddress = (0, bmt_1.bmtHash)(cacData);
    const signature = (0, bytes_1.bytesAtOffset)(data, SOC_SIGNATURE_OFFSET, SIGNATURE_SIZE);
    const identifier = (0, bytes_1.bytesAtOffset)(data, SOC_IDENTIFIER_OFFSET, IDENTIFIER_SIZE);
    const digest = (0, hash_1.keccak256Hash)(identifier, chunkAddress);
    const ownerAddress = (0, signer_1.recoverAddress)(signature, digest);
    return ownerAddress;
}
/**
 * Verifies if the data is a valid single owner chunk
 *
 * @param data    The chunk data
 * @param address The address of the single owner chunk
 *
 * @returns a single owner chunk or throws error
 */
function makeSingleOwnerChunkFromData(data, address) {
    const ownerAddress = recoverChunkOwner(data);
    const identifier = (0, bytes_1.bytesAtOffset)(data, SOC_IDENTIFIER_OFFSET, IDENTIFIER_SIZE);
    const socAddress = (0, hash_1.keccak256Hash)(identifier, ownerAddress);
    if (!(0, bytes_1.bytesEqual)(address, socAddress)) {
        throw new error_1.BeeError('SOC Data does not match given address!');
    }
    const signature = () => (0, bytes_1.bytesAtOffset)(data, SOC_SIGNATURE_OFFSET, SIGNATURE_SIZE);
    const span = () => (0, bytes_1.bytesAtOffset)(data, SOC_SPAN_OFFSET, span_1.SPAN_SIZE);
    const payload = () => (0, bytes_1.flexBytesAtOffset)(data, SOC_PAYLOAD_OFFSET, cac_1.MIN_PAYLOAD_SIZE, cac_1.MAX_PAYLOAD_SIZE);
    return {
        data,
        identifier: () => identifier,
        signature,
        span,
        payload,
        address: () => socAddress,
        owner: () => ownerAddress,
    };
}
exports.makeSingleOwnerChunkFromData = makeSingleOwnerChunkFromData;
function makeSOCAddress(identifier, address) {
    return (0, hash_1.keccak256Hash)(identifier, address);
}
exports.makeSOCAddress = makeSOCAddress;
/**
 * Creates a single owner chunk object
 *
 * @param chunk       A chunk object used for the span and payload
 * @param identifier  The identifier of the chunk
 * @param signer      The singer interface for signing the chunk
 */
function makeSingleOwnerChunk(chunk, identifier, signer) {
    return __awaiter(this, void 0, void 0, function* () {
        const chunkAddress = chunk.address();
        (0, cac_1.assertValidChunkData)(chunk.data, chunkAddress);
        const digest = (0, hash_1.keccak256Hash)(identifier, chunkAddress);
        const signature = yield (0, signer_1.sign)(signer, digest);
        const data = (0, serialize_1.serializeBytes)(identifier, signature, chunk.span(), chunk.payload());
        const address = makeSOCAddress(identifier, signer.address);
        return {
            data,
            identifier: () => identifier,
            signature: () => signature,
            span: () => chunk.span(),
            payload: () => chunk.payload(),
            address: () => address,
            owner: () => signer.address,
        };
    });
}
exports.makeSingleOwnerChunk = makeSingleOwnerChunk;
/**
 * Helper function to upload a chunk.
 *
 * It uses the Chunk API and calculates the address before uploading.
 *
 * @param ky              Ky instance
 * @param chunk           A chunk object
 * @param postageBatchId  Postage BatchId that will be assigned to uploaded data
 * @param options         Upload options
 */
function uploadSingleOwnerChunk(ky, chunk, postageBatchId, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const owner = (0, hex_1.bytesToHex)(chunk.owner());
        const identifier = (0, hex_1.bytesToHex)(chunk.identifier());
        const signature = (0, hex_1.bytesToHex)(chunk.signature());
        const data = (0, serialize_1.serializeBytes)(chunk.span(), chunk.payload());
        return socAPI.upload(ky, owner, identifier, signature, data, postageBatchId, options);
    });
}
exports.uploadSingleOwnerChunk = uploadSingleOwnerChunk;
/**
 * Helper function to create and upload SOC.
 *
 * @param ky              Ky instance
 * @param signer          The singer interface for signing the chunk
 * @param postageBatchId
 * @param identifier      The identifier of the chunk
 * @param data            The chunk data
 * @param options
 */
function uploadSingleOwnerChunkData(ky, signer, postageBatchId, identifier, data, options) {
    return __awaiter(this, void 0, void 0, function* () {
        (0, type_1.assertAddress)(postageBatchId);
        const cac = (0, cac_1.makeContentAddressedChunk)(data);
        const soc = yield makeSingleOwnerChunk(cac, identifier, signer);
        return uploadSingleOwnerChunk(ky, soc, postageBatchId, options);
    });
}
exports.uploadSingleOwnerChunkData = uploadSingleOwnerChunkData;
/**
 * Helper function to download SOC.
 *
 * @param url           The url of the Bee service
 * @param ownerAddress  The singer interface for signing the chunk
 * @param identifier    The identifier of the chunk
 */
function downloadSingleOwnerChunk(ky, ownerAddress, identifier) {
    return __awaiter(this, void 0, void 0, function* () {
        const address = makeSOCAddress(identifier, ownerAddress);
        const data = yield chunkAPI.download(ky, (0, hex_1.bytesToHex)(address));
        return makeSingleOwnerChunkFromData(data, address);
    });
}
exports.downloadSingleOwnerChunk = downloadSingleOwnerChunk;
