More consistent interfaces, better test coverage
This commit is contained in:
parent
2353fc41e2
commit
a411fb8695
|
@ -0,0 +1 @@
|
||||||
|
go-ricochet-coverage.out
|
|
@ -7,35 +7,44 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AuthenticationHandler manages the stae required for the AuthHiddenService
|
||||||
|
// authentication scheme for ricochet.
|
||||||
type AuthenticationHandler struct {
|
type AuthenticationHandler struct {
|
||||||
clientCookie [16]byte
|
clientCookie [16]byte
|
||||||
serverCookie [16]byte
|
serverCookie [16]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddClientCookie adds a client cookie to the state.
|
||||||
func (ah *AuthenticationHandler) AddClientCookie(cookie []byte) {
|
func (ah *AuthenticationHandler) AddClientCookie(cookie []byte) {
|
||||||
copy(ah.clientCookie[:], cookie[:16])
|
copy(ah.clientCookie[:], cookie[:16])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddServerCookie adds a server cookie to the state.
|
||||||
func (ah *AuthenticationHandler) AddServerCookie(cookie []byte) {
|
func (ah *AuthenticationHandler) AddServerCookie(cookie []byte) {
|
||||||
copy(ah.serverCookie[:], cookie[:16])
|
copy(ah.serverCookie[:], cookie[:16])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenRandom generates a random 16byte cookie string.
|
||||||
func (ah *AuthenticationHandler) GenRandom() [16]byte {
|
func (ah *AuthenticationHandler) GenRandom() [16]byte {
|
||||||
var cookie [16]byte
|
var cookie [16]byte
|
||||||
io.ReadFull(rand.Reader, cookie[:])
|
io.ReadFull(rand.Reader, cookie[:])
|
||||||
return cookie
|
return cookie
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenClientCookie generates and adds a client cookie to the state.
|
||||||
func (ah *AuthenticationHandler) GenClientCookie() [16]byte {
|
func (ah *AuthenticationHandler) GenClientCookie() [16]byte {
|
||||||
ah.clientCookie = ah.GenRandom()
|
ah.clientCookie = ah.GenRandom()
|
||||||
return ah.clientCookie
|
return ah.clientCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenServerCookie generates and adds a server cookie to the state.
|
||||||
func (ah *AuthenticationHandler) GenServerCookie() [16]byte {
|
func (ah *AuthenticationHandler) GenServerCookie() [16]byte {
|
||||||
ah.serverCookie = ah.GenRandom()
|
ah.serverCookie = ah.GenRandom()
|
||||||
return ah.serverCookie
|
return ah.serverCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenChallenge constructs the challenge parameter for the AuthHiddenService session.
|
||||||
|
// The challenge is the a Sha256HMAC(clientHostname+serverHostname, key=clientCookie+serverCookie)
|
||||||
func (ah *AuthenticationHandler) GenChallenge(clientHostname string, serverHostname string) []byte {
|
func (ah *AuthenticationHandler) GenChallenge(clientHostname string, serverHostname string) []byte {
|
||||||
key := make([]byte, 32)
|
key := make([]byte, 32)
|
||||||
copy(key[0:16], ah.clientCookie[:])
|
copy(key[0:16], ah.clientCookie[:])
|
||||||
|
|
|
@ -3,7 +3,7 @@ package goricochet
|
||||||
import "testing"
|
import "testing"
|
||||||
import "bytes"
|
import "bytes"
|
||||||
|
|
||||||
func TestAuthHandler(t *testing.T) {
|
func TestGenChallenge(t *testing.T) {
|
||||||
authHandler := new(AuthenticationHandler)
|
authHandler := new(AuthenticationHandler)
|
||||||
authHandler.AddClientCookie([]byte("abcdefghijklmnop"))
|
authHandler.AddClientCookie([]byte("abcdefghijklmnop"))
|
||||||
authHandler.AddServerCookie([]byte("qrstuvwxyz012345"))
|
authHandler.AddServerCookie([]byte("qrstuvwxyz012345"))
|
||||||
|
@ -14,3 +14,19 @@ func TestAuthHandler(t *testing.T) {
|
||||||
t.Errorf("AuthenticationHandler Challenge Is Invalid, Got %x, Expected %x", challenge, expectedChallenge)
|
t.Errorf("AuthenticationHandler Challenge Is Invalid, Got %x, Expected %x", challenge, expectedChallenge)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenClientCookie(t *testing.T) {
|
||||||
|
authHandler := new(AuthenticationHandler)
|
||||||
|
clientCookie := authHandler.GenClientCookie()
|
||||||
|
if clientCookie != authHandler.clientCookie {
|
||||||
|
t.Errorf("AuthenticationHandler Client Cookies are Different", clientCookie, authHandler.clientCookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenServerCookie(t *testing.T) {
|
||||||
|
authHandler := new(AuthenticationHandler)
|
||||||
|
serverCookie := authHandler.GenServerCookie()
|
||||||
|
if serverCookie != authHandler.serverCookie {
|
||||||
|
t.Errorf("AuthenticationHandler Server Cookies are Different", serverCookie, authHandler.serverCookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
package goricochet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/s-rah/go-ricochet/control"
|
|
||||||
"github.com/s-rah/go-ricochet/auth"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ControlBuilder struct {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cb *ControlBuilder) OpenChatChannel(channelId int32) ([]byte,error) {
|
|
||||||
oc := &Protocol_Data_Control.OpenChannel{
|
|
||||||
ChannelIdentifier: proto.Int32(channelId),
|
|
||||||
ChannelType: proto.String("im.ricochet.chat"),
|
|
||||||
}
|
|
||||||
pc := &Protocol_Data_Control.Packet{
|
|
||||||
OpenChannel: oc,
|
|
||||||
}
|
|
||||||
return proto.Marshal(pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cb* ControlBuilder) OpenAuthenticationChannel(channelId int32, clientCookie [16]byte) ([]byte,error) {
|
|
||||||
oc := &Protocol_Data_Control.OpenChannel{
|
|
||||||
ChannelIdentifier: proto.Int32(channelId),
|
|
||||||
ChannelType: proto.String("im.ricochet.auth.hidden-service" ),
|
|
||||||
}
|
|
||||||
err := proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, clientCookie[:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pc := &Protocol_Data_Control.Packet{
|
|
||||||
OpenChannel: oc,
|
|
||||||
}
|
|
||||||
return proto.Marshal(pc)
|
|
||||||
}
|
|
|
@ -20,13 +20,13 @@ func main() {
|
||||||
go ricochet.ListenAndWait()
|
go ricochet.ListenAndWait()
|
||||||
ricochet.OpenChatChannel(5)
|
ricochet.OpenChatChannel(5)
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 1)
|
||||||
ricochet.SendMessage("Hi I'm an echo bot, I echo what you say! ", 5)
|
ricochet.SendMessage(5, "Hi I'm an echo bot, I echo what you say!")
|
||||||
|
|
||||||
for true {
|
for true {
|
||||||
message,channel,_ := ricochet.Listen()
|
message,channel,_ := ricochet.Listen()
|
||||||
fmt.Print(message, channel)
|
fmt.Print(channel, message)
|
||||||
if message != "" {
|
if message != "" {
|
||||||
ricochet.SendMessage(message, 5)
|
ricochet.SendMessage(5, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package goricochet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/s-rah/go-ricochet/auth"
|
||||||
|
"github.com/s-rah/go-ricochet/chat"
|
||||||
|
"github.com/s-rah/go-ricochet/contact"
|
||||||
|
"github.com/s-rah/go-ricochet/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MessageBuilder allows a client to construct specific data packets for the
|
||||||
|
// ricochet protocol.
|
||||||
|
type MessageBuilder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenChatChannel contructs a message which will request to open a channel for
|
||||||
|
// chat on the given channelID.
|
||||||
|
func (mb *MessageBuilder) OpenChatChannel(channelID int32) ([]byte, error) {
|
||||||
|
oc := &Protocol_Data_Control.OpenChannel{
|
||||||
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
|
ChannelType: proto.String("im.ricochet.chat"),
|
||||||
|
}
|
||||||
|
pc := &Protocol_Data_Control.Packet{
|
||||||
|
OpenChannel: oc,
|
||||||
|
}
|
||||||
|
return proto.Marshal(pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenContactRequestChannel contructs a message which will reuqest to open a channel for
|
||||||
|
// a contact request on the given channelID, with the given nick and message.
|
||||||
|
func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string, message string) ([]byte, error) {
|
||||||
|
// Construct a Contact Request Channel
|
||||||
|
oc := &Protocol_Data_Control.OpenChannel{
|
||||||
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
|
ChannelType: proto.String("im.ricochet.contact.request"),
|
||||||
|
}
|
||||||
|
|
||||||
|
contactRequest := &Protocol_Data_ContactRequest.ContactRequest{
|
||||||
|
Nickname: proto.String(nick),
|
||||||
|
MessageText: proto.String(message),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pc := &Protocol_Data_Control.Packet{
|
||||||
|
OpenChannel: oc,
|
||||||
|
}
|
||||||
|
return proto.Marshal(pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenAuthenticationChannel constructs a message which will reuqest to open a channel for
|
||||||
|
// authentication on the given channelID, with the given cookie
|
||||||
|
func (mb *MessageBuilder) OpenAuthenticationChannel(channelID int32, clientCookie [16]byte) ([]byte, error) {
|
||||||
|
oc := &Protocol_Data_Control.OpenChannel{
|
||||||
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
|
ChannelType: proto.String("im.ricochet.auth.hidden-service"),
|
||||||
|
}
|
||||||
|
err := proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, clientCookie[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pc := &Protocol_Data_Control.Packet{
|
||||||
|
OpenChannel: oc,
|
||||||
|
}
|
||||||
|
return proto.Marshal(pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChatMessage constructs a chat message with the given content.
|
||||||
|
func (mb *MessageBuilder) ChatMessage(message string) ([]byte, error) {
|
||||||
|
cm := &Protocol_Data_Chat.ChatMessage{
|
||||||
|
MessageText: proto.String(message),
|
||||||
|
}
|
||||||
|
chatPacket := &Protocol_Data_Chat.Packet{
|
||||||
|
ChatMessage: cm,
|
||||||
|
}
|
||||||
|
return proto.Marshal(chatPacket)
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package goricochet
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestOpenChatChannel(t *testing.T) {
|
||||||
|
messageBuilder := new(MessageBuilder)
|
||||||
|
_, err := messageBuilder.OpenChatChannel(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error building open chat channel message: %s", err)
|
||||||
|
}
|
||||||
|
// TODO: More Indepth Test Of Output
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestOpenContactRequestChannel(t *testing.T) {
|
||||||
|
messageBuilder := new(MessageBuilder)
|
||||||
|
_, err := messageBuilder.OpenContactRequestChannel(3,"Nickname","Message")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error building open contact request channel message: %s", err)
|
||||||
|
}
|
||||||
|
// TODO: More Indepth Test Of Output
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenAuthenticationChannel(t *testing.T) {
|
||||||
|
messageBuilder := new(MessageBuilder)
|
||||||
|
_, err := messageBuilder.OpenAuthenticationChannel(1,[16]byte{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error building open authentication channel message: %s", err)
|
||||||
|
}
|
||||||
|
// TODO: More Indepth Test Of Output
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChatMessage(t *testing.T) {
|
||||||
|
messageBuilder := new(MessageBuilder)
|
||||||
|
_, err := messageBuilder.ChatMessage("Hello World")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error building chat message: %s", err)
|
||||||
|
}
|
||||||
|
// TODO: More Indepth Test Of Output
|
||||||
|
}
|
57
ricochet.go
57
ricochet.go
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/s-rah/go-ricochet/auth"
|
"github.com/s-rah/go-ricochet/auth"
|
||||||
"github.com/s-rah/go-ricochet/chat"
|
"github.com/s-rah/go-ricochet/chat"
|
||||||
"github.com/s-rah/go-ricochet/contact"
|
|
||||||
"github.com/s-rah/go-ricochet/control"
|
"github.com/s-rah/go-ricochet/control"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
@ -45,13 +44,13 @@ type Ricochet struct {
|
||||||
// RicochetData is a structure containing the raw data and the channel it the
|
// RicochetData is a structure containing the raw data and the channel it the
|
||||||
// message originated on.
|
// message originated on.
|
||||||
type RicochetData struct {
|
type RicochetData struct {
|
||||||
Channel int
|
Channel int32
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// RicochetMessage is a Wrapper Around Common Ricochet Protocol Strucutres
|
// RicochetMessage is a Wrapper Around Common Ricochet Protocol Strucutres
|
||||||
type RicochetMessage struct {
|
type RicochetMessage struct {
|
||||||
Channel int
|
Channel int32
|
||||||
ControlPacket *Protocol_Data_Control.Packet
|
ControlPacket *Protocol_Data_Control.Packet
|
||||||
DataPacket *Protocol_Data_Chat.Packet
|
DataPacket *Protocol_Data_Chat.Packet
|
||||||
AuthPacket *Protocol_Data_AuthHiddenService.Packet
|
AuthPacket *Protocol_Data_AuthHiddenService.Packet
|
||||||
|
@ -120,12 +119,11 @@ func (r *Ricochet) Connect(from string, to string) error {
|
||||||
|
|
||||||
r.negotiateVersion()
|
r.negotiateVersion()
|
||||||
|
|
||||||
|
|
||||||
authHandler := new(AuthenticationHandler)
|
authHandler := new(AuthenticationHandler)
|
||||||
clientCookie := authHandler.GenClientCookie()
|
clientCookie := authHandler.GenClientCookie()
|
||||||
|
|
||||||
controlBuilder := new(ControlBuilder)
|
messageBuilder := new(MessageBuilder)
|
||||||
data, err := controlBuilder.OpenAuthenticationChannel(1, clientCookie)
|
data, err := messageBuilder.OpenAuthenticationChannel(1, clientCookie)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Cannot Marshal Open Channel Message")
|
return errors.New("Cannot Marshal Open Channel Message")
|
||||||
|
@ -192,8 +190,8 @@ func (r *Ricochet) Connect(from string, to string) error {
|
||||||
// * Must have Previously issued a successful Connect()
|
// * Must have Previously issued a successful Connect()
|
||||||
// * If acting as the client, id must be odd, else even
|
// * If acting as the client, id must be odd, else even
|
||||||
func (r *Ricochet) OpenChatChannel(id int32) error {
|
func (r *Ricochet) OpenChatChannel(id int32) error {
|
||||||
controlBuilder := new(ControlBuilder)
|
messageBuilder := new(MessageBuilder)
|
||||||
data,err := controlBuilder.OpenChatChannel(id)
|
data, err := messageBuilder.OpenChatChannel(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("error constructing control channel message to open channel")
|
return errors.New("error constructing control channel message to open channel")
|
||||||
|
@ -207,26 +205,12 @@ func (r *Ricochet) OpenChatChannel(id int32) error {
|
||||||
// SendContactRequest initiates a contact request to the server.
|
// SendContactRequest initiates a contact request to the server.
|
||||||
// Prerequisites:
|
// Prerequisites:
|
||||||
// * Must have Previously issued a successful Connect()
|
// * Must have Previously issued a successful Connect()
|
||||||
func (r *Ricochet) SendContactRequest(nick string, message string) error {
|
func (r *Ricochet) SendContactRequest(channel int32, nick string, message string) error {
|
||||||
// Construct a Contact Request Channel
|
messageBuilder := new(MessageBuilder)
|
||||||
oc := &Protocol_Data_Control.OpenChannel{
|
data, err := messageBuilder.OpenContactRequestChannel(channel, nick, message)
|
||||||
ChannelIdentifier: proto.Int32(3),
|
|
||||||
ChannelType: proto.String("im.ricochet.contact.request"),
|
|
||||||
}
|
|
||||||
|
|
||||||
contactRequest := &Protocol_Data_ContactRequest.ContactRequest{
|
|
||||||
Nickname: proto.String(nick),
|
|
||||||
MessageText: proto.String(message),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest)
|
|
||||||
pc := &Protocol_Data_Control.Packet{
|
|
||||||
OpenChannel: oc,
|
|
||||||
}
|
|
||||||
data, err := proto.Marshal(pc)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Cannot Marshal Open Channel Message")
|
return errors.New("error constructing control channel message to send contact request")
|
||||||
}
|
}
|
||||||
|
|
||||||
r.sendPacket(data, 0)
|
r.sendPacket(data, 0)
|
||||||
|
@ -237,18 +221,17 @@ func (r *Ricochet) SendContactRequest(nick string, message string) error {
|
||||||
// Prerequisites:
|
// Prerequisites:
|
||||||
// * Must have previously issued a successful Connect()
|
// * Must have previously issued a successful Connect()
|
||||||
// * Must have previously opened channel with OpenChanel
|
// * Must have previously opened channel with OpenChanel
|
||||||
func (r *Ricochet) SendMessage(message string, channel int) {
|
func (r *Ricochet) SendMessage(channel int32, message string) error {
|
||||||
// Construct a Contact Request Channel
|
messageBuilder := new(MessageBuilder)
|
||||||
cm := &Protocol_Data_Chat.ChatMessage{
|
data, err := messageBuilder.ChatMessage(message)
|
||||||
MessageText: proto.String(message),
|
|
||||||
}
|
if err != nil {
|
||||||
chatPacket := &Protocol_Data_Chat.Packet{
|
return errors.New("error constructing control channel message to send chat message")
|
||||||
ChatMessage: cm,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data, _ := proto.Marshal(chatPacket)
|
|
||||||
r.logger.Printf("Sending Message on Channel: %d", channel)
|
r.logger.Printf("Sending Message on Channel: %d", channel)
|
||||||
r.sendPacket(data, channel)
|
r.sendPacket(data, channel)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// negotiateVersion Perform version negotiation with the connected host.
|
// negotiateVersion Perform version negotiation with the connected host.
|
||||||
|
@ -277,7 +260,7 @@ func (r *Ricochet) negotiateVersion() error {
|
||||||
|
|
||||||
// sendPacket places the data into a structure needed for the client to
|
// sendPacket places the data into a structure needed for the client to
|
||||||
// decode the packet and writes the packet to the network.
|
// decode the packet and writes the packet to the network.
|
||||||
func (r *Ricochet) sendPacket(data []byte, channel int) {
|
func (r *Ricochet) sendPacket(data []byte, channel int32) {
|
||||||
header := make([]byte, 4+len(data))
|
header := make([]byte, 4+len(data))
|
||||||
header[0] = byte(len(header) >> 8)
|
header[0] = byte(len(header) >> 8)
|
||||||
header[1] = byte(len(header) & 0x00FF)
|
header[1] = byte(len(header) & 0x00FF)
|
||||||
|
@ -294,7 +277,7 @@ func (r *Ricochet) sendPacket(data []byte, channel int) {
|
||||||
// Prerequisites:
|
// Prerequisites:
|
||||||
// * Must have previously issued a successful Connect()
|
// * Must have previously issued a successful Connect()
|
||||||
// * Must have previously ran "go ricochet.ListenAndWait()"
|
// * Must have previously ran "go ricochet.ListenAndWait()"
|
||||||
func (r *Ricochet) Listen() (string, int, error) {
|
func (r *Ricochet) Listen() (string, int32, error) {
|
||||||
var message RicochetMessage
|
var message RicochetMessage
|
||||||
message = <-r.channel
|
message = <-r.channel
|
||||||
r.logger.Printf("Received Chat Message on Channel %d", message.Channel)
|
r.logger.Printf("Received Chat Message on Channel %d", message.Channel)
|
||||||
|
@ -439,7 +422,7 @@ func (r *Ricochet) getMessages() ([]RicochetData, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data := RicochetData{
|
data := RicochetData{
|
||||||
Channel: int(channel),
|
Channel: int32(channel),
|
||||||
Data: buf[pos+4 : pos+size],
|
Data: buf[pos+4 : pos+size],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue