Client: send join request with signed message to verify validity
diff --git a/core/client/Makefile b/core/client/Makefile
index 93b2ccf..4c39648 100644
--- a/core/client/Makefile
+++ b/core/client/Makefile
@@ -1,6 +1,7 @@
-run:
-	go run github.com/giolekva/pcloud/core/client/cmd/pcloud
+build:
+	go build -o pcloud github.com/giolekva/pcloud/core/client/cmd/pcloud
 
 aar: export ANDROID_SDK_ROOT=/Users/lekva/Library/Android/sdk/
 aar:
+	mkdir -p android/app/libs
 	go run gioui.org/cmd/gogio -target android -buildmode archive -o android/app/libs/pcloud.aar github.com/giolekva/pcloud/core/client/cmd/pcloud
diff --git a/core/client/android/app/libs/pcloud.aar b/core/client/android/app/libs/pcloud.aar
new file mode 100644
index 0000000..051cdeb
--- /dev/null
+++ b/core/client/android/app/libs/pcloud.aar
Binary files differ
diff --git a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java
index 3e737d4..ec1c76d 100644
--- a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java
+++ b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudActivity.java
@@ -27,7 +27,6 @@
         super.onCreate(savedInstanceState);
         view = new GioView(this);
         setContentView(view);
-        qrcodeScanned("yayaya");
     }
 
     @Override
@@ -70,7 +69,7 @@
     public String launchBarcodeScanner() {
         ScanOptions options = new ScanOptions();
         options.setDesiredBarcodeFormats(ScanOptions.QR_CODE);
-        options.setPrompt("Join PCloud network");
+        options.setPrompt("Join PCloud mesh");
         options.setCameraId(0);  // Use a specific camera of the device
         options.setBeepEnabled(true);
         options.setBarcodeImageEnabled(false);
diff --git a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java
index d8b5723..1e2b64a 100644
--- a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java
+++ b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java
@@ -7,9 +7,7 @@
 public class PCloudApp extends Application {
     @Override
     public void onCreate() {
-        System.out.println("fooo");
         super.onCreate();
         Gio.init(this);
-        System.out.println("bar");
     }
 }
diff --git a/core/client/cmd/pcloud/callbacks_android.go b/core/client/cmd/pcloud/callbacks_android.go
index 107b998..df399f9 100644
--- a/core/client/cmd/pcloud/callbacks_android.go
+++ b/core/client/cmd/pcloud/callbacks_android.go
@@ -3,7 +3,6 @@
 // JNI implementations of Java native callback methods.
 
 import (
-	"fmt"
 	"unsafe"
 
 	"github.com/giolekva/pcloud/core/client/jni"
@@ -16,5 +15,5 @@
 func Java_me_lekva_pcloud_PCloudActivity_qrcodeScanned(env *C.JNIEnv, this C.jobject, contents C.jobject) {
 	jenv := (*jni.Env)(unsafe.Pointer(env))
 	code := jni.GoString(jenv, jni.String(contents))
-	fmt.Printf("!!!! QRCODE SCANNED: %s\n", code)
+	p.InviteQRCodeScanned([]byte(code))
 }
diff --git a/core/client/cmd/pcloud/client.go b/core/client/cmd/pcloud/client.go
index 0d1fed4..aa1a3c3 100644
--- a/core/client/cmd/pcloud/client.go
+++ b/core/client/cmd/pcloud/client.go
@@ -2,15 +2,20 @@
 
 import (
 	"bytes"
+	"crypto/rand"
 	"crypto/tls"
 	"encoding/json"
-	"fmt"
+	"errors"
+	"io"
 	"net/http"
+
+	"golang.org/x/crypto/curve25519"
 )
 
 type VPNClient interface {
 	Address() string
 	Sign(message []byte) ([]byte, error)
+	Join(apiAddr string, message, signature []byte) (interface{}, error)
 }
 
 type directVPNClient struct {
@@ -46,7 +51,6 @@
 	}
 	r, err := client.Post(c.addr+"/api/sign", "application/json", &data)
 	if err != nil {
-		fmt.Println(111111)
 		return nil, err
 	}
 	resp := &signResp{}
@@ -55,3 +59,60 @@
 	}
 	return resp.Signature, nil
 }
