Initial commit: JustAMessenger v0.1.0

Серверная часть (Go):
- WebSocket сервер с бинарным протоколом
- XChaCha20-Poly1305 шифрование
- zstd сжатие с дедупликацией (64KB чанки)
- SQLite хранилище (WAL режим)
- Управление гильдиями, каналами, ролями
- Федерация между серверами (ed25519)
- REST API + WebSocket endpoints

Клиентская часть (Flutter):
- Material Design 3 тёмная тема (Discord-like)
- WebSocket соединение с сервером
- Экраны: сплэш, логин, домашний, гильдии, чат
- Модели: пользователи, гильдии, каналы, сообщения, роли
- Сервисы: соединение, API, криптография, тема
- Виджеты: иконки гильдий, сообщения, ввод чата
- Web сборка (PWA)

Документация:
- AGENTS.md — контекст для ИИ ассистентов
- docs/protocol.md — спецификация протокола
This commit is contained in:
SashegDev
2026-06-06 22:39:14 +00:00
commit 096c4d0a2d
40 changed files with 5054 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
package compression
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"io"
"sync"
"github.com/klauspost/compress/zstd"
)
var (
encOnce sync.Once
decOnce sync.Once
encoder *zstd.Encoder
decoder *zstd.Decoder
)
func getEncoder() *zstd.Encoder {
encOnce.Do(func() {
opts := []zstd.EOption{
zstd.WithEncoderLevel(zstd.SpeedBestCompression),
zstd.WithWindowSize(1 << 24),
}
enc, _ := zstd.NewWriter(nil, opts...)
encoder = enc
})
return encoder
}
func getDecoder() *zstd.Decoder {
decOnce.Do(func() {
dec, _ := zstd.NewReader(nil)
decoder = dec
})
return decoder
}
type Chunk struct {
Hash [32]byte
Data []byte
Offset int64
Size int
}
type DedupStore struct {
mu sync.RWMutex
chunks map[[32]byte][]byte
}
func NewDedupStore() *DedupStore {
return &DedupStore{chunks: make(map[[32]byte][]byte)}
}
func chunkData(data []byte, chunkSize int) []Chunk {
var chunks []Chunk
for offset := 0; offset < len(data); offset += chunkSize {
end := offset + chunkSize
if end > len(data) {
end = len(data)
}
chunk := data[offset:end]
hash := sha256.Sum256(chunk)
chunks = append(chunks, Chunk{
Hash: hash,
Data: chunk,
Offset: int64(offset),
Size: len(chunk),
})
}
return chunks
}
func (ds *DedupStore) Deduplicate(data []byte, chunkSize int) ([]Chunk, int, error) {
chunks := chunkData(data, chunkSize)
uniqueSize := 0
ds.mu.Lock()
defer ds.mu.Unlock()
for i, c := range chunks {
if existing, ok := ds.chunks[c.Hash]; ok {
chunks[i].Data = existing
chunks[i].Size = len(existing)
} else {
ds.chunks[c.Hash] = c.Data
uniqueSize += len(c.Data)
}
}
return chunks, uniqueSize, nil
}
func Compress(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, nil
}
var buf bytes.Buffer
enc := getEncoder()
enc.Reset(&buf)
if _, err := enc.Write(data); err != nil {
return nil, err
}
if err := enc.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func Decompress(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, nil
}
dec := getDecoder()
return dec.DecodeAll(data, nil)
}
type Compressor struct {
ChunkSize int
DedupStore *DedupStore
}
func New(chunkSize int) *Compressor {
if chunkSize <= 0 {
chunkSize = 65536
}
return &Compressor{
ChunkSize: chunkSize,
DedupStore: NewDedupStore(),
}
}
type CompressResult struct {
Data []byte
OriginalSize int64
CompressedSize int64
Chunks int
UniqueChunks int
}
func (c *Compressor) CompressFile(data []byte) (*CompressResult, error) {
originalSize := int64(len(data))
chunks, uniqueSize, err := c.DedupStore.Deduplicate(data, c.ChunkSize)
if err != nil {
return nil, err
}
var buf bytes.Buffer
buf.Write(binary.AppendVarint(nil, int64(len(chunks))))
for _, chunk := range chunks {
buf.Write(chunk.Hash[:])
buf.Write(binary.AppendVarint(nil, int64(chunk.Offset)))
buf.Write(binary.AppendVarint(nil, int64(chunk.Size)))
}
compressed, err := Compress(data)
if err != nil {
return nil, err
}
return &CompressResult{
Data: compressed,
OriginalSize: originalSize,
CompressedSize: int64(len(compressed)),
Chunks: len(chunks),
UniqueChunks: uniqueSize / c.ChunkSize,
}, nil
}
func (c *Compressor) DecompressFile(compressed []byte) ([]byte, error) {
return Decompress(compressed)
}
func CompressStream(r io.Reader, w io.Writer) error {
enc := getEncoder()
defer enc.Close()
enc.Reset(w)
if _, err := io.Copy(enc, r); err != nil {
return err
}
return enc.Close()
}
func DecompressStream(r io.Reader, w io.Writer) error {
dec := getDecoder()
defer dec.Close()
dec.Reset(r)
if _, err := io.Copy(w, dec); err != nil {
return err
}
return nil
}