274 lines
6.9 KiB
Svelte
274 lines
6.9 KiB
Svelte
<script>
|
|
export let buffer;
|
|
export let onBufferChange;
|
|
|
|
let destinationMAC = 'AA:BB:CC:DD:EE:FF';
|
|
let sourceMAC = 'AA:BB:CC:DD:EE:FF';
|
|
let etherType = '0800';
|
|
let errors = { dest: '', src: '' };
|
|
let currentChecksum = [0x00, 0x00, 0x00, 0x00];
|
|
|
|
function validateMAC(mac) {
|
|
const macRegex = /^[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5}$/;
|
|
if (!macRegex.test(mac)) {
|
|
return 'Неверный формат MAC или некорректные значения';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function calculateChecksum(headerBuffer) {
|
|
//имитация контрольной суммы (на практике CRC32)
|
|
let sum = 0;
|
|
for (let i = 0; i < headerBuffer.length; i++) {
|
|
sum = (sum + headerBuffer[i]) & 0xFFFFFFFF;
|
|
}
|
|
|
|
for (let i = 0; i < 46; i++) {
|
|
sum = (sum + 0x00) & 0xFFFFFFFF;
|
|
}
|
|
|
|
return [
|
|
(sum >> 24) & 0xFF,
|
|
(sum >> 16) & 0xFF,
|
|
(sum >> 8) & 0xFF,
|
|
sum & 0xFF
|
|
];
|
|
}
|
|
|
|
function updateBuffer() {
|
|
const destError = validateMAC(destinationMAC);
|
|
const srcError = validateMAC(sourceMAC);
|
|
errors = { dest: destError, src: srcError };
|
|
|
|
if (destError || srcError) {
|
|
onBufferChange(new Uint8Array(0));
|
|
return;
|
|
}
|
|
|
|
const headerBuffer = new Uint8Array(14);
|
|
|
|
const destBytes = destinationMAC.split(':').map(byte => parseInt(byte, 16));
|
|
destBytes.forEach((byte, i) => headerBuffer[i] = byte);
|
|
|
|
const srcBytes = sourceMAC.split(':').map(byte => parseInt(byte, 16));
|
|
srcBytes.forEach((byte, i) => headerBuffer[i + 6] = byte);
|
|
|
|
headerBuffer[12] = parseInt(etherType.substring(0, 2), 16);
|
|
headerBuffer[13] = parseInt(etherType.substring(2, 4), 16);
|
|
|
|
currentChecksum = calculateChecksum(headerBuffer);
|
|
|
|
const fullBuffer = new Uint8Array(64);
|
|
fullBuffer.set(headerBuffer, 0);
|
|
for (let i = 14; i < 60; i++) {
|
|
fullBuffer[i] = 0x00;
|
|
}
|
|
fullBuffer.set(new Uint8Array(currentChecksum), 60);
|
|
|
|
onBufferChange(fullBuffer);
|
|
}
|
|
|
|
function handleMACInput(field, value) {
|
|
const upperValue = value.toUpperCase();
|
|
if (field === 'dest') {
|
|
destinationMAC = upperValue;
|
|
} else {
|
|
sourceMAC = upperValue;
|
|
}
|
|
updateBuffer();
|
|
}
|
|
|
|
function handleTypeChange(value) {
|
|
etherType = value;
|
|
updateBuffer();
|
|
}
|
|
|
|
$: if (buffer) {
|
|
//инициализация при первом рендере
|
|
if (buffer.length === 0) {
|
|
updateBuffer();
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="ethernet-builder">
|
|
<h3>Конструктор Ethernet кадра</h3>
|
|
|
|
<div class="field">
|
|
<label>Destination MAC:</label>
|
|
<input
|
|
type="text"
|
|
bind:value={destinationMAC}
|
|
on:input={(e) => handleMACInput('dest', e.target.value)}
|
|
placeholder="AA:BB:CC:DD:EE:FF"
|
|
class:error={errors.dest}
|
|
/>
|
|
{#if errors.dest}
|
|
<span class="error-text">{errors.dest}</span>
|
|
{/if}
|
|
<span class="hint">Адрес получателя</span>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Source MAC:</label>
|
|
<input
|
|
type="text"
|
|
bind:value={sourceMAC}
|
|
on:input={(e) => handleMACInput('src', e.target.value)}
|
|
placeholder="AA:BB:CC:DD:EE:FF"
|
|
class:error={errors.src}
|
|
/>
|
|
{#if errors.src}
|
|
<span class="error-text">{errors.src}</span>
|
|
{/if}
|
|
<span class="hint">Адрес отправителя</span>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>EtherType:</label>
|
|
<select bind:value={etherType} on:change={(e) => handleTypeChange(e.target.value)}>
|
|
<option value="0800">0x0800 - IPv4</option>
|
|
<option value="0806">0x0806 - ARP</option>
|
|
<option value="86DD">0x86DD - IPv6</option>
|
|
</select>
|
|
<span class="hint">Тип инкапсулированного протокола</span>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Data (46 байт):</label>
|
|
<div class="data-field">
|
|
<code>0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00</code>
|
|
</div>
|
|
<span class="hint">Полезная нагрузка (заполнена нулями)</span>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>FCS (Frame Check Sequence):</label>
|
|
<div class="checksum-field">
|
|
<code>
|
|
{'0x' + currentChecksum.map(b => b.toString(16).padStart(2,'0')).join('').toUpperCase()}
|
|
</code>
|
|
</div>
|
|
<span class="hint">Контрольная сумма (рассчитывается автоматически)</span>
|
|
</div>
|
|
|
|
<div class="frame-preview">
|
|
<h4>Структура кадра (64 байта):</h4>
|
|
<div class="frame-layout">
|
|
<div class="frame-field" style="background: #e3f2fd;">
|
|
<span>Destination MAC</span>
|
|
<small>6 bytes</small>
|
|
</div>
|
|
<div class="frame-field" style="background: #fff3e0;">
|
|
<span>Source MAC</span>
|
|
<small>6 bytes</small>
|
|
</div>
|
|
<div class="frame-field" style="background: #e8f5e8;">
|
|
<span>EtherType</span>
|
|
<small>2 bytes</small>
|
|
</div>
|
|
<div class="frame-field" style="background: #f3e5f5;">
|
|
<span>Data</span>
|
|
<small>46 bytes</small>
|
|
</div>
|
|
<div class="frame-field" style="background: #ffebee;">
|
|
<span>FCS</span>
|
|
<small>4 bytes</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.ethernet-builder {
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.field {
|
|
margin: 15px 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
}
|
|
|
|
.field label {
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
|
|
.field input, .field select {
|
|
padding: 8px 12px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 4px;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
|
|
.field input:focus, .field select:focus {
|
|
border-color: #2196F3;
|
|
outline: none;
|
|
}
|
|
|
|
.error {
|
|
border-color: #f44336 !important;
|
|
background-color: #ffebee;
|
|
}
|
|
|
|
.error-text {
|
|
color: #f44336;
|
|
font-size: 0.8em;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.hint {
|
|
font-size: 0.8em;
|
|
color: #666;
|
|
font-style: italic;
|
|
}
|
|
|
|
.data-field, .checksum-field {
|
|
background: #f5f5f5;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
border: 1px solid #ddd;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.frame-preview {
|
|
margin-top: 20px;
|
|
padding-top: 15px;
|
|
border-top: 1px solid #eee;
|
|
}
|
|
|
|
.frame-layout {
|
|
display: flex;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.frame-field {
|
|
flex: 1;
|
|
padding: 10px;
|
|
text-align: center;
|
|
border-right: 1px solid #ccc;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.frame-field:last-child {
|
|
border-right: none;
|
|
}
|
|
|
|
.frame-field span {
|
|
display: block;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.frame-field small {
|
|
color: #666;
|
|
}
|
|
</style> |