i2pchat

Secure, anonymous peer-to-peer chat over I2P

View the Project on GitHub

Architecture

I2PChat is a Qt/C++ instant messaging application that operates over the I2P anonymous network using the SAM v3 protocol bridge. It provides encrypted, decentralized chat with file transfer capabilities.

Directory Layout

I2PChat.pro           — Qt project file
src/backend/          — Core logic: network, protocol, user management
src/gui/              — Qt Widgets UI layer
docs/                 — Local documentation
site/                 — GitHub Pages website
www/                  — Web profile assets (b32.i2p profiles)
qss/                  — Qt stylesheets
sounds/               — Notification sound files
templates/            — HTML templates
packaging/            — Packaging & platform helpers

src/backend — Layer Overview

Session Layer (I2P Rendezvous)

FileRole
SessionControllerSingle SAM session lifecycle — connects to SAM, creates a STREAM session, handles hello handshake, session create, dest generate, naming lookup, and auto-reconnect on disconnect. One per application.
I2PStreamOne per logical connection. Wraps a QTcpSocket to SAM, performs STREAM CONNECT (outgoing) or STREAM ACCEPT (incoming), handles the SAM v3 handshake, reassembles fragmented SAM data, fires signDataReceived.

Stream Management Layer

FileRole
ConnectionManagerOwns the SessionController and all CI2PStream objects. Creates/destroys streams, manages stream ID allocation, dispatches incoming streams to CCore.
I2PSamMessageAnalyserParses SAM v3 protocol text lines into SAM_MESSAGE structs.

Core / Application Logic

FileRole
CoreCentral orchestrator. Owns ConnectionManager, UserManager, CProtocol, CFileTransferManager, CSoundManager. Manages the online state machine.
ProtocolWire protocol for I2PChat peers. Handles slotInputUnknown (initial CHATSYSTEM handshake) and slotInputKnown (parsed known packets).
PacketManagerPer-stream reassembly buffer. Slices complete packets by the 4-byte hex length header and emits signAPacketIsComplete.

User Management

FileRole
UserPer-contact model: nickname, I2P destination, protocol version, chat message history, unsent message queue, online state machine.
UserManagerPersisted user list (users.config), CRUD, sorting.
UserBlockManagerBlock list persistence and lookup.
UnsentChatMessageStorageOffline message queue persisted to disk.

File Transfer

FileRole
FileTransferManagerManages active send/receive transfers, protocol version guard.
FileTransferSendOutgoing file transfer — streams file chunks over a dedicated I2P stream.
FileTransferReceiveIncoming file transfer — reassembles chunks, writes to disk.
HttpOverStreamObjectHTTP parsing over I2P stream (for web profile).

Utilities

FileRole
BaseBase64/Base32 encoding/decoding.
ChatmessageChangerText transformation pipeline (emoticons, formatting).
DebugMessageManagerIn-memory ring buffer of debug messages.
SoundManagerAudio notification dispatcher.
TextEmotionChangerSmiley/emoticon substitution.

src/gui — UI Layer

FileRole
form_MainMain window: user list, online status combo, tray icon, context menus.
form_chatwidgetChat window per contact: message display (HTML), send field, emoticon/color/font controls.
form_settingsguiSettings dialog (7 tabs).
form_aboutAbout dialog.
form_DebugMessagesDebug log viewer.
form_fileReceiveIncoming file transfer dialog.
form_fileSendOutgoing file transfer dialog.
form_newUserAdd-contact dialog.
form_renameRename-contact dialog.
form_TopicSubscribe(Disabled) Topic subscription.
form_UserSearch(Disabled) User search.

Data Flow — Connection Establishment

Outgoing (you connect to a known contact)

User clicks "Online"
  → form_Main::onlineComboBoxChanged()
    → CCore::setOnlineStatus(ONLINE)
      [if OFFLINE] → mNextOnlineStatus=ONLINE, mCurrentOnlineStatus=TRYTOCONNECT
        → CCore::init()
          → CConnectionManager::doCreateSession(STREAM, privkey, opts)
            → CSessionController::doConnect()           // TCP to SAM
              → slotConnected() → HELLO VERSION...
                → slotReadFromSocket()
                  → HELLO_REPLAY OK → doSessionCreate()
                    → SESSION STATUS OK → signSessionStreamStatusOK(true)
                      → CCore::slotStreamControllerStatusOK(true)
                        → createStreamObjectsForAllUsers()
                          → createStreamObjectForUser(user)
                            → CI2PStream::doConnect(dest)   // per-contact
                              → SAM handshake → STREAM CONNECT
                                → STREAM STATUS OK → signStreamStatusReceived(OK)
                                  → CCore::slotStreamStatusReceived
                                    → CProtocol::newConnectionChat(ID)
                                      → send "CHATSYSTEM\t0.6\n" over SAM stream
    → remote side receives FIRSTPACKET, processes slotInputUnknown
      → remote sends back version, client info → slotInputKnown replies

