core: Implement inbound contact requests
This does not yet include persistence of inbound requests in the config, and it's mostly untested, but it's more or less complete.
This commit is contained in:
parent
3d13263fac
commit
dcda924d56
|
@ -133,6 +133,12 @@ func (c *Contact) Data() *ricochet.Contact {
|
|||
return data
|
||||
}
|
||||
|
||||
func (c *Contact) IsRequest() bool {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
return c.data.Request.Pending
|
||||
}
|
||||
|
||||
func (c *Contact) Conversation() *Conversation {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
@ -312,6 +318,7 @@ func (c *Contact) connectOutbound(ctx context.Context, connChannel chan *protoco
|
|||
// is fragile and dumb.
|
||||
// XXX BUG: This means no backoff for authentication failure
|
||||
handler := &ProtocolConnection{
|
||||
Core: c.core,
|
||||
Conn: oc,
|
||||
Contact: c,
|
||||
MyHostname: c.core.Identity.Address()[9:],
|
||||
|
|
|
@ -17,6 +17,7 @@ type ContactList struct {
|
|||
events *utils.Publisher
|
||||
|
||||
contacts map[int]*Contact
|
||||
inboundRequests map[string]*InboundContactRequest
|
||||
}
|
||||
|
||||
func LoadContactList(core *Ricochet) (*ContactList, error) {
|
||||
|
@ -80,30 +81,43 @@ func (this *ContactList) ContactByAddress(address string) *Contact {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (this *ContactList) AddContactRequest(address, name, fromName, text string) (*Contact, error) {
|
||||
if !IsAddressValid(address) {
|
||||
return nil, errors.New("Invalid ricochet address")
|
||||
func (cl *ContactList) InboundRequestByAddress(address string) *InboundContactRequest {
|
||||
cl.mutex.RLock()
|
||||
defer cl.mutex.RUnlock()
|
||||
for _, request := range cl.inboundRequests {
|
||||
if request.Address == address {
|
||||
return request
|
||||
}
|
||||
if len(fromName) > 0 && !IsNicknameAcceptable(fromName) {
|
||||
return nil, errors.New("Invalid nickname")
|
||||
}
|
||||
if len(text) > 0 && !IsMessageAcceptable(text) {
|
||||
return nil, errors.New("Invalid message")
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNewContact adds a new contact to the persistent contact list, broadcasts a
|
||||
// contact add RPC event, and returns a newly constructed Contact. AddNewContact
|
||||
// does not create contact requests or trigger any other protocol behavior.
|
||||
//
|
||||
// Generally, you will use AddContactRequest (for outbound requests) and
|
||||
// AddOrUpdateInboundContactRequest plus InboundContactRequest.Accept() instead of
|
||||
// using this function directly.
|
||||
func (this *ContactList) AddNewContact(configContact ConfigContact) (*Contact, error) {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
|
||||
address, ok := AddressFromOnion(configContact.Hostname)
|
||||
if !ok {
|
||||
return nil, errors.New("Invalid ricochet address")
|
||||
}
|
||||
|
||||
for _, contact := range this.contacts {
|
||||
if contact.Address() == address {
|
||||
return nil, errors.New("Contact already exists with this address")
|
||||
}
|
||||
if contact.Nickname() == name {
|
||||
if contact.Nickname() == configContact.Nickname {
|
||||
return nil, errors.New("Contact already exists with this nickname")
|
||||
}
|
||||
}
|
||||
|
||||
// XXX check inbound requests
|
||||
// XXX check inbound requests (but this can be called for an inbound req too)
|
||||
|
||||
// Write new contact into config
|
||||
config := this.core.Config.OpenWrite()
|
||||
|
@ -118,18 +132,6 @@ func (this *ContactList) AddContactRequest(address, name, fromName, text string)
|
|||
}
|
||||
|
||||
contactId := maxContactId + 1
|
||||
onion, _ := OnionFromAddress(address)
|
||||
configContact := ConfigContact{
|
||||
Hostname: onion,
|
||||
Nickname: name,
|
||||
WhenCreated: time.Now().Format(time.RFC3339),
|
||||
Request: ConfigContactRequest{
|
||||
Pending: true,
|
||||
MyNickname: fromName,
|
||||
Message: text,
|
||||
},
|
||||
}
|
||||
|
||||
config.Contacts[strconv.Itoa(contactId)] = configContact
|
||||
if err := config.Save(); err != nil {
|
||||
return nil, err
|
||||
|
@ -150,10 +152,54 @@ func (this *ContactList) AddContactRequest(address, name, fromName, text string)
|
|||
}
|
||||
this.events.Publish(event)
|
||||
|
||||
// XXX Should this be here? Is it ok for inbound where we might pass conn over momentarily?
|
||||
contact.StartConnection()
|
||||
return contact, nil
|
||||
}
|
||||
|
||||
// AddContactRequest creates a new outbound contact request with the given parameters,
|
||||
// adds it to the contact list, and returns the newly constructed Contact.
|
||||
//
|
||||
// If an inbound request already exists for this address, that request will be automatically
|
||||
// accepted, and the returned contact will already be fully established.
|
||||
func (cl *ContactList) AddContactRequest(address, name, fromName, text string) (*Contact, error) {
|
||||
onion, valid := OnionFromAddress(address)
|
||||
if !valid {
|
||||
return nil, errors.New("Invalid ricochet address")
|
||||
}
|
||||
if !IsNicknameAcceptable(name) {
|
||||
return nil, errors.New("Invalid nickname")
|
||||
}
|
||||
if len(fromName) > 0 && !IsNicknameAcceptable(fromName) {
|
||||
return nil, errors.New("Invalid 'from' nickname")
|
||||
}
|
||||
if len(text) > 0 && !IsMessageAcceptable(text) {
|
||||
return nil, errors.New("Invalid message")
|
||||
}
|
||||
|
||||
configContact := ConfigContact{
|
||||
Hostname: onion,
|
||||
Nickname: name,
|
||||
WhenCreated: time.Now().Format(time.RFC3339),
|
||||
Request: ConfigContactRequest{
|
||||
Pending: true,
|
||||
MyNickname: fromName,
|
||||
Message: text,
|
||||
},
|
||||
}
|
||||
contact, err := cl.AddNewContact(configContact)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if inboundRequest := cl.InboundRequestByAddress(address); inboundRequest != nil {
|
||||
contact.UpdateContactRequest("Accepted")
|
||||
inboundRequest.AcceptWithContact(contact)
|
||||
}
|
||||
|
||||
return contact, nil
|
||||
}
|
||||
|
||||
func (this *ContactList) RemoveContact(contact *Contact) error {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
|
@ -186,6 +232,99 @@ func (this *ContactList) RemoveContact(contact *Contact) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// AddOrUpdateInboundContactRequest creates or modifies an inbound contact request for
|
||||
// the hostname, with an optional nickname suggestion and introduction message
|
||||
//
|
||||
// The nickname, message, and address must be validated before calling this function.
|
||||
//
|
||||
// This function may change the state of the request, and the caller is responsible for
|
||||
// sending a reply message and closing the channel or connection as appropriate. If the
|
||||
// request is still active when the state changes spontaneously later (e.g. it's accepted
|
||||
// by the user), replies will be sent by InboundContactRequest.
|
||||
//
|
||||
// This function may return either an InboundContactRequest (which may be pending or already
|
||||
// rejected), an existing contact (which should be treated as accepting the request), or
|
||||
// neither, which is considered a rejection.
|
||||
func (cl *ContactList) AddOrUpdateInboundContactRequest(address, nickname, message string) (*InboundContactRequest, *Contact) {
|
||||
cl.mutex.Lock()
|
||||
defer cl.mutex.Unlock()
|
||||
|
||||
// Look up existing request
|
||||
if request := cl.inboundRequests[address]; request != nil {
|
||||
// Errors in Update will change the state of the request, which the caller sends as a reply
|
||||
request.Update(nickname, message)
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// Check for existing contacts or outbound contact requests
|
||||
for _, contact := range cl.contacts {
|
||||
if contact.Address() == address {
|
||||
if contact.IsRequest() {
|
||||
contact.UpdateContactRequest("Accepted")
|
||||
}
|
||||
return nil, contact
|
||||
}
|
||||
}
|
||||
|
||||
// Create new request
|
||||
request := CreateInboundContactRequest(cl.core, address, nickname, message)
|
||||
request.StatusChanged = cl.inboundRequestChanged
|
||||
cl.inboundRequests[address] = request
|
||||
// XXX update config
|
||||
requestData := request.Data()
|
||||
event := ricochet.ContactEvent{
|
||||
Type: ricochet.ContactEvent_ADD,
|
||||
Subject: &ricochet.ContactEvent_Request{
|
||||
Request: &requestData,
|
||||
},
|
||||
}
|
||||
cl.events.Publish(event)
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// RemoveInboundContactRequest removes the record of an inbound contact request,
|
||||
// without taking any other actions. Generally you will want to act on a request with
|
||||
// Accept() or Reject(), rather than call this function directly, but it is valid to
|
||||
// call on any inbound request regardless.
|
||||
func (cl *ContactList) RemoveInboundContactRequest(request *InboundContactRequest) error {
|
||||
cl.mutex.Lock()
|
||||
defer cl.mutex.Unlock()
|
||||
requestData := request.Data()
|
||||
|
||||
if cl.inboundRequests[requestData.Address] != request {
|
||||
return errors.New("Request is not in contact list")
|
||||
}
|
||||
|
||||
request.CloseConnection()
|
||||
// XXX Remove from config
|
||||
|
||||
delete(cl.inboundRequests, requestData.Address)
|
||||
|
||||
event := ricochet.ContactEvent{
|
||||
Type: ricochet.ContactEvent_DELETE,
|
||||
Subject: &ricochet.ContactEvent_Request{
|
||||
Request: &requestData,
|
||||
},
|
||||
}
|
||||
cl.events.Publish(event)
|
||||
return nil
|
||||
}
|
||||
|
||||
// inboundRequestChanged is called by the StatusChanged callback of InboundContactRequest
|
||||
// after the request has been modified, such as through Update() or Reject(). Accepted
|
||||
// requests do not pass through this function, because they're immediately removed.
|
||||
func (cl *ContactList) inboundRequestChanged(request *InboundContactRequest) {
|
||||
// XXX update config
|
||||
requestData := request.Data()
|
||||
event := ricochet.ContactEvent{
|
||||
Type: ricochet.ContactEvent_UPDATE,
|
||||
Subject: &ricochet.ContactEvent_Request{
|
||||
Request: &requestData,
|
||||
},
|
||||
}
|
||||
cl.events.Publish(event)
|
||||
}
|
||||
|
||||
func (this *ContactList) StartConnections() {
|
||||
for _, contact := range this.Contacts() {
|
||||
contact.StartConnection()
|
||||
|
|
|
@ -114,6 +114,7 @@ func (is *identityService) OnNewConnection(oc *protocol.OpenConnection) {
|
|||
// XXX Should have pre-auth handling, timeouts
|
||||
identity := is.Identity
|
||||
handler := &ProtocolConnection{
|
||||
Core: identity.core,
|
||||
Conn: oc,
|
||||
GetContactByHostname: func(hostname string) *Contact {
|
||||
address, ok := AddressFromPlainHost(hostname)
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/ricochet-im/ricochet-go/rpc"
|
||||
protocol "github.com/s-rah/go-ricochet"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type InboundContactRequest struct {
|
||||
core *Ricochet
|
||||
mutex sync.Mutex
|
||||
data ricochet.ContactRequest
|
||||
conn *protocol.OpenConnection
|
||||
channelID int32
|
||||
Address string
|
||||
|
||||
// Called when the request state is changed
|
||||
StatusChanged func(request *InboundContactRequest)
|
||||
}
|
||||
|
||||
// CreateInboundContactRequest constructs a new InboundContactRequest, usually from a newly
|
||||
// received request on an open connection. Requests are managed through the ContactList, so
|
||||
// generally you should use ContactList.AddOrUpdateInboundContactRequest instead of calling
|
||||
// this function directly.
|
||||
func CreateInboundContactRequest(core *Ricochet, address, nickname, message string) *InboundContactRequest {
|
||||
cr := &InboundContactRequest{
|
||||
core: core,
|
||||
data: ricochet.ContactRequest{
|
||||
Direction: ricochet.ContactRequest_INBOUND,
|
||||
Address: address,
|
||||
Text: message,
|
||||
FromNickname: nickname,
|
||||
WhenCreated: time.Now().Format(time.RFC3339),
|
||||
},
|
||||
Address: address,
|
||||
}
|
||||
|
||||
return cr
|
||||
}
|
||||
|
||||
// XXX There should be stricter management & a timeout for this connection
|
||||
func (cr *InboundContactRequest) SetConnection(conn *protocol.OpenConnection, channelID int32) {
|
||||
cr.mutex.Lock()
|
||||
defer cr.mutex.Unlock()
|
||||
|
||||
if cr.conn != nil && cr.conn != conn {
|
||||
log.Printf("Replacing connection on an inbound contact request")
|
||||
cr.conn.Close()
|
||||
}
|
||||
cr.conn = conn
|
||||
cr.channelID = channelID
|
||||
}
|
||||
|
||||
func (cr *InboundContactRequest) CloseConnection() {
|
||||
if cr.conn != nil {
|
||||
cr.conn.Close()
|
||||
cr.conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (cr *InboundContactRequest) Update(nickname, message string) {
|
||||
cr.mutex.Lock()
|
||||
defer cr.mutex.Unlock()
|
||||
|
||||
if cr.data.Rejected {
|
||||
return
|
||||
}
|
||||
|
||||
// These should already be validated, but just in case..
|
||||
if len(nickname) == 0 || IsNicknameAcceptable(nickname) {
|
||||
cr.data.FromNickname = nickname
|
||||
}
|
||||
if len(message) == 0 || IsMessageAcceptable(message) {
|
||||
cr.data.Text = message
|
||||
}
|
||||
|
||||
if cr.StatusChanged != nil {
|
||||
cr.StatusChanged(cr)
|
||||
}
|
||||
}
|
||||
|
||||
func (cr *InboundContactRequest) Accept() (*Contact, error) {
|
||||
cr.mutex.Lock()
|
||||
defer cr.mutex.Unlock()
|
||||
|
||||
if cr.data.Rejected {
|
||||
log.Printf("Accept called on an inbound contact request that was already rejected; request is %v", cr)
|
||||
return nil, errors.New("Contact request has already been rejected")
|
||||
}
|
||||
|
||||
log.Printf("Accepting contact request from %s", cr.data.Address)
|
||||
|
||||
onion, _ := OnionFromAddress(cr.data.Address)
|
||||
configContact := ConfigContact{
|
||||
Hostname: onion,
|
||||
Nickname: cr.data.FromNickname,
|
||||
WhenCreated: cr.data.WhenCreated,
|
||||
}
|
||||
contact, err := cr.core.Identity.ContactList().AddNewContact(configContact)
|
||||
if err != nil {
|
||||
log.Printf("Error occurred in accepting contact request: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cr.AcceptWithContact(contact); err != nil {
|
||||
return contact, err
|
||||
}
|
||||
return contact, nil
|
||||
}
|
||||
|
||||
func (cr *InboundContactRequest) AcceptWithContact(contact *Contact) error {
|
||||
if contact.Address() != cr.data.Address {
|
||||
return errors.New("Contact address does not match request in accept")
|
||||
}
|
||||
|
||||
cr.core.Identity.ContactList().RemoveInboundContactRequest(cr)
|
||||
|
||||
// Pass the open connection to the new contact
|
||||
if cr.conn != nil && !cr.conn.Closed {
|
||||
cr.conn.AckContactRequest(cr.channelID, "Accepted")
|
||||
cr.conn.CloseChannel(cr.channelID)
|
||||
contact.OnConnectionAuthenticated(cr.conn, true)
|
||||
cr.conn = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr *InboundContactRequest) Reject() {
|
||||
cr.mutex.Lock()
|
||||
defer cr.mutex.Unlock()
|
||||
|
||||
if cr.data.Rejected {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Rejecting contact request from %s", cr.data.Address)
|
||||
cr.data.Rejected = true
|
||||
|
||||
// Signal update to the callback (probably from ContactList)
|
||||
if cr.StatusChanged != nil {
|
||||
cr.StatusChanged(cr)
|
||||
}
|
||||
|
||||
if cr.conn != nil && !cr.conn.Closed {
|
||||
cr.conn.AckContactRequest(cr.channelID, "Rejected")
|
||||
cr.conn.CloseChannel(cr.channelID)
|
||||
cr.conn.Close()
|
||||
cr.conn = nil
|
||||
|
||||
// The request can be removed once a protocol response is sent
|
||||
cr.core.Identity.ContactList().RemoveInboundContactRequest(cr)
|
||||
}
|
||||
}
|
||||
|
||||
func (cr *InboundContactRequest) Data() ricochet.ContactRequest {
|
||||
return cr.data
|
||||
}
|
||||
|
||||
func (cr *InboundContactRequest) IsRejected() bool {
|
||||
cr.mutex.Lock()
|
||||
defer cr.mutex.Unlock()
|
||||
return cr.data.Rejected
|
||||
}
|
|
@ -9,6 +9,8 @@ import (
|
|||
)
|
||||
|
||||
type ProtocolConnection struct {
|
||||
Core *Ricochet
|
||||
|
||||
Conn *protocol.OpenConnection
|
||||
Contact *Contact
|
||||
|
||||
|
@ -95,6 +97,46 @@ func (pc *ProtocolConnection) OnAuthenticationResult(channelID int32, result boo
|
|||
|
||||
// Contact Management
|
||||
func (pc *ProtocolConnection) OnContactRequest(channelID int32, nick string, message string) {
|
||||
if pc.Conn.Client || !pc.Conn.IsAuthed || pc.Contact != nil {
|
||||
pc.Conn.CloseChannel(channelID)
|
||||
return
|
||||
}
|
||||
|
||||
address, ok := AddressFromPlainHost(pc.Conn.OtherHostname)
|
||||
if !ok {
|
||||
pc.Conn.CloseChannel(channelID)
|
||||
return
|
||||
}
|
||||
if len(nick) > 0 && !IsNicknameAcceptable(nick) {
|
||||
log.Printf("protocol: Stripping unacceptable nickname from inbound request; encoded: %x", []byte(nick))
|
||||
nick = ""
|
||||
}
|
||||
if len(message) > 0 && !IsMessageAcceptable(message) {
|
||||
log.Printf("protocol: Stripping unacceptable message from inbound request; len: %d, encoded: %x", len(message), []byte(message))
|
||||
message = ""
|
||||
}
|
||||
|
||||
contactList := pc.Core.Identity.ContactList()
|
||||
request, contact := contactList.AddOrUpdateInboundContactRequest(address, nick, message)
|
||||
|
||||
if contact != nil {
|
||||
// Accepted immediately
|
||||
pc.Conn.AckContactRequestOnResponse(channelID, "Accepted")
|
||||
pc.Conn.CloseChannel(channelID)
|
||||
contact.OnConnectionAuthenticated(pc.Conn, true)
|
||||
} else if request != nil && !request.IsRejected() {
|
||||
// Pending
|
||||
pc.Conn.AckContactRequestOnResponse(channelID, "Pending")
|
||||
request.SetConnection(pc.Conn, channelID)
|
||||
} else {
|
||||
// Rejected
|
||||
pc.Conn.AckContactRequestOnResponse(channelID, "Rejected")
|
||||
pc.Conn.CloseChannel(channelID)
|
||||
pc.Conn.Close()
|
||||
if request != nil {
|
||||
contactList.RemoveInboundContactRequest(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *ProtocolConnection) OnContactRequestAck(channelID int32, status string) {
|
||||
|
|
|
@ -151,11 +151,32 @@ func (s *RpcServer) DeleteContact(ctx context.Context, req *ricochet.DeleteConta
|
|||
}
|
||||
|
||||
func (s *RpcServer) AcceptInboundRequest(ctx context.Context, req *ricochet.ContactRequest) (*ricochet.Contact, error) {
|
||||
return nil, NotImplementedError
|
||||
if req.Direction != ricochet.ContactRequest_INBOUND {
|
||||
return nil, errors.New("Request must be inbound")
|
||||
}
|
||||
contactList := s.Core.Identity.ContactList()
|
||||
request := contactList.InboundRequestByAddress(req.Address)
|
||||
if request == nil {
|
||||
return nil, errors.New("Request does not exist")
|
||||
}
|
||||
contact, err := request.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contact.Data(), nil
|
||||
}
|
||||
|
||||
func (s *RpcServer) RejectInboundRequest(ctx context.Context, req *ricochet.ContactRequest) (*ricochet.RejectInboundRequestReply, error) {
|
||||
return nil, NotImplementedError
|
||||
if req.Direction != ricochet.ContactRequest_INBOUND {
|
||||
return nil, errors.New("Request must be inbound")
|
||||
}
|
||||
contactList := s.Core.Identity.ContactList()
|
||||
request := contactList.InboundRequestByAddress(req.Address)
|
||||
if request == nil {
|
||||
return nil, errors.New("Request does not exist")
|
||||
}
|
||||
request.Reject()
|
||||
return &ricochet.RejectInboundRequestReply{}, nil
|
||||
}
|
||||
|
||||
func (s *RpcServer) MonitorConversations(req *ricochet.MonitorConversationsRequest, stream ricochet.RicochetCore_MonitorConversationsServer) error {
|
||||
|
|
|
@ -207,6 +207,8 @@ type ContactRequest struct {
|
|||
Nickname string `protobuf:"bytes,3,opt,name=nickname" json:"nickname,omitempty"`
|
||||
Text string `protobuf:"bytes,4,opt,name=text" json:"text,omitempty"`
|
||||
FromNickname string `protobuf:"bytes,5,opt,name=fromNickname" json:"fromNickname,omitempty"`
|
||||
WhenCreated string `protobuf:"bytes,6,opt,name=whenCreated" json:"whenCreated,omitempty"`
|
||||
Rejected bool `protobuf:"varint,7,opt,name=rejected" json:"rejected,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ContactRequest) Reset() { *m = ContactRequest{} }
|
||||
|
@ -249,6 +251,20 @@ func (m *ContactRequest) GetFromNickname() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *ContactRequest) GetWhenCreated() string {
|
||||
if m != nil {
|
||||
return m.WhenCreated
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ContactRequest) GetRejected() bool {
|
||||
if m != nil {
|
||||
return m.Rejected
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type MonitorContactsRequest struct {
|
||||
}
|
||||
|
||||
|
@ -451,38 +467,39 @@ func init() {
|
|||
func init() { proto.RegisterFile("contact.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 520 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0x51, 0x6f, 0xd3, 0x30,
|
||||
0x10, 0x6e, 0xd2, 0xac, 0x69, 0xaf, 0x5d, 0xc9, 0xac, 0x09, 0x05, 0xf6, 0x52, 0x59, 0x08, 0xf5,
|
||||
0x85, 0x80, 0x0a, 0xef, 0xac, 0x6b, 0x32, 0x51, 0x08, 0x49, 0xf1, 0x12, 0xf1, 0x9c, 0x26, 0x46,
|
||||
0x0b, 0x74, 0x76, 0x49, 0x5c, 0xa0, 0xff, 0x90, 0x9f, 0xc2, 0x23, 0x3f, 0x01, 0xd9, 0x49, 0x3a,
|
||||
0xba, 0x01, 0x42, 0x7b, 0xf3, 0x7d, 0xdf, 0x77, 0xf6, 0x7d, 0xbe, 0x3b, 0x38, 0x4c, 0x39, 0x13,
|
||||
0x49, 0x2a, 0x9c, 0x75, 0xc1, 0x05, 0x47, 0xdd, 0x22, 0x4f, 0x79, 0x7a, 0x49, 0x05, 0xfe, 0xae,
|
||||
0x83, 0x39, 0xab, 0x38, 0x34, 0x04, 0x3d, 0xcf, 0x6c, 0x6d, 0xa4, 0x8d, 0x0f, 0x88, 0x9e, 0x67,
|
||||
0xc8, 0x06, 0x33, 0xc9, 0xb2, 0x82, 0x96, 0xa5, 0xad, 0x8f, 0xb4, 0x71, 0x8f, 0x34, 0x21, 0x7a,
|
||||
0x08, 0x5d, 0x96, 0xa7, 0x9f, 0x58, 0x72, 0x45, 0xed, 0xb6, 0xa2, 0x76, 0x31, 0x1a, 0x41, 0xff,
|
||||
0xeb, 0x25, 0x65, 0xb3, 0x82, 0x26, 0x82, 0x66, 0xb6, 0xa1, 0xe8, 0xdf, 0x21, 0xf4, 0x08, 0x0e,
|
||||
0x57, 0x49, 0x29, 0x66, 0x9c, 0x31, 0x9a, 0x4a, 0xcd, 0x81, 0xd2, 0xec, 0x83, 0x68, 0x02, 0x66,
|
||||
0x41, 0x3f, 0x6f, 0x68, 0x29, 0xec, 0xce, 0x48, 0x1b, 0xf7, 0x27, 0xb6, 0xd3, 0x54, 0xed, 0xd4,
|
||||
0x15, 0x93, 0x8a, 0x27, 0x8d, 0x10, 0x3d, 0x83, 0x4e, 0x29, 0x12, 0xb1, 0x29, 0x6d, 0x18, 0x69,
|
||||
0xe3, 0xe1, 0x1f, 0x52, 0x9c, 0x0b, 0xc5, 0x93, 0x5a, 0x87, 0xe7, 0xd0, 0xa9, 0x10, 0xd4, 0x07,
|
||||
0x33, 0x0e, 0xde, 0x04, 0xe1, 0xfb, 0xc0, 0x6a, 0xc9, 0x20, 0x3c, 0x3f, 0xf7, 0xe7, 0x81, 0x67,
|
||||
0x69, 0x08, 0xa0, 0x13, 0x06, 0xea, 0xac, 0x4b, 0x82, 0x78, 0xef, 0x62, 0xef, 0x22, 0xb2, 0xda,
|
||||
0x68, 0x00, 0x5d, 0xe2, 0xbd, 0xf6, 0x66, 0x91, 0xe7, 0x5a, 0x06, 0xfe, 0xa1, 0xc1, 0x70, 0xbf,
|
||||
0x30, 0x74, 0x0a, 0xbd, 0x2c, 0x2f, 0x68, 0x2a, 0x72, 0xce, 0xd4, 0xc7, 0x0e, 0x27, 0xf8, 0x6f,
|
||||
0x2e, 0x1c, 0xb7, 0x51, 0x92, 0xeb, 0xa4, 0x3b, 0xf6, 0x00, 0x81, 0x21, 0xe8, 0x37, 0x51, 0x7f,
|
||||
0xbe, 0x3a, 0x23, 0x0c, 0x83, 0x0f, 0x05, 0xbf, 0x0a, 0x9a, 0x9c, 0xea, 0xd3, 0xf7, 0x30, 0xfc,
|
||||
0x18, 0x7a, 0xbb, 0x2a, 0xa4, 0xd5, 0x79, 0x70, 0x16, 0xc6, 0x81, 0x6b, 0xb5, 0xa4, 0xd5, 0x30,
|
||||
0x8e, 0xaa, 0x48, 0xc3, 0x36, 0xdc, 0x7f, 0xcb, 0x59, 0x2e, 0x78, 0x51, 0x7b, 0x28, 0x6b, 0x13,
|
||||
0xf8, 0xa7, 0x06, 0x83, 0x1a, 0xf3, 0xbe, 0x50, 0x26, 0xd0, 0x53, 0x30, 0xc4, 0x76, 0x4d, 0x6b,
|
||||
0xf7, 0x27, 0xb7, 0xdc, 0x2b, 0x95, 0x13, 0x6d, 0xd7, 0x94, 0x28, 0x21, 0x7a, 0x02, 0x66, 0x3d,
|
||||
0xac, 0xca, 0x71, 0x7f, 0x72, 0x74, 0x2b, 0xe7, 0x55, 0x8b, 0x34, 0x1a, 0xf4, 0xe2, 0x7a, 0x4c,
|
||||
0xda, 0xff, 0x1e, 0x13, 0x99, 0x55, 0x4b, 0xf1, 0x4b, 0x30, 0xe4, 0x93, 0xa8, 0x0b, 0x46, 0x10,
|
||||
0xfb, 0x7e, 0x65, 0x70, 0x11, 0x2e, 0x62, 0x7f, 0x1a, 0xc9, 0x96, 0x9b, 0xd0, 0x9e, 0xba, 0xae,
|
||||
0xa5, 0xcb, 0xde, 0xc7, 0x0b, 0x57, 0x82, 0x6d, 0x79, 0x76, 0x3d, 0xdf, 0x8b, 0x3c, 0xcb, 0x38,
|
||||
0xeb, 0x81, 0x59, 0x6e, 0x96, 0x1f, 0x69, 0x2a, 0xf0, 0x11, 0xdc, 0x9b, 0x66, 0xd9, 0xee, 0xad,
|
||||
0xf5, 0x6a, 0x8b, 0x4f, 0xe1, 0xd8, 0xa5, 0x2b, 0x2a, 0xe8, 0x8d, 0x79, 0xf8, 0xef, 0x0d, 0xc3,
|
||||
0xc7, 0x80, 0x6e, 0xdc, 0x20, 0xef, 0x3d, 0x81, 0x07, 0x84, 0xca, 0x47, 0xe7, 0x6c, 0xc9, 0x37,
|
||||
0x2c, 0x6b, 0x16, 0x40, 0x92, 0xcb, 0x8e, 0xda, 0xed, 0xe7, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff,
|
||||
0x94, 0xcf, 0x1c, 0xce, 0xec, 0x03, 0x00, 0x00,
|
||||
// 539 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xd1, 0x72, 0x93, 0x40,
|
||||
0x14, 0x2d, 0x84, 0x02, 0xb9, 0x69, 0x23, 0xdd, 0xe9, 0x38, 0xd8, 0xbe, 0x30, 0x3b, 0x8e, 0x93,
|
||||
0x17, 0xd1, 0x89, 0xbe, 0xdb, 0x34, 0xd0, 0x31, 0x1a, 0x21, 0x6e, 0x61, 0x7c, 0x26, 0xb0, 0x4e,
|
||||
0xd1, 0x74, 0x89, 0xb0, 0x51, 0xf3, 0x43, 0x7e, 0x8b, 0x9f, 0xe3, 0x27, 0x38, 0xbb, 0x40, 0x6a,
|
||||
0x12, 0x75, 0x1c, 0xdf, 0xf6, 0x9e, 0x73, 0x2e, 0xbb, 0x7b, 0xee, 0x59, 0xe0, 0x38, 0x2d, 0x18,
|
||||
0x4f, 0x52, 0xee, 0x2e, 0xcb, 0x82, 0x17, 0xc8, 0x2c, 0xf3, 0xb4, 0x48, 0x6f, 0x28, 0xc7, 0xdf,
|
||||
0x55, 0x30, 0xc6, 0x35, 0x87, 0xfa, 0xa0, 0xe6, 0x99, 0xad, 0x38, 0xca, 0xe0, 0x90, 0xa8, 0x79,
|
||||
0x86, 0x6c, 0x30, 0x92, 0x2c, 0x2b, 0x69, 0x55, 0xd9, 0xaa, 0xa3, 0x0c, 0xba, 0xa4, 0x2d, 0xd1,
|
||||
0x19, 0x98, 0x2c, 0x4f, 0x3f, 0xb2, 0xe4, 0x96, 0xda, 0x1d, 0x49, 0x6d, 0x6a, 0xe4, 0x40, 0xef,
|
||||
0xcb, 0x0d, 0x65, 0xe3, 0x92, 0x26, 0x9c, 0x66, 0xb6, 0x26, 0xe9, 0x5f, 0x21, 0xf4, 0x10, 0x8e,
|
||||
0x17, 0x49, 0xc5, 0xc7, 0x05, 0x63, 0x34, 0x15, 0x9a, 0x43, 0xa9, 0xd9, 0x06, 0xd1, 0x10, 0x8c,
|
||||
0x92, 0x7e, 0x5a, 0xd1, 0x8a, 0xdb, 0xba, 0xa3, 0x0c, 0x7a, 0x43, 0xdb, 0x6d, 0x4f, 0xed, 0x36,
|
||||
0x27, 0x26, 0x35, 0x4f, 0x5a, 0x21, 0x7a, 0x0a, 0x7a, 0xc5, 0x13, 0xbe, 0xaa, 0x6c, 0x70, 0x94,
|
||||
0x41, 0xff, 0x37, 0x2d, 0xee, 0xb5, 0xe4, 0x49, 0xa3, 0xc3, 0x13, 0xd0, 0x6b, 0x04, 0xf5, 0xc0,
|
||||
0x88, 0x83, 0xd7, 0x41, 0xf8, 0x2e, 0xb0, 0x0e, 0x44, 0x11, 0x5e, 0x5d, 0x4d, 0x27, 0x81, 0x6f,
|
||||
0x29, 0x08, 0x40, 0x0f, 0x03, 0xb9, 0x56, 0x05, 0x41, 0xfc, 0xb7, 0xb1, 0x7f, 0x1d, 0x59, 0x1d,
|
||||
0x74, 0x04, 0x26, 0xf1, 0x5f, 0xf9, 0xe3, 0xc8, 0xf7, 0x2c, 0x0d, 0x7f, 0x53, 0xa1, 0xbf, 0x7d,
|
||||
0x30, 0x74, 0x01, 0xdd, 0x2c, 0x2f, 0x69, 0xca, 0xf3, 0x82, 0x49, 0x63, 0xfb, 0x43, 0xfc, 0xa7,
|
||||
0x5b, 0xb8, 0x5e, 0xab, 0x24, 0x77, 0x4d, 0xff, 0x39, 0x03, 0x04, 0x1a, 0xa7, 0x5f, 0x79, 0x63,
|
||||
0xbe, 0x5c, 0x23, 0x0c, 0x47, 0xef, 0xcb, 0xe2, 0x36, 0x68, 0x7b, 0x6a, 0xd3, 0xb7, 0xb0, 0xdd,
|
||||
0xd9, 0xe9, 0xfb, 0xb3, 0x3b, 0x03, 0xb3, 0xa4, 0x1f, 0xea, 0xb1, 0x19, 0x8e, 0x32, 0x30, 0xc9,
|
||||
0xa6, 0xc6, 0x8f, 0xa0, 0xbb, 0xb9, 0x83, 0x30, 0x6a, 0x12, 0x5c, 0x86, 0x71, 0xe0, 0x59, 0x07,
|
||||
0xc2, 0xa8, 0x30, 0x8e, 0xea, 0x4a, 0xc1, 0x36, 0xdc, 0x7f, 0x53, 0xb0, 0x9c, 0x17, 0x65, 0xe3,
|
||||
0x40, 0xd5, 0x58, 0x80, 0x7f, 0x28, 0x70, 0xd4, 0x60, 0xfe, 0x67, 0xca, 0x38, 0x7a, 0x02, 0x1a,
|
||||
0x5f, 0x2f, 0x69, 0xe3, 0xdd, 0xf9, 0x9e, 0x77, 0x52, 0xe5, 0x46, 0xeb, 0x25, 0x25, 0x52, 0x88,
|
||||
0x1e, 0x83, 0xd1, 0x44, 0x5d, 0xfa, 0xd5, 0x1b, 0x9e, 0xec, 0xf5, 0xbc, 0x3c, 0x20, 0xad, 0x06,
|
||||
0x3d, 0xbf, 0x0b, 0x59, 0xe7, 0xef, 0x21, 0x13, 0x5d, 0x8d, 0x14, 0xbf, 0x00, 0x4d, 0x6c, 0x89,
|
||||
0x4c, 0xd0, 0x82, 0x78, 0x3a, 0xad, 0x2f, 0x38, 0x0b, 0x67, 0xf1, 0x74, 0x14, 0x89, 0xc0, 0x18,
|
||||
0xd0, 0x19, 0x79, 0x9e, 0xa5, 0x8a, 0xe4, 0xc4, 0x33, 0x4f, 0x80, 0x1d, 0xb1, 0xf6, 0xfc, 0xa9,
|
||||
0x1f, 0xf9, 0x96, 0x76, 0xd9, 0x05, 0xa3, 0x5a, 0xcd, 0x85, 0x6d, 0xf8, 0x04, 0xee, 0x8d, 0xb2,
|
||||
0x6c, 0xb3, 0xd7, 0x72, 0xb1, 0xc6, 0x17, 0x70, 0xea, 0xd1, 0x05, 0xe5, 0x74, 0x27, 0x4d, 0xff,
|
||||
0xfc, 0x3e, 0xf1, 0x29, 0xa0, 0x9d, 0x2f, 0x88, 0xef, 0x9e, 0xc3, 0x03, 0x22, 0x67, 0x35, 0x61,
|
||||
0xf3, 0x62, 0xc5, 0xb2, 0xf6, 0xf9, 0x08, 0x72, 0xae, 0xcb, 0x3f, 0xc3, 0xb3, 0x9f, 0x01, 0x00,
|
||||
0x00, 0xff, 0xff, 0xe5, 0x62, 0x19, 0x3f, 0x2a, 0x04, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ message ContactRequest {
|
|||
string nickname = 3;
|
||||
string text = 4;
|
||||
string fromNickname = 5;
|
||||
string whenCreated = 6;
|
||||
bool rejected = 7;
|
||||
}
|
||||
|
||||
message MonitorContactsRequest {
|
||||
|
|
Loading…
Reference in New Issue