From fd0e2afddbfa7d21a8cd62eab0682d321333ad4c Mon Sep 17 00:00:00 2001 From: juancwu <46619361+juancwu@users.noreply.github.com> Date: Sun, 7 Dec 2025 21:17:52 -0500 Subject: [PATCH] improve server relay and client --- .gitignore | 3 + cmd/client/main.go | 406 ++++++++++++++++++++++++++++++++---------- cmd/server/main.go | 101 +++++++++-- go.mod | 6 + go.sum | 26 +++ pkg/protocol/types.go | 28 ++- 6 files changed, 461 insertions(+), 109 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..524098e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +contacts.json +identity.json diff --git a/cmd/client/main.go b/cmd/client/main.go index 4aa5a0f..de94f0c 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -7,66 +7,149 @@ import ( "encoding/json" "fmt" "log" + "os" + "sort" "strings" "gossip/pkg/protocol" "github.com/charmbracelet/bubbles/textarea" + "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/gorilla/websocket" "golang.org/x/crypto/nacl/box" ) +const ( + IdentityFile = "identity.json" + ContactsFile = "contacts.json" +) + +type AppMode int + +const ( + ModeNormal AppMode = iota + ModeInsert + ModeConnect + ModeList +) + type KeyPair struct { Public *[32]byte Private *[32]byte PubHex string } +type StoredIdentity struct { + PublicKey string `json:"public_key"` + PrivateKey string `json:"private_key"` +} + +func loadOrGenerateKeys() (KeyPair, error) { + fileBytes, err := os.ReadFile(IdentityFile) + if err == nil { + var stored StoredIdentity + if err := json.Unmarshal(fileBytes, &stored); err == nil { + pubBytes, _ := hex.DecodeString(stored.PublicKey) + privBytes, _ := hex.DecodeString(stored.PrivateKey) + var pubKey, privKey [32]byte + copy(pubKey[:], pubBytes) + copy(privKey[:], privBytes) + return KeyPair{Public: &pubKey, Private: &privKey, PubHex: stored.PublicKey}, nil + } + } + pub, priv, err := box.GenerateKey(rand.Reader) + if err != nil { + return KeyPair{}, err + } + kp := KeyPair{Public: pub, Private: priv, PubHex: hex.EncodeToString(pub[:])} + saveData := StoredIdentity{PublicKey: kp.PubHex, PrivateKey: hex.EncodeToString(priv[:])} + bytes, _ := json.MarshalIndent(saveData, "", " ") + _ = os.WriteFile(IdentityFile, bytes, 0600) + return kp, nil +} + +type ContactBook map[string]string + +func loadContacts() ContactBook { + cb := make(ContactBook) + data, err := os.ReadFile(ContactsFile) + if err == nil { + json.Unmarshal(data, &cb) + } + return cb +} + +func (cb ContactBook) save() { + data, _ := json.MarshalIndent(cb, "", " ") + os.WriteFile(ContactsFile, data, 0600) +} + type model struct { - conn *websocket.Conn - keys KeyPair - targetHex string - targetPub *[32]byte - viewport viewport.Model - textarea textarea.Model - messages []string - err error + conn *websocket.Conn + keys KeyPair + contacts ContactBook + + mode AppMode + myAccountNum string + + targetAcc string + targetName string + targetHex string + targetPub *[32]byte + + viewport viewport.Model + textarea textarea.Model + + connectInput textinput.Model + + contactList []string + listCursor int + + messages []string + err error } type wsMsg protocol.Message func main() { - pub, priv, err := box.GenerateKey(rand.Reader) + keys, err := loadOrGenerateKeys() if err != nil { log.Fatal(err) } - keys := KeyPair{Public: pub, Private: priv, PubHex: hex.EncodeToString(pub[:])} + contacts := loadContacts() c, _, err := websocket.DefaultDialer.Dial("ws://localhost:8080/ws", nil) if err != nil { - log.Fatal("Could not connect to server:", err) + log.Fatal("Could not connect:", err) } defer c.Close() - loginMsg := protocol.Message{Type: "login", Sender: keys.PubHex} - c.WriteJSON(loginMsg) + c.WriteJSON(protocol.Message{Type: protocol.TypeLogin, Sender: keys.PubHex}) ta := textarea.New() - ta.Placeholder = "Type a message (or /connect )..." - ta.Focus() - ta.SetHeight(2) + ta.Placeholder = "Type message..." + ta.SetHeight(3) ta.ShowLineNumbers = false + ta.Blur() + + ti := textinput.New() + ti.Placeholder = "Enter Name or Account #" + ti.CharLimit = 20 + ti.Width = 30 vp := viewport.New(80, 20) - vp.SetContent(fmt.Sprintf("Your ID: %s\nTo start, type: /connect ", keys.PubHex)) + vp.SetContent("Welcome. Press 'i' to type, 'c' to connect, 'l' for contacts.") m := model{ - conn: c, - keys: keys, - textarea: ta, - viewport: vp, + conn: c, + keys: keys, + contacts: contacts, + textarea: ta, + connectInput: ti, + viewport: vp, + mode: ModeNormal, } p := tea.NewProgram(m) @@ -88,96 +171,239 @@ func main() { } } -func (m model) Init() tea.Cmd { - return textarea.Blink -} +func (m model) Init() tea.Cmd { return textarea.Blink } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var ( - tiCmd tea.Cmd - vpCmd tea.Cmd - ) + var cmd tea.Cmd + var cmds []tea.Cmd switch msg := msg.(type) { case tea.KeyMsg: - switch msg.Type { - case tea.KeyCtrlC, tea.KeyEsc: - return m, tea.Quit - case tea.KeyEnter: - input := m.textarea.Value() - if input == "" { + switch m.mode { + + case ModeNormal: + switch msg.String() { + case "ctrl+c", "q": + return m, tea.Quit + case "i": + m.mode = ModeInsert + m.textarea.Focus() + m.textarea.CursorEnd() return m, nil - } - - if dest, ok := strings.CutPrefix(input, "/connect "); ok { - m.targetHex = dest - - decoded, _ := hex.DecodeString(dest) - var keyArr [32]byte - copy(keyArr[:], decoded) - m.targetPub = &keyArr - - m.messages = append(m.messages, "System: Target set to "+dest[:8]+"...") + case "c": + m.mode = ModeConnect + m.connectInput.Reset() + m.connectInput.Focus() + return m, nil + case "l": + m.mode = ModeList + m.contactList = []string{} + for k := range m.contacts { + m.contactList = append(m.contactList, k) + } + sort.Strings(m.contactList) + m.listCursor = 0 + return m, nil + case "d": + m.targetPub = nil + m.targetAcc = "" + m.targetName = "" + m.messages = append(m.messages, "System: Disconnected.") m.viewport.SetContent(strings.Join(m.messages, "\n")) - m.textarea.Reset() m.viewport.GotoBottom() return m, nil - } - - if m.targetPub == nil { - m.messages = append(m.messages, "System: No target set! Use /connect first.") - } else { - var nonce [24]byte - rand.Read(nonce[:]) - encrypted := box.Seal(nonce[:], []byte(input), &nonce, m.targetPub, m.keys.Private) - b64Content := base64.StdEncoding.EncodeToString(encrypted) - - outMsg := protocol.Message{ - Type: "msg", - Sender: m.keys.PubHex, - Target: m.targetHex, - Content: b64Content, + case "enter": + input := strings.TrimSpace(m.textarea.Value()) + if input == "" { + return m, nil } - - m.conn.WriteJSON(outMsg) - - m.messages = append(m.messages, "Me: "+input) + if m.targetPub == nil { + m.messages = append(m.messages, "System: ⛔ Not connected! Press 'c' to connect.") + } else { + var nonce [24]byte + rand.Read(nonce[:]) + encrypted := box.Seal(nonce[:], []byte(input), &nonce, m.targetPub, m.keys.Private) + outMsg := protocol.Message{ + Type: protocol.TypeMsg, + Sender: m.keys.PubHex, + Target: m.targetHex, + Content: base64.StdEncoding.EncodeToString(encrypted), + } + m.conn.WriteJSON(outMsg) + m.messages = append(m.messages, "Me: "+input) + m.textarea.Reset() + } + m.viewport.SetContent(strings.Join(m.messages, "\n")) + m.viewport.GotoBottom() } - m.textarea.Reset() - m.viewport.SetContent(strings.Join(m.messages, "\n")) - m.viewport.GotoBottom() + case ModeInsert: + if msg.String() == "esc" { + m.mode = ModeNormal + m.textarea.Blur() + return m, nil + } + + case ModeConnect: + if msg.String() == "esc" { + m.mode = ModeNormal + m.connectInput.Blur() + return m, nil + } + if msg.String() == "enter" { + target := m.connectInput.Value() + if num, ok := m.contacts[target]; ok { + m.targetName = target + target = num + } else { + m.targetName = target + } + req := protocol.Message{ + Type: protocol.TypeLookup, + Sender: m.keys.PubHex, + Content: target, + } + m.conn.WriteJSON(req) + m.messages = append(m.messages, fmt.Sprintf("System: Connecting to %s...", m.targetName)) + m.viewport.SetContent(strings.Join(m.messages, "\n")) + m.viewport.GotoBottom() + m.mode = ModeNormal + m.connectInput.Blur() + return m, nil + } + + case ModeList: + switch msg.String() { + case "esc", "q": + m.mode = ModeNormal + return m, nil + case "k", "up": + if m.listCursor > 0 { + m.listCursor-- + } + case "j", "down": + if m.listCursor < len(m.contactList)-1 { + m.listCursor++ + } + case "enter": + if len(m.contactList) > 0 { + selection := m.contactList[m.listCursor] + targetNum := m.contacts[selection] + m.targetName = selection + req := protocol.Message{ + Type: protocol.TypeLookup, + Sender: m.keys.PubHex, + Content: targetNum, + } + m.conn.WriteJSON(req) + m.messages = append(m.messages, fmt.Sprintf("System: Connecting to %s...", m.targetName)) + m.viewport.SetContent(strings.Join(m.messages, "\n")) + m.viewport.GotoBottom() + } + m.mode = ModeNormal + return m, nil + } } case wsMsg: - encBytes, _ := base64.StdEncoding.DecodeString(msg.Content) - - senderBytes, _ := hex.DecodeString(msg.Sender) - var senderKey [32]byte - copy(senderKey[:], senderBytes) - - var nonce [24]byte - copy(nonce[:], encBytes[:24]) - decrypted, ok := box.Open(nil, encBytes[24:], &nonce, &senderKey, m.keys.Private) - - if !ok { - m.messages = append(m.messages, "System: Failed to decrypt message from "+msg.Sender[:8]) - } else { - m.messages = append(m.messages, fmt.Sprintf("Friend (%s): %s", msg.Sender[:8], string(decrypted))) + switch msg.Type { + case protocol.TypeIdentity: + m.myAccountNum = msg.Content + m.messages = append(m.messages, fmt.Sprintf("System: 🟢 Online. Account #%s", m.myAccountNum)) + case protocol.TypeLookupResponse: + if msg.Content == "" { + m.messages = append(m.messages, fmt.Sprintf("System: ❌ Account #%s not found.", msg.Target)) + } else { + m.targetAcc = msg.Target + m.targetHex = msg.Content + decoded, _ := hex.DecodeString(m.targetHex) + var keyArr [32]byte + copy(keyArr[:], decoded) + m.targetPub = &keyArr + displayName := m.targetName + if displayName == "" { + displayName = m.targetAcc + } + m.messages = append(m.messages, fmt.Sprintf("System: 🔒 Secure channel established with %s", displayName)) + } + case protocol.TypeMsg: + encBytes, _ := base64.StdEncoding.DecodeString(msg.Content) + senderBytes, _ := hex.DecodeString(msg.Sender) + var senderKey [32]byte + copy(senderKey[:], senderBytes) + var nonce [24]byte + copy(nonce[:], encBytes[:24]) + decrypted, ok := box.Open(nil, encBytes[24:], &nonce, &senderKey, m.keys.Private) + if !ok { + m.messages = append(m.messages, "System: ⚠️ Decryption failed!") + } else { + m.messages = append(m.messages, fmt.Sprintf("%s: %s", m.targetName, string(decrypted))) + } } m.viewport.SetContent(strings.Join(m.messages, "\n")) m.viewport.GotoBottom() } - m.textarea, tiCmd = m.textarea.Update(msg) - m.viewport, vpCmd = m.viewport.Update(msg) - return m, tea.Batch(tiCmd, vpCmd) + m.viewport, cmd = m.viewport.Update(msg) + cmds = append(cmds, cmd) + + if m.mode == ModeInsert { + m.textarea, cmd = m.textarea.Update(msg) + cmds = append(cmds, cmd) + } + + if m.mode == ModeConnect { + m.connectInput, cmd = m.connectInput.Update(msg) + cmds = append(cmds, cmd) + } + + return m, tea.Batch(cmds...) } func (m model) View() string { - return fmt.Sprintf( - "%s\n\n%s", - m.viewport.View(), - m.textarea.View(), - ) + "\n\nPress Esc to quit." + var status string + var modeColor string + + switch m.mode { + case ModeNormal: + status = "-- NORMAL -- (i: insert, c: connect, l: list, d: disconnect)" + modeColor = "\033[1;34m" // Blue + case ModeInsert: + status = "-- INSERT -- (Esc: normal, Enter: newline)" + modeColor = "\033[1;32m" // Green + case ModeConnect: + status = "-- CONNECT -- (Enter: confirm, Esc: cancel)" + modeColor = "\033[1;33m" // Yellow + case ModeList: + status = "-- CONTACTS -- (j/k: move, Enter: select)" + modeColor = "\033[1;35m" // Purple + } + + var mainView string + + if m.mode == ModeConnect { + dialog := fmt.Sprintf( + "Connect to User:\n\n%s", + m.connectInput.View(), + ) + mainView = fmt.Sprintf("\n\n %s\n\n", dialog) + } else if m.mode == ModeList { + var items []string + items = append(items, "Select a Contact:\n") + for i, name := range m.contactList { + cursor := " " + if i == m.listCursor { + cursor = ">" + } + items = append(items, fmt.Sprintf("%s %s (#%s)", cursor, name, m.contacts[name])) + } + if len(m.contactList) == 0 { + items = append(items, " (No contacts saved. Use /add in Insert mode or edit contacts.json)") + } + mainView = strings.Join(items, "\n") + } else { + mainView = fmt.Sprintf("%s\n\n%s", m.viewport.View(), m.textarea.View()) + } + + return fmt.Sprintf("%s%s\033[0m\n\n%s", modeColor, status, mainView) } diff --git a/cmd/server/main.go b/cmd/server/main.go index ad3226b..0ed29d4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,34 +1,62 @@ package main import ( + "database/sql" "encoding/json" + "fmt" "log" "net/http" + "os" + "strconv" "sync" "gossip/pkg/protocol" "github.com/gorilla/websocket" + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/joho/godotenv" ) type Server struct { + db *sql.DB clients map[string]*websocket.Conn mu sync.Mutex upgrader websocket.Upgrader } func main() { + if err := godotenv.Load(); err != nil { + panic(err) + } + + dbuser := os.Getenv("DBUSER") + dbname := os.Getenv("DBNAME") + dbport := os.Getenv("DBPORT") + dbhost := os.Getenv("DBHOST") + dbpass := os.Getenv("DBPASS") + + connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=require", dbuser, dbpass, dbhost, dbport, dbname) + + db, err := sql.Open("pgx", connStr) + if err != nil { + log.Fatal("Failed to open DB:", err) + } + defer db.Close() + + if err := db.Ping(); err != nil { + log.Fatal("Failed to connect to DB:", err) + } + srv := &Server{ + db: db, clients: make(map[string]*websocket.Conn), upgrader: websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { return true }, + CheckOrigin: func(r *http.Request) bool { return true }, }, } http.HandleFunc("/ws", srv.handleWS) - log.Println("Relay Server listening on :8080") + log.Println("Gossip Relay Server listening on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } @@ -54,22 +82,47 @@ func (s *Server) handleWS(w http.ResponseWriter, r *http.Request) { } switch msg.Type { - case "login": - s.mu.Lock() - s.clients[msg.Sender] = conn - s.mu.Unlock() + case protocol.TypeLogin: myPubKey = msg.Sender - log.Printf("Client connected: %s...", myPubKey[:8]) - case "msg": + + s.mu.Lock() + s.clients[myPubKey] = conn + s.mu.Unlock() + + accNum, err := s.getOrCreateUser(myPubKey) + if err != nil { + log.Println("DB Error:", err) + continue + } + + resp := protocol.Message{ + Type: protocol.TypeIdentity, + Target: myPubKey, + Content: strconv.Itoa(accNum), + } + conn.WriteJSON(resp) + log.Printf("User %d connected (%s...)", accNum, myPubKey[:8]) + + case protocol.TypeLookup: + targetAcc, _ := strconv.Atoi(msg.Content) + foundKey, ok := s.lookupKey(targetAcc) + + resp := protocol.Message{ + Type: protocol.TypeLookupResponse, + Target: msg.Content, + } + if ok { + resp.Content = foundKey + } + conn.WriteJSON(resp) + + case protocol.TypeMsg: s.mu.Lock() targetConn, ok := s.clients[msg.Target] s.mu.Unlock() if ok { - err = targetConn.WriteMessage(websocket.TextMessage, data) - if err != nil { - log.Printf("Failed to relay to %s", msg.Target[:8]) - } + targetConn.WriteMessage(websocket.TextMessage, data) } } } @@ -78,6 +131,24 @@ func (s *Server) handleWS(w http.ResponseWriter, r *http.Request) { s.mu.Lock() delete(s.clients, myPubKey) s.mu.Unlock() - log.Printf("Client disconnected: %s...", myPubKey[:8]) } } + +func (s *Server) getOrCreateUser(pubKey string) (int, error) { + var accNum int + err := s.db.QueryRow("SELECT account_number FROM users WHERE public_key=$1", pubKey).Scan(&accNum) + if err == nil { + return accNum, nil + } + err = s.db.QueryRow("INSERT INTO users (public_key) VALUES ($1) RETURNING account_number", pubKey).Scan(&accNum) + return accNum, err +} + +func (s *Server) lookupKey(accNum int) (string, bool) { + var pubKey string + err := s.db.QueryRow("SELECT public_key FROM users WHERE account_number=$1", accNum).Scan(&pubKey) + if err != nil { + return "", false + } + return pubKey, true +} diff --git a/go.mod b/go.mod index 70cbbf5..48d500a 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbletea v1.3.10 github.com/gorilla/websocket v1.5.3 + github.com/jackc/pgx/v5 v5.7.6 + github.com/joho/godotenv v1.5.1 golang.org/x/crypto v0.45.0 ) @@ -18,6 +20,9 @@ require ( github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -27,6 +32,7 @@ require ( github.com/muesli/termenv v0.16.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect ) diff --git a/go.sum b/go.sum index 7396145..e7f18d2 100644 --- a/go.sum +++ b/go.sum @@ -20,10 +20,23 @@ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0G github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= +github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -38,18 +51,31 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/protocol/types.go b/pkg/protocol/types.go index ffbbb27..c0a3ab6 100644 --- a/pkg/protocol/types.go +++ b/pkg/protocol/types.go @@ -1,8 +1,28 @@ package protocol +// MessageType ensures we only use valid protocol commands +type MessageType string + +const ( + // TypeLogin: Client -> Server (Handshake with Public Key) + TypeLogin MessageType = "login" + + // TypeIdentity: Server -> Client (Here is your Account Number) + TypeIdentity MessageType = "identity" + + // TypeLookup: Client -> Server (Who owns Account #10050?) + TypeLookup MessageType = "lookup" + + // TypeLookupResponse: Server -> Client (Here is the Public Key for #10050) + TypeLookupResponse MessageType = "lookup_response" + + // TypeMsg: Client -> Server -> Client (Encrypted Payload) + TypeMsg MessageType = "msg" +) + type Message struct { - Type string `json:"type"` - Sender string `json:"sender"` - Target string `json:"target"` - Content string `json:"content"` + Type MessageType `json:"type"` + Sender string `json:"sender"` // Hex Public Key + Target string `json:"target"` // Hex Public Key (msg) or Account # (lookup) + Content string `json:"content"` }