mirror of
https://github.com/originalmk/archat-server.git
synced 2024-10-03 19:26:10 +00:00
273 lines
5.9 KiB
Go
273 lines
5.9 KiB
Go
package server
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
"krzyzanowski.dev/p2pchat/common"
|
|
)
|
|
|
|
type Account struct {
|
|
nickname string
|
|
passHash []byte
|
|
}
|
|
|
|
type ServerContext struct {
|
|
peersList []*Peer
|
|
peersListLock sync.RWMutex
|
|
accounts map[string]*Account
|
|
accountsLock sync.RWMutex
|
|
}
|
|
|
|
type HandlerContext struct {
|
|
peer *Peer
|
|
srvCtx *ServerContext
|
|
}
|
|
|
|
type Peer struct {
|
|
id int
|
|
conn net.Conn
|
|
hasAccount bool
|
|
account *Account
|
|
}
|
|
|
|
func peerSliceIndexOf(s []*Peer, id int) int {
|
|
i := 0
|
|
var p *Peer
|
|
for i, p = range s {
|
|
if p.id == id {
|
|
break
|
|
}
|
|
}
|
|
return i
|
|
}
|
|
|
|
func peerSliceRemove(s *[]*Peer, i int) {
|
|
(*s)[i] = (*s)[len(*s)-1]
|
|
*s = (*s)[:len(*s)-1]
|
|
}
|
|
|
|
func handleDisconnection(handlerCtx *HandlerContext) {
|
|
handlerCtx.srvCtx.peersListLock.Lock()
|
|
p := handlerCtx.srvCtx.peersList[peerSliceIndexOf(handlerCtx.srvCtx.peersList, handlerCtx.peer.id)]
|
|
log.Printf("[Server] %s disconnected\n", p.conn.RemoteAddr())
|
|
peerSliceRemove(&handlerCtx.srvCtx.peersList, peerSliceIndexOf(handlerCtx.srvCtx.peersList, handlerCtx.peer.id))
|
|
handlerCtx.srvCtx.peersListLock.Unlock()
|
|
}
|
|
|
|
func handlePeer(handlerCtx *HandlerContext) {
|
|
br := bufio.NewReader(handlerCtx.peer.conn)
|
|
bw := bufio.NewWriter(handlerCtx.peer.conn)
|
|
|
|
for {
|
|
reqBytes, err := br.ReadBytes('\n')
|
|
|
|
if err == io.EOF {
|
|
handleDisconnection(handlerCtx)
|
|
break
|
|
} else if err != nil {
|
|
log.Println(err)
|
|
break
|
|
}
|
|
|
|
if len(reqBytes) <= 1 {
|
|
log.Println("got request without id")
|
|
break
|
|
}
|
|
|
|
reqBytes = reqBytes[:len(reqBytes)-1]
|
|
operationCode := reqBytes[0]
|
|
reqJsonBytes := reqBytes[1:]
|
|
var resBytes []byte
|
|
|
|
if operationCode == common.EchoRID {
|
|
resBytes, err = handleEcho(handlerCtx, reqJsonBytes)
|
|
} else if operationCode == common.ListPeersRID {
|
|
resBytes, err = handleListPeers(handlerCtx, reqJsonBytes)
|
|
} else if operationCode == common.AuthRID {
|
|
resBytes, err = handleAuth(handlerCtx, reqJsonBytes)
|
|
}
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
|
|
resBytes = append(resBytes, '\n')
|
|
_, err = bw.Write(resBytes)
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
|
|
err = bw.Flush()
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleEcho(_ *HandlerContext, reqBytes []byte) (resBytes []byte, err error) {
|
|
var echoReq common.EchoRequest
|
|
err = json.Unmarshal(reqBytes, &echoReq)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
echoRes := common.EchoResponse(echoReq)
|
|
resBytes, err = json.Marshal(echoRes)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return resBytes, nil
|
|
}
|
|
|
|
func handleListPeers(handlerCtx *HandlerContext, reqBytes []byte) (resBytes []byte, err error) {
|
|
var listPeersReq common.ListPeersRequest
|
|
err = json.Unmarshal(reqBytes, &listPeersReq)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
handlerCtx.srvCtx.peersListLock.RLock()
|
|
peersFreeze := make([]*Peer, len(handlerCtx.srvCtx.peersList))
|
|
copy(peersFreeze, handlerCtx.srvCtx.peersList)
|
|
handlerCtx.srvCtx.peersListLock.RUnlock()
|
|
listPeersRes := common.ListPeersResponse{PeersInfo: make([]common.PeerInfo, 0)}
|
|
|
|
for _, peer := range peersFreeze {
|
|
listPeersRes.PeersInfo = append(
|
|
listPeersRes.PeersInfo,
|
|
common.PeerInfo{
|
|
ID: peer.id,
|
|
Addr: peer.conn.RemoteAddr().String(),
|
|
HasNickaname: peer.hasAccount,
|
|
Nickname: peer.account.nickname,
|
|
},
|
|
)
|
|
}
|
|
|
|
resBytes, err = json.Marshal(listPeersRes)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return resBytes, nil
|
|
}
|
|
|
|
func handleAuth(handlerCtx *HandlerContext, reqBytes []byte) (resBytes []byte, err error) {
|
|
var authReq common.AuthRequest
|
|
err = json.Unmarshal(reqBytes, &authReq)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check if account already exists
|
|
handlerCtx.srvCtx.accountsLock.RLock()
|
|
account, ok := handlerCtx.srvCtx.accounts[authReq.Nickname]
|
|
handlerCtx.srvCtx.accountsLock.RUnlock()
|
|
var authRes common.AuthResponse
|
|
|
|
if ok {
|
|
// Check if password matches
|
|
if bcrypt.CompareHashAndPassword(account.passHash, []byte(authReq.Password)) == nil {
|
|
authRes = common.AuthResponse{IsSuccess: true}
|
|
handlerCtx.srvCtx.peersListLock.Lock()
|
|
handlerCtx.peer.hasAccount = true
|
|
handlerCtx.peer.account = account
|
|
handlerCtx.srvCtx.peersListLock.Unlock()
|
|
} else {
|
|
authRes = common.AuthResponse{IsSuccess: false}
|
|
}
|
|
} else {
|
|
authRes = common.AuthResponse{IsSuccess: true}
|
|
passHash, err := bcrypt.GenerateFromPassword([]byte(authReq.Password), bcrypt.DefaultCost)
|
|
|
|
if err != nil {
|
|
authRes = common.AuthResponse{IsSuccess: false}
|
|
} else {
|
|
newAcc := Account{authReq.Nickname, passHash}
|
|
handlerCtx.srvCtx.accountsLock.Lock()
|
|
handlerCtx.srvCtx.accounts[newAcc.nickname] = &newAcc
|
|
handlerCtx.srvCtx.accountsLock.Unlock()
|
|
handlerCtx.srvCtx.peersListLock.Lock()
|
|
handlerCtx.peer.hasAccount = true
|
|
handlerCtx.peer.account = &newAcc
|
|
handlerCtx.srvCtx.peersListLock.Unlock()
|
|
}
|
|
}
|
|
|
|
resBytes, err = json.Marshal(authRes)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return resBytes, nil
|
|
}
|
|
|
|
func printConnectedPeers(srvCtx *ServerContext) {
|
|
srvCtx.peersListLock.RLock()
|
|
log.Println("[Server] displaying all connections:")
|
|
|
|
for _, p := range srvCtx.peersList {
|
|
nick := "-"
|
|
|
|
if p.hasAccount {
|
|
nick = p.account.nickname
|
|
}
|
|
|
|
log.Printf("[Server] ID#%d, Addr:%s, Auth:%t, Nick:%s\n", p.id, p.conn.RemoteAddr(), p.hasAccount, nick)
|
|
}
|
|
|
|
srvCtx.peersListLock.RUnlock()
|
|
}
|
|
|
|
func RunServer() {
|
|
idCounter := 0
|
|
srvCtx := &ServerContext{peersList: make([]*Peer, 0), accounts: make(map[string]*Account)}
|
|
ln, err := net.Listen("tcp", ":8080")
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
printConnectedPeers(srvCtx)
|
|
time.Sleep(time.Second * 5)
|
|
}
|
|
}()
|
|
|
|
for {
|
|
c, err := ln.Accept()
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
break
|
|
}
|
|
|
|
log.Printf("[Server] client connected %s\n", c.RemoteAddr())
|
|
idCounter++
|
|
newPeer := Peer{idCounter, c, false, nil}
|
|
srvCtx.peersListLock.Lock()
|
|
srvCtx.peersList = append(srvCtx.peersList, &newPeer)
|
|
srvCtx.peersListLock.Unlock()
|
|
go handlePeer(&HandlerContext{&newPeer, srvCtx})
|
|
}
|
|
}
|