github.com/holochain/holochain-proto@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  }