| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package vango |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "time" |
| |
| "v.io/v23" |
| "v.io/v23/context" |
| "v.io/v23/discovery" |
| "v.io/v23/naming" |
| "v.io/v23/rpc" |
| "v.io/v23/security" |
| libdiscovery "v.io/x/ref/lib/discovery" |
| "v.io/x/ref/lib/stats" |
| ) |
| |
| const ( |
| rpcTimeout = 10 * time.Second |
| tcpServerName = "tmp/vango/tcp" |
| btServerName = "tmp/vango/bt" |
| interfaceName = "v.io/x/jni/impl/google/services/vango.EchoServer" |
| vangoStat = "vango" |
| ) |
| |
| var ( |
| bleServerName = naming.Endpoint{Protocol: "ble"}.Name() |
| |
| // vangoFuncs is a map containing go functions keys by unique strings |
| // intended to be run by java/android applications using Vango.run(key). |
| // Users must add function entries to this map and rebuild lib/android-lib in |
| // the vanadium java repository. |
| vangoFuncs = map[string]func(*context.T, io.Writer) error{ |
| "tcp-client": tcpClientFunc, |
| "tcp-server": tcpServerFunc, |
| "bt-client": btClientFunc, |
| "bt-server": btServerFunc, |
| "ble-client": bleClientFunc, |
| "ble-server": bleServerFunc, |
| "all": AllFunc, |
| } |
| ) |
| |
| func tcpServerFunc(ctx *context.T, _ io.Writer) error { |
| ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{Proxy: "proxy"}) |
| return runServer(ctx, tcpServerName) |
| } |
| |
| func tcpClientFunc(ctx *context.T, _ io.Writer) error { |
| return runClient(ctx, tcpServerName) |
| } |
| |
| func btServerFunc(ctx *context.T, _ io.Writer) error { |
| ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{Addrs: rpc.ListenAddrs{{Protocol: "bt", Address: "/0"}}}) |
| return runServer(ctx, btServerName) |
| } |
| |
| func btClientFunc(ctx *context.T, _ io.Writer) error { |
| return runClient(ctx, btServerName) |
| } |
| |
| func bleServerFunc(ctx *context.T, _ io.Writer) error { |
| ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{Addrs: rpc.ListenAddrs{{Protocol: "ble", Address: "na"}}}) |
| return runServer(ctx, "") |
| } |
| |
| func bleClientFunc(ctx *context.T, _ io.Writer) error { |
| return runClient(ctx, bleServerName) |
| } |
| |
| // AllFunc runs a server, advertises it, scans for other servers and makes an |
| // Echo RPC to every advertised remote server. |
| func AllFunc(ctx *context.T, output io.Writer) error { |
| ls := rpc.ListenSpec{Proxy: "proxy"} |
| addRegisteredProto(&ls, "tcp", ":0") |
| addRegisteredProto(&ls, "bt", "/0") |
| fmt.Fprintf(output, "Listening on: %+v (and proxy)\n", ls.Addrs) |
| ctx, server, err := v23.WithNewServer( |
| v23.WithListenSpec(ctx, ls), |
| mountName(ctx, "all"), |
| &echoServer{}, |
| security.AllowEveryone()) |
| if err != nil { |
| return err |
| } |
| ad := &discovery.Advertisement{ |
| InterfaceName: interfaceName, |
| Attributes: discovery.Attributes{ |
| "Hello": "There", |
| }, |
| } |
| d, err := v23.NewDiscovery(ctx) |
| if err != nil { |
| return err |
| } |
| stoppedAd, err := libdiscovery.AdvertiseServer(ctx, d, server, "", ad, nil) |
| if err != nil { |
| return err |
| } |
| updates, err := d.Scan(ctx, "v.InterfaceName=\""+interfaceName+"\"") |
| if err != nil { |
| return err |
| } |
| var ( |
| status = server.Status() |
| counter = 0 |
| peerByAdId = make(map[discovery.AdId]*peer) |
| lastCall = make(map[discovery.AdId]time.Time) |
| callResults = make(chan string) |
| activeCalls = 0 |
| quit = false |
| myaddrs = serverAddrs(status) |
| ticker = time.NewTicker(time.Second) |
| call = func(p *peer) { |
| counter++ |
| activeCalls++ |
| lastCall[p.adId] = time.Now() |
| go func(msg string) { |
| summary, err := p.call(ctx, msg) |
| if err != nil { |
| ctx.Infof("Failed to call [%v]: %v", p.description, err) |
| callResults <- "" |
| return |
| } |
| callResults <- summary |
| }(fmt.Sprintf("Hello #%d", counter)) |
| } |
| statRequest = make(chan chan<- string) |
| ) |
| defer ticker.Stop() |
| stats.NewStringFunc(vangoStat, func() string { |
| r := make(chan string) |
| statRequest <- r |
| return <-r |
| }) |
| defer stats.Delete(vangoStat) |
| fmt.Fprintln(output, "My AdID:", ad.Id) |
| fmt.Fprintln(output, "My addrs:", myaddrs) |
| ctx.Infof("SERVER STATUS: %+v", status) |
| for !quit { |
| select { |
| case <-ctx.Done(): |
| quit = true |
| case <-status.Dirty: |
| status = server.Status() |
| newaddrs := serverAddrs(status) |
| changed := len(newaddrs) != len(myaddrs) |
| if !changed { |
| for i := range newaddrs { |
| if newaddrs[i] != myaddrs[i] { |
| changed = true |
| break |
| } |
| } |
| } |
| if changed { |
| myaddrs = newaddrs |
| fmt.Fprintln(output, "My addrs:", myaddrs) |
| } |
| ctx.Infof("SERVER STATUS: %+v", status) |
| case u, scanning := <-updates: |
| if !scanning { |
| fmt.Fprintln(output, "SCANNING STOPPED") |
| quit = true |
| break |
| } |
| if u.IsLost() { |
| if p, ok := peerByAdId[u.Id()]; ok { |
| fmt.Fprintln(output, "LOST:", p.description) |
| } |
| delete(peerByAdId, u.Id()) |
| delete(lastCall, u.Id()) |
| break |
| } |
| p, err := newPeer(ctx, u) |
| if err != nil { |
| ctx.Info(err) |
| break |
| } |
| peerByAdId[p.adId] = p |
| fmt.Fprintln(output, "FOUND:", p.description) |
| call(p) |
| case r := <-callResults: |
| activeCalls-- |
| if len(r) > 0 { |
| fmt.Fprintln(output, r) |
| } |
| case <-stoppedAd: |
| fmt.Fprintln(output, "STOPPED ADVERTISING") |
| stoppedAd = nil |
| case <-ticker.C: |
| // Call all peers that haven't been called in a while |
| now := time.Now() |
| for id, t := range lastCall { |
| if now.Sub(t) > rpcTimeout { |
| call(peerByAdId[id]) |
| } |
| } |
| case s := <-statRequest: |
| idx := 1 |
| ret := new(bytes.Buffer) |
| fmt.Fprintln(ret, "ACTIVE CALLS:", activeCalls) |
| fmt.Fprintln(ret, "PEERS") |
| for id, p := range peerByAdId { |
| fmt.Fprintf(ret, "%2d) %s -- %v\n", idx, p.description, lastCall[id]) |
| } |
| s <- ret.String() |
| } |
| } |
| fmt.Println(output, "EXITING: Cleaning up") |
| for activeCalls > 0 { |
| <-callResults |
| activeCalls-- |
| } |
| // Exhaust the scanned updates queue. |
| // (The channel will be closed as a by-product of the context being Done). |
| for range updates { |
| } |
| fmt.Fprintln(output, "EXITING: Done") |
| return nil |
| } |