5 "golang.org/x/crypto/bcrypt"
11 type byClientID []*ClientConn
13 func (s byClientID) Len() int {
17 func (s byClientID) Swap(i, j int) {
18 s[i], s[j] = s[j], s[i]
21 func (s byClientID) Less(i, j int) bool {
22 return s[i].uint16ID() < s[j].uint16ID()
25 const template = `Nickname: %s
30 -------- File Downloads ---------
34 ------- Folder Downloads --------
38 --------- File Uploads ----------
42 -------- Folder Uploads ---------
46 ------- Waiting Downloads -------
52 // ClientConn represents a client connected to a Server
53 type ClientConn struct {
54 Connection io.ReadWriteCloser
66 Transfers map[int][]*FileTransfer
70 func (cc *ClientConn) sendAll(t int, fields ...Field) {
71 for _, c := range sortedClients(cc.Server.Clients) {
72 cc.Server.outbox <- *NewTransaction(t, c.ID, fields...)
76 func (cc *ClientConn) handleTransaction(transaction *Transaction) error {
77 requestNum := binary.BigEndian.Uint16(transaction.Type)
78 if handler, ok := TransactionHandlers[requestNum]; ok {
79 for _, reqField := range handler.RequiredFields {
80 field := transaction.GetField(reqField.ID)
82 // Validate that required field is present
84 cc.Server.Logger.Errorw(
85 "Missing required field",
86 "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID,
91 if len(field.Data) < reqField.minLen {
92 cc.Server.Logger.Infow(
93 "Field does not meet minLen",
94 "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID,
100 cc.Server.Logger.Infow(
101 "Received Transaction",
102 "login", cc.Account.Login,
103 "name", string(cc.UserName),
104 "RequestType", handler.Name,
107 transactions, err := handler.Handler(cc, transaction)
111 for _, t := range transactions {
112 cc.Server.outbox <- t
115 cc.Server.Logger.Errorw(
116 "Unimplemented transaction type received",
117 "UserName", string(cc.UserName), "RequestID", requestNum,
122 defer cc.Server.mux.Unlock()
124 if requestNum != tranKeepAlive {
125 // reset the user idle timer
128 // if user was previously idle, mark as not idle and notify other connected clients that
129 // the user is no longer away
131 flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(*cc.Flags)))
132 flagBitmap.SetBit(flagBitmap, userFlagAway, 0)
133 binary.BigEndian.PutUint16(*cc.Flags, uint16(flagBitmap.Int64()))
137 tranNotifyChangeUser,
138 NewField(fieldUserID, *cc.ID),
139 NewField(fieldUserFlags, *cc.Flags),
140 NewField(fieldUserName, cc.UserName),
141 NewField(fieldUserIconID, *cc.Icon),
149 func (cc *ClientConn) Authenticate(login string, password []byte) bool {
150 if account, ok := cc.Server.Accounts[login]; ok {
151 return bcrypt.CompareHashAndPassword([]byte(account.Password), password) == nil
157 func (cc *ClientConn) uint16ID() uint16 {
158 id, _ := byteToInt(*cc.ID)
162 // Authorize checks if the user account has the specified permission
163 func (cc *ClientConn) Authorize(access int) bool {
168 i := big.NewInt(int64(binary.BigEndian.Uint64(*cc.Account.Access)))
170 return i.Bit(63-access) == 1
173 // Disconnect notifies other clients that a client has disconnected
174 func (cc *ClientConn) Disconnect() {
176 defer cc.Server.mux.Unlock()
178 delete(cc.Server.Clients, binary.BigEndian.Uint16(*cc.ID))
180 cc.notifyOthers(*NewTransaction(tranNotifyDeleteUser, nil, NewField(fieldUserID, *cc.ID)))
182 if err := cc.Connection.Close(); err != nil {
183 cc.Server.Logger.Errorw("error closing client connection", "RemoteAddr", cc.RemoteAddr)
187 // notifyOthers sends transaction t to other clients connected to the server
188 func (cc *ClientConn) notifyOthers(t Transaction) {
189 for _, c := range sortedClients(cc.Server.Clients) {
190 if c.ID != cc.ID && c.Agreed {
192 cc.Server.outbox <- t
197 // NewReply returns a reply Transaction with fields for the ClientConn
198 func (cc *ClientConn) NewReply(t *Transaction, fields ...Field) Transaction {
199 reply := Transaction{
205 ErrorCode: []byte{0, 0, 0, 0},
212 // NewErrReply returns an error reply Transaction with errMsg
213 func (cc *ClientConn) NewErrReply(t *Transaction, errMsg string) Transaction {
220 ErrorCode: []byte{0, 0, 0, 1},
222 NewField(fieldError, []byte(errMsg)),
227 // sortedClients is a utility function that takes a map of *ClientConn and returns a sorted slice of the values.
228 // The purpose of this is to ensure that the ordering of client connections is deterministic so that test assertions work.
229 func sortedClients(unsortedClients map[uint16]*ClientConn) (clients []*ClientConn) {
230 for _, c := range unsortedClients {
231 clients = append(clients, c)
233 sort.Sort(byClientID(clients))