diff --git a/examples/echobot/main.go b/examples/echobot/main.go index cd6bc16..7ce0de9 100644 --- a/examples/echobot/main.go +++ b/examples/echobot/main.go @@ -1,32 +1,31 @@ package main import ( - "fmt" "github.com/s-rah/go-ricochet" - "time" ) +type EchoBotService struct { + goricochet.StandardRicochetService +} + +func (ebs * EchoBotService) OnAuthenticationResult(channelID int32, serverHostname string, result bool) { + if true { + ebs.Ricochet().OpenChatChannel(5) + ebs.Ricochet().SendMessage(5, "Hi I'm an echo bot, I echo what you say!") + } +} + +func (ebs * EchoBotService) OnChatMessage(channelID int32, serverHostname string, messageId int32, message string) { + ebs.Ricochet().AckChatMessage(channelID, messageId) + ebs.Ricochet().SendMessage(5, message) +} + func main() { - ricochet := new(goricochet.Ricochet) - - // You will want to replace these values with your own test credentials - ricochet.Init("./private_key", true) - ricochet.Connect("kwke2hntvyfqm7dr", "127.0.0.1:55555|jlq67qzo6s4yp3sp") - - // Not needed past the initial run - // TODO need to wait for contact response before sending OpenChannel - // ricochet.SendContactRequest("EchoBot", "I'm an EchoBot") - - go ricochet.ListenAndWait() - ricochet.OpenChatChannel(5) - time.Sleep(time.Second * 1) - ricochet.SendMessage(5, "Hi I'm an echo bot, I echo what you say!") - - for true { - message,channel,_ := ricochet.Listen() - fmt.Print(channel, message) - if message != "" { - ricochet.SendMessage(5, message) - } + ricochetService := new(EchoBotService) + ricochetService.Init("./private_key", "kwke2hntvyfqm7dr") + err := ricochetService.Ricochet().Connect("kwke2hntvyfqm7dr", "127.0.0.1:55555|jlq67qzo6s4yp3sp") + if err == nil { + ricochetService.OnConnect("jlq67qzo6s4yp3sp") + ricochetService.Ricochet().ListenAndWait("jlq67qzo6s4yp3sp", ricochetService) } } diff --git a/messagebuilder.go b/messagebuilder.go index b4c414b..7c9eb33 100644 --- a/messagebuilder.go +++ b/messagebuilder.go @@ -26,6 +26,18 @@ func (mb *MessageBuilder) OpenChatChannel(channelID int32) ([]byte, error) { return proto.Marshal(pc) } +// AckOpenChannel constructs a message to acknowledge a previous open channel operation. +func (mb *MessageBuilder) AckOpenChannel(channelID int32, opened bool) ([]byte, error) { + cr := &Protocol_Data_Control.ChannelResult{ + ChannelIdentifier: proto.Int32(channelID), + Opened: proto.Bool(opened), + } + pc := &Protocol_Data_Control.Packet{ + ChannelResult: cr, + } + 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) { @@ -79,3 +91,15 @@ func (mb *MessageBuilder) ChatMessage(message string) ([]byte, error) { } return proto.Marshal(chatPacket) } + +// AckChatMessage constructs a chat message acknowledgement. +func (mb *MessageBuilder) AckChatMessage(messageID int32) ([]byte, error) { + cr := &Protocol_Data_Chat.ChatAcknowledge{ + MessageId: proto.Uint32(uint32(messageID)), + Accepted: proto.Bool(true), + } + pc := &Protocol_Data_Chat.Packet{ + ChatAcknowledge: cr, + } + return proto.Marshal(pc) +} diff --git a/messagedecoder.go b/messagedecoder.go new file mode 100644 index 0000000..48f758b --- /dev/null +++ b/messagedecoder.go @@ -0,0 +1,110 @@ +package goricochet + +import ( + "errors" + "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/control" +) + +type MessageDecoder struct { +} + +// Conceptual Chat Message - we construct this to avoid polluting the +// the main ricochet code with protobuf cruft - and enable us to minimise the +// code that may break in the future. +type RicochetChatMessage struct { + Ack bool + MessageID int32 + Message string + Accepted bool +} + +// Conceptual Control Message - we construct this to avoid polluting the +// the main ricochet code with protobuf cruft - and enable us to minimise the +// code that may break in the future. +type RicochetControlMessage struct { + Ack bool + Type string + ChannelID int32 + Accepted bool + ClientCookie [16]byte + ServerCookie [16]byte +} + +// DecodeAuthMessage +func (md *MessageDecoder) DecodeAuthMessage(data []byte) (bool, error) { + res := new(Protocol_Data_AuthHiddenService.Packet) + err := proto.Unmarshal(data[:], res) + if err != nil { + return false, errors.New("error unmarshalling control message type") + } + return res.GetResult().GetAccepted(), nil +} + +// DecodeControlMessage +func (md *MessageDecoder) DecodeControlMessage(data []byte) (*RicochetControlMessage, error) { + res := new(Protocol_Data_Control.Packet) + err := proto.Unmarshal(data[:], res) + + if err != nil { + return nil, errors.New("error unmarshalling control message type") + } + + if res.GetOpenChannel() != nil { + ricochetControlMessage := new(RicochetControlMessage) + ricochetControlMessage.Ack = false + + if res.GetOpenChannel().GetChannelType() == "im.ricochet.auth.hidden-service" { + ricochetControlMessage.Type = "openauthchannel" + } + + ricochetControlMessage.Type = "openchannel" + ricochetControlMessage.ChannelID = int32(res.GetOpenChannel().GetChannelIdentifier()) + return ricochetControlMessage, nil + } else if res.GetChannelResult() != nil { + ricochetControlMessage := new(RicochetControlMessage) + ricochetControlMessage.Ack = true + ricochetControlMessage.ChannelID = int32(res.GetOpenChannel().GetChannelIdentifier()) + + serverCookie, err := proto.GetExtension(res.GetChannelResult(), Protocol_Data_AuthHiddenService.E_ServerCookie) + + if err == nil { + ricochetControlMessage.Type = "openauthchannel" + copy(ricochetControlMessage.ServerCookie[:], serverCookie.([]byte)) + } else { + ricochetControlMessage.Type = "openchannel" + + } + + return ricochetControlMessage, nil + } + return nil, errors.New("unknown control message type") +} + +// DecodeChatMessage takes a byte representing a data packet and returns a +// constructed RicochetControlMessage +func (md *MessageDecoder) DecodeChatMessage(data []byte) (*RicochetChatMessage, error) { + res := new(Protocol_Data_Chat.Packet) + err := proto.Unmarshal(data[:], res) + + if err != nil { + return nil, err + } + + if res.GetChatMessage() != nil { + ricochetChatMessage := new(RicochetChatMessage) + ricochetChatMessage.Ack = false + ricochetChatMessage.MessageID = int32(res.GetChatMessage().GetMessageId()) + ricochetChatMessage.Message = res.GetChatMessage().GetMessageText() + return ricochetChatMessage, nil + } else if res.GetChatAcknowledge != nil { + ricochetChatMessage := new(RicochetChatMessage) + ricochetChatMessage.Ack = true + ricochetChatMessage.MessageID = int32(res.GetChatAcknowledge().GetMessageId()) + ricochetChatMessage.Accepted = res.GetChatAcknowledge().GetAccepted() + return ricochetChatMessage, nil + } + return nil, errors.New("chat message type not supported") +} diff --git a/ricochet.go b/ricochet.go index a786475..5e785ca 100644 --- a/ricochet.go +++ b/ricochet.go @@ -1,44 +1,21 @@ package goricochet import ( - "crypto" - "crypto/rsa" - "crypto/x509" - "encoding/asn1" "encoding/binary" - "encoding/pem" "errors" "fmt" "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/control" "io/ioutil" "log" "net" "os" ) -// MessageType details the different kinds of messages used by Ricochet -type MessageType int - -const ( - // CONTROL messages are those sent on channel 0 - CONTROL MessageType = iota - // AUTH messages are those that deal with authentication - AUTH = iota - // DATA covers both chat and (later) file handling and other non-control messages. - DATA = iota -) - // Ricochet is a protocol to conducting anonymous IM. type Ricochet struct { - conn net.Conn - privateKey *rsa.PrivateKey - logger *log.Logger - channelState map[int]int - channel chan RicochetMessage - known bool + conn net.Conn + logger *log.Logger } // RicochetData is a structure containing the raw data and the channel it the @@ -48,58 +25,14 @@ type RicochetData struct { Data []byte } -// RicochetMessage is a Wrapper Around Common Ricochet Protocol Strucutres -type RicochetMessage struct { - Channel int32 - ControlPacket *Protocol_Data_Control.Packet - DataPacket *Protocol_Data_Chat.Packet - AuthPacket *Protocol_Data_AuthHiddenService.Packet -} - -func (r *Ricochet) IsKnownContact() bool { - return r.known -} - -// Init sets up the Ricochet object. It takes in a filename of a hidden service -// private_key file so it can successfully authenticate itself with other -// clients. -func (r *Ricochet) Init(filename string, debugLog bool) { +// Init sets up the Ricochet object. +func (r *Ricochet) Init(debugLog bool) { if debugLog { r.logger = log.New(os.Stdout, "[Ricochet]: ", log.Ltime|log.Lmicroseconds) } else { r.logger = log.New(ioutil.Discard, "[Ricochet]: ", log.Ltime|log.Lmicroseconds) } - - pemData, err := ioutil.ReadFile(filename) - - if err != nil { - r.logger.Print("Error Reading Private Key: ", err) - } - - block, _ := pem.Decode(pemData) - if block == nil || block.Type != "RSA PRIVATE KEY" { - r.logger.Print("No valid PEM data found") - } - - r.privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) - r.handleFatal(err, "Private key can't be decoded") - - r.channelState = make(map[int]int) - r.channel = make(chan RicochetMessage) -} - -func (r *Ricochet) StartService(server RicochetService, port string) { - // Listen - ln, _ := net.Listen("tcp", port) - conn, _ := ln.Accept() - go r.runService(conn, server) -} - -func (r *Ricochet) runService(conn net.Conn, server RicochetService) { - // Negotiate Version - - // Loop For Messages } // Connect sets up a ricochet connection between from and to which are @@ -108,7 +41,6 @@ func (r *Ricochet) runService(conn net.Conn, server RicochetService) { // be open and authenticated. // To specify a local port using the format "127.0.0.1:[port]|ricochet-id". func (r *Ricochet) Connect(from string, to string) error { - var err error networkResolver := new(NetworkResolver) r.conn, to, err = networkResolver.Resolve(to) @@ -117,46 +49,27 @@ func (r *Ricochet) Connect(from string, to string) error { return err } - r.negotiateVersion() - - authHandler := new(AuthenticationHandler) - clientCookie := authHandler.GenClientCookie() + return r.negotiateVersion() +} +// Authenticate opens an Authentication Channel and send a client cookie +func (r *Ricochet) Authenticate(channelID int32, clientCookie [16]byte) error { messageBuilder := new(MessageBuilder) - data, err := messageBuilder.OpenAuthenticationChannel(1, clientCookie) + data, err := messageBuilder.OpenAuthenticationChannel(channelID, clientCookie) if err != nil { return errors.New("Cannot Marshal Open Channel Message") } - + r.logger.Printf("Sending Open Channel with Auth Request (channel:%d)", channelID) r.sendPacket(data, 0) + return nil +} - response, _ := r.getMessages() - openChannelResponse, _ := r.decodePacket(response[0], CONTROL) - r.logger.Print("Received Response: ", openChannelResponse) - channelResult := openChannelResponse.ControlPacket.GetChannelResult() - - if channelResult.GetOpened() == true { - r.logger.Print("Channel Opened Successfully: ", channelResult.GetChannelIdentifier()) - } - - sCookie, _ := proto.GetExtension(channelResult, Protocol_Data_AuthHiddenService.E_ServerCookie) - authHandler.AddServerCookie(sCookie.([]byte)) - - // DER Encode the Public Key - publickeybytes, err := asn1.Marshal(rsa.PublicKey{ - N: r.privateKey.PublicKey.N, - E: r.privateKey.PublicKey.E, - }) - - signature, _ := rsa.SignPKCS1v15(nil, r.privateKey, crypto.SHA256, authHandler.GenChallenge(from, to)) - - signatureBytes := make([]byte, 128) - copy(signatureBytes[:], signature[:]) - +// SendProof sends an authentication proof in response to a challenge. +func (r *Ricochet) SendProof(channelID int32, publickeyBytes []byte, signatureBytes []byte) error { // Construct a Proof Message proof := &Protocol_Data_AuthHiddenService.Proof{ - PublicKey: publickeybytes, + PublicKey: publickeyBytes, Signature: signatureBytes, } @@ -165,23 +78,14 @@ func (r *Ricochet) Connect(from string, to string) error { Result: nil, } - data, err = proto.Marshal(ahsPacket) - r.sendPacket(data, 1) - - response, err = r.getMessages() + data, err := proto.Marshal(ahsPacket) if err != nil { return err } - resultResponse, _ := r.decodePacket(response[0], AUTH) - r.logger.Print("Received Result: ", resultResponse) - - if resultResponse.AuthPacket.GetResult().GetAccepted() != true { - return errors.New("authorization failed") - } - - r.known = resultResponse.AuthPacket.GetResult().GetIsKnownContact() + r.logger.Printf("Sending Proof Auth Request (channel:%d)", channelID) + r.sendPacket(data, channelID) return nil } @@ -217,6 +121,32 @@ func (r *Ricochet) SendContactRequest(channel int32, nick string, message string return nil } +// AckOpenChannel acknowledges a previously received open channel message +// Prerequisites: +// * Must have Previously issued a successful Connect() +func (r *Ricochet) AckOpenChannel(channel int32, result bool) error { + messageBuilder := new(MessageBuilder) + data, err := messageBuilder.AckOpenChannel(channel, result) + if err != nil { + return errors.New("Failed to serialize open channel ack") + } + r.sendPacket(data, 0) + return nil +} + +// AckChatMessage acknowledges a previously received chat message. +// Prerequisites: +// * Must have Previously issued a successful Connect() +func (r *Ricochet) AckChatMessage(channel int32, messageID int32) error { + messageBuilder := new(MessageBuilder) + data, err := messageBuilder.AckChatMessage(messageID) + if err != nil { + return errors.New("Failed to serialize chat message ack") + } + r.sendPacket(data, channel) + return nil +} + // SendMessage sends a Chat Message (message) to a give Channel (channel). // Prerequisites: // * Must have previously issued a successful Connect() @@ -267,141 +197,75 @@ func (r *Ricochet) sendPacket(data []byte, channel int32) { header[2] = 0x00 header[3] = byte(channel) copy(header[4:], data[:]) - fmt.Fprintf(r.conn, "%s", header) } -// Listen blocks and waits for a new message to arrive from the connected user -// once a message has arrived, it returns the message and the channel it occured -// on, else it returns an error. -// Prerequisites: -// * Must have previously issued a successful Connect() -// * Must have previously ran "go ricochet.ListenAndWait()" -func (r *Ricochet) Listen() (string, int32, error) { - var message RicochetMessage - message = <-r.channel - r.logger.Printf("Received Chat Message on Channel %d", message.Channel) - if message.DataPacket.GetChatMessage() == nil { - return "", 0, errors.New("Did not receive a chat message") - } - - messageID := message.DataPacket.GetChatMessage().GetMessageId() - cr := &Protocol_Data_Chat.ChatAcknowledge{ - MessageId: proto.Uint32(messageID), - Accepted: proto.Bool(true), - } - - pc := &Protocol_Data_Chat.Packet{ - ChatAcknowledge: cr, - } - - data, err := proto.Marshal(pc) - if err != nil { - return "", 0, errors.New("Failed to serialize chat message") - } - - r.sendPacket(data, message.Channel) - return message.DataPacket.GetChatMessage().GetMessageText(), message.Channel, nil -} - // ListenAndWait is intended to be a background thread listening for all messages // a client will send, automaticall responding to some, and making the others available to // Listen() // Prerequisites: // * Must have previously issued a successful Connect() -func (r *Ricochet) ListenAndWait() error { +func (r *Ricochet) ListenAndWait(serverHostname string, service RicochetService) error { for true { packets, err := r.getMessages() - if err != nil { - return errors.New("Error attempted to get new messages") - } + r.handleFatal(err, "Error attempted to get new messages") + + messageDecoder := new(MessageDecoder) for _, packet := range packets { + + if len(packet.Data) == 0 { + r.logger.Printf("Closing Channel %d", packet.Channel) + service.OnChannelClose(packet.Channel, serverHostname) + break + } + if packet.Channel == 0 { - // This is a Control Channel Message - message, err := r.decodePacket(packet, CONTROL) - if err != nil { - r.logger.Printf("Failed to decode control packet, discarding") - break - } + message, err := messageDecoder.DecodeControlMessage(packet.Data) - // Automatically accept new channels - if message.ControlPacket.GetOpenChannel() != nil { - // TODO Reject if already in use. - cr := &Protocol_Data_Control.ChannelResult{ - ChannelIdentifier: proto.Int32(message.ControlPacket.GetOpenChannel().GetChannelIdentifier()), - Opened: proto.Bool(true), - } - - pc := &Protocol_Data_Control.Packet{ - ChannelResult: cr, - } - - data, err := proto.Marshal(pc) - // TODO we should set up some kind of error channel. - r.handleFatal(err, "error marshalling control protocol") - - r.logger.Printf("Client Opening Channel: %d\n", message.ControlPacket.GetOpenChannel().GetChannelIdentifier()) - r.sendPacket(data, 0) - r.channelState[int(message.ControlPacket.GetOpenChannel().GetChannelIdentifier())] = 1 - break - } - - if message.ControlPacket.GetChannelResult() != nil { - channelResult := message.ControlPacket.GetChannelResult() - if channelResult.GetOpened() == true { - r.logger.Print("Channel Opened Successfully: ", channelResult.GetChannelIdentifier()) - r.channelState[int(message.ControlPacket.GetChannelResult().GetChannelIdentifier())] = 1 - } - break - } - - r.logger.Printf("Received Unknown Control Message\n") - - } else if packet.Channel == 3 { - // Contact Request - r.logger.Printf("Received Unknown Message on Channel 3\n") - } else { - // At this point the only other expected type of message - // is a Chat Message - message, err := r.decodePacket(packet, DATA) if err != nil { r.logger.Printf("Failed to decode data packet, discarding") break } - r.channel <- message + + if message.Type == "openchannel" && message.Ack == false { + r.logger.Printf("new open channel request %d %s", message.ChannelID, serverHostname) + service.OnOpenChannelRequest(message.ChannelID, serverHostname) + } else if message.Type == "openchannel" && message.Ack == true { + r.logger.Printf("new open channel request ack %d %s", message.ChannelID, serverHostname) + service.OnOpenChannelRequestAck(message.ChannelID, serverHostname, message.Accepted) + } else if message.Type == "openauthchannel" && message.Ack == true { + r.logger.Printf("new authentication challenge %d %s", message.ChannelID, serverHostname) + service.OnAuthenticationChallenge(message.ChannelID, serverHostname, message.ServerCookie) + } else { + r.logger.Printf("Received Unknown Control Message\n", message) + } + } else if packet.Channel == 1 { + result, _ := messageDecoder.DecodeAuthMessage(packet.Data) + r.logger.Printf("newreceived auth result %d", packet.Channel) + service.OnAuthenticationResult(1, serverHostname, result) + } else { + + // At this point the only other expected type of message is a Chat Message + messageDecoder := new(MessageDecoder) + message, err := messageDecoder.DecodeChatMessage(packet.Data) + if err != nil { + r.logger.Printf("Failed to decode data packet, discarding on channel %d", packet.Channel) + break + } + + if message.Ack == true { + service.OnChatMessageAck(packet.Channel, serverHostname, message.MessageID) + } else { + service.OnChatMessage(packet.Channel, serverHostname, message.MessageID, message.Message) + } } } } return nil } -// decodePacket take a raw RicochetData message and decodes it based on a given MessageType -func (r *Ricochet) decodePacket(packet RicochetData, t MessageType) (rm RicochetMessage, err error) { - - rm.Channel = packet.Channel - - if t == CONTROL { - res := new(Protocol_Data_Control.Packet) - err = proto.Unmarshal(packet.Data[:], res) - rm.ControlPacket = res - } else if t == AUTH { - res := new(Protocol_Data_AuthHiddenService.Packet) - err = proto.Unmarshal(packet.Data[:], res) - rm.AuthPacket = res - } else if t == DATA { - res := new(Protocol_Data_Chat.Packet) - err = proto.Unmarshal(packet.Data[:], res) - rm.DataPacket = res - } - - if err != nil { - return rm, errors.New("Error Unmarshalling Response") - } - return rm, err -} - // getMessages returns an array of new messages received from the ricochet client func (r *Ricochet) getMessages() ([]RicochetData, error) { buf, err := r.recv() @@ -432,6 +296,7 @@ func (r *Ricochet) getMessages() ([]RicochetData, error) { finished = true } } + r.logger.Printf("Got %d Packets", len(datas)) return datas, nil } diff --git a/ricochetservice.go b/ricochetservice.go index 8186553..929038d 100644 --- a/ricochetservice.go +++ b/ricochetservice.go @@ -1,7 +1,16 @@ package goricochet type RicochetService interface { - OnConnect(id string) error - OnContactRequest(id string) error - OnMessage(id string, message string, channel int) error + OnConnect(serverHostname string) + OnAuthenticationChallenge(channelID int32, serverHostname string, serverCookie [16]byte) + OnAuthenticationResult(channelID int32, serverHostname string, result bool) + + OnOpenChannelRequest(channelID int32, serverHostname string) + OnOpenChannelRequestAck(channelID int32, serverHostname string, result bool) + OnChannelClose(channelID int32, serverHostname string) + + OnContactRequest(channelID string, serverHostname string, nick string, message string) + + OnChatMessage(channelID int32, serverHostname string, messageID int32, message string) + OnChatMessageAck(channelID int32, serverHostname string, messageID int32) } diff --git a/standardricochetservice.go b/standardricochetservice.go new file mode 100644 index 0000000..fee2cbe --- /dev/null +++ b/standardricochetservice.go @@ -0,0 +1,89 @@ +package goricochet + +import ( + "crypto" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + //"encoding/binary" + "encoding/pem" + "io/ioutil" +) + +type StandardRicochetService struct { + ricochet *Ricochet + authHandler map[string]*AuthenticationHandler + privateKey *rsa.PrivateKey + hostname string +} + +func (srs *StandardRicochetService) Init(filename string, hostname string) { + srs.ricochet = new(Ricochet) + srs.ricochet.Init(true) + srs.authHandler = make(map[string]*AuthenticationHandler) + srs.hostname = hostname + + pemData, err := ioutil.ReadFile(filename) + + if err != nil { + // r.logger.Print("Error Reading Private Key: ", err) + } + + block, _ := pem.Decode(pemData) + if block == nil || block.Type != "RSA PRIVATE KEY" { + // r.logger.Print("No valid PEM data found") + } + + srs.privateKey, _ = x509.ParsePKCS1PrivateKey(block.Bytes) +} + +func (srs *StandardRicochetService) OnConnect(serverHostname string) { + srs.authHandler[serverHostname] = new(AuthenticationHandler) + clientCookie := srs.authHandler[serverHostname].GenClientCookie() + srs.ricochet.Authenticate(1, clientCookie) +} + +// OnAuthenticationChallenge constructs a valid authentication challenge to the serverCookie +func (srs *StandardRicochetService) OnAuthenticationChallenge(channelID int32, serverHostname string, serverCookie [16]byte) { + srs.authHandler[serverHostname].AddServerCookie(serverCookie[:]) + + // DER Encode the Public Key + publickeyBytes, _ := asn1.Marshal(rsa.PublicKey{ + N: srs.privateKey.PublicKey.N, + E: srs.privateKey.PublicKey.E, + }) + + signature, _ := rsa.SignPKCS1v15(nil, srs.privateKey, crypto.SHA256, srs.authHandler[serverHostname].GenChallenge(srs.hostname, serverHostname)) + // TODO Handle Errors + signatureBytes := make([]byte, 128) + copy(signatureBytes[:], signature[:]) + srs.ricochet.SendProof(1, publickeyBytes, signatureBytes) +} + +func (srs *StandardRicochetService) Ricochet() *Ricochet { + return srs.ricochet +} + +func (srs *StandardRicochetService) OnAuthenticationResult(channelID int32, serverHostname string, result bool) { + +} + +func (srs *StandardRicochetService) OnOpenChannelRequest(channelID int32, serverHostname string) { + srs.ricochet.AckOpenChannel(channelID, true) +} + +func (srs *StandardRicochetService) OnOpenChannelRequestAck(channelID int32, serverHostname string, result bool) { +} + +func (srs *StandardRicochetService) OnChannelClose(channelID int32, serverHostname string) { +} + +func (srs *StandardRicochetService) OnContactRequest(channelID string, serverHostname string, nick string, message string) { +} + +func (srs *StandardRicochetService) OnChatMessage(channelID int32, serverHostname string, messageId int32, message string) { + srs.ricochet.AckChatMessage(channelID, messageId) +} + +func (srs *StandardRicochetService) OnChatMessageAck(channelID int32, serverHostname string, messageId int32) { +}