* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
import { util } from '@kit.ArkTS';
* 文件元数据接口
*/
export interface FileMetadata {
originalExtension: string;
timestamp: string;
version: string;
algorithm?: string;
fileSize?: number;
tag?: string;
hash?: string;
}
* 元数据解析结果
*/
export interface MetadataParseResult {
success: boolean;
metadata?: FileMetadata;
data?: ArrayBuffer;
tag?: ArrayBuffer;
message?: string;
}
* 元数据助手类 - 统一处理元数据头的创建和解析
*/
export class MetadataHelper {
private static readonly MAGIC_NUMBER = 0x4D455441;
private static readonly CURRENT_VERSION = '1.0';
private static readonly MIN_DATA_SIZE = 8;
* 创建带元数据头的二进制数据
* @param data 原始数据
* @param metadata 元数据
* @returns 包含元数据头的二进制数据
*/
static createDataWithMetadata(data: ArrayBuffer, metadata: FileMetadata): ArrayBuffer {
const finalMetadata: FileMetadata = {
...metadata,
version: metadata.version || this.CURRENT_VERSION
};
const metadataStr = JSON.stringify(finalMetadata);
const encoder = new util.TextEncoder();
const headerData = encoder.encode(metadataStr);
const headerSize = 8 + headerData.length;
const totalSize = headerSize + data.byteLength;
const combinedData = new Uint8Array(totalSize);
const dataView = new DataView(combinedData.buffer);
dataView.setUint32(0, this.MAGIC_NUMBER, false);
dataView.setUint32(4, headerData.length, false);
combinedData.set(headerData, 8);
combinedData.set(new Uint8Array(data), headerSize);
return combinedData.buffer;
}
* 解析带元数据头的二进制数据
* @param combinedData 包含元数据头的二进制数据
* @returns 解析结果
*/
static parseDataWithMetadata(combinedData: ArrayBuffer): MetadataParseResult {
try {
if (combinedData.byteLength < this.MIN_DATA_SIZE) {
return {
success: false,
message: '数据太小,无法包含有效的元数据头'
};
}
const dataView = new DataView(combinedData);
const magicNumber = dataView.getUint32(0, false);
if (magicNumber !== this.MAGIC_NUMBER) {
return {
success: false,
message: '无效的文件格式:魔数不匹配'
};
}
const metadataLength = dataView.getUint32(4, false);
if (metadataLength === 0 || metadataLength > 1024 * 1024) {
return {
success: false,
message: `无效的元数据长度: ${metadataLength}`
};
}
const headerSize = 8 + metadataLength;
if (headerSize >= combinedData.byteLength) {
return {
success: false,
message: '元数据头损坏:没有足够的空间容纳原始数据'
};
}
const metadataBytes = new Uint8Array(combinedData, 8, metadataLength);
const decoder = new util.TextDecoder('utf-8');
const metadataStr = decoder.decode(metadataBytes);
const metadata = JSON.parse(metadataStr) as FileMetadata;
if (!metadata.version) {
return {
success: false,
message: '元数据缺少版本信息'
};
}
const data = combinedData.slice(headerSize);
const result: MetadataParseResult = {
success: true,
metadata,
data,
message: '元数据头解析成功'
};
if (metadata.tag) {
try {
result.tag = this.base64ToArrayBuffer(metadata.tag);
} catch (error) {
console.warn('Tag 解码失败:', error);
}
}
return result;
} catch (error) {
return {
success: false,
message: `元数据解析失败: ${error.message}`
};
}
}
* 检查数据是否包含有效的元数据头
* @param data 要检查的数据
* @returns 是否包含有效的元数据头
*/
static hasValidMetadataHeader(data: ArrayBuffer): boolean {
try {
if (data.byteLength < this.MIN_DATA_SIZE) {
return false;
}
const dataView = new DataView(data);
const magicNumber = dataView.getUint32(0, false);
return magicNumber === this.MAGIC_NUMBER;
} catch {
return false;
}
}
* 从文件名创建默认元数据
* @param fileName 文件名
* @param algorithm 算法类型
* @param fileSize 文件大小(可选)
* @returns 默认元数据对象
*/
static createDefaultMetadata(
fileName: string,
algorithm: string,
fileSize?: number,
tag?: ArrayBuffer,
hash?: ArrayBuffer
): FileMetadata {
const extension = this.getFileExtension(fileName);
const metadata: FileMetadata = {
originalExtension: extension,
timestamp: new Date().toISOString(),
version: this.CURRENT_VERSION,
algorithm: algorithm,
fileSize: fileSize,
};
if (tag && tag.byteLength > 0) {
metadata.tag = this.arrayBufferToBase64(tag);
}
if (hash && hash.byteLength > 0) {
metadata.hash = this.arrayBufferToBase64(hash);
}
return metadata;
}
* ArrayBuffer 转换为 Base64 字符串
*/
static arrayBufferToBase64(buffer: ArrayBuffer): string {
try {
const base64Helper = new util.Base64Helper();
const uint8Array = new Uint8Array(buffer);
return base64Helper.encodeToStringSync(uint8Array);
} catch (error) {
console.error('ArrayBuffer 转 Base64 失败:', error);
return '';
}
}
* Base64 字符串转换为 ArrayBuffer
*/
static base64ToArrayBuffer(base64: string): ArrayBuffer {
try {
const base64Helper = new util.Base64Helper();
const uint8Array = base64Helper.decodeSync(base64);
return uint8Array.buffer;
} catch (error) {
console.error('Base64 转 ArrayBuffer 失败:', error);
return new ArrayBuffer(0);
}
}
* 从文件名中提取扩展名
* @param fileName 文件名
* @returns 文件扩展名(不含点)
*/
static getFileExtension(fileName: string): string {
if (!fileName) {
return '';
}
const parts = fileName.split('.');
return parts.length > 1 ? parts[parts.length - 1] : '';
}
* 从文件名中提取基础名称(去除扩展名)
* @param fileName 文件名
* @returns 基础文件名
*/
static getFileBaseName(fileName: string): string {
if (!fileName) {
return '';
}
const lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex === -1) {
return fileName;
}
return fileName.substring(0, lastDotIndex);
}
* 生成带元数据头的文件名
* @param originalFileName 原始文件名
* @param suffix 后缀(如:encrypted, signature)
* @param originalExtension 原始扩展名(可选,从元数据中获取)
* @returns 生成的文件名
*/
static generateMetadataFileName(originalFileName: string, suffix: string, originalExtension?: string): string {
const baseName = this.getFileBaseName(originalFileName);
if (originalExtension) {
return `${baseName}.${originalExtension}.${suffix}`;
} else {
return `${baseName}.${suffix}`;
}
}
}