+
+type joinReq struct {
+	Message   []byte `json:"message"`
+	Signature []byte `json:"signature"`
+	Name      string `json:"name"`
+	PublicKey []byte `json:"public_key"`
+	IPCidr    string `json:"ip_cidr"`
+}
+
+type joinResp struct {
+}
+
+func (c *directVPNClient) Join(apiAddr string, message, signature []byte) (interface{}, error) {
+	if c.addr != "" {
+		return nil, errors.New("Already joined")
+	}
+	c.addr = apiAddr
+	pubKey, _, err := x25519Keypair()
+	if err != nil {
+		return nil, err
+	}
+	req := joinReq{
+		message,
+		signature,
+		"test",
+		pubKey,
+		"111.0.0.13/24",
+	}
+	var data bytes.Buffer
+	if err := json.NewEncoder(&data).Encode(req); err != nil {
+		return nil, err
+	}
+	client := &http.Client{
+		// TODO(giolekva): remove, for some reason valid certificates are not accepted on gioui android.
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+		},
+	}
+	r, err := client.Post(c.addr+"/api/join", "application/json", &data)
+	if err != nil {
+		return nil, err
+	}
+	resp := &joinResp{}
+	if err := json.NewDecoder(r.Body).Decode(resp); err != nil {
+		return nil, err
+	}
+	return nil, nil
+}
+
+func x25519Keypair() ([]byte, []byte, error) {
+	var pubkey, privkey [32]byte
+	if _, err := io.ReadFull(rand.Reader, privkey[:]); err != nil {
+		return nil, nil, err
+	}
+	curve25519.ScalarBaseMult(&pubkey, &privkey)
+	return pubkey[:], privkey[:], nil
+}
diff --git a/core/client/cmd/pcloud/main.go b/core/client/cmd/pcloud/main.go
index 16fa940..e85fc42 100644
--- a/core/client/cmd/pcloud/main.go
+++ b/core/client/cmd/pcloud/main.go
@@ -1,34 +1,49 @@
 package main
 
 import (
+	"bytes"
+	"encoding/json"
 	"flag"
 	"fmt"
+	"image"
+	"image/png"
 
 	"gioui.org/app"
 	"gioui.org/io/system"
 	"gioui.org/layout"
 	"gioui.org/op"
 	"gioui.org/unit"
+	"github.com/skip2/go-qrcode"
 )
 
-var vpnApiAddr = flag.String("vpn-api-addr", "https://vpn.lekva.me", "VPN API server address")
+var vpnApiAddr = flag.String("vpn-api-addr", "", "VPN API server address")
 
