Browse Source

Lots of peerand serverstuff

Sarah Jamie Lewis 1 year ago
parent
commit
914be9e46a

+ 11 - 8
model/group.go

@@ -3,11 +3,12 @@ package model
 import (
 	"crypto/rand"
 	"fmt"
-	"golang.org/x/crypto/nacl/secretbox"
-	"io"
-	"github.com/golang/protobuf/proto"
 	"git.mascherari.press/cwtch/protocol"
+	"github.com/golang/protobuf/proto"
 	"github.com/s-rah/go-ricochet/utils"
+	"golang.org/x/crypto/nacl/secretbox"
+	"io"
+	"log"
 	"time"
 )
 
@@ -52,7 +53,9 @@ func (g *Group) Invite() []byte {
 		GroupName:      g.GroupID,
 		GroupSharedKey: g.GroupKey[:],
 		ServerHost:     g.GroupServer,
+		SignedGroupId:  g.SignedGroupID[:],
 	}
+	log.Printf("INVITEBEFORE %v", gci)
 	cp := &protocol.CwtchPeerPacket{
 		GroupChatInvite: gci,
 	}
@@ -63,11 +66,11 @@ func (g *Group) Invite() []byte {
 
 func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, verified bool) {
 	timelineMessage := Message{
-		Message: message.GetText(),
-		Timestamp: time.Unix(int64(message.GetTimestamp()),0),
+		Message:   message.GetText(),
+		Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
 		Signature: message.GetSignature(),
-		Verified:verified,
-		PeerID: message.GetOnion(),
+		Verified:  verified,
+		PeerID:    message.GetOnion(),
 	}
 	g.Timeline = append(g.Timeline, timelineMessage)
 }
@@ -88,7 +91,7 @@ func (g *Group) EncryptMessage(message *protocol.DecryptedGroupMessage) []byte {
 	if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
 		panic(err)
 	}
-	wire,err := proto.Marshal(message)
+	wire, err := proto.Marshal(message)
 	utils.CheckError(err)
 	encrypted := secretbox.Seal(nonce[:], []byte(wire), &nonce, &g.GroupKey)
 	return encrypted

+ 5 - 6
model/group_test.go