Incoming (a contact connects to you)

CI2PStream (ACCEPT mode) receives data
  → slotReadFromSocket() → STREAM STATUS + DESTINATION
    → signModeAcceptIncomingStream(ID)
      → CConnectionManager::slotModeAcceptIncomingStream()
        → stream moved from StreamIncomingListener → allStreams
        → emit signIncomingStream(stream)
          → CCore::slotIncomingStream()
            → connect stream → signDataReceived → mProtocol → slotInputUnknown
        → remaining buffered data sent as signDataReceived
          → CProtocol::slotInputUnknown()
            → detects "CHATSYSTEM\t" → parses version
            → if ID<0: CProtocol::newConnectionChat(ID)  // send our FIRSTPACKET back
            → adds/updates user via UserManager
            → setStreamTypeToKnown() → creates PacketManager
              → subsequent data routed through PacketManager → slotInputKnown

Online State Machine

                setOnlineStatus(ONLINE/AWAY/...)
USEROFFLINE —————————————————————————→ USERTRYTOCONNECT
  ↑                                                              ↓
                                                      session OK ↓
                                                                 ↓
                                                 USERTRYTOCONNECT ——→ mNextOnlineStatus
                                                                         ↓
                                                                 session ↓
                                                                 created ↓
                                                                         ↓
                                                               USERONLINE / USERAWAY /
                                                               USERWANTTOCHAT / USERINVISIBLE /
                                                               USERDONT_DISTURB
                                                                         ↓
  ←—————— setOnlineStatus(OFFLINE) ←——————————————└————————————←
                               stopCore()

Note: stopCore() is only called on explicit offline — not on SAM disconnect.
Session loss sets mCurrentOnlineStatus = USEROFFLINE without cleaning up streams.

SAM Protocol Integration

I2PChat communicates with the local I2P router over TCP via the SAM v3 bridge (127.0.0.1:7656). All SAM I/O is managed by SessionController and CI2PStream.

SAM v3 Handshake Flow

I2PChat → SAM:  HELLO VERSION MIN=3.1 MAX=3.3
SAM → I2PChat:  HELLO REPLY RESULT=OK VERSION=3.3

I2PChat → SAM:  SESSION CREATE ID=i2pchat STYLE=STREAM DESTINATION=TRANSIENT
                [SIGNATURE_TYPE=EdDSA_SHA512_Ed25519]
SAM → I2PChat:  SESSION STATUS RESULT=OK DESTINATION=<base64>

Key Management

Socket Stealing (v3)

After SESSION CREATE, the control socket is still a command channel. When STREAM CONNECT or STREAM ACCEPT executes, SAM marks the handler as stolen and the TCP connection becomes a transparent bidirectional pipe to the I2P peer. From I2PChat's perspective, the QTcpSocket is now a raw stream.

Naming Lookup

I2PChat → SAM:  NAMING LOOKUP NAME=<b32|hostname>
SAM → I2PChat:  NAMING REPLY RESULT=OK NAME=<name> VALUE=<base64>

Used to resolve .b32.i2p addresses and hostnames to full I2P destinations.

Full SAM v3 protocol reference: https://i2p.net/en/docs/api/samv3/

Limits

ItemValue
SAM HELLO timeout75 s
Stream send size1–32768 bytes per message
Stale session expiry10 min
Control socket timeout60 s (first cmd), 3 min (subsequent)

Protocol Packet Format

Offset  Size  Description
0       4     Packet length in bytes (hex ASCII), excludes these 4 bytes
4       4     Tag (ASCII) — identifies command or message type
8       N     Payload (tag-dependent)
TagMeaning
0000–0002Response: echo of ping, client version, client name
0003Chat message
0004–0008Online status updates
0009–0010Protocol version, max file transfer protocol
0011–0014User info (nick, gender, age, interests)
0015–0016Block notifications
0017–0018Min file transfer protocol, avatar image
0019Avatar changed notification
1000–1008Commands: PING, GET_CLIENTVERSION/NAME/USER_ONLINESTATUS etc.

License

Licensed under GPLv3.