Initial commit: learning network protocols

This commit is contained in:
belred
2025-11-24 21:23:58 +03:00
commit 082867f9f2
23 changed files with 4496 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
<script>
import { writable } from 'svelte/store';
export let buffer = new Uint8Array([0x00]);
export let onBufferChange;
const bits = writable([]);
$: {
const value = buffer[0] || 0;
const newBits = [];
for (let i = 7; i >= 0; i--) {
newBits.push({
position: i,
isSet: !!(value & (1 << i))
});
}
bits.set(newBits);
}
function toggleBit(position) {
const currentValue = buffer[0] || 0;
const newValue = currentValue ^ (1 << position);
const newBuffer = new Uint8Array([newValue]);
onBufferChange(newBuffer);
}
</script>
<div class="bit-editor">
<h4>Редактор битов:</h4>
<div class="bit-positions">
<span class="label">Бит:</span>
{#each [7,6,5,4,3,2,1,0] as pos}
<span class="position">{pos}</span>
{/each}
</div>
<div class="bit-controls">
<span class="label">Значение:</span>
{#each $bits as bit}
<button
class:active={bit.isSet}
class="bit-toggle"
on:click={() => toggleBit(bit.position)}
>
{bit.isSet ? '1' : '0'}
</button>
{/each}
</div>
<div class="bit-help">
<small>Кликните на бит, чтобы переключить 0/1</small>
</div>
</div>
<style>
.bit-editor {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
margin: 16px 0;
}
.bit-positions, .bit-controls {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.label {
font-weight: bold;
min-width: 80px;
}
.position {
padding: 8px 12px;
background: #e0e0e0;
border-radius: 6px;
font-size: 0.9em;
min-width: 16px;
text-align: center;
}
.bit-toggle {
padding: 8px 12px;
border: 2px solid #2196F3;
background: white;
border-radius: 6px;
cursor: pointer;
min-width: 40px;
font-family: 'Courier New', monospace;
font-weight: bold;
transition: all 0.2s;
}
.bit-toggle.active {
background: #2196F3;
color: white;
}
.bit-toggle:hover {
transform: translateY(-2px);
}
.bit-help {
color: #666;
font-style: italic;
}
</style>
+274
View File
@@ -0,0 +1,274 @@
<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>
+69
View File
@@ -0,0 +1,69 @@
<script>
export let data = new Uint8Array([]);
export let highlightedBytes = [];
$: hexString = Array.from(data)
.map((byte, index) => {
const hex = byte.toString(16).padStart(2, '0').toUpperCase();
return { hex, index };
});
</script>
<div class="hex-viewer">
<div class="bytes">
{#each hexString as {hex, index}}
<span
class:highlighted={highlightedBytes.includes(index)}
class="byte"
>
{'0x'+hex}
</span>
{/each}
</div>
<div class="info">
Размер: {data.length} байт(а)
{#if data.length === 1}
| Десятичное: {data[0]} | Двоичное: {data[0].toString(2).padStart(8, '0')}
{/if}
</div>
</div>
<style>
.hex-viewer {
border: 1px solid #ccc;
border-radius: 8px;
padding: 16px;
background: #f9f9f9;
font-family: 'Courier New', monospace;
margin: 16px 0;
}
.bytes {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 12px;
}
.byte {
padding: 4px 8px;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
transition: all 0.2s;
}
.byte.highlighted {
background: #4CAF50;
color: white;
border-color: #388E3C;
}
.info {
font-size: 0.9em;
color: #666;
border-top: 1px solid #eee;
padding-top: 8px;
}
</style>
+143
View File
@@ -0,0 +1,143 @@
<script>
import { lessons } from "$lib/data/lessons";
export let lesson;
</script>
<div class="lesson-layout">
<header class="lesson-header">
<div class="header-content">
<a href="/" class="back-button">← Назад к урокам</a>
<h1>{lesson.title}</h1>
<div class="meta">
<span class="category">{lesson.category}</span>
<span class="difficulty">{lesson.difficulty}</span>
</div>
</div>
</header>
<div class="lesson-body">
<section class="theory-section">
<h2>Теория</h2>
<div class="theory-content">
{@html lesson.theory}
</div>
</section>
<section class="practice-section">
<h2>Практика</h2>
<div class="objective">
<h2>Задание</h2>
<p>{lesson.objective}</p>
</div>
<slot></slot>
</section>
</div>
</div>
<style>
.lesson-layout {
min-height: 100vh;
background: white;
}
.lesson-header {
background: #2196F3;
color: white;
padding: 20px;
margin-bottom: 0;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
}
.back-button {
color: white;
text-decoration: none;
font-size: 0.9em;
margin-bottom: 10px;
display: inline-block;
}
.back-button:hover {
text-decoration: underline;
}
.lesson-header h1 {
margin: 0 0 8px 0;
}
.meta {
display: flex;
gap: 12px;
}
.category, .difficulty {
background: rgba(255,255,255,0.2);
padding: 4px 8px;
border-radius: 12px;
font-size: 0.8em;
}
.lesson-body {
max-width: 1000px;
margin: 0 auto;
padding: 30px 20px;
}
.theory-section {
margin-bottom: 40px;
}
/*секция теории*/
.theory-section h2 {
color: #2196F3;
border-bottom: 2px solid #2196F3;
padding-bottom: 10px;
margin-bottom: 20px;
}
.theory-content {
line-height: 1.6;
background: #ffffff;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #51adf8;
}
.theory-content {
line-height: 1.6;
color: #333;
}
/*секция практики*/
.practice-section h2 {
color: rgb(234, 88, 9);
border-bottom: 2px solid #FF9800;
padding-bottom: 10px;
margin-bottom: 20px;
}
<!--.objective {
background: #fffcf6;
padding: 10px;
border-radius: 8px;
border-left: 4px solid #FF9800;
margin-bottom: 25px;
}
.objective p {
margin: 0;
font-size: 1.1em;
font-weight: 500;
}-->
@media (max-width: 768px) {
.lesson-body {
padding: 20px 15px;
}
}
</style>
+122
View File
@@ -0,0 +1,122 @@
<script>
export let message = '';
export let type = 'success'; //'success' или 'error'
export let duration = 3000;
export let onClose;
let isVisible = false;
import { onMount, onDestroy } from 'svelte';
onMount(() => {
setTimeout(() => {
isVisible = true;
}, 100);
if (duration > 0) {
const timer = setTimeout(() => {
close();
}, duration);
onDestroy(() => clearTimeout(timer));
}
});
function close() {
isVisible = false;
setTimeout(() => {
onClose && onClose();
}, 300);
}
</script>
{#if message}
<div class="notification-container">
<div class:visible={isVisible} class="notification {type}">
<div class="notification-content">
{#if type === 'success'}
<span class="icon"></span>
{:else}
<span class="icon"></span>
{/if}
<span class="text">{message}</span>
</div>
<button class="close-button" on:click={close}>×</button>
</div>
</div>
{/if}
<style>
.notification-container {
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
z-index: 1000;
pointer-events: none;
}
.notification {
background: white;
border-radius: 8px;
padding: 16px 20px;
margin: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
display: flex;
align-items: center;
gap: 12px;
max-width: 400px;
transform: translateY(-100px);
opacity: 0;
transition: all 0.3s ease;
pointer-events: all;
}
.notification.visible {
transform: translateY(0);
opacity: 1;
}
.notification.success {
border-left: 4px solid #4CAF50;
}
.notification.error {
border-left: 4px solid #f44336;
}
.notification-content {
display: flex;
align-items: center;
gap: 8px;
flex-grow: 1;
}
.icon {
font-size: 1.2em;
}
.text {
font-weight: 500;
}
.close-button {
background: none;
border: none;
font-size: 1.5em;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
color: #666;
}
.close-button:hover {
color: #000;
}
</style>