7 "golang.org/x/crypto/bcrypt"
15 // ClientConn represents a client connected to a Server
16 type ClientConn struct {
17 Connection io.ReadWriteCloser
31 transfersMU sync.Mutex
32 transfers map[int]map[[4]byte]*FileTransfer
39 func (cc *ClientConn) sendAll(t [2]byte, fields ...Field) {
40 for _, c := range cc.Server.Clients {
41 cc.Server.outbox <- NewTransaction(t, c.ID, fields...)
45 func (cc *ClientConn) handleTransaction(transaction Transaction) {
46 if handler, ok := TransactionHandlers[transaction.Type]; ok {
47 cc.logger.Debug("Received Transaction", "RequestType", transaction.Type)
49 for _, t := range handler(cc, &transaction) {
55 defer cc.Server.mux.Unlock()
57 if transaction.Type != TranKeepAlive {
58 // reset the user idle timer
61 // if user was previously idle, mark as not idle and notify other connected clients that
62 // the user is no longer away
64 cc.Flags.Set(UserFlagAway, 0)
69 NewField(FieldUserID, cc.ID[:]),
70 NewField(FieldUserFlags, cc.Flags[:]),
71 NewField(FieldUserName, cc.UserName),
72 NewField(FieldUserIconID, cc.Icon),
78 func (cc *ClientConn) Authenticate(login string, password []byte) bool {
79 if account, ok := cc.Server.Accounts[login]; ok {
80 return bcrypt.CompareHashAndPassword([]byte(account.Password), password) == nil
86 // Authorize checks if the user account has the specified permission
87 func (cc *ClientConn) Authorize(access int) bool {
90 if cc.Account == nil {
93 return cc.Account.Access.IsSet(access)
96 // Disconnect notifies other clients that a client has disconnected
97 func (cc *ClientConn) Disconnect() {
99 delete(cc.Server.Clients, cc.ID)
100 cc.Server.mux.Unlock()
102 for _, t := range cc.notifyOthers(NewTransaction(TranNotifyDeleteUser, [2]byte{}, NewField(FieldUserID, cc.ID[:]))) {
103 cc.Server.outbox <- t
106 if err := cc.Connection.Close(); err != nil {
107 cc.Server.Logger.Error("error closing client connection", "RemoteAddr", cc.RemoteAddr)
111 // notifyOthers sends transaction t to other clients connected to the server
112 func (cc *ClientConn) notifyOthers(t Transaction) (trans []Transaction) {
114 defer cc.Server.mux.Unlock()
115 for _, c := range cc.Server.Clients {
118 trans = append(trans, t)
124 // NewReply returns a reply Transaction with fields for the ClientConn
125 func (cc *ClientConn) NewReply(t *Transaction, fields ...Field) Transaction {
134 // NewErrReply returns an error reply Transaction with errMsg
135 func (cc *ClientConn) NewErrReply(t *Transaction, errMsg string) []Transaction {
136 return []Transaction{
141 ErrorCode: [4]byte{0, 0, 0, 1},
143 NewField(FieldError, []byte(errMsg)),
149 var clientSortFunc = func(a, b *ClientConn) int {
151 binary.BigEndian.Uint16(a.ID[:]),
152 binary.BigEndian.Uint16(b.ID[:]),
156 // sortedClients is a utility function that takes a map of *ClientConn and returns a sorted slice of the values.
157 // The purpose of this is to ensure that the ordering of client connections is deterministic so that test assertions work.
158 func sortedClients(unsortedClients map[[2]byte]*ClientConn) (clients []*ClientConn) {
159 for _, c := range unsortedClients {
160 clients = append(clients, c)
163 slices.SortFunc(clients, clientSortFunc)
168 const userInfoTemplate = `Nickname: %s
173 -------- File Downloads ---------
176 ------- Folder Downloads --------
179 --------- File Uploads ----------
182 -------- Folder Uploads ---------
185 ------- Waiting Downloads -------
190 func formatDownloadList(fts map[[4]byte]*FileTransfer) (s string) {
195 for _, dl := range fts {
202 func (cc *ClientConn) String() string {
203 cc.transfersMU.Lock()
204 defer cc.transfersMU.Unlock()
205 template := fmt.Sprintf(
211 formatDownloadList(cc.transfers[FileDownload]),
212 formatDownloadList(cc.transfers[FolderDownload]),
213 formatDownloadList(cc.transfers[FileUpload]),
214 formatDownloadList(cc.transfers[FolderUpload]),
218 return strings.ReplaceAll(template, "\n", "\r")