@@ -1,21 +1,20 @@
 package model
 
 import (
-	"testing"
 	"git.mascherari.press/cwtch/protocol"
 	"github.com/golang/protobuf/proto"
+	"testing"
 	"time"
 )
 
 func TestGroup(t *testing.T) {
 	g := NewGroup("server.onion")
 	dgm := &protocol.DecryptedGroupMessage{
-		Onion: proto.String("onion"),
-		Text: proto.String("Hello World!"),
-		Timestamp: proto.Int32(int32(time.Now().Unix())),
+		Onion:         proto.String("onion"),
+		Text:          proto.String("Hello World!"),
+		Timestamp:     proto.Int32(int32(time.Now().Unix())),
 		SignedGroupId: []byte{},
-		Signature: []byte{},
-
+		Signature:     []byte{},
 	}
 	encMessage := g.EncryptMessage(dgm)
 	ok, message := g.DecryptMessage(encMessage)

+ 0 - 1
model/message.go

@@ -4,7 +4,6 @@ import (
 	"time"
 )
 
-
 // Message is a local representation of a given message sent over a group chat channel.
 type Message struct {
 	Timestamp time.Time

+ 27 - 17
model/profile.go

@@ -3,34 +3,33 @@ package model
 import (
 	"crypto/rand"
 	"crypto/rsa"
+	"encoding/asn1"
 	"encoding/json"
 	"git.mascherari.press/cwtch/protocol"
 	"github.com/golang/protobuf/proto"
 	"github.com/s-rah/go-ricochet/utils"
 	"golang.org/x/crypto/ed25519"
 	"io/ioutil"
-	"time"
-	"encoding/asn1"
+	"log"
 	"strconv"
+	"time"
 )
 
-
 // PublicProfile is a local copy of a CwtchIdentity
 type PublicProfile struct {
 	Name             string
 	Ed25519PublicKey ed25519.PublicKey
-	Trusted		bool
-	Blocked		bool
+	Trusted          bool
+	Blocked          bool
 }
 
-
 // Profile encapsulates all the attributes necessary to be a Cwtch Peer.
 type Profile struct {
 	PublicProfile
 	Contacts          map[string]PublicProfile
 	Ed25519PrivateKey ed25519.PrivateKey
 	OnionPrivateKey   *rsa.PrivateKey
-	Onion	string
+	Onion             string
 	Groups            map[string]*Group
 }
 
@@ -96,6 +95,12 @@ func (p *Profile) VerifyMessage(onion string, message string, signature []byte)
 	return ed25519.Verify(p.Contacts[onion].Ed25519PublicKey, []byte(message), signature)
 }
 
+// VerifyMessage confirms the authenticity of a message given an onion, message and signature.
+func (p *Profile) VerifyGroupMessage(onion string, groupID string, message string, timestamp int32, signature []byte) bool {
+	m := message + groupID + strconv.Itoa(int(timestamp))
+	return ed25519.Verify(p.Contacts[onion].Ed25519PublicKey, []byte(m), signature)
+}
+
 // SignMessage takes a given message and returns an Ed21159 signature
 func (p *Profile) SignMessage(message string) []byte {
 	sig := ed25519.Sign(p.Ed25519PrivateKey, []byte(message))
@@ -107,11 +112,14 @@ func (p *Profile) SignMessage(message string) []byte {
 func (p *Profile) StartGroup(server string) (groupID string, invite []byte) {
 	group := NewGroup(server)
 	groupID = group.GroupID
+	signedGroupId := p.SignMessage(groupID)
+	group.SignGroup(signedGroupId)
 	invite = group.Invite()
+	p.AddGroup(group)
 	return
 }
 
-func (p *Profile) GetGroupByGroupId(groupID string) (*Group) {
+func (p *Profile) GetGroupByGroupId(groupID string) *Group {
 	return p.Groups[groupID]
 }
 
@@ -119,6 +127,7 @@ func (p *Profile) GetGroupByGroupId(groupID string) (*Group) {
 func (p *Profile) ProcessInvite(gci *protocol.GroupChatInvite, peerHostname string) {
 	group := new(Group)
 	group.GroupID = gci.GetGroupName()
+	group.SignedGroupID = gci.GetSignedGroupId()
 	copy(group.GroupKey[:], gci.GetGroupSharedKey()[:])
 	group.GroupServer = gci.GetServerHost()
 	group.Accepted = false
@@ -126,7 +135,7 @@ func (p *Profile) ProcessInvite(gci *protocol.GroupChatInvite, peerHostname stri
 	p.AddGroup(group)
 }
 
-// AddGroup is a conveniance method for adding a group to a profle.
+// AddGroup is a conveniance method for adding a group to a profile.
 func (p *Profile) AddGroup(group *Group) {
 	existingGroup, exists := p.Groups[group.GroupID]
 	if !exists {
@@ -145,12 +154,13 @@ func (p *Profile) AddGroup(group *Group) {
 }
 
 // AttemptDecryption takes a ciphertext and signature and attempts to decrypt it under known groups.
-func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) {
+func (p *Profile) AttemptDecryption(ciphertext []byte) {
 	for _, group := range p.Groups {
 		success, dgm := group.DecryptMessage(ciphertext)
+		log.Printf("Decrypt Attempt %v %v", success, dgm)
 		if success {
 			// FIXME
-			verified := p.VerifyMessage(dgm.GetOnion(), dgm.GetText(), dgm.GetSignature())
+			verified := p.VerifyGroupMessage(dgm.GetOnion(), group.GroupID, dgm.GetText(), dgm.GetTimestamp(), dgm.GetSignature())
 			group.AddMessage(dgm, verified)
 		}
 	}
@@ -162,12 +172,12 @@ func (p *Profile) EncryptMessageToGroup(message string, groupID string) (ciphert
 	group := p.Groups[groupID]
 	timestamp := time.Now().Unix()
 	signature = p.SignMessage(message + groupID + strconv.Itoa(int(timestamp)))
-	dm := &protocol.DecryptedGroupMessage {
-		Onion: proto.String(p.Onion),
-		Text: proto.String(message),
-		SignedGroupId: group.SignedGroupID,
-		Timestamp: proto.Int32(int32(timestamp)),
-		Signature: signature,
+	dm := &protocol.DecryptedGroupMessage{
+		Onion:         proto.String(p.Onion),
+		Text:          proto.String(message),
+		SignedGroupId: group.SignedGroupID[:],
+		Timestamp:     proto.Int32(int32(timestamp)),
+		Signature:     signature,
 	}
 	ciphertext = group.EncryptMessage(dm)
 	return

+ 1 - 1
model/profile_test

@@ -1 +1 @@
-{"Name":"Sarah","Ed25519PublicKey":"08Q49pmOe/8Edn/jR1Qq8d26SU1MzPbJ2PmJ64S6BY8=","Trusted":false,"Blocked":false,"Contacts":{},"Ed25519PrivateKey":"/hhjerGW66QyhAKPtwGbBBzhY5/auK6T2b/vjRGuXnjTxDj2mY57/wR2f+NHVCrx3bpJTUzM9snY+YnrhLoFjw==","OnionPrivateKey":{"N":146328154189193884086641737732621103864374553173215703384122944250992002089624680210913011506081609406766798073335357829207459154214042241028123233139603802979024475926282158611908322463042012748984945285776401165814190414695182764240512995788636041616865870623261762908273890987461235956226167821197856348839,"E":65537,"D":131000281712443101868127073809425903015557376501501621205628292187530749753611841209312116988644890475820095160882129401029342867327559796231167833968091809253818237330927694966481136799400760644381429766064366889564088512676631446803130802140311950212661905465119640487794003192289760147304713580532618877313,"Primes":[11046905119315044599912769443513046649297228971109751902148448569336221142449837623971448415395377389790939200512488865059980264758837294990542341171570219,13246076852180554978507253920836715985718751717246338099834768101497493531807293249274887847720805074264534287152405900588792629153510158525691602692356981],"Precomputed":{"Dp":9951771949347089936659442878755668922509550306762893515157001442446411893285295532588832483100280468945131000782113044435070797127756136171042614443284033,"Dq":6285403633811601060799526716666618760759292321939158372044213473615958219816946235957557750406970050497863607761501422044192947211740832046507430314584393,"Qinv":6371034795994819655941418536323418056681675622606010416374029760428023006446243689515420082688972242794843497150013283993628462297774942497647603736899599,"CRTValues":[]}},"Onion":"3qs4j52zt24qqnor","Groups":{}}
+{"Name":"Sarah","Ed25519PublicKey":"57Vq0cuSR354/iWji5HYo1wRLejxtUHt3oG7Su/apTs=","Trusted":false,"Blocked":false,"Contacts":{},"Ed25519PrivateKey":"J8ccHg0nDRTt2hUcWFgcja7zp3q+stire73hfHQDlGDntWrRy5JHfnj+JaOLkdijXBEt6PG1Qe3egbtK79qlOw==","OnionPrivateKey":{"N":106324796443231372795329965451792784374908475257731042677987977989945962496419819543299936408371285042498791020833780363910141715665590232533252599354509160780458961503863367842703504977430864504358617442294691508603803037645994752383061089701523494391193738887906210739432038880652477581768063943643214298049,"E":65537,"D":52882540623824249319263554234503221073355763301661673056925036705376788585582196893867658378736750343245656531655368795367730890395281737333932003731626103306294071690361237344309695692052986927494790956908776032726936870574424804180862906949851814015510618834696423905205181829311680766288721499332239092113,"Primes":[10562771257917076828400600569279483678021278216385409474099349073602122852652613259317666843104807170827922546052248527778489213047126675685744594423055011,10065994410656021893110220970838146088057898596082565348257922502730196228417367015775815130802667391440031211811041050535960601302390904626913561708368459],"Precomputed":{"Dp":14666710170902757089650955213153379231578136284710503412469914181268492295823547104657028590300707272919739257072411249032493376066779490782348262698903,"Dq":8351748051846008307669886865591879879827216596130210009260002655117828861809706712999156561217721929245207091771627754763620453429647494239789002112611857,"Qinv":5157808396374745421942830050538059633959311365944586097995200192447276074688521466971512517666145546534306106545362826255395595072097617674779280863162908,"CRTValues":[]}},"Onion":"cf3nb4kb4ekfep7t","Groups":{}}

+ 15 - 30
model/profile_test.go

@@ -38,41 +38,26 @@ func TestProfileIdentity(t *testing.T) {
 }
 
 func TestProfileGroup(t *testing.T) {
-	/**sarah := GenerateNewProfile("Sarah")
+	sarah := GenerateNewProfile("Sarah")
 	alice := GenerateNewProfile("Alice")
-	sarah.AddContact("alice.onion", alice.PublicProfile)
-	alice.AddContact("sarah.onion", sarah.PublicProfile)
+	sarah.AddContact(alice.Onion, alice.PublicProfile)
+	alice.AddContact(sarah.Onion, sarah.PublicProfile)
 
-	group := NewGroup("server.onion")
-	alice.AddGroup(group)
-	sarah.AddGroup(group)
+	gid, invite := alice.StartGroup("aaa.onion")
+	gci := &protocol.CwtchPeerPacket{}
+	proto.Unmarshal(invite, gci)
+	sarah.ProcessInvite(gci.GetGroupChatInvite(), alice.Onion)
 
+	group := alice.GetGroupByGroupId(gid)
 	c, s := sarah.EncryptMessageToGroup("Hello World", group.GroupID)
-	ok, gid, onion, message := alice.AttemptDecryption(c, s)
-
-	if ok && gid == group.GroupID && onion == "sarah.onion" && message == "Hello World" {
-		t.Logf("Success!")
-	} else {
-		t.Errorf("Failed to decrypt group message %v %v %v %v", ok, gid, onion, message)
-	}
+	alice.AttemptDecryption(c, s)
 
-	group2 := NewGroup("server2.onion")
-	sarah.AddGroup(group2)
-	alice.AddGroup(group2)
+	gid2, invite2 := alice.StartGroup("bbb.onion")
+	gci2 := &protocol.CwtchPeerPacket{}
+	proto.Unmarshal(invite2, gci2)
+	sarah.ProcessInvite(gci2.GetGroupChatInvite(), alice.Onion)
+	group2 := alice.GetGroupByGroupId(gid2)
 	c2, _ := sarah.EncryptMessageToGroup("Hello World", group2.GroupID)
-	ok, gid, onion, message = alice.AttemptDecryption(c2, s)
-	if onion != "not-verified" {
-		t.Errorf("verification should have failed %v %v %v %v", ok, gid, onion, message)
-	}
-
-	bob := GenerateNewProfile("Bob")
-	bob.AddGroup(group)
-	c, s = bob.EncryptMessageToGroup("Hello", group.GroupID)
-	ok, gid, onion, message = alice.AttemptDecryption(c, s)
+	alice.AttemptDecryption(c2, s)
 
-	if ok && gid == group.GroupID && onion == "not-verified" && message == "Hello" {
-		t.Logf("Success!")
-	} else {
-		t.Errorf("Failed to decrypt unverified group message %v %v %v %v", ok, gid, onion, message)
-	}*/
 }

+ 7 - 5
peer/connections/connectionsmanager.go

@@ -2,21 +2,22 @@ package connections
 
 import (
 	"git.mascherari.press/cwtch/model"
-	"time"
+	"git.mascherari.press/cwtch/protocol"
 	"sync"
+	"time"
 )
 
 type Manager struct {
 	peerConnections   map[string]*PeerPeerConnection
 	serverConnections map[string]*PeerServerConnection
-	lock sync.Mutex
+	lock              sync.Mutex
 }
 
 func NewConnectionsManager() *Manager {
 	m := new(Manager)
 	m.peerConnections = make(map[string]*PeerPeerConnection)
 	m.serverConnections = make(map[string]*PeerServerConnection)
-	return m;
+	return m
 }
 
 func (m *Manager) ManagePeerConnection(host string, profile *model.Profile) {
@@ -28,17 +29,18 @@ func (m *Manager) ManagePeerConnection(host string, profile *model.Profile) {
 
 }
 
-func (m *Manager) ManageServerConnection(host string) {
+func (m *Manager) ManageServerConnection(host string, handler func(string, *protocol.GroupMessage)) {
 	m.lock.Lock()
 	psc := NewPeerServerConnection(host)
 	go psc.Run()
+	psc.GroupMessageHandler = handler
 	m.serverConnections[host] = psc
 	m.lock.Unlock()
 }
 
 func (m *Manager) GetPeerPeerConnectionForOnion(host string) (ppc *PeerPeerConnection) {
 	m.lock.Lock()
-	ppc =  m.peerConnections[host]
+	ppc = m.peerConnections[host]
 	m.lock.Unlock()
 	return
 }

+ 27 - 8
peer/connections/peerserverconnection.go

@@ -1,6 +1,7 @@
 package connections
 
 import (
+	"errors"
 	"git.mascherari.press/cwtch/peer/fetch"
 	"git.mascherari.press/cwtch/peer/listen"
 	"git.mascherari.press/cwtch/peer/send"
@@ -10,6 +11,7 @@ import (
 	"github.com/s-rah/go-ricochet/connection"
 	"github.com/s-rah/go-ricochet/identity"
 	"github.com/s-rah/go-ricochet/utils"
+	"log"
 	"time"
 )
 
@@ -36,6 +38,7 @@ func (psc *PeerServerConnection) GetState() ConnectionState {
 
 // Run manages the setup and teardown of a peer server connection
 func (psc *PeerServerConnection) Run() error {
+	log.Printf("Connecting to %v", psc.Server)
 	rc, err := goricochet.Open(psc.Server)
 	if err == nil {
 		rc.TraceLog(true)
@@ -58,7 +61,6 @@ func (psc *PeerServerConnection) Run() error {
 						return nil
 					})
 				}()
-
 				psc.connection.Process(psc)
 			}
 		}
@@ -73,25 +75,42 @@ func (psc *PeerServerConnection) Break() error {
 }
 
 func (psc *PeerServerConnection) SendGroupMessage(gm *protocol.GroupMessage) {
+	for psc.state != AUTHENTICATED {
+		time.Sleep(time.Second * 2)
+	}
+	log.Printf("Opening a Channel to Send")
 	psc.connection.Do(func() error {
 		psc.connection.RequestOpenChannel("im.cwtch.server.send", &send.CwtchPeerSendChannel{})
 		return nil
 	})
+	log.Printf("Waiting...")
 	// TODO We have to wait to receive the channel result before we can continue
 	// We should have a better mechanism for this kindof interaction
-	time.Sleep(time.Second * 1)
-	psc.connection.Do(func() error {
+	log.Printf("CWTCH PEER Sending...")
+send:
+	time.Sleep(time.Second * 2)
+	err := psc.connection.Do(func() error {
 		channel := psc.connection.Channel("im.cwtch.server.send", channels.Outbound)
-		if channel != nil {
-			sendchannel, ok := channel.Handler.(*send.CwtchPeerSendChannel)
-			if ok {
-				sendchannel.SendGroupMessage(gm)
-			}
+		if channel == nil {
+			return errors.New("No Channel")
+		}
+		sendchannel, ok := channel.Handler.(*send.CwtchPeerSendChannel)
+		if ok {
+			sendchannel.SendGroupMessage(gm)
+		} else {
+			return errors.New("Failed")
 		}
 		return nil
 	})
+	for err != nil {
+		log.Printf("CHANNEL ERROR %v", err)
+		goto send
+	}
+
+	log.Printf("Done")
 }
 
 func (psc *PeerServerConnection) HandleGroupMessage(gm *protocol.GroupMessage) {
+	log.Printf("Received Group Message: %v", gm)
 	psc.GroupMessageHandler(psc.Server, gm)
 }

+ 18 - 14
peer/cwtch_peer.go

@@ -2,6 +2,7 @@ package peer
 
 import (
 	"encoding/json"
+	"errors"
 	"git.mascherari.press/cwtch/model"
 	"git.mascherari.press/cwtch/peer/connections"
 	"git.mascherari.press/cwtch/peer/peer"
@@ -11,7 +12,6 @@ import (
 	"github.com/s-rah/go-ricochet/connection"
 	"io/ioutil"
 	"sync"
-	"errors"
 )
 
 /**
@@ -24,12 +24,11 @@ Move CwtchPeerChannel under peer/
        Write tests for Peer Channel
 */
 
-
 type CwtchPeer struct {
 	connection.AutoConnectionHandler
-	Profile           *model.Profile
-	mutex             sync.Mutex
-	Log               chan string `json:"-"`
+	Profile            *model.Profile
+	mutex              sync.Mutex
+	Log                chan string `json:"-"`
 	connectionsManager *connections.Manager
 }
 
@@ -64,8 +63,8 @@ func (cp *CwtchPeer) PeerWithOnion(onion string) {
 
 // InviteOnionToGroup kicks off the invite process
 func (cp *CwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
-	group:= cp.Profile.GetGroupByGroupId(groupid)
-	if  group == nil {
+	group := cp.Profile.GetGroupByGroupId(groupid)
+	if group == nil {
 		invite := group.Invite()
 		ppc := cp.connectionsManager.GetPeerPeerConnectionForOnion(onion)
 		ppc.SendGroupInvite(invite)
@@ -73,15 +72,22 @@ func (cp *CwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
 	return errors.New("group id could not be found")
 }
 
+func (cp *CwtchPeer) ReceiveGroupMessage(server string, gm *protocol.GroupMessage) {
+	cp.Profile.AttemptDecryption(gm.Ciphertext)
+}
+
 func (cp *CwtchPeer) JoinServer(onion string) {
-	cp.connectionsManager.ManageServerConnection(onion)
+	cp.connectionsManager.ManageServerConnection(onion, cp.ReceiveGroupMessage)
 }
 
 func (cp *CwtchPeer) SendMessageToGroup(groupid string, message string) {
-	// Lookup Group
-	// Lookup Sever Connection
-	// If no server connection, spin off server connection
-	// Else group.EncryptMessage(message) and send result to server
+	group := cp.Profile.GetGroupByGroupId(groupid)
+	psc := cp.connectionsManager.GetPeerServerConnectionForOnion(group.GroupServer)
+	ct, _ := cp.Profile.EncryptMessageToGroup(message, groupid)
+	gm := &protocol.GroupMessage{
+		Ciphertext: ct,
+	}
+	psc.SendGroupMessage(gm)
 }
 
 func (cp *CwtchPeer) Listen() error {
@@ -110,7 +116,6 @@ func (cp *CwtchPeer) Listen() error {
 	return nil
 }
 
-
 type CwtchPeerInstance struct {
 	rai *application.ApplicationInstance
 	ra  *application.RicochetApplication
@@ -121,7 +126,6 @@ func (cpi *CwtchPeerInstance) Init(rai *application.ApplicationInstance, ra *app
 	cpi.ra = ra
 }
 
-
 type CwtchPeerHandler struct {
 	Onion string
 	Peer  *CwtchPeer

+ 21 - 0
peer/cwtch_peer_server_intergration_test.go

@@ -0,0 +1,21 @@
+package peer
+
+import (
+	"testing"
+	"time"
+)
+
+func TestCwtchPeerIntegration(t *testing.T) {
+	alice := NewCwtchPeer("Alice")
+	id, _ := alice.Profile.StartGroup("ylhbhtypevo4ympq")
+	alice.Profile.AddContact(alice.Profile.Onion, alice.Profile.PublicProfile)
+	alice.JoinServer("ylhbhtypevo4ympq")
+	//	time.Sleep(time.Second *5)
+	alice.SendMessageToGroup(id, "Hello")
+	alice.SendMessageToGroup(id, "My")
+	alice.SendMessageToGroup(id, "Name Is")
+	alice.SendMessageToGroup(id, "ALICE!!!")
+	time.Sleep(time.Second * 5)
+	group := alice.Profile.Groups[id]
+	t.Logf("%v", group.Timeline)
+}

+ 1 - 0
peer/send/peer_send_channel.go

@@ -81,6 +81,7 @@ func (cplc *CwtchPeerSendChannel) SendGroupMessage(gm *protocol.GroupMessage) {
 	}
 	packet, _ := proto.Marshal(csp)
 	cplc.channel.SendMessage(packet)
+	cplc.channel.CloseChannel()
 }
 
 // Packet should never be

+ 325 - 0
protocol/ControlChannel.pb.go

@@ -0,0 +1,325 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: ControlChannel.proto
+
+/*
+Package protocol is a generated protocol buffer package.
+
+It is generated from these files:
+	ControlChannel.proto
+	cwtch-profile.proto
+	group_message.proto
+
+It has these top-level messages:
+	Packet
+	OpenChannel
+	ChannelResult
+	KeepAlive
+	EnableFeatures
+	FeaturesEnabled
+	CwtchPeerPacket
+	CwtchIdentity
+	GroupChatInvite
+	CwtchServerPacket
+	FetchMessage
+	GroupMessage
+	DecryptedGroupMessage
+*/
+package protocol
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type ChannelResult_CommonError int32
+
+const (
+	ChannelResult_GenericError      ChannelResult_CommonError = 0
+	ChannelResult_UnknownTypeError  ChannelResult_CommonError = 1
+	ChannelResult_UnauthorizedError ChannelResult_CommonError = 2
+	ChannelResult_BadUsageError     ChannelResult_CommonError = 3
+	ChannelResult_FailedError       ChannelResult_CommonError = 4
+)
+
+var ChannelResult_CommonError_name = map[int32]string{
+	0: "GenericError",
+	1: "UnknownTypeError",
+	2: "UnauthorizedError",
+	3: "BadUsageError",
+	4: "FailedError",
+}
+var ChannelResult_CommonError_value = map[string]int32{
+	"GenericError":      0,
+	"UnknownTypeError":  1,
+	"UnauthorizedError": 2,
+	"BadUsageError":     3,
+	"FailedError":       4,
+}
+
+func (x ChannelResult_CommonError) Enum() *ChannelResult_CommonError {
+	p := new(ChannelResult_CommonError)
+	*p = x
+	return p
+}
+func (x ChannelResult_CommonError) String() string {
+	return proto.EnumName(ChannelResult_CommonError_name, int32(x))
+}
+func (x *ChannelResult_CommonError) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(ChannelResult_CommonError_value, data, "ChannelResult_CommonError")
+	if err != nil {
+		return err
+	}
+	*x = ChannelResult_CommonError(value)
+	return nil
+}
+func (ChannelResult_CommonError) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{2, 0} }
+
+type Packet struct {
+	// Must contain exactly one field
+	OpenChannel      *OpenChannel     `protobuf:"bytes,1,opt,name=open_channel,json=openChannel" json:"open_channel,omitempty"`
+	ChannelResult    *ChannelResult   `protobuf:"bytes,2,opt,name=channel_result,json=channelResult" json:"channel_result,omitempty"`
+	KeepAlive        *KeepAlive       `protobuf:"bytes,3,opt,name=keep_alive,json=keepAlive" json:"keep_alive,omitempty"`
+	EnableFeatures   *EnableFeatures  `protobuf:"bytes,4,opt,name=enable_features,json=enableFeatures" json:"enable_features,omitempty"`
+	FeaturesEnabled  *FeaturesEnabled `protobuf:"bytes,5,opt,name=features_enabled,json=featuresEnabled" json:"features_enabled,omitempty"`
+	XXX_unrecognized []byte           `json:"-"`
+}
+
+func (m *Packet) Reset()                    { *m = Packet{} }
+func (m *Packet) String() string            { return proto.CompactTextString(m) }
+func (*Packet) ProtoMessage()               {}
+func (*Packet) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (m *Packet) GetOpenChannel() *OpenChannel {
+	if m != nil {
+		return m.OpenChannel
+	}
+	return nil
+}
+
+func (m *Packet) GetChannelResult() *ChannelResult {
+	if m != nil {
+		return m.ChannelResult
+	}
+	return nil
+}
+
+func (m *Packet) GetKeepAlive() *KeepAlive {
+	if m != nil {
+		return m.KeepAlive
+	}
+	return nil
+}
+
+func (m *Packet) GetEnableFeatures() *EnableFeatures {
+	if m != nil {
+		return m.EnableFeatures
+	}
+	return nil
+}
+
+func (m *Packet) GetFeaturesEnabled() *FeaturesEnabled {
+	if m != nil {
+		return m.FeaturesEnabled
+	}
+	return nil
+}
+
+type OpenChannel struct {
+	ChannelIdentifier            *int32  `protobuf:"varint,1,req,name=channel_identifier,json=channelIdentifier" json:"channel_identifier,omitempty"`
+	ChannelType                  *string `protobuf:"bytes,2,req,name=channel_type,json=channelType" json:"channel_type,omitempty"`
+	proto.XXX_InternalExtensions `json:"-"`
+	XXX_unrecognized             []byte `json:"-"`
+}
+
+func (m *OpenChannel) Reset()                    { *m = OpenChannel{} }
+func (m *OpenChannel) String() string            { return proto.CompactTextString(m) }
+func (*OpenChannel) ProtoMessage()               {}
+func (*OpenChannel) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+var extRange_OpenChannel = []proto.ExtensionRange{
+	{100, 536870911},
+}
+
+func (*OpenChannel) ExtensionRangeArray() []proto.ExtensionRange {
+	return extRange_OpenChannel
+}
+
+func (m *OpenChannel) GetChannelIdentifier() int32 {
+	if m != nil && m.ChannelIdentifier != nil {
+		return *m.ChannelIdentifier
+	}
+	return 0
+}
+
+func (m *OpenChannel) GetChannelType() string {
+	if m != nil && m.ChannelType != nil {
+		return *m.ChannelType
+	}
+	return ""
+}
+
+type ChannelResult struct {
+	ChannelIdentifier            *int32                     `protobuf:"varint,1,req,name=channel_identifier,json=channelIdentifier" json:"channel_identifier,omitempty"`
+	Opened                       *bool                      `protobuf:"varint,2,req,name=opened" json:"opened,omitempty"`
+	CommonError                  *ChannelResult_CommonError `protobuf:"varint,3,opt,name=common_error,json=commonError,enum=protocol.ChannelResult_CommonError" json:"common_error,omitempty"`
+	proto.XXX_InternalExtensions `json:"-"`
+	XXX_unrecognized             []byte `json:"-"`
+}
+
+func (m *ChannelResult) Reset()                    { *m = ChannelResult{} }
+func (m *ChannelResult) String() string            { return proto.CompactTextString(m) }
+func (*ChannelResult) ProtoMessage()               {}
+func (*ChannelResult) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+
+var extRange_ChannelResult = []proto.ExtensionRange{
+	{100, 536870911},
+}
+
+func (*ChannelResult) ExtensionRangeArray() []proto.ExtensionRange {
+	return extRange_ChannelResult
+}
+
+func (m *ChannelResult) GetChannelIdentifier() int32 {
+	if m != nil && m.ChannelIdentifier != nil {
+		return *m.ChannelIdentifier
+	}
+	return 0
+}
+
+func (m *ChannelResult) GetOpened() bool {
+	if m != nil && m.Opened != nil {
+		return *m.Opened
+	}
+	return false
+}
+
+func (m *ChannelResult) GetCommonError() ChannelResult_CommonError {
+	if m != nil && m.CommonError != nil {
+		return *m.CommonError
+	}
+	return ChannelResult_GenericError
+}
+
+type KeepAlive struct {
+	ResponseRequested *bool  `protobuf:"varint,1,req,name=response_requested,json=responseRequested" json:"response_requested,omitempty"`
+	XXX_unrecognized  []byte `json:"-"`
+}
+
+func (m *KeepAlive) Reset()                    { *m = KeepAlive{} }
+func (m *KeepAlive) String() string            { return proto.CompactTextString(m) }
+func (*KeepAlive) ProtoMessage()               {}
+func (*KeepAlive) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
+
+func (m *KeepAlive) GetResponseRequested() bool {
+	if m != nil && m.ResponseRequested != nil {
+		return *m.ResponseRequested
+	}
+	return false
+}
+
+type EnableFeatures struct {
+	Feature                      []string `protobuf:"bytes,1,rep,name=feature" json:"feature,omitempty"`
+	proto.XXX_InternalExtensions `json:"-"`
+	XXX_unrecognized             []byte `json:"-"`
+}
+
+func (m *EnableFeatures) Reset()                    { *m = EnableFeatures{} }
+func (m *EnableFeatures) String() string            { return proto.CompactTextString(m) }
+func (*EnableFeatures) ProtoMessage()               {}
+func (*EnableFeatures) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
+
+var extRange_EnableFeatures = []proto.ExtensionRange{
+	{100, 536870911},
+}
+
+func (*EnableFeatures) ExtensionRangeArray() []proto.ExtensionRange {
+	return extRange_EnableFeatures
+}
+
+func (m *EnableFeatures) GetFeature() []string {
+	if m != nil {
+		return m.Feature
+	}
+	return nil
+}
+
+type FeaturesEnabled struct {
+	Feature                      []string `protobuf:"bytes,1,rep,name=feature" json:"feature,omitempty"`
+	proto.XXX_InternalExtensions `json:"-"`
+	XXX_unrecognized             []byte `json:"-"`
+}
+
+func (m *FeaturesEnabled) Reset()                    { *m = FeaturesEnabled{} }
+func (m *FeaturesEnabled) String() string            { return proto.CompactTextString(m) }
+func (*FeaturesEnabled) ProtoMessage()               {}
+func (*FeaturesEnabled) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
+
+var extRange_FeaturesEnabled = []proto.ExtensionRange{
+	{100, 536870911},
+}
+
+func (*FeaturesEnabled) ExtensionRangeArray() []proto.ExtensionRange {
+	return extRange_FeaturesEnabled
+}
+
+func (m *FeaturesEnabled) GetFeature() []string {
+	if m != nil {
+		return m.Feature
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*Packet)(nil), "protocol.Packet")
+	proto.RegisterType((*OpenChannel)(nil), "protocol.OpenChannel")
+	proto.RegisterType((*ChannelResult)(nil), "protocol.ChannelResult")
+	proto.RegisterType((*KeepAlive)(nil), "protocol.KeepAlive")
+	proto.RegisterType((*EnableFeatures)(nil), "protocol.EnableFeatures")
+	proto.RegisterType((*FeaturesEnabled)(nil), "protocol.FeaturesEnabled")
+	proto.RegisterEnum("protocol.ChannelResult_CommonError", ChannelResult_CommonError_name, ChannelResult_CommonError_value)
+}
+
+func init() { proto.RegisterFile("ControlChannel.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 461 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0x4d, 0x8f, 0xd3, 0x30,
+	0x10, 0x25, 0xe9, 0xee, 0x92, 0x4e, 0xfa, 0x91, 0x9a, 0x5d, 0x30, 0xb7, 0x12, 0x2e, 0x15, 0x12,
+	0x3d, 0x54, 0x20, 0x21, 0x0e, 0x48, 0x4b, 0xd9, 0x22, 0xc4, 0x01, 0x64, 0xd1, 0x73, 0x64, 0x92,
+	0x29, 0x1b, 0x35, 0x6b, 0x1b, 0xc7, 0x05, 0x2d, 0xa7, 0xfe, 0x0e, 0xfe, 0x0c, 0x7f, 0x0d, 0xc5,
+	0x89, 0x9b, 0x14, 0x09, 0x09, 0x4e, 0xc9, 0x9b, 0xf7, 0xde, 0x8c, 0xfc, 0x66, 0xe0, 0x7c, 0x29,
+	0x85, 0xd1, 0xb2, 0x58, 0x5e, 0x73, 0x21, 0xb0, 0x98, 0x2b, 0x2d, 0x8d, 0x24, 0x81, 0xfd, 0xa4,
+	0xb2, 0x88, 0x7f, 0xf9, 0x70, 0xf6, 0x91, 0xa7, 0x5b, 0x34, 0xe4, 0x05, 0x0c, 0xa4, 0x42, 0x91,
+	0xa4, 0xb5, 0x94, 0x7a, 0x53, 0x6f, 0x16, 0x2e, 0x2e, 0xe6, 0x4e, 0x3b, 0xff, 0xa0, 0x50, 0x34,
+	0x7d, 0x58, 0x28, 0x5b, 0x40, 0x5e, 0xc1, 0xa8, 0x31, 0x25, 0x1a, 0xcb, 0x5d, 0x61, 0xa8, 0x6f,
+	0xbd, 0x0f, 0x5a, 0xaf, 0xf3, 0x59, 0x9a, 0x0d, 0xd3, 0x2e, 0x24, 0x0b, 0x80, 0x2d, 0xa2, 0x4a,
+	0x78, 0x91, 0x7f, 0x43, 0xda, 0xb3, 0xde, 0x7b, 0xad, 0xf7, 0x3d, 0xa2, 0xba, 0xac, 0x28, 0xd6,
+	0xdf, 0xba, 0x5f, 0x72, 0x09, 0x63, 0x14, 0xfc, 0x73, 0x81, 0xc9, 0x06, 0xb9, 0xd9, 0x69, 0x2c,
+	0xe9, 0x89, 0x35, 0xd2, 0xd6, 0x78, 0x65, 0x05, 0xab, 0x86, 0x67, 0x23, 0x3c, 0xc2, 0xe4, 0x0d,
+	0x44, 0xce, 0x9b, 0xd4, 0x54, 0x46, 0x4f, 0x6d, 0x8f, 0x87, 0x6d, 0x0f, 0xa7, 0xae, 0x7b, 0x65,
+	0x6c, 0xbc, 0x39, 0x2e, 0xc4, 0x39, 0x84, 0x9d, 0x60, 0xc8, 0x53, 0x20, 0x2e, 0x8b, 0x3c, 0x43,
+	0x61, 0xf2, 0x4d, 0x8e, 0x9a, 0x7a, 0x53, 0x7f, 0x76, 0xca, 0x26, 0x0d, 0xf3, 0xee, 0x40, 0x90,
+	0x47, 0x30, 0x70, 0x72, 0x73, 0xab, 0x90, 0xfa, 0x53, 0x7f, 0xd6, 0x67, 0x61, 0x53, 0xfb, 0x74,
+	0xab, 0xf0, 0x49, 0x10, 0x64, 0xd1, 0x7e, 0xbf, 0xdf, 0xfb, 0xf1, 0x4f, 0x1f, 0x86, 0x47, 0x41,
+	0xfe, 0xef, 0xb4, 0xfb, 0x70, 0x56, 0xed, 0x0d, 0x33, 0x3b, 0x27, 0x60, 0x0d, 0x22, 0x2b, 0x18,
+	0xa4, 0xf2, 0xe6, 0x46, 0x8a, 0x04, 0xb5, 0x96, 0xda, 0xae, 0x60, 0xb4, 0x78, 0xfc, 0x97, 0xf5,
+	0xcd, 0x97, 0x56, 0x7b, 0x55, 0x49, 0x59, 0x98, 0xb6, 0x20, 0x56, 0x10, 0x76, 0x38, 0x12, 0xc1,
+	0xe0, 0x2d, 0x0a, 0xd4, 0x79, 0x6a, 0x71, 0x74, 0x87, 0x9c, 0x43, 0xb4, 0x16, 0x5b, 0x21, 0xbf,
+	0x8b, 0xea, 0x69, 0x75, 0xd5, 0x23, 0x17, 0x30, 0x59, 0x0b, 0xbe, 0x33, 0xd7, 0x52, 0xe7, 0x3f,
+	0x30, 0xab, 0xcb, 0x3e, 0x99, 0xc0, 0xf0, 0x35, 0xcf, 0xd6, 0x25, 0xff, 0xd2, 0x28, 0x7b, 0x64,
+	0x0c, 0xe1, 0x8a, 0xe7, 0x85, 0xd3, 0x9c, 0x74, 0xc2, 0x79, 0x09, 0xfd, 0xc3, 0xa1, 0x54, 0xb9,
+	0x68, 0x2c, 0x95, 0x14, 0x25, 0x26, 0x1a, 0xbf, 0xee, 0xb0, 0x34, 0x98, 0xd9, 0x5c, 0x02, 0x36,
+	0x71, 0x0c, 0x73, 0x44, 0xfc, 0x0c, 0x46, 0xc7, 0xb7, 0x42, 0x28, 0xdc, 0x6d, 0x16, 0x4d, 0xbd,
+	0x69, 0x6f, 0xd6, 0x67, 0x0e, 0x76, 0x26, 0x3e, 0x87, 0xf1, 0x1f, 0xd7, 0xf1, 0x2f, 0xb6, 0xdf,
+	0x01, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x32, 0x16, 0x1e, 0x93, 0x03, 0x00, 0x00,
+}

+ 53 - 0
protocol/ControlChannel.proto

@@ -0,0 +1,53 @@
+syntax = "proto2";
+package protocol;
+
+message Packet {
+    // Must contain exactly one field
+    optional OpenChannel open_channel = 1;
+    optional ChannelResult channel_result = 2;
+    optional KeepAlive keep_alive = 3;
+    optional EnableFeatures enable_features = 4;
+    optional FeaturesEnabled features_enabled = 5;
+}
+
+message OpenChannel {
+    required int32 channel_identifier = 1;      // Arbitrary unique identifier for this channel instance
+    required string channel_type = 2;           // String identifying channel type; e.g. im.ricochet.chat
+
+    // It is valid to extend the OpenChannel message to add fields specific
+    // to the requested channel_type.
+    extensions 100 to max;
+}
+
+message ChannelResult {
+    required int32 channel_identifier = 1;      // Matching the value from OpenChannel
+    required bool opened = 2;                   // If the channel is now open
+
+    enum CommonError {
+        GenericError = 0;
+        UnknownTypeError = 1;
+        UnauthorizedError = 2;
+        BadUsageError = 3;
+        FailedError = 4;
+    }
+
+    optional CommonError common_error = 3;
+
+    // As with OpenChannel, it is valid to extend this message with fields specific
+    // to the channel type.
+    extensions 100 to max;
+}
+
+message KeepAlive {
+    required bool response_requested = 1;
+}
+
+message EnableFeatures {
+    repeated string feature = 1;
+    extensions 100 to max;
+}
+
+message FeaturesEnabled {
+    repeated string feature = 1;
+    extensions 100 to max;
+}

+ 13 - 0
server/app/main.go

@@ -0,0 +1,13 @@
+package main
+
+import (
+	cwtchserver "git.mascherari.press/cwtch/server"
+	"log"
+)
+
+func main() {
+	server := new(cwtchserver.Server)
+	log.Printf("starting cwtch server...")
+
+	server.Run("./private_key")
+}

+ 1 - 0
server/server.go

@@ -64,5 +64,6 @@ func (s *Server) Run(privateKeyFile string) {
 	})
 
 	cwtchserver.Init(pk, af, new(application.AcceptAllContactManager))
+	log.Printf("cwtch server running on cwtch:%s", l.Addr().String()[0:16])
 	cwtchserver.Run(l)
 }

+ 6 - 0
server/server_instance_test.go

@@ -6,6 +6,7 @@ import (
 	"github.com/s-rah/go-ricochet/application"
 	"os"
 	"testing"
+	"time"
 )
 
 func TestServerInstance(t *testing.T) {
@@ -27,4 +28,9 @@ func TestServerInstance(t *testing.T) {
 	if len(res) != 1 {
 		t.Errorf("Expected Group Messages Instead got %v", res)
 	}
+
+	//	ra.HandleApplicationInstance(ai)
+	si.HandleGroupMessage(&gm)
+
+	time.Sleep(time.Second * 2)
 }