mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-07 14:57:12 -04:00
Merge 71faa2bb0c
into 07eea0f484
This commit is contained in:
commit
e6a958bc99
11 changed files with 7085 additions and 8471 deletions
11
components.d.ts
vendored
11
components.d.ts
vendored
|
@ -83,6 +83,7 @@ declare module '@vue/runtime-core' {
|
||||||
GitMemo: typeof import('./src/tools/git-memo/git-memo.vue')['default']
|
GitMemo: typeof import('./src/tools/git-memo/git-memo.vue')['default']
|
||||||
'GitMemo.content': typeof import('./src/tools/git-memo/git-memo.content.md')['default']
|
'GitMemo.content': typeof import('./src/tools/git-memo/git-memo.content.md')['default']
|
||||||
HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default']
|
HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default']
|
||||||
|
HexConverter: typeof import('./src/tools/hex-converter/hex-converter.vue')['default']
|
||||||
HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default']
|
HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default']
|
||||||
'Home.page': typeof import('./src/pages/Home.page.vue')['default']
|
'Home.page': typeof import('./src/pages/Home.page.vue')['default']
|
||||||
HtmlEntities: typeof import('./src/tools/html-entities/html-entities.vue')['default']
|
HtmlEntities: typeof import('./src/tools/html-entities/html-entities.vue')['default']
|
||||||
|
@ -131,18 +132,24 @@ declare module '@vue/runtime-core' {
|
||||||
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
||||||
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||||
|
NCode: typeof import('naive-ui')['NCode']
|
||||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDivider: typeof import('naive-ui')['NDivider']
|
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
NH1: typeof import('naive-ui')['NH1']
|
NH1: typeof import('naive-ui')['NH1']
|
||||||
NH3: typeof import('naive-ui')['NH3']
|
NH3: typeof import('naive-ui')['NH3']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
|
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||||
NLayout: typeof import('naive-ui')['NLayout']
|
NLayout: typeof import('naive-ui')['NLayout']
|
||||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||||
NMenu: typeof import('naive-ui')['NMenu']
|
NMenu: typeof import('naive-ui')['NMenu']
|
||||||
|
NP: typeof import('naive-ui')['NP']
|
||||||
|
NRadio: typeof import('naive-ui')['NRadio']
|
||||||
|
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||||
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
NTable: typeof import('naive-ui')['NTable']
|
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||||
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
||||||
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
||||||
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
|
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
"@vueuse/head": "^1.0.0",
|
"@vueuse/head": "^1.0.0",
|
||||||
"@vueuse/router": "^10.0.0",
|
"@vueuse/router": "^10.0.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
|
"byte-data": "^19.0.1",
|
||||||
"change-case": "^4.1.2",
|
"change-case": "^4.1.2",
|
||||||
"colord": "^2.9.3",
|
"colord": "^2.9.3",
|
||||||
"composerize-ts": "^0.6.2",
|
"composerize-ts": "^0.6.2",
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
"figlet": "^1.7.0",
|
"figlet": "^1.7.0",
|
||||||
"figue": "^1.2.0",
|
"figue": "^1.2.0",
|
||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
|
"hex-array": "^1.0.0",
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.7.0",
|
||||||
"iarna-toml-esm": "^3.0.5",
|
"iarna-toml-esm": "^3.0.5",
|
||||||
"ibantools": "^4.3.3",
|
"ibantools": "^4.3.3",
|
||||||
|
@ -115,6 +117,7 @@
|
||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.2",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
|
"@types/hex-array": "^1.0.2",
|
||||||
"@types/jsdom": "^21.0.0",
|
"@types/jsdom": "^21.0.0",
|
||||||
"@types/lodash": "^4.14.192",
|
"@types/lodash": "^4.14.192",
|
||||||
"@types/mime-types": "^2.1.1",
|
"@types/mime-types": "^2.1.1",
|
||||||
|
|
14776
pnpm-lock.yaml
generated
14776
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -128,7 +128,7 @@ function activateOption(option: PaletteOption) {
|
||||||
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable />
|
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable />
|
||||||
|
|
||||||
<div v-for="(options, category) in filteredSearchResult" :key="category">
|
<div v-for="(options, category) in filteredSearchResult" :key="category">
|
||||||
<div ml-3 mt-3 text-sm font-bold text-primary op-60>
|
<div ml-3 mt-3 text-sm text-primary font-bold op-60>
|
||||||
{{ category }}
|
{{ category }}
|
||||||
</div>
|
</div>
|
||||||
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
|
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
|
||||||
|
|
250
src/tools/hex-converter/hex-converter.service.test.ts
Normal file
250
src/tools/hex-converter/hex-converter.service.test.ts
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { cleanHex, decodeNumber, decodeStruct, encodeStruct, getCoderFromTypeName, parseNumber } from './hex-converter.service';
|
||||||
|
|
||||||
|
describe('cleanHex', () => {
|
||||||
|
it('should remove 0x and \\x prefixes from a hex string', () => {
|
||||||
|
expect(cleanHex('0x1234')).toBe('1234');
|
||||||
|
expect(cleanHex('\\x1234')).toBe('1234');
|
||||||
|
expect(cleanHex('1234')).toBe('1234');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('decodeNumber', () => {
|
||||||
|
it('should convert a number to binary', () => {
|
||||||
|
expect(decodeNumber(10, 8, 'bin')).toBe('00001010');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert a number to hex', () => {
|
||||||
|
expect(decodeNumber(10, 8, 'hex')).toBe('0a');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert a number to character', () => {
|
||||||
|
expect(decodeNumber(65, 8, 'char')).toBe('A');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the number itself for decimal conversion', () => {
|
||||||
|
expect(decodeNumber(10, 8, 'dec')).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseNumber', () => {
|
||||||
|
it('should parse hexadecimal input correctly', () => {
|
||||||
|
expect(parseNumber('0x1F')).toBe(31);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse binary input correctly', () => {
|
||||||
|
expect(parseNumber('0b1101')).toBe(13);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse string input correctly into code points', () => {
|
||||||
|
expect(parseNumber('abc')).toEqual([97, 98, 99]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return number when input is already a number', () => {
|
||||||
|
expect(parseNumber(42)).toBe(42);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 0 for falsy input', () => {
|
||||||
|
expect(parseNumber('')).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCoderFromTypeName', () => {
|
||||||
|
it('should return correct coder options for a basic integer type', () => {
|
||||||
|
const result = getCoderFromTypeName('uint16');
|
||||||
|
expect(result.type.bits).toBe(16);
|
||||||
|
expect(result.type.signed).toBe(false);
|
||||||
|
expect(result.size).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle big-endian types correctly', () => {
|
||||||
|
const result = getCoderFromTypeName('int32be');
|
||||||
|
expect(result.type.bits).toBe(32);
|
||||||
|
expect(result.type.be).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct coder options for a float type', () => {
|
||||||
|
const result = getCoderFromTypeName('float');
|
||||||
|
expect(result.type.bits).toBe(32);
|
||||||
|
expect(result.type.fp).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct coder options for a character type', () => {
|
||||||
|
const result = getCoderFromTypeName('char[4]');
|
||||||
|
expect(result.type.bits).toBe(8);
|
||||||
|
expect(result.size).toBe(4);
|
||||||
|
expect(result.formatter(65, 8)).toBe('A');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('decodeStruct', () => {
|
||||||
|
it('should decode a flat struct correctly from a hex array', () => {
|
||||||
|
const struct = {
|
||||||
|
field1: 'uint16',
|
||||||
|
field2: 'char[3]',
|
||||||
|
};
|
||||||
|
const hexArray = new Uint8Array([0x01, 0x0, 0x61, 0x62, 0x63]);
|
||||||
|
|
||||||
|
const result = decodeStruct({ struct, hexArray });
|
||||||
|
expect(result.field1).toBe(1);
|
||||||
|
expect(result.field2).toBe('abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decode a nested struct correctly', () => {
|
||||||
|
const struct = {
|
||||||
|
type: 'int8',
|
||||||
|
header: {
|
||||||
|
version: 'uint8',
|
||||||
|
type: 'uint16be',
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
value: 'floatbe',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const hexArray = new Uint8Array([0xFF, 0x01, 0x00, 0x02, 0x3F, 0x80, 0x0, 0x0]); // float 1.0
|
||||||
|
|
||||||
|
const result = decodeStruct({ struct, hexArray });
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: -1,
|
||||||
|
header: { version: 1, type: 2 },
|
||||||
|
payload: { value: 1.0 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw on bad buffer length', () => {
|
||||||
|
const struct = {
|
||||||
|
type: 'int8',
|
||||||
|
header: {
|
||||||
|
version: 'uint8',
|
||||||
|
type: 'uint16be',
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
value: 'floatbe',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const hexArray = new Uint8Array([0xFF, 0x01, 0x00, 0x02, 0x3F, 0x80]); // missing last two bytes
|
||||||
|
|
||||||
|
expect(() => decodeStruct({ struct, hexArray })).toThrowError(
|
||||||
|
'Bad buffer length reading value(floatbe) at offset 4',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when decoding an unsupported array type', () => {
|
||||||
|
const struct = {
|
||||||
|
list: ['uint8'],
|
||||||
|
};
|
||||||
|
const hexArray = new Uint8Array([0x01, 0x02, 0x03]);
|
||||||
|
|
||||||
|
expect(() => decodeStruct({ struct, hexArray })).toThrowError(
|
||||||
|
'Cannot decode a struct with array',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should throw an error when decoding an unsized array type', () => {
|
||||||
|
const struct = {
|
||||||
|
list: 'uint8[]',
|
||||||
|
};
|
||||||
|
const hexArray = new Uint8Array([0x01, 0x02, 0x03]);
|
||||||
|
|
||||||
|
expect(() => decodeStruct({ struct, hexArray })).toThrowError(
|
||||||
|
'Unsupported unsized array: uint8[]',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('encodeStruct', () => {
|
||||||
|
it('should encode a flat struct correctly', () => {
|
||||||
|
const struct = {
|
||||||
|
field1: 'uint8',
|
||||||
|
field2: 'char[3]',
|
||||||
|
};
|
||||||
|
const jsonObject = {
|
||||||
|
field1: 1,
|
||||||
|
field2: 'abc',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = encodeStruct({ struct, jsonObject });
|
||||||
|
expect(result).toEqual(new Uint8Array([0x01, 0x61, 0x62, 0x63]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should encode a nested struct correctly', () => {
|
||||||
|
const struct = {
|
||||||
|
type: 'int8',
|
||||||
|
header: {
|
||||||
|
version: 'uint8',
|
||||||
|
type: 'uint16be',
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
value: 'floatbe',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const jsonObject = {
|
||||||
|
type: -1,
|
||||||
|
header: { version: 1, type: 2 },
|
||||||
|
payload: { value: 1.0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = encodeStruct({ struct, jsonObject });
|
||||||
|
expect(result).toEqual(new Uint8Array([0xFF, 0x01, 0x00, 0x02, 0x3F, 0x80, 0x0, 0x0]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if array size is incorrect', () => {
|
||||||
|
const struct = {
|
||||||
|
field1: 'uint8',
|
||||||
|
field2: 'char[3]',
|
||||||
|
};
|
||||||
|
const jsonObject = {
|
||||||
|
field1: 1,
|
||||||
|
field2: 'abcd', // Too long
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => encodeStruct({ struct, jsonObject })).toThrowError(
|
||||||
|
'Unexpected array size \'field2\'=\'97,98,99,100\' expected 3 elements',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if value type is incorrect', () => {
|
||||||
|
const struct = {
|
||||||
|
field1: 'uint8',
|
||||||
|
field2: 'char[3]',
|
||||||
|
};
|
||||||
|
const jsonObject = {
|
||||||
|
field1: 1,
|
||||||
|
field2: 123, // Invalid type
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => encodeStruct({ struct, jsonObject })).toThrowError(
|
||||||
|
'Unexpected non array \'field2\'=\'123\'',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if value type is incorrect', () => {
|
||||||
|
const struct = {
|
||||||
|
field1: 'uint8',
|
||||||
|
field2: 'int16',
|
||||||
|
};
|
||||||
|
const jsonObject = {
|
||||||
|
field1: 1,
|
||||||
|
field2: '123', // Invalid type
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => encodeStruct({ struct, jsonObject })).toThrowError(
|
||||||
|
'Unexpected array size \'field2\'=\'49,50,51\' expected 1 elements',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should encode multiple integer types correctly', () => {
|
||||||
|
const struct = {
|
||||||
|
a: 'uint8',
|
||||||
|
b: 'uint16',
|
||||||
|
c: 'uint32',
|
||||||
|
};
|
||||||
|
const jsonObject = {
|
||||||
|
a: 1,
|
||||||
|
b: 513,
|
||||||
|
c: 67305985,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = encodeStruct({ struct, jsonObject });
|
||||||
|
expect(result).toEqual(new Uint8Array([0x01, 0x01, 0x02, 0x01, 0x02, 0x03, 0x04]));
|
||||||
|
});
|
||||||
|
});
|
193
src/tools/hex-converter/hex-converter.service.ts
Normal file
193
src/tools/hex-converter/hex-converter.service.ts
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
import { pack, unpack } from 'byte-data';
|
||||||
|
|
||||||
|
export type Conversion = 'dec' | 'bin' | 'hex' | 'char';
|
||||||
|
|
||||||
|
export function cleanHex(hex: string): string {
|
||||||
|
return hex.replace(/\\x|0x/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeNumber(n: number, bits: number, conv: Conversion) {
|
||||||
|
if (conv === 'bin') {
|
||||||
|
return n.toString(2).padStart(bits, '0');
|
||||||
|
}
|
||||||
|
if (conv === 'hex') {
|
||||||
|
return n.toString(16).padStart(bits / 4, '0');
|
||||||
|
}
|
||||||
|
if (conv === 'char') {
|
||||||
|
return String.fromCodePoint(n);
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseNumber(input: string | number): number | number[] {
|
||||||
|
if (!input) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (typeof input === 'number') {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^0x[0-9a-fA-F]+$/.test(input)) {
|
||||||
|
return Number.parseInt(input.substring(2), 16); // Parse as hexadecimal
|
||||||
|
}
|
||||||
|
else if (/^0b[01]+$/.test(input)) {
|
||||||
|
return Number.parseInt(input.substring(2), 2); // Parse as binary
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...input].map(c => c.codePointAt(0) || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeModelAndObject(model: Record<string, any>, object: Record<string, any>): Record<string, any> {
|
||||||
|
const merged: Record<string, any> = {};
|
||||||
|
|
||||||
|
for (const key in model) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(model, key)) {
|
||||||
|
if (Array.isArray(object[key])) {
|
||||||
|
merged[key] = [model[key], object[key].map(parseNumber)];
|
||||||
|
}
|
||||||
|
else if (typeof object[key] === 'object' && !Array.isArray(object[key])) {
|
||||||
|
merged[key] = mergeModelAndObject(model[key], object[key]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
merged[key] = [model[key], parseNumber(object[key])];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CoderOption {
|
||||||
|
type: {
|
||||||
|
bits: number
|
||||||
|
fp?: boolean
|
||||||
|
be?: boolean
|
||||||
|
signed?: boolean
|
||||||
|
}
|
||||||
|
size: number
|
||||||
|
formatter: (n: number, bits: number) => string | number
|
||||||
|
join?: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getCoderFromTypeName(typeName: string): CoderOption {
|
||||||
|
if (typeName.includes('[]')) {
|
||||||
|
throw new Error(`Unsupported unsized array: ${typeName}`);
|
||||||
|
}
|
||||||
|
const [, prefix, baseTypeName, bigEndian, arraySize] = /^((?:0x|0b)?)(u?int\d+|w?char|half|float|double)(be)?(?:\[(\d+)\])?$/.exec(typeName) || [];
|
||||||
|
let conv = 'dec';
|
||||||
|
if (prefix === '0x') {
|
||||||
|
conv = 'hex';
|
||||||
|
}
|
||||||
|
if (prefix === '0b') {
|
||||||
|
conv = 'bin';
|
||||||
|
}
|
||||||
|
|
||||||
|
const arraySizeNumber = Number.isNaN(Number(arraySize)) ? 1 : Number(arraySize);
|
||||||
|
if (baseTypeName === 'char' || baseTypeName === 'wchar') {
|
||||||
|
return {
|
||||||
|
type: {
|
||||||
|
bits: baseTypeName === 'char' ? 8 : 16,
|
||||||
|
be: !!bigEndian,
|
||||||
|
},
|
||||||
|
size: arraySizeNumber,
|
||||||
|
formatter: n => String.fromCodePoint(n),
|
||||||
|
join: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (baseTypeName === 'float' || baseTypeName === 'double' || baseTypeName === 'half') {
|
||||||
|
return {
|
||||||
|
type: {
|
||||||
|
bits: baseTypeName === 'float' ? 32 : (baseTypeName === 'double' ? 64 : 16),
|
||||||
|
be: !!bigEndian,
|
||||||
|
fp: true,
|
||||||
|
},
|
||||||
|
size: arraySizeNumber,
|
||||||
|
formatter: n => n,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, unsigned, bits] = /^(u?)int(\d+)$/.exec(baseTypeName || '') || [];
|
||||||
|
return {
|
||||||
|
type: {
|
||||||
|
bits: Number(bits),
|
||||||
|
be: !!bigEndian,
|
||||||
|
signed: !unsigned,
|
||||||
|
},
|
||||||
|
size: arraySizeNumber,
|
||||||
|
formatter: (n, bits) => decodeNumber(n, bits, conv as Conversion),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeStruct({ struct, hexArray }: { struct: object; hexArray: Uint8Array }) {
|
||||||
|
let offset = 0;
|
||||||
|
const readMember = (obj: any) => {
|
||||||
|
const result: Record<string, any> = {};
|
||||||
|
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
if (Array.isArray(obj[key])) {
|
||||||
|
throw new TypeError(`Cannot decode a struct with array (key=${key}). Must be expressed as string with fixed length`);
|
||||||
|
}
|
||||||
|
else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
||||||
|
result[key] = readMember(obj[key]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const coderOption = getCoderFromTypeName(obj[key]);
|
||||||
|
const arr = [];
|
||||||
|
for (let i = 0; i < coderOption.size; i++) {
|
||||||
|
const dataSize = Math.ceil(coderOption.type.bits / 8);
|
||||||
|
if (offset + dataSize > hexArray.length) {
|
||||||
|
throw new Error(`Bad buffer length reading ${key}(${obj[key]}) at offset ${offset}`);
|
||||||
|
}
|
||||||
|
arr.push(coderOption.formatter(unpack(hexArray, coderOption.type, offset), coderOption.type.bits));
|
||||||
|
offset += dataSize;
|
||||||
|
}
|
||||||
|
if (coderOption.join) {
|
||||||
|
result[key] = arr.join('');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result[key] = coderOption.size > 1 ? arr : arr[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
return readMember(struct);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeStruct({ struct, jsonObject }: { struct: object; jsonObject: object }): Uint8Array {
|
||||||
|
const mergedObject = mergeModelAndObject(struct, jsonObject);
|
||||||
|
|
||||||
|
let buffer: Array<number> = [];
|
||||||
|
const writeMember = (obj: any) => {
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
||||||
|
writeMember(obj[key]);
|
||||||
|
}
|
||||||
|
else if (Array.isArray(obj[key])) {
|
||||||
|
const [typeName, value] = obj[key];
|
||||||
|
const coderOption = getCoderFromTypeName(typeName);
|
||||||
|
if (coderOption.size > 1 && !Array.isArray(value)) {
|
||||||
|
throw new TypeError(`Unexpected non array '${key}'='${value}'`);
|
||||||
|
}
|
||||||
|
if (Array.isArray(value) && value.length !== coderOption.size) {
|
||||||
|
throw new TypeError(`Unexpected array size '${key}'='${value}' expected ${coderOption.size} elements`);
|
||||||
|
}
|
||||||
|
const valueArr = !Array.isArray(value) ? [value] : value;
|
||||||
|
for (let i = 0; i < coderOption.size; i++) {
|
||||||
|
buffer = [...buffer, ...pack(valueArr[i], coderOption.type)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new TypeError(`Unexpected '${key}'='${obj[key]}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
writeMember(mergedObject);
|
||||||
|
|
||||||
|
return new Uint8Array(buffer);
|
||||||
|
}
|
303
src/tools/hex-converter/hex-converter.vue
Normal file
303
src/tools/hex-converter/hex-converter.vue
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import hexArray from 'hex-array';
|
||||||
|
import { packArray, packString, unpackArray, unpackString } from 'byte-data';
|
||||||
|
import JSON5 from 'json5';
|
||||||
|
import { type Conversion, cleanHex, decodeNumber, decodeStruct, encodeStruct } from './hex-converter.service';
|
||||||
|
import { useValidation } from '@/composable/validation';
|
||||||
|
|
||||||
|
const mode = ref<'simple' | 'struct'>('simple');
|
||||||
|
|
||||||
|
const bits = ref(32);
|
||||||
|
const floatingPoint = ref(false);
|
||||||
|
const signed = ref(false);
|
||||||
|
const bigEndian = ref(false);
|
||||||
|
const decodeAs = ref<'dec' | 'bin' | 'hexa' | 'char' | 'utf8'>('dec');
|
||||||
|
|
||||||
|
const uppercase = ref(false);
|
||||||
|
const grouping = ref(1);
|
||||||
|
const rowlength = ref(0);
|
||||||
|
|
||||||
|
const hexInput = ref('');
|
||||||
|
const decodedOutput = computed(() => {
|
||||||
|
try {
|
||||||
|
const buffer = hexArray.fromString(cleanHex(hexInput.value));
|
||||||
|
if (decodeAs.value === 'utf8') {
|
||||||
|
return unpackString(buffer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return unpackArray(buffer, {
|
||||||
|
bits: bits.value,
|
||||||
|
fp: floatingPoint.value,
|
||||||
|
signed: signed.value,
|
||||||
|
be: bigEndian.value,
|
||||||
|
}, 0, buffer.length, true).map(n => decodeNumber(n, bits.value, decodeAs.value as Conversion)).join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
return e.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const numberInput = ref('');
|
||||||
|
const encodedOutput = computed(() => {
|
||||||
|
try {
|
||||||
|
const values = numberInput.value.split(/\s+/).map(Number);
|
||||||
|
return hexArray.toString(
|
||||||
|
new Uint8Array(
|
||||||
|
packArray(values, {
|
||||||
|
bits: bits.value,
|
||||||
|
fp: floatingPoint.value,
|
||||||
|
signed: signed.value,
|
||||||
|
be: bigEndian.value,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
uppercase: uppercase.value,
|
||||||
|
grouping: grouping.value,
|
||||||
|
rowlength: rowlength.value,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
return e.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringInput = ref('');
|
||||||
|
const utf8Output = computed(() => {
|
||||||
|
try {
|
||||||
|
return hexArray.toString(
|
||||||
|
new Uint8Array(packString(stringInput.value)),
|
||||||
|
{
|
||||||
|
uppercase: uppercase.value,
|
||||||
|
grouping: grouping.value,
|
||||||
|
rowlength: rowlength.value,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
return e.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const structDefinition = ref(`{
|
||||||
|
x: "int32",
|
||||||
|
y: "int32",
|
||||||
|
}`);
|
||||||
|
const structDefinitionValidation = useValidation({
|
||||||
|
source: structDefinition,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
message: 'Struct definition is not a valid JSON',
|
||||||
|
validator: value => JSON5.parse(value.trim()),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const hexStructInput = ref('');
|
||||||
|
const decodedStructOutput = computed(() => {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(
|
||||||
|
decodeStruct({
|
||||||
|
struct: JSON5.parse(structDefinition.value),
|
||||||
|
hexArray: hexArray.fromString(cleanHex(hexStructInput.value)),
|
||||||
|
}),
|
||||||
|
null, 2);
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
return e.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const jsonStructInput = ref('');
|
||||||
|
const encodedStructOutput = computed(() => {
|
||||||
|
try {
|
||||||
|
return hexArray.toString(
|
||||||
|
encodeStruct({
|
||||||
|
struct: JSON5.parse(structDefinition.value),
|
||||||
|
jsonObject: JSON5.parse(jsonStructInput.value),
|
||||||
|
}), {
|
||||||
|
uppercase: uppercase.value,
|
||||||
|
grouping: grouping.value,
|
||||||
|
rowlength: rowlength.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
return e.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-radio-group v-model:value="mode" name="radiogroup" mb-2 flex justify-center>
|
||||||
|
<n-space>
|
||||||
|
<n-radio
|
||||||
|
value="simple"
|
||||||
|
label="Simple Encoder/Decoder"
|
||||||
|
/>
|
||||||
|
<n-radio
|
||||||
|
value="struct"
|
||||||
|
label="C/C++ typed struct"
|
||||||
|
/>
|
||||||
|
</n-space>
|
||||||
|
</n-radio-group>
|
||||||
|
|
||||||
|
<div v-if="mode === 'simple'">
|
||||||
|
<c-card title="Hex Options" mb-1>
|
||||||
|
<c-select
|
||||||
|
v-model:value="decodeAs"
|
||||||
|
label="Decode/Encode As:"
|
||||||
|
label-position="left" mb-1
|
||||||
|
:options="[{ value: 'dec', label: 'Decimal' }, { value: 'bin', label: 'Binary' }, { value: 'hex', label: 'Hexadecimal' }, { value: 'char', label: 'Char/ASCII' }, { value: 'utf8', label: 'UTF8 string' }]"
|
||||||
|
/>
|
||||||
|
<n-space v-if="decodeAs !== 'utf8'" align="baseline" justify="center">
|
||||||
|
<n-form-item label="Bits:" label-placement="left">
|
||||||
|
<n-input-number v-model:value="bits" :min="1" style="width: 6em" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<n-checkbox v-model:checked="floatingPoint">
|
||||||
|
Floating Point
|
||||||
|
</n-checkbox>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<n-checkbox v-model:checked="signed">
|
||||||
|
Signed
|
||||||
|
</n-checkbox>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<n-checkbox v-model:checked="bigEndian">
|
||||||
|
Big Endian
|
||||||
|
</n-checkbox>
|
||||||
|
</n-form-item>
|
||||||
|
</n-space>
|
||||||
|
</c-card>
|
||||||
|
<c-card title="Hex Data Decoder" mb-3>
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="hexInput"
|
||||||
|
multiline
|
||||||
|
placeholder="Put your Hex data here..."
|
||||||
|
rows="2"
|
||||||
|
label="Hex Data to decode"
|
||||||
|
raw-text
|
||||||
|
mb-5
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-form-item label="Your decoded values:">
|
||||||
|
<textarea-copyable :value="decodedOutput" />
|
||||||
|
</n-form-item>
|
||||||
|
</c-card>
|
||||||
|
<c-card v-if="decodeAs !== 'utf8'" title="Hex Data Encoder" mt-3>
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="numberInput"
|
||||||
|
multiline
|
||||||
|
placeholder="Put your Numbers array here..."
|
||||||
|
rows="2"
|
||||||
|
label="Numbers array to encode"
|
||||||
|
raw-text
|
||||||
|
mb-5
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-form-item label="Your encoded numbers array as Hex:">
|
||||||
|
<textarea-copyable :value="encodedOutput" />
|
||||||
|
</n-form-item>
|
||||||
|
</c-card>
|
||||||
|
<c-card v-if="decodeAs === 'utf8'" title="Hex UTF8 String Encoder" mt-3>
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="stringInput"
|
||||||
|
multiline
|
||||||
|
placeholder="Put your text here..."
|
||||||
|
rows="5"
|
||||||
|
label="String to encode"
|
||||||
|
raw-text
|
||||||
|
mb-5
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-form-item label="Your encoded string as UTF8 Hex:">
|
||||||
|
<textarea-copyable :value="utf8Output" />
|
||||||
|
</n-form-item>
|
||||||
|
</c-card>
|
||||||
|
<c-card title="Hex Encoding Output" mt-1>
|
||||||
|
<n-space align="baseline" justify="center">
|
||||||
|
<n-form-item label="Uppercase" label-placement="left">
|
||||||
|
<n-switch v-model:value="uppercase" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="Group by" label-placement="left">
|
||||||
|
<n-input-number v-model:value="grouping" :min="0" style="width: 6em" mr-1 /> digits (0 = no grouping)
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="Split as rows by" label-placement="left">
|
||||||
|
<n-input-number v-model:value="rowlength" :min="0" style="width: 6em" mr-1 /> group of digits (0 = no rows)
|
||||||
|
</n-form-item>
|
||||||
|
</n-space>
|
||||||
|
</c-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="mode === 'struct'">
|
||||||
|
<c-card title="Struct Definition">
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="structDefinition"
|
||||||
|
multiline
|
||||||
|
placeholder="Put your Struct defintion here..."
|
||||||
|
rows="5"
|
||||||
|
label="C/C+ like struct definition"
|
||||||
|
raw-text
|
||||||
|
mb-5
|
||||||
|
:validation="structDefinitionValidation"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Instructions</summary>
|
||||||
|
<n-p>
|
||||||
|
Define you struct definition in JSON format: keys = struct member names ; value = type
|
||||||
|
<br>
|
||||||
|
Types syntax: u?int{size}(be)? | float(be)? | double(be)? | char | wchar(be)? | <type>[{array size}]
|
||||||
|
<br>
|
||||||
|
where "u" means "unsigned" ; "be" means "Big Endian" ; {size} is number of bits ; {array size} fixed size for arrays
|
||||||
|
<br>
|
||||||
|
can prefix integer with 0x or 0b to display as hex and binary
|
||||||
|
</n-p>
|
||||||
|
</details>
|
||||||
|
</c-card>
|
||||||
|
<c-card title="Hex Struct Decoder" m-t-1>
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="hexStructInput"
|
||||||
|
multiline
|
||||||
|
placeholder="Put your Hex data here..."
|
||||||
|
rows="5"
|
||||||
|
label="Hex Data to decode"
|
||||||
|
raw-text
|
||||||
|
mb-5
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-form-item label="Your decoded values:">
|
||||||
|
<textarea-copyable :value="decodedStructOutput" />
|
||||||
|
</n-form-item>
|
||||||
|
</c-card>
|
||||||
|
<c-card title="Hex Struct Encoder" m-t-1>
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="jsonStructInput"
|
||||||
|
multiline
|
||||||
|
placeholder="Put your Struct to encode here..."
|
||||||
|
rows="5"
|
||||||
|
label="Struct json to encode"
|
||||||
|
raw-text
|
||||||
|
mb-5
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-form-item label="Your encoded struct as Hex:">
|
||||||
|
<textarea-copyable :value="encodedStructOutput" />
|
||||||
|
</n-form-item>
|
||||||
|
</c-card>
|
||||||
|
<c-card title="Hex Encoding Output" mt-1>
|
||||||
|
<n-space align="baseline" justify="center">
|
||||||
|
<n-form-item label="Uppercase" label-placement="left">
|
||||||
|
<n-switch v-model:value="uppercase" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="Group by" label-placement="left">
|
||||||
|
<n-input-number v-model:value="grouping" :min="0" style="width: 6em" mr-1 /> digits (0 = no grouping)
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="Split as rows by" label-placement="left">
|
||||||
|
<n-input-number v-model:value="rowlength" :min="0" style="width: 6em" mr-1 /> group of digits (0 = no rows)
|
||||||
|
</n-form-item>
|
||||||
|
</n-space>
|
||||||
|
</c-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
12
src/tools/hex-converter/index.ts
Normal file
12
src/tools/hex-converter/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Binary } from '@vicons/tabler';
|
||||||
|
import { defineTool } from '../tool';
|
||||||
|
|
||||||
|
export const tool = defineTool({
|
||||||
|
name: 'Hex Encoder/Decoder',
|
||||||
|
path: '/hex-converter',
|
||||||
|
description: 'Encode and decode Hex buffers to number (bits, endianess, sign or floating point or chars) and structures',
|
||||||
|
keywords: ['hex', 'encode', 'decode', 'endianess', 'float', 'bits', 'hex', 'struct'],
|
||||||
|
component: () => import('./hex-converter.vue'),
|
||||||
|
icon: Binary,
|
||||||
|
createdAt: new Date('2025-02-09'),
|
||||||
|
});
|
|
@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter';
|
||||||
import { tool as base64StringConverter } from './base64-string-converter';
|
import { tool as base64StringConverter } from './base64-string-converter';
|
||||||
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
||||||
import { tool as emailNormalizer } from './email-normalizer';
|
import { tool as emailNormalizer } from './email-normalizer';
|
||||||
|
import { tool as hexConverter } from './hex-converter';
|
||||||
|
|
||||||
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
||||||
|
|
||||||
|
@ -98,6 +99,7 @@ export const toolsByCategory: ToolCategory[] = [
|
||||||
components: [
|
components: [
|
||||||
dateTimeConverter,
|
dateTimeConverter,
|
||||||
baseConverter,
|
baseConverter,
|
||||||
|
hexConverter,
|
||||||
romanNumeralConverter,
|
romanNumeralConverter,
|
||||||
base64StringConverter,
|
base64StringConverter,
|
||||||
base64FileConverter,
|
base64FileConverter,
|
||||||
|
|
|
@ -151,7 +151,7 @@ function onSearchInput() {
|
||||||
>
|
>
|
||||||
<div flex-1 truncate>
|
<div flex-1 truncate>
|
||||||
<slot name="displayed-value">
|
<slot name="displayed-value">
|
||||||
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full lh-normal color-current @input="onSearchInput">
|
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full color-current lh-normal @input="onSearchInput">
|
||||||
<span v-else-if="selectedOption" lh-normal>
|
<span v-else-if="selectedOption" lh-normal>
|
||||||
{{ selectedOption.label }}
|
{{ selectedOption.label }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -39,7 +39,7 @@ const headers = computed(() => {
|
||||||
<template>
|
<template>
|
||||||
<div class="relative overflow-x-auto rounded">
|
<div class="relative overflow-x-auto rounded">
|
||||||
<table class="w-full border-collapse text-left text-sm text-gray-500 dark:text-gray-400" role="table" :aria-label="description">
|
<table class="w-full border-collapse text-left text-sm text-gray-500 dark:text-gray-400" role="table" :aria-label="description">
|
||||||
<thead v-if="!hideHeaders" class="bg-#ffffff uppercase text-gray-700 dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
|
<thead v-if="!hideHeaders" class="bg-#ffffff text-gray-700 uppercase dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
|
||||||
<tr>
|
<tr>
|
||||||
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
|
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
|
||||||
{{ header.label }}
|
{{ header.label }}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue