1 package battlemap 2 3 import ( 4 "encoding/json" 5 "net/http" 6 "strconv" 7 "strings" 8 "sync" 9 "sync/atomic" 10 "time" 11 12 "golang.org/x/net/websocket" 13 "vimagination.zapto.org/jsonrpc" 14 "vimagination.zapto.org/keystore" 15 ) 16 17 type socket struct { 18 *Battlemap 19 mu sync.RWMutex 20 conns map[*conn]struct{} 21 nextID ID 22 } 23 24 func (s *socket) Init(b *Battlemap, _ links) error { 25 s.Battlemap = b 26 s.conns = make(map[*conn]struct{}) 27 return nil 28 } 29 30 func (s *socket) ServeConn(wconn *websocket.Conn) { 31 var ( 32 cu keystore.Uint64 33 c conn 34 ) 35 s.config.Get("currentUserMap", &cu) 36 s.mu.Lock() 37 s.nextID++ 38 id := s.nextID 39 s.conns[&c] = struct{}{} 40 s.mu.Unlock() 41 c = conn{ 42 Battlemap: s.Battlemap, 43 rpc: jsonrpc.New(wconn, &c), 44 ConnData: ConnData{ 45 CurrentMap: uint64(cu), 46 ID: id, 47 userState: s.authConn(wconn), 48 }, 49 } 50 c.rpc.Handle() 51 s.mu.Lock() 52 delete(s.conns, &c) 53 s.mu.Unlock() 54 } 55 56 type conn struct { 57 *Battlemap 58 rpc *jsonrpc.Server 59 ConnData 60 } 61 62 // ID is a unique connection ID for a websocket RPC connection 63 type ID uint64 64 65 // SocketIDFromRequest gets the connection ID (if available) from the HTTP 66 // headers. It returns zero if no ID is found. 67 func SocketIDFromRequest(r *http.Request) ID { 68 id, _ := strconv.ParseUint(r.Header.Get("X-ID"), 10, 0) 69 return ID(id) 70 } 71 72 // ConnData represents all of the data required to handle a websocket RPC 73 // connection. 74 type ConnData struct { 75 CurrentMap uint64 76 ID ID 77 userState 78 } 79 80 func (c *conn) HandleRPC(method string, data json.RawMessage) (interface{}, error) { 81 cd := ConnData{ 82 CurrentMap: atomic.LoadUint64(&c.CurrentMap), 83 ID: ID(atomic.LoadUint64((*uint64)(&c.ID))), 84 userState: c.userState, 85 } 86 switch method { 87 case "conn.ready": 88 if c.IsAdmin() { 89 c.rpc.Send(jsonrpc.Response{ 90 ID: broadcastCurrentUserMap, 91 Result: cd.CurrentMap, 92 }) 93 } else if cd.CurrentMap > 0 { 94 c.maps.mu.RLock() 95 mapData := c.maps.maps[uint64(cd.CurrentMap)] 96 c.maps.mu.RUnlock() 97 c.rpc.Send(jsonrpc.Response{ 98 ID: broadcastCurrentUserMapData, 99 Result: json.RawMessage(mapData.UserJSON), 100 }) 101 } 102 return nil, nil 103 case "conn.currentTime": 104 return time.Now().Unix(), nil 105 case "maps.setCurrentMap": 106 if cd.IsAdmin() { 107 if err := json.Unmarshal(data, &cd.CurrentMap); err != nil { 108 return nil, err 109 } 110 atomic.StoreUint64(&c.CurrentMap, cd.CurrentMap) 111 return nil, nil 112 } 113 case "maps.signalPosition": 114 who := userAdmin 115 if cd.IsAdmin() { 116 who = userAny 117 } else if !cd.IsUser() { 118 break 119 } 120 c.socket.broadcastMapChange(cd, broadcastSignalPosition, data, who) 121 return nil, nil 122 case "maps.signalMovePosition": 123 if cd.IsAdmin() { 124 c.socket.broadcastMapChange(cd, broadcastSignalMovePosition, data, userNotAdmin) 125 return nil, nil 126 } 127 case "maps.signalMeasure": 128 if cd.IsAdmin() { 129 c.socket.broadcastMapChange(cd, broadcastSignalMeasure, data, userNotAdmin) 130 return nil, nil 131 } 132 case "broadcast": 133 if cd.IsAdmin() || cd.IsUser() { 134 cd.CurrentMap = 0 135 c.socket.broadcastMapChange(cd, broadcastAny, data, userAny) 136 return nil, nil 137 } 138 case "broadcastWindow": 139 if cd.IsAdmin() { 140 cd.CurrentMap = 0 141 c.socket.broadcastMapChange(cd, broadcastWindow, data, userAny) 142 return nil, nil 143 } 144 default: 145 pos := strings.IndexByte(method, '.') 146 if pos <= 0 { 147 return nil, ErrUnknownMethod 148 } 149 submethod := method[pos+1:] 150 method = method[:pos] 151 switch method { 152 case "imageAssets": 153 if cd.IsAdmin() { 154 return c.images.RPCData(cd, submethod, data) 155 } 156 case "audioAssets": 157 if cd.IsAdmin() { 158 return c.audio.RPCData(cd, submethod, data) 159 } 160 case "characters": 161 if cd.IsAdmin() || submethod == "get" { 162 return c.chars.RPCData(cd, submethod, data) 163 } 164 case "music": 165 if cd.IsAdmin() || submethod == "list" { 166 return c.musicPacks.RPCData(cd, submethod, data) 167 } 168 case "maps": 169 if submethod == "getUserMap" { 170 var currentUserMap keystore.Uint64 171 c.config.Get("currentUserMap", ¤tUserMap) 172 return currentUserMap, nil 173 } else if cd.IsAdmin() { 174 return c.maps.RPCData(cd, submethod, data) 175 } 176 case "plugins": 177 if cd.IsAdmin() || submethod == "list" { 178 return c.plugins.RPCData(cd, submethod, data) 179 } 180 } 181 } 182 return nil, ErrUnknownMethod 183 } 184