github.com/metacurrency/holochain@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/bridge.go (about) 1 // Copyright (C) 2013-2018, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.) 2 // Use of this source code is governed by GPLv3 found in the LICENSE file 3 //---------------------------------------------------------------------------------------- 4 // implements the abstractions and functions for application bridging 5 // 6 7 package holochain 8 9 import ( 10 "bytes" 11 "encoding/json" 12 "errors" 13 "fmt" 14 . "github.com/holochain/holochain-proto/hash" 15 "github.com/tidwall/buntdb" 16 "io/ioutil" 17 "net/http" 18 "path/filepath" 19 "strings" 20 ) 21 22 // BridgeApp describes a data necessary for bridging 23 type BridgeApp struct { 24 Name string //Name of other side 25 DNA Hash // DNA of other side 26 Side int 27 BridgeGenesisCallerData string 28 BridgeGenesisCalleeData string 29 Port string // only used if side == BridgeCallee 30 BridgeZome string // only used if side == BridgeCaller 31 } 32 33 // Bridge holds data returned by GetBridges 34 type Bridge struct { 35 CalleeApp Hash 36 CalleeName string 37 Token string 38 Side int 39 } 40 41 type BridgeSpec map[string]map[string]bool 42 43 var BridgeAppNotFoundErr = errors.New("bridge app not found") 44 45 // AddBridgeAsCallee registers a token for allowing bridged calls from some other app 46 // and calls bridgeGenesis in any zomes with bridge functions 47 func (h *Holochain) AddBridgeAsCallee(fromDNA Hash, appData string) (token string, err error) { 48 h.Debugf("Adding bridge to callee %s from caller %v with appData: %s", h.Name(), fromDNA, appData) 49 err = h.initBridgeDB() 50 if err != nil { 51 return 52 } 53 var capability *Capability 54 55 bridgeSpec := h.makeBridgeSpec() 56 var bridgeSpecB []byte 57 58 if bridgeSpec != nil { 59 bridgeSpecB, err = json.Marshal(bridgeSpec) 60 if err != nil { 61 return 62 } 63 } 64 65 capability, err = NewCapability(h.bridgeDB, string(bridgeSpecB), nil) 66 if err != nil { 67 return 68 } 69 70 for zomeName, _ := range bridgeSpec { 71 var r Ribosome 72 r, _, err = h.MakeRibosome(zomeName) 73 if err != nil { 74 return 75 } 76 h.Debugf("Running BridgeCallee Genesis for %s", zomeName) 77 err = r.BridgeGenesis(BridgeCallee, fromDNA, appData) 78 if err != nil { 79 return 80 } 81 } 82 83 token = capability.Token 84 85 return 86 } 87 88 func (h *Holochain) initBridgeDB() (err error) { 89 if h.bridgeDB == nil { 90 h.bridgeDB, err = buntdb.Open(filepath.Join(h.DBPath(), BridgeDBFileName)) 91 } 92 return 93 } 94 95 func checkBridgeSpec(spec BridgeSpec, zomeType string, function string) bool { 96 f, ok := spec[zomeType] 97 if ok { 98 _, ok = f[function] 99 } 100 return ok 101 } 102 103 func (h *Holochain) makeBridgeSpec() (spec BridgeSpec) { 104 var funcs map[string]bool 105 for _, z := range h.nucleus.dna.Zomes { 106 for _, f := range z.BridgeFuncs { 107 if spec == nil { 108 spec = make(BridgeSpec) 109 } 110 _, ok := spec[z.Name] 111 if !ok { 112 funcs = make(map[string]bool) 113 spec[z.Name] = funcs 114 115 } 116 funcs[f] = true 117 } 118 } 119 return 120 } 121 122 // BridgeCall executes a function exposed through a bridge 123 func (h *Holochain) BridgeCall(zomeType string, function string, arguments interface{}, token string) (result interface{}, err error) { 124 if h.bridgeDB == nil { 125 err = errors.New("no active bridge") 126 return 127 } 128 c := Capability{Token: token, db: h.bridgeDB} 129 130 var bridgeSpecStr string 131 bridgeSpecStr, err = c.Validate(nil) 132 if err == nil { 133 if bridgeSpecStr != "*" { 134 bridgeSpec := make(BridgeSpec) 135 err = json.Unmarshal([]byte(bridgeSpecStr), &bridgeSpec) 136 if err == nil { 137 if !checkBridgeSpec(bridgeSpec, zomeType, function) { 138 err = errors.New("function not bridged") 139 return 140 } 141 } 142 } 143 if err == nil { 144 result, err = h.Call(zomeType, function, arguments, ZOME_EXPOSURE) 145 } 146 } 147 148 if err != nil { 149 err = errors.New("bridging error: " + err.Error()) 150 151 } 152 153 return 154 } 155 156 // AddBridgeAsCaller associates a token with an application DNA hash and url for accessing it 157 // it also runs BridgeGenesis in the bridgeZome 158 func (h *Holochain) AddBridgeAsCaller(bridgeZome string, calleeDNA Hash, calleeName string, token string, url string, appData string) (err error) { 159 h.Debugf("Adding bridge to caller %s for callee %s (%v) with appData: %s", h.Name(), calleeName, calleeDNA, appData) 160 err = h.initBridgeDB() 161 if err != nil { 162 return 163 } 164 toDNAStr := calleeDNA.String() 165 err = h.bridgeDB.Update(func(tx *buntdb.Tx) error { 166 _, _, err = tx.Set("app:"+toDNAStr, token+"%%"+url+"%%"+calleeName, nil) 167 if err != nil { 168 return err 169 } 170 return nil 171 }) 172 if err != nil { 173 return 174 } 175 176 var zome *Zome 177 178 // get the zome that does the bridging, as we need to run the bridgeGenesis function in it 179 zome, err = h.GetZome(bridgeZome) 180 if err != nil { 181 err = fmt.Errorf("error getting bridging zome: %v", err) 182 return 183 } 184 var r Ribosome 185 r, _, err = h.MakeRibosome(zome.Name) 186 if err != nil { 187 return 188 } 189 190 h.Debugf("Running BridgeCaller Genesis for %s", zome.Name) 191 err = r.BridgeGenesis(BridgeCaller, calleeDNA, appData) 192 if err != nil { 193 return 194 } 195 return 196 } 197 198 func getBridgeAppVals(value string) (token string, url string, name string) { 199 x := strings.Split(value, "%%") 200 token = x[0] 201 url = x[1] 202 name = x[2] 203 return 204 } 205 206 // GetBridgeToken returns a token given the a hash 207 func (h *Holochain) GetBridgeToken(hash Hash) (token string, url string, err error) { 208 if h.bridgeDB == nil { 209 err = errors.New("no active bridge") 210 return 211 } 212 err = h.bridgeDB.View(func(tx *buntdb.Tx) (e error) { 213 var value string 214 value, e = tx.Get("app:" + hash.String()) 215 if e == buntdb.ErrNotFound { 216 e = BridgeAppNotFoundErr 217 } 218 if e == nil { 219 token, url, _ = getBridgeAppVals(value) 220 } 221 return 222 }) 223 h.Debugf("found bridge token %s with url %s for %s", token, url, hash.String()) 224 return 225 } 226 227 // BuildBridgeToCaller connects h to a running app specified by BridgeApp that will be the Caller, i.e. the the BridgeCaller 228 func (h *Holochain) BuildBridgeToCaller(app *BridgeApp, port string) (err error) { 229 var token string 230 token, err = h.AddBridgeAsCallee(app.DNA, app.BridgeGenesisCalleeData) 231 if err != nil { 232 h.Debugf("adding bridge to caller %s from %s failed with %v\n", app.Name, h.Name(), err) 233 return 234 } 235 236 h.Debugf("%s generated token %s for %s\n", h.Name(), token, app.Name) 237 238 data := map[string]string{"Type": "ToCaller", "Zome": app.BridgeZome, "DNA": h.DNAHash().String(), "Token": token, "Port": port, "Data": app.BridgeGenesisCallerData} 239 dataJSON, err := json.Marshal(data) 240 if err != nil { 241 return 242 } 243 244 body := bytes.NewBuffer(dataJSON) 245 var resp *http.Response 246 247 resp, err = http.Post(fmt.Sprintf("http://0.0.0.0:%s/setup-bridge/", app.Port), "application/json", body) 248 if err == nil { 249 defer resp.Body.Close() 250 if resp.StatusCode != 200 { 251 err = errors.New(resp.Status) 252 } 253 } 254 if err != nil { 255 h.Debugf("adding bridge to caller %s from %s failed with %s\n", app.Name, h.Name(), err) 256 } 257 return 258 } 259 260 // BuildBridgeToCallee connects h to a running app specified by BridgeApp that will be the Callee, i.e. the the BridgeCallee 261 func (h *Holochain) BuildBridgeToCallee(app *BridgeApp) (err error) { 262 263 data := map[string]string{"Type": "ToCallee", "DNA": h.DNAHash().String(), "Data": app.BridgeGenesisCalleeData} 264 dataJSON, err := json.Marshal(data) 265 if err != nil { 266 return 267 } 268 body := bytes.NewBuffer(dataJSON) 269 var resp *http.Response 270 resp, err = http.Post(fmt.Sprintf("http://0.0.0.0:%s/setup-bridge/", app.Port), "application/json", body) 271 272 if err == nil { 273 defer resp.Body.Close() 274 275 if resp.StatusCode != 200 { 276 err = errors.New(resp.Status) 277 } 278 } 279 if err != nil { 280 return 281 } 282 283 var b []byte 284 b, err = ioutil.ReadAll(resp.Body) 285 286 if err != nil { 287 h.Debugf("adding bridge to callee %s from %s failed with %v\n", app.Name, h.Name(), err) 288 return 289 } 290 291 token := string(b) 292 h.Debugf("%s received token %s from %s\n", h.Name(), token, app.Name) 293 294 // the url is currently through the webserver 295 err = h.AddBridgeAsCaller(app.BridgeZome, app.DNA, app.Name, token, fmt.Sprintf("http://localhost:%s", app.Port), app.BridgeGenesisCallerData) 296 if err != nil { 297 h.Debugf("adding bridge to callee %s from %s failed with %s\n", app.Name, h.Name(), err) 298 return 299 } 300 301 return 302 } 303 304 // GetBridges returns a list of the active bridges on the holochain 305 func (h *Holochain) GetBridges() (bridges []Bridge, err error) { 306 if h.bridgeDB == nil { 307 bridgeDBFile := filepath.Join(h.DBPath(), BridgeDBFileName) 308 if FileExists(bridgeDBFile) { 309 h.bridgeDB, err = buntdb.Open(bridgeDBFile) 310 if err != nil { 311 return 312 } 313 } 314 } 315 if h.bridgeDB != nil { 316 err = h.bridgeDB.View(func(tx *buntdb.Tx) error { 317 err = tx.Ascend("", func(key, value string) bool { 318 x := strings.Split(key, ":") 319 var hash Hash 320 switch x[0] { 321 case "app": 322 hash, err = NewHash(x[1]) 323 if err != nil { 324 return false 325 } 326 _, _, name := getBridgeAppVals(value) 327 bridges = append(bridges, Bridge{CalleeApp: hash, CalleeName: name, Side: BridgeCaller}) 328 case "tok": 329 bridges = append(bridges, Bridge{Token: x[1], Side: BridgeCallee}) 330 } 331 return true 332 }) 333 return err 334 }) 335 } 336 return 337 }