├── .gitignore
├── README.md
├── web
├── index.html
└── index.js
├── relay.go
├── bluetooth_plugin.go
└── main.go
/.gitignore:
--------------------------------------------------------------------------------
1 | src
2 | pkg
3 | bin
4 | main
5 | bluetooth_plugin
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scyther
2 |
3 | Native where native doesn't work
4 |
5 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Scyther Test
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/relay.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/gorilla/websocket"
6 | "log"
7 | "net/rpc"
8 | )
9 |
10 | type Args struct{}
11 |
12 | type BLAdvertisement struct {
13 | ID string
14 | Name string
15 | LocalName string
16 | TxPowerLevel int
17 | ManufacturerData []byte
18 | }
19 |
20 | func relay(ws *websocket.Conn, message string, messageType int) {
21 | log.Printf("Relaying data\n")
22 | res := ScytherMessage{"response", "Bad"}
23 | ret, _ := json.Marshal(&res)
24 | err := ws.WriteMessage(messageType, ret)
25 | if err != nil {
26 | log.Println("write:", err)
27 | }
28 | }
29 |
30 | func get_privilege(ws *websocket.Conn, message string, messageType int, rpcClient *rpc.Client) {
31 | log.Printf("Getting privileges\n")
32 | reply := new([]BLAdvertisement)
33 | args := Args{}
34 | err := rpcClient.Call("Bluetooth.Peripheral", args, &reply)
35 | // log.Println(*reply)
36 | d, _ := json.Marshal(reply)
37 | n := len(d)
38 | res := ScytherMessage{"response", string(d[:n])}
39 | d, _ = json.Marshal(res)
40 | err = ws.WriteMessage(messageType, d)
41 | if err != nil {
42 | log.Println("write:", err)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/bluetooth_plugin.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net"
7 | "net/rpc"
8 |
9 | "github.com/paypal/gatt"
10 | "github.com/paypal/gatt/examples/option"
11 | )
12 |
13 | type Args struct{}
14 | type BLAdvertisement struct {
15 | ID string
16 | Name string
17 | LocalName string
18 | TxPowerLevel int
19 | ManufacturerData []byte
20 | }
21 | type Bluetooth int
22 |
23 | var blAds []BLAdvertisement
24 |
25 | func (t *Bluetooth) Peripheral(args *Args, reply *[]BLAdvertisement) error {
26 | *reply = blAds
27 | return nil
28 | }
29 |
30 | func onStateChanged(d gatt.Device, s gatt.State) {
31 | fmt.Println("State:", s)
32 | switch s {
33 | case gatt.StatePoweredOn:
34 | fmt.Println("scanning...")
35 | d.Scan([]gatt.UUID{}, false)
36 | return
37 | default:
38 | d.StopScanning()
39 | }
40 | }
41 |
42 | func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
43 | blAd := BLAdvertisement{p.ID(), p.Name(), a.LocalName, a.TxPowerLevel, a.ManufacturerData}
44 | blAds = append(blAds, blAd)
45 | fmt.Printf("\nPeripheral ID:%s, NAME:(%s)\n", p.ID(), p.Name())
46 | fmt.Println(" Local Name =", a.LocalName)
47 | fmt.Println(" TX Power Level =", a.TxPowerLevel)
48 | fmt.Println(" Manufacturer Data =", a.ManufacturerData)
49 | fmt.Println(" Service Data =", a.ServiceData)
50 | }
51 |
52 | func main() {
53 | blAds = make([]BLAdvertisement, 0, 20)
54 | d, err := gatt.NewDevice(option.DefaultClientOptions...)
55 | if err != nil {
56 | log.Fatalf("Failed to open device, err: %s\n", err)
57 | return
58 | }
59 | // Register handlers.
60 | d.Handle(gatt.PeripheralDiscovered(onPeriphDiscovered))
61 | d.Init(onStateChanged)
62 |
63 | bl := new(Bluetooth)
64 | rpc.Register(bl)
65 | rpc.HandleHTTP()
66 | l, e := net.Listen("tcp", ":13922")
67 | if e != nil {
68 | log.Fatal("listen error:", e)
69 | }
70 | go rpc.Accept(l)
71 | fmt.Println("loaded bluetooth plugin")
72 |
73 | select {}
74 | }
75 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "flag"
6 | "github.com/gorilla/websocket"
7 | "log"
8 | "net/http"
9 | "net/rpc"
10 | )
11 |
12 | type ScytherMessage struct {
13 | Type string `json:"type"`
14 | Value string `json:"value"`
15 | }
16 |
17 | var HttpAddr = flag.String("http", ":13921", "Host:Port")
18 | var RcpAddr = flag.String("rcp", ":13922", "RcpHost:Port")
19 |
20 | var upgrader = websocket.Upgrader{
21 | ReadBufferSize: 1024,
22 | WriteBufferSize: 1024,
23 | CheckOrigin: func(r *http.Request) bool {
24 | return true
25 | },
26 | }
27 |
28 | func authorize(w http.ResponseWriter, r *http.Request) {
29 | var rpcClient *rpc.Client
30 | isConnected := false
31 | conn, err := upgrader.Upgrade(w, r, nil)
32 | if err != nil {
33 | log.Println(err)
34 | return
35 | }
36 | defer conn.Close()
37 | for {
38 | mt, message, err := conn.ReadMessage()
39 | if err != nil {
40 | log.Println("read:", err)
41 | break
42 | }
43 |
44 | res := ScytherMessage{}
45 | _ = json.Unmarshal(message, &res)
46 | log.Printf("Received message type %s, with data %s\n", res.Type, res.Value)
47 |
48 | if res.Type == "handshake" && res.Value == "hello scyther native" {
49 | isConnected = true
50 | data := ScytherMessage{"handshake", "hello scyther web"}
51 | ret, _ := json.Marshal(&data)
52 | err = conn.WriteMessage(mt, ret)
53 | if err != nil {
54 | log.Println("write:", err)
55 | }
56 | continue
57 | }
58 |
59 | if isConnected {
60 | log.Printf("I am connected %s", res.Type)
61 | switch res.Type {
62 | case "request":
63 | go relay(conn, res.Value, mt)
64 | case "privilege":
65 | rpcClient, err = rpc.Dial("tcp", *RcpAddr)
66 | if err != nil {
67 | log.Fatal(err)
68 | }
69 | go get_privilege(conn, res.Value, mt, rpcClient)
70 | default:
71 | data := ScytherMessage{"response", "No method implemented"}
72 | ret, _ := json.Marshal(&data)
73 | err = conn.WriteMessage(mt, ret)
74 | if err != nil {
75 | log.Println("write:", err)
76 | }
77 | }
78 | } else {
79 | data := ScytherMessage{"response", "Unauthorized"}
80 | ret, _ := json.Marshal(&data)
81 | err = conn.WriteMessage(mt, ret)
82 | if err != nil {
83 | log.Println("write:", err)
84 | }
85 | }
86 | }
87 | }
88 |
89 | func main() {
90 | flag.Parse()
91 | http.HandleFunc("/auth", authorize)
92 | log.Printf("Server started on %s\n", *HttpAddr)
93 | log.Fatal(http.ListenAndServe(*HttpAddr, nil))
94 | }
95 |
--------------------------------------------------------------------------------
/web/index.js:
--------------------------------------------------------------------------------
1 | var nativeSocket;
2 |
3 | try {
4 | nativeSocket = new WebSocket('ws://localhost:13921/auth')
5 | } catch (e) {
6 | console.log("Error: ", err);
7 | }
8 |
9 | var scytherReady = false;
10 |
11 | var handshake = function() {
12 | nativeSocket.send(JSON.stringify({
13 | type: 'handshake',
14 | value: 'hello scyther native'
15 | }));
16 | };
17 |
18 | var validateHandshake = function(message) {
19 | if (message.type === 'handshake' && message.value === 'hello scyther web') {
20 | // We have a Scyther server running, yay!
21 | scytherReady = true;
22 | console.log('Scyther is ready! \\m/')
23 | } else {
24 | console.log('Not a Scyther server :(');
25 | }
26 | };
27 |
28 | var populateDiscovered = function(message) {
29 | message = JSON.parse(message);
30 | var tagTotal = "";
31 | var deviceData = {};
32 | for (var bt of message) {
33 | deviceData[bt.ID] = bt.ManufacturerData;
34 | }
35 | for (var bt in deviceData) {
36 | tagTotal += "| " + bt + " | " + deviceData[bt] + " |
";
37 | }
38 | document.getElementById("placeholder").innerHTML = "| Device ID | Manufacturer Data |
" + tagTotal + "
";
39 | };
40 |
41 | var handleMessage = function(message) {
42 | message = JSON.parse(message.data);
43 | // console.log(message);
44 | switch(message.type) {
45 | case 'handshake':
46 | validateHandshake(message);
47 | break;
48 | case 'response':
49 | populateDiscovered(message.value);
50 | break;
51 | default:
52 | console.log('Unsupported message');
53 | }
54 | }
55 |
56 | nativeSocket.addEventListener('open', function() {
57 | // ready to start handshake
58 | handshake();
59 | });
60 |
61 | nativeSocket.addEventListener('error', function(err) {
62 | // oops! something went wrong
63 | scytherReady = false;
64 | console.log("Error: ", err);
65 | });
66 |
67 | nativeSocket.addEventListener('close', function() {
68 | // server closed the connection
69 | scytherReady = false;
70 | console.log("Connection closed")
71 | });
72 |
73 | nativeSocket.addEventListener('message', function(msg) {
74 | // received a message
75 | handleMessage(msg);
76 | });
77 |
78 | var btn = document.getElementById("discover");
79 | btn.addEventListener('click', function() {
80 | nativeSocket.send(JSON.stringify({type: "privilege", value: ""}));
81 | });
82 |
--------------------------------------------------------------------------------