Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to the Go daemon #8

Merged
merged 3 commits into from
Mar 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
identity.key
21 changes: 5 additions & 16 deletions go-server/chatroom.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"context"
"encoding/json"

"github.com/libp2p/go-libp2p/core/peer"

Expand All @@ -18,6 +17,7 @@ const ChatRoomBufSize = 128
type ChatRoom struct {
// Messages is a channel of messages received from other peers in the chat room
Messages chan *ChatMessage
SysMessages chan *ChatMessage

ctx context.Context
ps *pubsub.PubSub
Expand Down Expand Up @@ -60,6 +60,7 @@ func JoinChatRoom(ctx context.Context, ps *pubsub.PubSub, selfID peer.ID, nickna
nick: nickname,
roomName: roomName,
Messages: make(chan *ChatMessage, ChatRoomBufSize),
SysMessages: make(chan *ChatMessage, ChatRoomBufSize),
}

// start reading messages from the subscription in a loop
Expand All @@ -69,16 +70,7 @@ func JoinChatRoom(ctx context.Context, ps *pubsub.PubSub, selfID peer.ID, nickna

// Publish sends a message to the pubsub topic.
func (cr *ChatRoom) Publish(message string) error {
m := ChatMessage{
Message: message,
SenderID: cr.self.Pretty(),
SenderNick: cr.nick,
}
msgBytes, err := json.Marshal(m)
if err != nil {
return err
}
return cr.topic.Publish(cr.ctx, msgBytes)
return cr.topic.Publish(cr.ctx, []byte(message))
}

func (cr *ChatRoom) ListPeers() []peer.ID {
Expand All @@ -99,12 +91,9 @@ func (cr *ChatRoom) readLoop() {
}
cm := new(ChatMessage)
cm.Message = string(msg.Data)
cm.SenderID = string(msg.ID)
cm.SenderNick = "unknown"
cm.SenderID = msg.ID
cm.SenderNick = string(msg.ID[len(msg.ID)-8])

if err != nil {
continue
}
// send valid messages onto the Messages channel
cr.Messages <- cm
}
Expand Down
49 changes: 49 additions & 0 deletions go-server/identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main
// Borrowed from https://github.com/libp2p/go-libp2p-relay-daemon/blob/master/identity.go

import (
"fmt"
"os"

"github.com/libp2p/go-libp2p/core/crypto"
)

// LoadIdentity reads a private key from the given path and, if it does not
// exist, generates a new one.
func LoadIdentity(idPath string) (crypto.PrivKey, error) {
if _, err := os.Stat(idPath); err == nil {
return ReadIdentity(idPath)
} else if os.IsNotExist(err) {
fmt.Printf("Generating peer identity in %s\n", idPath)
return GenerateIdentity(idPath)
} else {
return nil, err
}
}

// ReadIdentity reads a private key from the given path.
func ReadIdentity(path string) (crypto.PrivKey, error) {
bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}

return crypto.UnmarshalPrivateKey(bytes)
}

// GenerateIdentity writes a new random private key to the given path.
func GenerateIdentity(path string) (crypto.PrivKey, error) {
privk, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 0)
if err != nil {
return nil, err
}

bytes, err := crypto.MarshalPrivateKey(privk)
if err != nil {
return nil, err
}

err = os.WriteFile(path, bytes, 0400)

return privk, err
}
38 changes: 33 additions & 5 deletions go-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"sync"
"time"
Expand All @@ -28,7 +30,7 @@ const DiscoveryInterval = time.Hour
// DiscoveryServiceTag is used in our mDNS advertisements to discover other chat peers.
const DiscoveryServiceTag = "universal-connectivity"

var ChatMsgChan chan *ChatMessage
var SysMsgChan chan *ChatMessage