-func processUIEvents(a App, events []UIEvent) error {
-	for _, e := range events {
-		switch e.(type) {
-		case EventScanBarcode:
-			return a.LaunchBarcodeScanner()
-		default:
-			return fmt.Errorf("Unhandled event: %#v", e)
-		}
-	}
-	return nil
+var p *processor
+
+type processor struct {
+	vc  VPNClient
+	app App
+	ui  *UI
+
+	inviteQrCh        chan image.Image
+	inviteQrScannedCh chan []byte
 }
 
-func run() error {
-	a := createApp()
-	vc := NewDirectVPNClient(*vpnApiAddr)
-	ui := NewUI(vc)
+func newProcessor() *processor {
+	return &processor{
+		vc:                NewDirectVPNClient(*vpnApiAddr),
+		app:               createApp(),
+		ui:                NewUI(),
+		inviteQrCh:        make(chan image.Image, 1),
+		inviteQrScannedCh: make(chan []byte, 1),
+	}
+}
+
+func (p *processor) InviteQRCodeScanned(code []byte) {
+	p.inviteQrScannedCh <- code
+}
+
+func (p *processor) run() error {
 	w := app.NewWindow(
 		app.Size(unit.Px(1500), unit.Px(1500)),
 		app.Title("PCloud"),
@@ -39,35 +54,109 @@
 		case e := <-w.Events():
 			switch e := e.(type) {
 			case app.ViewEvent:
-				if err := a.OnView(e); err != nil {
+				if err := p.app.OnView(e); err != nil {
 					return err
 				} else {
 					w.Invalidate()
 				}
 			case *system.CommandEvent:
 				if e.Type == system.CommandBack {
-					if ui.OnBack() {
+					if p.ui.OnBack() {
 						e.Cancel = true
 						w.Invalidate()
 					}
 				}
 			case system.FrameEvent:
 				gtx := layout.NewContext(&ops, e)
-				events := ui.Layout(gtx)
+				events := p.ui.Layout(gtx)
 				e.Frame(&ops)
-				if err := processUIEvents(a, events); err != nil {
+				if err := p.processUIEvents(events); err != nil {
 					return err
 				}
 			}
+		case img := <-p.inviteQrCh:
+			p.ui.InviteQRGenerated(img)
+			w.Invalidate()
+		case code := <-p.inviteQrScannedCh:
+			go func() {
+				p.JoinNetworkAndConnect(code)
+			}()
 		}
 	}
 	return nil
 }
 
+func (p *processor) processUIEvents(events []UIEvent) error {
+	for _, e := range events {
+		switch e.(type) {
+		case EventGetInviteQRCode:
+			go func() {
+				if img, err := p.generateInviteQRCode(); err == nil {
+					p.inviteQrCh <- img
+				} else {
+					// TODO(giolekva): do not panic
+					panic(err)
+				}
+			}()
+		case EventScanBarcode:
+			return p.app.LaunchBarcodeScanner()
+		default:
+			return fmt.Errorf("Unhandled event: %#v", e)
+		}
+	}
+	return nil
+}
+
+type qrCodeData struct {
+	VPNApiAddr string `json:"vpn_api_addr"`
+	Message    []byte `json:"message"`
+	Signature  []byte `json:"signature"`
+}
+
+func (p *processor) generateInviteQRCode() (image.Image, error) {
+	message := []byte("Hello PCloud")
+	signature, err := p.vc.Sign(message)
+	if err != nil {
+		return nil, err
+	}
+	c := qrCodeData{
+		p.vc.Address(),
+		message,
+		signature,
+	}
+	var data bytes.Buffer
+	if err := json.NewEncoder(&data).Encode(c); err != nil {
+		return nil, err
+	}
+	qr, err := qrcode.Encode(data.String(), qrcode.Medium, 1024)
+	if err != nil {
+		return nil, err
+	}
+	img, err := png.Decode(bytes.NewReader(qr))
+	if err != nil {
+		return nil, err
+	}
+	return img, nil
+}
+
+func (p *processor) JoinNetworkAndConnect(code []byte) {
+	var invite qrCodeData
+	if err := json.NewDecoder(bytes.NewReader(code)).Decode(&invite); err != nil {
+		panic(err)
+	}
+	config, err := p.vc.Join(invite.VPNApiAddr, invite.Message, invite.Signature)
+	if err != nil {
+		panic(err)
+	}
+	fmt.Printf("-- VPN CONFIG %#v\n", config)
+
+}
+
 func main() {
 	flag.Parse()
+	p = newProcessor()
 	go func() {
-		if err := run(); err != nil {
+		if err := p.run(); err != nil {
 			panic(err)
 		}
 	}()
diff --git a/core/client/cmd/pcloud/ui.go b/core/client/cmd/pcloud/ui.go
index 2a0a382..50e74ff 100644
--- a/core/client/cmd/pcloud/ui.go
+++ b/core/client/cmd/pcloud/ui.go
@@ -1,18 +1,14 @@
 package main
 
 import (
-	"bytes"
-	"encoding/json"
 	"image"
 	"image/color"
-	"image/png"
 
 	"gioui.org/layout"
 	"gioui.org/op/clip"
 	"gioui.org/op/paint"
 	"gioui.org/unit"
 	"gioui.org/widget"
-	"github.com/skip2/go-qrcode"
 )
 
 type (
@@ -21,8 +17,6 @@
 )
 
 type UI struct {
-	vc VPNClient
-
 	invite struct {
 		open widget.Clickable
 		show bool
@@ -36,10 +30,12 @@
 	}
 }
 
-func NewUI(vc VPNClient) *UI {
-	return &UI{
-		vc: vc,
-	}
+func NewUI() *UI {
+	return &UI{}
+}
+
+func (ui *UI) InviteQRGenerated(img image.Image) {
+	ui.invite.qr = img
 }
 
 func (ui *UI) OnBack() bool {
@@ -59,9 +55,10 @@
 		ui.join.show = false
 		ui.invite.show = true
 		ui.invite.qr = nil
+		events = append(events, EventGetInviteQRCode{})
 	} else if ui.join.open.Clicked() {
-		ui.invite.show = false
-		ui.join.show = true
+		// ui.invite.show = false
+		// ui.join.show = true
 		events = append(events, EventScanBarcode{})
 	}
 	if ui.invite.show {
@@ -114,11 +111,7 @@
 
 func (ui *UI) layoutInvite(gtx C) D {
 	if ui.invite.qr == nil {
-		img, err := prepareConfigQRCode(ui.vc)
-		if err != nil {
-			panic(err)
-		}
-		ui.invite.qr = img
+		return ColorBox(gtx, gtx.Constraints.Max, color.NRGBA{})
 	}
 	d := ui.invite.qr.Bounds().Max.Sub(ui.invite.qr.Bounds().Min)
 	return layout.Inset{
@@ -137,37 +130,3 @@
 	}
 	return ColorBox(gtx, gtx.Constraints.Min, color.NRGBA{R: 255, G: 255, B: 255, A: 255})
 }
-
-// helpers
-
-type qrCodeData struct {
-	VPNApiAddr string `json:"vpn_api_addr"`
-	Message    []byte `json:"message"`
-	Signature  []byte `json:"signature"`
-}
-
-func prepareConfigQRCode(vc VPNClient) (image.Image, error) {
-	message := []byte("Hello PCloud")
-	signature, err := vc.Sign(message)
-	if err != nil {
-		return nil, err
-	}
-	c := qrCodeData{
-		vc.Address(),
-		message,
-		signature,
-	}
-	var data bytes.Buffer
-	if err := json.NewEncoder(&data).Encode(c); err != nil {
-		return nil, err
-	}
-	qr, err := qrcode.Encode(data.String(), qrcode.Medium, 1024)
-	if err != nil {
-		return nil, err
-	}
-	img, err := png.Decode(bytes.NewReader(qr))
-	if err != nil {
-		return nil, err
-	}
-	return img, nil
-}
diff --git a/core/client/cmd/pcloud/ui_events.go b/core/client/cmd/pcloud/ui_events.go
index 044abbf..581c5d4 100644
--- a/core/client/cmd/pcloud/ui_events.go
+++ b/core/client/cmd/pcloud/ui_events.go
@@ -3,3 +3,5 @@
 type UIEvent interface{}
 
 type EventScanBarcode struct{}
+
+type EventGetInviteQRCode struct{}
diff --git a/core/client/go.mod b/core/client/go.mod
index 4f10753..1460cca 100644
--- a/core/client/go.mod
+++ b/core/client/go.mod
@@ -7,5 +7,6 @@
 	gioui.org/cmd v0.0.0-20211214104907-f5c9d2725c7b
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/slackhq/nebula v1.5.1
+	golang.org/x/crypto v0.0.0-20211202192323-5770296d904e
 	golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098 // indirect
 )
diff --git a/core/client/pcloud b/core/client/pcloud
new file mode 100755
index 0000000..76ca2c2
--- /dev/null
+++ b/core/client/pcloud
Binary files differ