diff --git a/src/lib/components/ByteEditor.svelte b/src/lib/components/ByteEditor.svelte new file mode 100644 index 0000000..ffff467 --- /dev/null +++ b/src/lib/components/ByteEditor.svelte @@ -0,0 +1,354 @@ + + +
+

Редактор {byteLength} байт

+ +
+
+ + +
+ + {#if byteLength > 1} +
+ + +
+ {/if} +
+ +
+ +
+ +
+ {#if error} +
{error}
+ {/if} +
+ +
+
Байты в памяти:
+
+ {#if buffer && startIndex + byteLength <= buffer.length} + {#each Array.from({length: byteLength}, (_, i) => i) as i} +
+ [{startIndex + i}] + 0x{buffer[startIndex + i].toString(16).padStart(2, '0').toUpperCase()} + ({buffer[startIndex + i]}) +
+ {/each} + {/if} +
+
+
+ + \ No newline at end of file diff --git a/src/lib/components/HexEditor.svelte b/src/lib/components/HexEditor.svelte new file mode 100644 index 0000000..66aff6d --- /dev/null +++ b/src/lib/components/HexEditor.svelte @@ -0,0 +1,180 @@ + + +
+

Редактор буфера:

+ +
+ {#each hexString as {hex, index}} + + {/each} +
+ +
+ +
+ + {#if showToolSelector && selectedByteIndex >= 0 && !isByteReadOnly(selectedByteIndex)} + + {/if} +
+ + \ No newline at end of file diff --git a/src/lib/components/ToolSelector.svelte b/src/lib/components/ToolSelector.svelte new file mode 100644 index 0000000..bac15c6 --- /dev/null +++ b/src/lib/components/ToolSelector.svelte @@ -0,0 +1,197 @@ + + +{#if show} +
+
+
+

Редактирование байта {startIndex}

+
+ +
+ {#each availableTools as tool} + + {/each} +
+ +
+ {#if selectedTool === 'bit'} + { + const newBuffer = new Uint8Array(buffer); + newBuffer[startIndex] = newByte[0]; + onBufferChange(newBuffer); + }} + /> + {:else if selectedToolData} + + {/if} +
+ + +
+
+{/if} + + \ No newline at end of file diff --git a/src/lib/components/WiresharkView.svelte b/src/lib/components/WiresharkView.svelte new file mode 100644 index 0000000..89d5bd6 --- /dev/null +++ b/src/lib/components/WiresharkView.svelte @@ -0,0 +1,174 @@ + + +
+

Структура Ethernet кадра

+ +
+
+ +
{formatMAC(destinationMAC)}
+ MAC адрес получателя +
+ +
+ +
{formatMAC(sourceMAC)}
+ MAC адрес отправителя +
+ +
+ +
{formatEtherType(etherType)}
+ Тип протокола +
+ +
+ +
{formatHex(frameData)}
+ Полезная нагрузка +
+ +
+ +
{formatHex(fcs)}
+ Контрольная сумма +
+
+ +
+

Структура кадра (64 байта):

+
+
+ Destination MAC + 6 bytes +
+
+ Source MAC + 6 bytes +
+
+ EtherType + 2 bytes +
+
+ Data + 46 bytes +
+
+ FCS + 4 bytes +
+
+
+
+ + \ No newline at end of file diff --git a/src/lib/data/lessons.js b/src/lib/data/lessons.js index bd450c2..adf7da0 100644 --- a/src/lib/data/lessons.js +++ b/src/lib/data/lessons.js @@ -6,6 +6,7 @@ export const lessons = [ category: 'Основы', difficulty: 'Начинающий', component: 'BitEditor', + additionalComponents: ['HexViewer'], theory: `

@@ -99,12 +100,90 @@ export const lessons = [ }, { id: 2, + slug: 'mac-address-edit', + title: 'MAC адреса - редактирование 6 байт', + category: 'Ethernet', + difficulty: 'Начинающий', + component: 'HexEditor', + + theory: ` +
+

+ MAC адреса +

+ +
+

Что такое MAC адрес?

+
    +
  • MAC адрес - уникальный идентификатор сетевого устройства
  • +
  • Формат: XX:XX:XX:XX:XX:XX (6 байтов)
  • +
  • Пример: 12:34:56:78:9A:BC
  • +
  • Первые 3 байта - идентификатор производителя (OUI)
  • +
  • Последние 3 байта - серийный номер устройства
  • +
+
+ +
+

Как редактировать MAC-адрес?

+ +

Форматы представления чисел:

+
    +
  • Hex (16-ричный): 0xFF, 0x1A3B
  • +
  • Decimal (10-ричный): 255, 6715
  • +
  • Binary (2-ичный): 11111111, 110100011011
  • +
  • Octal (8-ричный): 377, 15073
  • +
+ +

Endianness (порядок байт):

+
    +
  • Big Endian: старший байт по младшему адресу (сетевой порядок)
  • +
  • Little Endian: младший байт по младшему адресу (x86, ARM)
  • +
  • Пример: 0x12345678 +
      +
    • Big Endian: 12 34 56 78
    • +
    • Little Endian: 78 56 34 12
    • +
    +
  • +
+
+
+`, + + objective: 'Установите MAC адрес 12:34:56:78:9A:BC', + + initialBuffer: new Uint8Array(6), + + validate: (buffer) => { + const expectedMAC = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC]; + return expectedMAC.every((expected, i) => buffer[i] === expected); + }, + + hints: [ + 'Для HEX: разделите байт на две половинки по 4 бита. 0x34 = 00110100 → 0011(3) 0100(4)', + 'Кликайте на байты в HexView чтобы редактировать их', + 'Степени двойки по битам: 128,64,32,16,8,4,2,1' + ] + }, + { + id: 3, slug: 'ethernet-frame', title: 'Структура Ethernet кадра', category: 'Ethernet', difficulty: 'Начинающий', - component: 'EthernetBuilder', - + component: 'HexEditor', + additionalComponents: ['WiresharkView'], + wiresharkFields: [ + { name: 'Destination MAC', start: 0, length: 6, format: 'mac', color: '#e3f2fd' }, + { name: 'Source MAC', start: 6, length: 6, format: 'mac', color: '#fff3e0' }, + { name: 'EtherType', start: 12, length: 2, format: 'ethertype', color: '#e8f5e8' }, + { name: 'Data', start: 14, length: 46, format: 'hex', color: '#f3e5f5' }, + { name: 'FCS', start: 60, length: 4, format: 'hex', color: '#ffebee' } + ], + readOnlyRanges: [ + { start: 14, end: 59 }, //Data (46 байт) + { start: 60, end: 63 } //FCS (4 байта) - рассчитывается автоматически + ], + theory: `

diff --git a/src/lib/utils/bufferProcessor.js b/src/lib/utils/bufferProcessor.js new file mode 100644 index 0000000..9bd73f0 --- /dev/null +++ b/src/lib/utils/bufferProcessor.js @@ -0,0 +1,10 @@ +import { updateEthernetBuffer } from './protocols/ethernet.js'; + +export function processBuffer(buffer, lessonId) { + switch(lessonId) { + case 3: //Ethernet frame lesson + return updateEthernetBuffer(buffer); + default: + return buffer; + } +} \ No newline at end of file diff --git a/src/lib/utils/protocols/ethernet.js b/src/lib/utils/protocols/ethernet.js new file mode 100644 index 0000000..30329a9 --- /dev/null +++ b/src/lib/utils/protocols/ethernet.js @@ -0,0 +1,38 @@ +//функции для работы с Ethernet +export function calculateFCS(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 + ]; +} + +export function updateEthernetBuffer(buffer, changes = {}) { + const updatedBuffer = new Uint8Array(buffer); + + Object.keys(changes).forEach(key => { + const index = parseInt(key); + if (index >= 0 && index < updatedBuffer.length) { + updatedBuffer[index] = changes[key]; + } + }); + + const headerBuffer = updatedBuffer.slice(0, 14); + const fcs = calculateFCS(headerBuffer); + fcs.forEach((byte, i) => { + updatedBuffer[60 + i] = byte; + }); + + return updatedBuffer; +} \ No newline at end of file diff --git a/src/routes/lessons/[slug]/+page.svelte b/src/routes/lessons/[slug]/+page.svelte index 6362b50..8dd53bd 100644 --- a/src/routes/lessons/[slug]/+page.svelte +++ b/src/routes/lessons/[slug]/+page.svelte @@ -3,13 +3,16 @@ import { lessons } from '$lib/data/lessons'; import { progressStorage } from '$lib/utils/storage'; import { onMount, afterUpdate } from 'svelte'; + import { processBuffer } from '$lib/utils/bufferProcessor'; import HexViewer from '$lib/components/HexViewer.svelte'; import LessonLayout from '$lib/components/LessonLayout.svelte'; import Notification from '$lib/components/Notification.svelte'; import BitEditor from '$lib/components/BitEditor.svelte'; - import EthernetBuilder from '$lib/components/EthernetBuilder.svelte'; + //import EthernetBuilder from '$lib/components/EthernetBuilder.svelte'; + import HexEditor from '$lib/components/HexEditor.svelte'; + import WiresharkView from '$lib/components/WiresharkView.svelte'; let currentBuffer; let isCompleted = false; @@ -18,6 +21,8 @@ let notification = { show: false, message: '', type: 'success' }; let lesson = null; let lessonComponent = null; + let additionalComponents = []; + let readOnlyRanges = []; afterUpdate(() => { if ($page.params.slug) { @@ -25,6 +30,8 @@ if (lesson) { lessonComponent = getLessonComponent(lesson.component); + additionalComponents = getAdditionalComponents(lesson.additionalComponents || []); + readOnlyRanges = lesson.readOnlyRanges || []; } } }); @@ -33,13 +40,26 @@ switch(componentName) { case 'BitEditor': return BitEditor; - case 'EthernetBuilder': - return EthernetBuilder; + case 'HexEditor': + return HexEditor; default: - return; + return null; } } + function getAdditionalComponents(componentNames) { + return componentNames.map(name => { + switch(name) { + case 'HexViewer': + return HexViewer; + case 'WiresharkView': + return WiresharkView; + default: + return null; + } + }).filter(Boolean); + } + onMount(() => { //инициализация при загрузке на клиенте if ($page.params.slug) { @@ -47,6 +67,8 @@ if (lesson) { lessonComponent = getLessonComponent(lesson.component); + additionalComponents = getAdditionalComponents(lesson.additionalComponents || []); + readOnlyRanges = lesson.readOnlyRanges || []; currentBuffer = new Uint8Array(lesson.initialBuffer); isCompleted = progressStorage.isCompleted(lesson.id.toString()); } @@ -54,7 +76,13 @@ }); function handleBufferChange(newBuffer) { - currentBuffer = newBuffer; + //обрабатываем буфер через отдельный процессор + if (lesson) { + const processedBuffer = processBuffer(newBuffer, lesson.id); + currentBuffer = processedBuffer; + } else { + currentBuffer = newBuffer; + } } function showNextHint() { @@ -92,11 +120,17 @@ this={lessonComponent} bind:buffer={currentBuffer} onBufferChange={handleBufferChange} + readOnlyRanges={readOnlyRanges || []} /> - {#if currentBuffer} - - {/if} + {#each additionalComponents as Component} + {#if currentBuffer} + + {/if} + {/each}