// Borrowed from https://medium.com/rahasak/libp2p-pubsub-peer-discovery-with-kademlia-dht-c8b131550ac7
// NewDHT attempts to connect to a bunch of bootstrap peers and returns a new DHT.
Expand Down Expand Up @@ -76,7 +78,7 @@ func Discover(ctx context.Context, h host.Host, dht *dht.IpfsDHT, rendezvous str

discovery.Advertise(ctx, routingDiscovery, rendezvous)

ticker := time.NewTicker(time.Second * 1)
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()

for {
Expand Down Expand Up @@ -108,19 +110,45 @@ func Discover(ctx context.Context, h host.Host, dht *dht.IpfsDHT, rendezvous str
}

func LogMsgf(f string, msg ...any) {
ChatMsgChan <- &ChatMessage{Message: fmt.Sprintf(f, msg...), SenderID: "system", SenderNick: "system"}
SysMsgChan <- &ChatMessage{Message: fmt.Sprintf(f, msg...), SenderID: "system", SenderNick: "system"}
}

func main() {
// parse some flags to set our nickname and the room to join
nickFlag := flag.String("nick", "", "nickname to use in chat. will be generated if empty")
roomFlag := flag.String("room", "universal-connectivity", "name of chat room to join")
idPath := flag.String("identity", "identity.key", "path to the private key (PeerID) file")
useLogger := flag.Bool("logger", false, "write logs to file")
flag.Parse()

if *useLogger {
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Println("failed to open log file", err)
log.SetOutput(ioutil.Discard)
} else {
defer f.Close()
log.SetOutput(f)
}
} else {
log.SetOutput(ioutil.Discard)
}

ctx := context.Background()

// load our private key to generate the same peerID each time
privk, err := LoadIdentity(*idPath)
if err != nil {
panic(err)
}

// create a new libp2p Host that listens on a random TCP port
h, err := libp2p.New(libp2p.Transport(quicTransport.NewTransport), libp2p.Transport(webtransport.New), libp2p.ListenAddrStrings("/ip4/0.0.0.0/udp/0/quic-v1", "/ip4/0.0.0.0/udp/0/quic-v1/webtransport", "/ip6/::/udp/0/quic-v1", "/ip6/::/udp/0/quic-v1/webtransport"))
h, err := libp2p.New(
libp2p.Identity(privk),
libp2p.Transport(quicTransport.NewTransport),
libp2p.Transport(webtransport.New),
libp2p.ListenAddrStrings("/ip4/0.0.0.0/udp/0/quic-v1", "/ip4/0.0.0.0/udp/0/quic-v1/webtransport", "/ip6/::/udp/0/quic-v1", "/ip6/::/udp/0/quic-v1/webtransport"),
)
if err != nil {
panic(err)
}
Expand All @@ -145,7 +173,7 @@ func main() {
if err != nil {
panic(err)
}
ChatMsgChan = cr.Messages
SysMsgChan = cr.SysMessages

// setup DHT with empty discovery peers
// so this will be a discovery peer for others
Expand Down
32 changes: 30 additions & 2 deletions go-server/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"io"
"log"
"time"

"github.com/gdamore/tcell/v2"
Expand All @@ -19,6 +20,7 @@ type ChatUI struct {
peersList *tview.TextView

msgW io.Writer
sysW io.Writer
inputCh chan string
doneCh chan struct{}
}
Expand All @@ -41,6 +43,19 @@ func NewChatUI(cr *ChatRoom) *ChatUI {
app.Draw()
})

// make a text view to contain our error messages
sysBox := tview.NewTextView()
sysBox.SetDynamicColors(true)
sysBox.SetBorder(true)
sysBox.SetTitle("System")

// text views are io.Writers, but they don't automatically refresh.
// this sets a change handler to force the app to redraw when we get
// new messages to display.
sysBox.SetChangedFunc(func() {
app.Draw()
})

// an input field for typing messages into
inputCh := make(chan string, 32)
input := tview.NewInputField().
Expand Down Expand Up @@ -87,8 +102,9 @@ func NewChatUI(cr *ChatRoom) *ChatUI {

flex := tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(chatPanel, 0, 1, false).
AddItem(input, 1, 1, true)
AddItem(chatPanel, 0, 3, false).
AddItem(sysBox, 0, 2, false).
AddItem(input, 2, 1, true)

app.SetRoot(flex, true)

Expand All @@ -97,6 +113,7 @@ func NewChatUI(cr *ChatRoom) *ChatUI {
app: app,
peersList: peersList,
msgW: msgBox,
sysW: sysBox,
inputCh: inputCh,
doneCh: make(chan struct{}, 1),
}
Expand Down Expand Up @@ -138,6 +155,13 @@ func (ui *ChatUI) displayChatMessage(cm *ChatMessage) {
fmt.Fprintf(ui.msgW, "%s %s\n", prompt, cm.Message)
}

// displayChatMessage writes a ChatMessage from the room to the message window,
// with the sender's nick highlighted in green.
func (ui *ChatUI) displaySysMessage(cm *ChatMessage) {
fmt.Fprintf(ui.sysW, "%s\n", cm.Message)
log.Println(cm.Message)
}

// displaySelfMessage writes a message from ourself to the message window,
// with our nick highlighted in yellow.
func (ui *ChatUI) displaySelfMessage(msg string) {
Expand Down Expand Up @@ -166,6 +190,10 @@ func (ui *ChatUI) handleEvents() {
// when we receive a message from the chat room, print it to the message window
ui.displayChatMessage(m)

case s := <-ui.cr.SysMessages:
// when we receive a message from the chat room, print it to the message window
ui.displaySysMessage(s)

case <-peerRefreshTicker.C:
// refresh the list of peers in the chat room periodically
ui.refreshPeers()
Expand Down