From 6ebe876701cb188a48118ddc76e4b209a86bc637 Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Sun, 11 Aug 2019 15:48:07 +0200 Subject: [PATCH 01/10] fix unittest --- basic_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basic_test.go b/basic_test.go index 5d2fe0a..0f340f4 100644 --- a/basic_test.go +++ b/basic_test.go @@ -56,7 +56,7 @@ func pingPong(t *testing.T, continue } default: - t.Log("unahndled message Type: %T", msg) + t.Logf("unhandled message type: %T", msg) } } } From 16684c5acfd9abe4d27d52478c7a6c6d5113d29c Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Thu, 5 Sep 2019 11:11:12 +0200 Subject: [PATCH 02/10] fix typo --- communicationhandler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/communicationhandler.go b/communicationhandler.go index 4140dd7..e0fc20b 100644 --- a/communicationhandler.go +++ b/communicationhandler.go @@ -149,7 +149,7 @@ func (sc *SessionContext) sendLoop() { case string: sc.ErrorChan <- errors.New(t) default: - sc.ErrorChan <- fmt.Errorf("An unknown error has occured: %v", r) + sc.ErrorChan <- fmt.Errorf("An unknown error has occurred: %v", r) } } }() From f7f8176a52600f768980686095773ed6ba0493fd Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Sun, 11 Aug 2019 15:58:18 +0200 Subject: [PATCH 03/10] fix typos in blob func's --- blob.go | 6 +++--- messagetypes.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/blob.go b/blob.go index 0504269..30fe1dc 100644 --- a/blob.go +++ b/blob.go @@ -74,7 +74,7 @@ func uploadBlob(blob []byte) ([16]byte, error) { } // encryptAsymAndUpload encrypts a blob with recipients PK and the sc owners SK -func encryptAndUploadAsym(sc SessionContext, plainImage []byte, recipientName string) (blobNonce nonce, ServerID byte, size uint32, blobID [16]byte, err error) { +func encryptAsymAndUpload(sc SessionContext, plainImage []byte, recipientName string) (blobNonce nonce, ServerID byte, size uint32, blobID [16]byte, err error) { // Get contact public key threemaID := sc.ID recipient, inContacts := threemaID.Contacts.Get(recipientName) @@ -97,8 +97,8 @@ func encryptAndUploadAsym(sc SessionContext, plainImage []byte, recipientName st return blobNonce, blobID[0], uint32(len(ciphertext)), blobID, nil } -// encryptAsymAndUpload encrypts a blob with recipients PK and the sc owners SK -func encryptAndUploadSym(plainImage []byte) (key [32]byte, ServerID byte, size uint32, blobID [16]byte, err error) { +// encryptSymAndUpload encrypts a blob with recipients PK and the sc owners SK +func encryptSymAndUpload(plainImage []byte) (key [32]byte, ServerID byte, size uint32, blobID [16]byte, err error) { // fixed nonce of the form [000000....1] nonce := [24]byte{} nonce[23] = 1 diff --git a/messagetypes.go b/messagetypes.go index c4909a9..dbb3255 100644 --- a/messagetypes.go +++ b/messagetypes.go @@ -221,7 +221,7 @@ func (im *ImageMessage) SetImageData(filename string, sc SessionContext) error { return errors.New("could not load image") } - im.Nonce, im.ServerID, im.Size, im.BlobID, err = encryptAndUploadAsym(sc, plainImage, im.recipient.String()) + im.Nonce, im.ServerID, im.Size, im.BlobID, err = encryptAsymAndUpload(sc, plainImage, im.recipient.String()) return err } @@ -288,7 +288,7 @@ func (am *AudioMessage) SetAudioData(filename string, sc SessionContext) error { // TODO: Should we have a whole media lib as dependency just to set this to the proper value? am.Duration = 0xFF - am.Key, am.ServerID, am.Size, am.BlobID, err = encryptAndUploadSym(plainAudio) + am.Key, am.ServerID, am.Size, am.BlobID, err = encryptSymAndUpload(plainAudio) return err } @@ -396,7 +396,7 @@ func (im *groupImageMessageBody) setImageData(filename string) error { return errors.New("could not load image") } - im.Key, im.ServerID, im.Size, im.BlobID, err = encryptAndUploadSym(plainImage) + im.Key, im.ServerID, im.Size, im.BlobID, err = encryptSymAndUpload(plainImage) return err } From 0c0e14982ac6e2886901faef423ed731911b3b39 Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Sun, 11 Aug 2019 22:05:53 +0200 Subject: [PATCH 04/10] parsing/serialize to (un)marshal dynamic --- basic_test.go | 10 +- buf_marshal.go | 37 ++ buf_unmarshal.go | 28 ++ communicationhandler.go | 112 ------ message.go | 65 ++++ message_deliveryreceipt.go | 64 ++++ message_text.go | 64 ++++ message_typingnotification.go | 52 +++ messagetypes.go | 627 ---------------------------------- packetdispatcher.go | 62 ++-- packethandler.go | 113 ++---- packetparser.go | 337 ------------------ packetserializer.go | 410 ---------------------- packettypes.go | 165 ++++++++- 14 files changed, 543 insertions(+), 1603 deletions(-) create mode 100644 buf_marshal.go create mode 100644 buf_unmarshal.go create mode 100644 message.go create mode 100644 message_deliveryreceipt.go create mode 100644 message_text.go create mode 100644 message_typingnotification.go delete mode 100644 messagetypes.go delete mode 100644 packetparser.go delete mode 100644 packetserializer.go diff --git a/basic_test.go b/basic_test.go index 0f340f4..3ea7878 100644 --- a/basic_test.go +++ b/basic_test.go @@ -35,8 +35,10 @@ func pingPong(t *testing.T, t.Fatal(errors.Wrap(err, "context couldnt run")) } - if err := ctx.SendTextMessage(remoteID, testMsg, sendChan); err != nil { + if msg, err := NewTextMessage(ctx, remoteID, testMsg); err != nil { t.Fatal(errors.Wrapf(err, "%s: couldn't send her message", ctx.ID.String())) + } else { + sendChan <- &msg } t.Logf("%s send message", ctx.ID.String()) for msg := range recvChan { @@ -47,9 +49,9 @@ func pingPong(t *testing.T, } switch thisMsg := msg.Msg.(type) { - case TextMessage: - if remoteID == thisMsg.Sender().String() { - if txt := thisMsg.Text(); txt != testMsg { + case *TextMessage: + if remoteID == thisMsg.Sender.String() { + if txt := thisMsg.String(); txt != testMsg { t.Errorf("%s: got wrong message back. Wanted: %q got %q", ctx.ID.String(), testMsg, txt) } wg.Done() diff --git a/buf_marshal.go b/buf_marshal.go new file mode 100644 index 0000000..ee6badb --- /dev/null +++ b/buf_marshal.go @@ -0,0 +1,37 @@ +package o3 + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "fmt" + "math/big" +) + +func bufMarshal(context string, buf *bytes.Buffer, i interface{}) { + err := binary.Write(buf, binary.LittleEndian, i) + if err != nil { + panic(fmt.Sprintf("%s: %s", context, err)) + } +} + +// bufMarshalPadding returns a byte slice filled with n repetitions of the byte value n +func bufMarshalPadding(buf *bytes.Buffer) { + paddingValueBig, err := rand.Int(rand.Reader, big.NewInt(255)) + if err != nil { + panic(err) + } + paddingValue := byte(paddingValueBig.Int64()) + padding := make([]byte, paddingValue) + for i := range padding { + padding[i] = paddingValue + } + bufMarshal("padding", buf, padding) +} + +func bugMarshalByte(context string, buf *bytes.Buffer, b byte) { + err := buf.WriteByte(b) + if err != nil { + panic(fmt.Sprintf("%s: %s", context, err)) + } +} diff --git a/buf_unmarshal.go b/buf_unmarshal.go new file mode 100644 index 0000000..204c2db --- /dev/null +++ b/buf_unmarshal.go @@ -0,0 +1,28 @@ +package o3 + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +func stripPadding(buf *bytes.Buffer) { + paddingLen := int(buf.Bytes()[buf.Len()-1]) + buf.Truncate(buf.Len() - paddingLen) +} + +func bufUnmarshal(context string, buf *bytes.Buffer, i interface{}) { + if err := binary.Read(buf, binary.LittleEndian, i); err != nil { + panic(fmt.Sprintf("%s: %s", context, err)) + } +} + +//helper function: not to be called directly +func bufUnmarshalBytes(buf *bytes.Buffer, size int) []byte { + b := make([]byte, size) + n, err := buf.Read(b) + if n != size || err != nil { + panic(fmt.Sprintf("%d bytes of data", size)) + } + return b +} diff --git a/communicationhandler.go b/communicationhandler.go index e0fc20b..8d34066 100644 --- a/communicationhandler.go +++ b/communicationhandler.go @@ -175,118 +175,6 @@ func (sc *SessionContext) sendLoop() { } } -// SendTextMessage sends a Text Message to the specified ID -// Enqueued messages will be received, not acknowledged and discarded -func (sc *SessionContext) SendTextMessage(recipient string, text string, sendMsgChan chan<- Message) error { - // build a message - tm, err := NewTextMessage(sc, recipient, text) - - // TODO: error handling - if err != nil { - return err - } - - sendMsgChan <- tm - - return nil -} - -// SendImageMessage sends a Image Message to the specified ID -// Enqueued messages will be received, not acknowledged and discarded -func (sc *SessionContext) SendImageMessage(recipient string, filename string, sendMsgChan chan<- Message) error { - // build a message - im, err := NewImageMessage(sc, recipient, filename) - - if err != nil { - return err - } - - sendMsgChan <- im - - return nil -} - -// SendAudioMessage sends a Audio Message to the specified ID -// Enqueued messages will be received, not acknowledged and discarded -// Works with various audio formats threema uses some kind of mp4 but mp3 works fine -func (sc *SessionContext) SendAudioMessage(recipient string, filename string, sendMsgChan chan<- Message) error { - // build a message - am, err := NewAudioMessage(sc, recipient, filename) - - if err != nil { - return err - } - - sendMsgChan <- am - - return nil -} - -// SendGroupTextMessage Sends a text message to all members -func (sc *SessionContext) SendGroupTextMessage(group Group, text string, sendMsgChan chan<- Message) (err error) { - - tms, err := NewGroupTextMessages(sc, group, text) - if err != nil { - return err - } - for _, msg := range tms { - sendMsgChan <- msg - } - - return nil -} - -// CreateNewGroup Creates a new group and notifies all members -func (sc *SessionContext) CreateNewGroup(group Group, sendMsgChan chan<- Message) (groupID [8]byte, err error) { - - group.GroupID = NewGrpID() - - sc.ChangeGroupMembers(group, sendMsgChan) - if err != nil { - return groupID, err - } - - sc.RenameGroup(group, sendMsgChan) - if err != nil { - return groupID, err - } - - return groupID, nil -} - -// RenameGroup Sends a message with the new group name to all members -func (sc *SessionContext) RenameGroup(group Group, sendMsgChan chan<- Message) (err error) { - - sgn := NewGroupManageSetNameMessages(sc, group) - for _, msg := range sgn { - sendMsgChan <- msg - } - - return nil -} - -// ChangeGroupMembers Sends a message with the new group member list to all members -func (sc *SessionContext) ChangeGroupMembers(group Group, sendMsgChan chan<- Message) (err error) { - - sgm := NewGroupManageSetMembersMessages(sc, group) - for _, msg := range sgm { - sendMsgChan <- msg - } - - return nil -} - -// LeaveGroup Sends a message to all members telling them the sender left the group -func (sc *SessionContext) LeaveGroup(group Group, sendMsgChan chan<- Message) (err error) { - - sgm := NewGroupMemberLeftMessages(sc, group) - for _, msg := range sgm { - sendMsgChan <- msg - } - - return nil -} - func (sc *SessionContext) receivePacket(reader io.Reader) (pkt interface{}, err error) { defer func() { if r := recover(); r != nil { diff --git a/message.go b/message.go new file mode 100644 index 0000000..d0a513e --- /dev/null +++ b/message.go @@ -0,0 +1,65 @@ +package o3 + +import ( + "encoding" + mrand "math/rand" + "time" +) + +// MsgType determines the type of message that is sent or received. Users usually +// won't use this directly and rather use message generator functions. +type MsgType uint8 + +//TODO: figure these out +type msgFlags struct { + PushMessage bool + NoQueuing bool + NoAckExpected bool + MessageHasAlreadyBeenDelivered bool + GroupMessage bool +} + +// NewMsgID returns a randomly generated message ID (not cryptographically secure!) +// TODO: Why mrand? +func NewMsgID() uint64 { + mrand.Seed(int64(time.Now().Nanosecond())) + msgID := uint64(mrand.Int63()) + return msgID +} + +type Message interface { + encoding.BinaryMarshaler + + Header() *MessageHeader +} + +// Message representing the various kinds of e2e ecrypted messages threema supports +type MessageHeader struct { + Sender IDString + Recipient IDString + ID uint64 + Time time.Time + PubNick PubNick +} + +func NewMessageHeader(sc *SessionContext, recipient string) *MessageHeader { + recipientID := NewIDString(recipient) + + return &MessageHeader{ + Sender: sc.ID.ID, + Recipient: recipientID, + ID: NewMsgID(), + Time: time.Now(), + PubNick: sc.ID.Nick, + } +} + +func (mh *MessageHeader) Header() *MessageHeader { + return mh +} + +var messageUnmarshal map[MsgType]func(*MessageHeader, []byte) (Message, error) + +func init() { + messageUnmarshal = make(map[MsgType]func(*MessageHeader, []byte) (Message, error)) +} diff --git a/message_deliveryreceipt.go b/message_deliveryreceipt.go new file mode 100644 index 0000000..2539523 --- /dev/null +++ b/message_deliveryreceipt.go @@ -0,0 +1,64 @@ +package o3 + +import ( + "bytes" + "errors" +) + +// MsgType mock enum +const MessageTypeDeliveryReceipt MsgType = 0x80 + +// MsgStatus represents the single-byte status field of DeliveryReceiptMessage +type MsgStatus uint8 + +//MsgStatus mock enum +const ( + MSGDELIVERED MsgStatus = 0x1 //indicates message was received by peer + MSGREAD MsgStatus = 0x2 //indicates message was read by peer + MSGAPPROVED MsgStatus = 0x3 //indicates message was approved (thumb up) by peer + MSGDISAPPROVED MsgStatus = 0x4 //indicates message was disapproved (thumb down) by peer +) + +// DeliveryReceiptMessage represents a delivery receipt as sent e2e encrypted to other threema users when a message has been received +type DeliveryReceiptMessage struct { + *MessageHeader + Status MsgStatus + MessageID uint64 +} + +func (msg DeliveryReceiptMessage) MarshalBinary() ([]byte, error) { + + buf := new(bytes.Buffer) + bufMarshal("msg-type", buf, uint8(MessageTypeDeliveryReceipt)) + bufUnmarshal("message status", buf, msg.Status) + bufUnmarshal("message id", buf, msg.MessageID) + bufMarshalPadding(buf) + + return buf.Bytes(), nil +} + +func (msg *DeliveryReceiptMessage) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + var t MsgType + bufUnmarshal("read message type", buf, &t) + if t != MessageTypeDeliveryReceipt { + return errors.New("not correct type") + } + stripPadding(buf) + bufUnmarshal("read message status", buf, &msg.Status) + bufUnmarshal("read message id", buf, &msg.MessageID) + + return nil +} + +func init() { + messageUnmarshal[MessageTypeDeliveryReceipt] = func(mh *MessageHeader, data []byte) (Message, error) { + tm := &DeliveryReceiptMessage{ + MessageHeader: mh, + } + if err := tm.UnmarshalBinary(data); err != nil { + return nil, err + } + return tm, nil + } +} diff --git a/message_text.go b/message_text.go new file mode 100644 index 0000000..0f7948e --- /dev/null +++ b/message_text.go @@ -0,0 +1,64 @@ +package o3 + +import ( + "bytes" + "errors" +) + +// MsgType mock enum +const MessageTypeText MsgType = 0x1 + +//TextMessage represents a text message as sent e2e encrypted to other threema users +type TextMessage struct { + *MessageHeader + Body string +} + +// NewTextMessage returns a TextMessage ready to be encrypted +func NewTextMessage(sc *SessionContext, recipient string, text string) (TextMessage, error) { + return TextMessage{ + MessageHeader: NewMessageHeader(sc, recipient), + Body: text, + }, nil +} + +// String returns the message text as string +func (tm TextMessage) String() string { + return tm.Body +} + +//Serialize returns a fully serialized byte slice of a TextMessage +func (tm TextMessage) MarshalBinary() ([]byte, error) { + + buf := new(bytes.Buffer) + bufMarshal("msg-type", buf, uint8(MessageTypeText)) + bufMarshal("body", buf, []byte(tm.Body)) + bufMarshalPadding(buf) + + return buf.Bytes(), nil +} + +func (tm *TextMessage) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + var t MsgType + bufUnmarshal("read message type", buf, &t) + if t != MessageTypeText { + return errors.New("not correct type") + } + stripPadding(buf) + + tm.Body = string(buf.Bytes()) + return nil +} + +func init() { + messageUnmarshal[MessageTypeText] = func(mh *MessageHeader, data []byte) (Message, error) { + tm := &TextMessage{ + MessageHeader: mh, + } + if err := tm.UnmarshalBinary(data); err != nil { + return nil, err + } + return tm, nil + } +} diff --git a/message_typingnotification.go b/message_typingnotification.go new file mode 100644 index 0000000..2c93e12 --- /dev/null +++ b/message_typingnotification.go @@ -0,0 +1,52 @@ +package o3 + +import ( + "bytes" + "errors" +) + +// MsgType mock enum +const MessageTypeTypingNotification MsgType = 0x90 + +//TextMessage represents a text message as sent e2e encrypted to other threema users +type TypingNotificationMessage struct { + *MessageHeader + OnOff byte +} + +//Serialize returns a fully serialized byte slice of a TextMessage +func (msg TypingNotificationMessage) MarshalBinary() ([]byte, error) { + + buf := new(bytes.Buffer) + bufMarshal("msg-type", buf, uint8(MessageTypeText)) + bufMarshal("body", buf, msg.OnOff) + bufMarshalPadding(buf) + + return buf.Bytes(), nil +} + +//TODO untested +func (msg *TypingNotificationMessage) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + var t MsgType + bufUnmarshal("read message type", buf, &t) + if t != MessageTypeTypingNotification { + return errors.New("not correct type") + } + stripPadding(buf) + bufMarshal("typing?", buf, &msg.OnOff) + + return nil +} + +func init() { + messageUnmarshal[MessageTypeTypingNotification] = func(mh *MessageHeader, data []byte) (Message, error) { + tm := &TypingNotificationMessage{ + MessageHeader: mh, + } + if err := tm.UnmarshalBinary(data); err != nil { + return nil, err + } + return tm, nil + } +} diff --git a/messagetypes.go b/messagetypes.go deleted file mode 100644 index dbb3255..0000000 --- a/messagetypes.go +++ /dev/null @@ -1,627 +0,0 @@ -package o3 - -import ( - "fmt" - "io/ioutil" - mrand "math/rand" - "time" - - "errors" -) - -// MsgType determines the type of message that is sent or received. Users usually -// won't use this directly and rather use message generator functions. -type MsgType uint8 - -// MsgType mock enum -const ( - TEXTMESSAGE MsgType = 0x1 //indicates a text message - IMAGEMESSAGE MsgType = 0x2 //indicates a image message - AUDIOMESSAGE MsgType = 0x14 //indicates a audio message - POLLMESSAGE MsgType = 0x15 //indicates a poll message - LOCATIONMESSAGE MsgType = 0x16 //indicates a location message - FILEMESSAGE MsgType = 0x17 //indicates a file message - GROUPTEXTMESSAGE MsgType = 0x41 //indicates a group text message - GROUPIMAGEMESSAGE MsgType = 0x43 //indicates a group image message - GROUPSETMEMEBERSMESSAGE MsgType = 0x4A //indicates a set group member message - GROUPSETNAMEMESSAGE MsgType = 0x4B //indicates a set group name message - GROUPMEMBERLEFTMESSAGE MsgType = 0x4C //indicates a group member left message - GROUPSETIMAGEMESSAGE MsgType = 0x50 //indicates a group set image message - DELIVERYRECEIPT MsgType = 0x80 //indicates a delivery receipt sent by the threema servers - TYPINGNOTIFICATION MsgType = 0x90 //indicates a typing notifiaction message - //GROUPSETIMAGEMESSAGE msgType = 76 -) - -// MsgStatus represents the single-byte status field of DeliveryReceiptMessage -type MsgStatus uint8 - -//MsgStatus mock enum -const ( - MSGDELIVERED MsgStatus = 0x1 //indicates message was received by peer - MSGREAD MsgStatus = 0x2 //indicates message was read by peer - MSGAPPROVED MsgStatus = 0x3 //indicates message was approved (thumb up) by peer - MSGDISAPPROVED MsgStatus = 0x4 //indicates message was disapproved (thumb down) by peer -) - -//TODO: figure these out -type msgFlags struct { - PushMessage bool - NoQueuing bool - NoAckExpected bool - MessageHasAlreadyBeenDelivered bool - GroupMessage bool -} - -// NewMsgID returns a randomly generated message ID (not cryptographically secure!) -// TODO: Why mrand? -func NewMsgID() uint64 { - mrand.Seed(int64(time.Now().Nanosecond())) - msgID := uint64(mrand.Int63()) - return msgID -} - -// NewGrpID returns a randomly generated group ID (not cryptographically secure!) -// TODO: Why mrand? -func NewGrpID() [8]byte { - mrand.Seed(int64(time.Now().Nanosecond())) - - grpIDbuf := make([]byte, 8) - mrand.Read(grpIDbuf) - - var grpID [8]byte - copy(grpID[:], grpIDbuf) - - return grpID -} - -// Message representing the various kinds of e2e ecrypted messages threema supports -type Message interface { - - //Sender returns the message's sender ID - Sender() IDString - - //Serialize returns a fully serialized byte slice of the message - Serialize() []byte - - header() messageHeader -} - -type messageHeader struct { - sender IDString - recipient IDString - id uint64 - time time.Time - pubNick PubNick -} - -func (mh messageHeader) Sender() IDString { - return mh.sender -} - -func (mh messageHeader) Recipient() IDString { - return mh.recipient -} - -func (mh messageHeader) ID() uint64 { - return mh.id -} - -func (mh messageHeader) Time() time.Time { - return mh.time -} - -func (mh messageHeader) PubNick() PubNick { - return mh.pubNick -} - -//TODO: WAT? -func (mh messageHeader) header() messageHeader { - return mh -} - -//--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<---- - -type textMessageBody struct { - text string -} - -//TextMessage represents a text message as sent e2e encrypted to other threema users -type TextMessage struct { - messageHeader - textMessageBody -} - -// NewTextMessage returns a TextMessage ready to be encrypted -func NewTextMessage(sc *SessionContext, recipient string, text string) (TextMessage, error) { - recipientID := NewIDString(recipient) - - tm := TextMessage{ - messageHeader{ - sender: sc.ID.ID, - recipient: recipientID, - id: NewMsgID(), - time: time.Now(), - pubNick: sc.ID.Nick, - }, - textMessageBody{text: text}, - } - return tm, nil -} - -// Text returns the message text -func (tm TextMessage) Text() string { - return tm.text -} - -// String returns the message text as string -func (tm TextMessage) String() string { - return tm.Text() -} - -//Serialize returns a fully serialized byte slice of a TextMessage -func (tm TextMessage) Serialize() []byte { - return serializeTextMsg(tm).Bytes() -} - -//Serialize returns a fully serialized byte slice of a TypingNotificationMessage -func (tn TypingNotificationMessage) Serialize() []byte { - return serializeTypingNotification(tn).Bytes() -} - -//--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<---- - -//ImageMessage represents an image message as sent e2e encrypted to other threema users -type ImageMessage struct { - messageHeader - imageMessageBody -} - -type imageMessageBody struct { - BlobID [16]byte - ServerID byte - Size uint32 - Nonce nonce -} - -// NewImageMessage returns a ImageMessage ready to be encrypted -func NewImageMessage(sc *SessionContext, recipient string, filename string) (ImageMessage, error) { - recipientID := NewIDString(recipient) - - im := ImageMessage{ - messageHeader{ - sender: sc.ID.ID, - recipient: recipientID, - id: NewMsgID(), - time: time.Now(), - pubNick: sc.ID.Nick, - }, - imageMessageBody{}, - } - err := im.SetImageData(filename, *sc) - if err != nil { - return ImageMessage{}, err - } - return im, nil -} - -// GetPrintableContent returns a printable represantion of a ImageMessage. -func (im ImageMessage) GetPrintableContent() string { - return fmt.Sprintf("ImageMSG: https://%2x.blob.threema.ch/%16x, Size: %d, Nonce: %24x", im.ServerID, im.BlobID, im.Size, im.Nonce.nonce) -} - -// GetImageData return the decrypted Image needs the recipients secret key -func (im ImageMessage) GetImageData(sc SessionContext) ([]byte, error) { - return downloadAndDecryptAsym(sc, im.BlobID, im.Sender().String(), im.Nonce) -} - -// SetImageData encrypts and uploads the image. Sets the blob info in the ImageMessage. Needs the recipients public key. -func (im *ImageMessage) SetImageData(filename string, sc SessionContext) error { - plainImage, err := ioutil.ReadFile(filename) - if err != nil { - return errors.New("could not load image") - } - - im.Nonce, im.ServerID, im.Size, im.BlobID, err = encryptAsymAndUpload(sc, plainImage, im.recipient.String()) - - return err -} - -//Serialize returns a fully serialized byte slice of an ImageMessage -func (im ImageMessage) Serialize() []byte { - return serializeImageMsg(im).Bytes() -} - -//--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<---- - -//AudioMessage represents an image message as sent e2e encrypted to other threema users -type AudioMessage struct { - messageHeader - audioMessageBody -} - -type audioMessageBody struct { - Duration uint16 // The audio clips duration in seconds - BlobID [16]byte - ServerID byte - Size uint32 - Key [32]byte -} - -// NewAudioMessage returns a ImageMessage ready to be encrypted -func NewAudioMessage(sc *SessionContext, recipient string, filename string) (AudioMessage, error) { - recipientID := NewIDString(recipient) - - im := AudioMessage{ - messageHeader{ - sender: sc.ID.ID, - recipient: recipientID, - id: NewMsgID(), - time: time.Now(), - pubNick: sc.ID.Nick, - }, - audioMessageBody{}, - } - err := im.SetAudioData(filename, *sc) - if err != nil { - return AudioMessage{}, err - } - return im, nil -} - -// GetPrintableContent returns a printable represantion of an AudioMessage -func (am AudioMessage) GetPrintableContent() string { - return fmt.Sprintf("AudioMSG: https://%2x.blob.threema.ch/%16x, Size: %d, Nonce: %24x", am.ServerID, am.BlobID, am.Size, am.Key) -} - -// GetAudioData return the decrypted audio, needs the recipients secret key -func (am AudioMessage) GetAudioData(sc SessionContext) ([]byte, error) { - return downloadAndDecryptSym(am.BlobID, am.Key) -} - -// SetAudioData encrypts and uploads the audio. Sets the blob info in the ImageMessage. Needs the recipients public key. -func (am *AudioMessage) SetAudioData(filename string, sc SessionContext) error { - plainAudio, err := ioutil.ReadFile(filename) - if err != nil { - return errors.New("could not load audio") - } - - // TODO: Should we have a whole media lib as dependency just to set this to the proper value? - am.Duration = 0xFF - - am.Key, am.ServerID, am.Size, am.BlobID, err = encryptSymAndUpload(plainAudio) - - return err -} - -//Serialize returns a fully serialized byte slice of an AudioMessage -func (am AudioMessage) Serialize() []byte { - return serializeAudioMsg(am).Bytes() -} - -//--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<---- - -//TypingNotificationMessage represents a typing notifiaction message -type TypingNotificationMessage struct { - messageHeader - typingNotificationBody -} - -type typingNotificationBody struct { - OnOff byte -} - -//--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<---- - -// NewGroupTextMessages returns a slice of GroupMemberTextMessages ready to be encrypted -func NewGroupTextMessages(sc *SessionContext, group Group, text string) ([]GroupTextMessage, error) { - gtm := make([]GroupTextMessage, len(group.Members)) - var tm TextMessage - var err error - - for i, member := range group.Members { - tm, err = NewTextMessage(sc, member.String(), text) - if err != nil { - return []GroupTextMessage{}, err - } - - gtm[i] = GroupTextMessage{ - groupMessageHeader{ - creatorID: group.CreatorID, - groupID: group.GroupID}, - tm} - } - - return gtm, nil - -} - -//GroupTextMessage represents a group text message as sent e2e encrypted to other threema users -type GroupTextMessage struct { - groupMessageHeader - TextMessage -} - -type groupMessageHeader struct { - creatorID IDString - groupID [8]byte -} - -// Serialize : returns byte representation of serialized group text message -func (gtm GroupTextMessage) Serialize() []byte { - return serializeGroupTextMsg(gtm).Bytes() -} - -type groupImageMessageBody struct { - BlobID [16]byte - ServerID byte - Size uint32 - Key [32]byte -} - -// GroupCreator returns the ID of the groups admin/creator as string -func (gmh groupMessageHeader) GroupCreator() IDString { - return gmh.creatorID -} - -// GroupID returns the ID of the group the message belongs to -func (gmh groupMessageHeader) GroupID() [8]byte { - return gmh.groupID -} - -//GroupImageMessage represents a group image message as sent e2e encrypted to other threema users -type GroupImageMessage struct { - groupMessageHeader - messageHeader - groupImageMessageBody -} - -//Serialize returns a fully serialized byte slice of a GroupImageMessage -func (im GroupImageMessage) Serialize() []byte { - return serializeGroupImageMsg(im).Bytes() -} - -// GetImageData return the decrypted Image needs the recipients secret key -func (im GroupImageMessage) GetImageData(sc SessionContext) ([]byte, error) { - return downloadAndDecryptSym(im.BlobID, im.Key) -} - -// SetImageData encrypts the given image symmetrically and adds it to the message -func (im *GroupImageMessage) SetImageData(filename string) error { - return im.groupImageMessageBody.setImageData(filename) -} - -func (im *groupImageMessageBody) setImageData(filename string) error { - plainImage, err := ioutil.ReadFile(filename) - if err != nil { - return errors.New("could not load image") - } - - im.Key, im.ServerID, im.Size, im.BlobID, err = encryptSymAndUpload(plainImage) - - return err -} - -// NewGroupMemberLeftMessages returns a slice of GroupMemberLeftMessages ready to be encrypted -func NewGroupMemberLeftMessages(sc *SessionContext, group Group) []GroupMemberLeftMessage { - gml := make([]GroupMemberLeftMessage, len(group.Members)) - - for i := 0; i < len(group.Members); i++ { - gml[i] = GroupMemberLeftMessage{ - groupMessageHeader{ - creatorID: group.CreatorID, - groupID: group.GroupID}, - messageHeader{ - sender: sc.ID.ID, - recipient: group.Members[i], - id: NewMsgID(), - time: time.Now(), - pubNick: sc.ID.Nick}} - - } - - return gml - -} - -//Serialize returns a fully serialized byte slice of a GroupMemberLeftMessage -func (gml GroupMemberLeftMessage) Serialize() []byte { - return serializeGroupMemberLeftMessage(gml).Bytes() -} - -//GroupMemberLeftMessage represents a group leaving message -type GroupMemberLeftMessage struct { - groupMessageHeader - messageHeader -} - -// NewDeliveryReceiptMessage returns a TextMessage ready to be encrypted -func NewDeliveryReceiptMessage(sc *SessionContext, recipient string, msgID uint64, msgStatus MsgStatus) (DeliveryReceiptMessage, error) { - recipientID := NewIDString(recipient) - - dm := DeliveryReceiptMessage{ - messageHeader{ - sender: sc.ID.ID, - recipient: recipientID, - id: NewMsgID(), - time: time.Now(), - pubNick: sc.ID.Nick, - }, - deliveryReceiptMessageBody{ - msgID: msgID, - status: msgStatus}, - } - return dm, nil -} - -type deliveryReceiptMessageBody struct { - status MsgStatus - msgID uint64 -} - -// DeliveryReceiptMessage represents a delivery receipt as sent e2e encrypted to other threema users when a message has been received -type DeliveryReceiptMessage struct { - messageHeader - deliveryReceiptMessageBody -} - -// GetPrintableContent returns a printable represantion of a DeliveryReceiptMessage. -func (dm DeliveryReceiptMessage) GetPrintableContent() string { - return fmt.Sprintf("Delivered: %x", dm.msgID) -} - -//Serialize returns a fully serialized byte slice of a SeliveryReceiptMessage -func (dm DeliveryReceiptMessage) Serialize() []byte { - return serializeDeliveryReceiptMsg(dm).Bytes() -} - -// Status returns the messages status -func (dm DeliveryReceiptMessage) Status() MsgStatus { - return dm.status -} - -// MsgID returns the message id -func (dm DeliveryReceiptMessage) MsgID() uint64 { - return dm.msgID -} - -// GROUP MANAGEMENT MESSAGES -//////////////////////////////////////////////////////////////// -// TODO: Implement message interface -type groupManageMessageHeader struct { - groupID [8]byte -} - -func (gmh groupManageMessageHeader) GroupID() [8]byte { - return gmh.groupID -} - -// NewGroupManageSetMembersMessages returns a slice of GroupManageSetMembersMessages ready to be encrypted -func NewGroupManageSetMembersMessages(sc *SessionContext, group Group) []GroupManageSetMembersMessage { - gms := make([]GroupManageSetMembersMessage, len(group.Members)) - - for i := 0; i < len(group.Members); i++ { - gms[i] = GroupManageSetMembersMessage{ - groupManageMessageHeader{ - groupID: group.GroupID}, - messageHeader{ - sender: sc.ID.ID, - recipient: group.Members[i], - id: NewMsgID(), - time: time.Now(), - pubNick: sc.ID.Nick}, - groupManageSetMembersMessageBody{ - groupMembers: group.Members}} - - } - - return gms - -} - -type groupManageSetMembersMessageBody struct { - groupMembers []IDString -} - -// GroupManageSetImageMessage represents the message sent e2e-encrypted by a group's creator to all members to set the group image -type GroupManageSetImageMessage struct { - groupManageMessageHeader - messageHeader - groupImageMessageBody -} - -// NewGroupManageSetImageMessages returns a slice of GroupManageSetImageMessages ready to be encrypted -func NewGroupManageSetImageMessages(sc *SessionContext, group Group, filename string) []GroupManageSetImageMessage { - gms := make([]GroupManageSetImageMessage, len(group.Members)) - - for i := 0; i < len(group.Members); i++ { - gms[i] = GroupManageSetImageMessage{ - groupManageMessageHeader{ - groupID: group.GroupID}, - messageHeader{}, //TODO: - groupImageMessageBody{}, - } - - err := gms[i].SetImageData(filename) - if err != nil { - //TODO: pretty sure this isn't a good idea - return nil - } - } - - return gms -} - -// GetImageData returns the decrypted Image -func (im GroupManageSetImageMessage) GetImageData(sc SessionContext) ([]byte, error) { - return downloadAndDecryptSym(im.BlobID, im.Key) -} - -// SetImageData encrypts the given image symmetrically and adds it to the message -func (im *GroupManageSetImageMessage) SetImageData(filename string) error { - return im.groupImageMessageBody.setImageData(filename) -} - -//Serialize returns a fully serialized byte slice of an ImageMessage -func (im GroupManageSetImageMessage) Serialize() []byte { - return serializeGroupManageSetImageMessage(im).Bytes() -} - -// GroupManageSetMembersMessage represents the message sent e2e encrypted by a group's creator to all members -type GroupManageSetMembersMessage struct { - groupManageMessageHeader - messageHeader - groupManageSetMembersMessageBody -} - -//Members returns a byte slice of IDString of all members contained in the message -func (gmm GroupManageSetMembersMessage) Members() []IDString { - return gmm.groupMembers -} - -//Serialize returns a fully serialized byte slice of a GroupManageSetMembersMessage -func (gmm GroupManageSetMembersMessage) Serialize() []byte { - return serializeGroupManageSetMembersMessage(gmm).Bytes() -} - -// NewGroupManageSetNameMessages returns a slice of GroupMenageSetNameMessages ready to be encrypted -func NewGroupManageSetNameMessages(sc *SessionContext, group Group) []GroupManageSetNameMessage { - gms := make([]GroupManageSetNameMessage, len(group.Members)) - - for i := 0; i < len(group.Members); i++ { - gms[i] = GroupManageSetNameMessage{ - groupManageMessageHeader{ - groupID: group.GroupID}, - messageHeader{ - sender: sc.ID.ID, - recipient: group.Members[i], - id: NewMsgID(), - time: time.Now(), - pubNick: sc.ID.Nick}, - groupManageSetNameMessageBody{ - groupName: group.Name}} - - } - - return gms - -} - -type groupManageSetNameMessageBody struct { - groupName string -} - -func (gmm groupManageSetNameMessageBody) Name() string { - return gmm.groupName -} - -//Serialize returns a fully serialized byte slice of a GroupManageSetNameMessage -func (gmm GroupManageSetNameMessage) Serialize() []byte { - return serializeGroupManageSetNameMessage(gmm).Bytes() -} - -//GroupManageSetNameMessage represents a group management messate to set the group name -type GroupManageSetNameMessage struct { - groupManageMessageHeader - messageHeader - groupManageSetNameMessageBody -} diff --git a/packetdispatcher.go b/packetdispatcher.go index 34e4614..6858ccc 100644 --- a/packetdispatcher.go +++ b/packetdispatcher.go @@ -24,9 +24,9 @@ func dispatcherPanicHandler(context string, i interface{}) error { return fmt.Errorf("%s: unknown dispatch error occurred: %#v", context, i) } -func writeHelper(wr io.Writer, buf *bytes.Buffer) { - i, err := wr.Write(buf.Bytes()) - if i != buf.Len() { +func writeHelper(wr io.Writer, data []byte) { + i, err := wr.Write(data) + if i != len(data) { panic("not enough bytes were transmitted") } if err != nil { @@ -34,6 +34,10 @@ func writeHelper(wr io.Writer, buf *bytes.Buffer) { } } +func writeBufferHelper(wr io.Writer, buf *bytes.Buffer) { + writeHelper(wr, buf.Bytes()) +} + func (sc *SessionContext) dispatchClientHello(wr io.Writer) { defer func() { if r := recover(); r != nil { @@ -45,7 +49,7 @@ func (sc *SessionContext) dispatchClientHello(wr io.Writer) { ch.ClientSPK = sc.clientSPK ch.NoncePrefix = sc.clientNonce.prefix() - buf := serializeClientHelloPkt(ch) + buf, _ := ch.MarshalBinary() writeHelper(wr, buf) } @@ -73,17 +77,17 @@ func (sc *SessionContext) dispatchAuthMsg(wr io.Writer) { } copy(app.Ciphertext[:], ct[0:48]) - appBuf := serializeAuthPktPayload(app) + appBuf, _ := app.MarshalBinary() //create auth packet ciphertext sc.clientNonce.setCounter(1) - apct := box.Seal(nil, appBuf.Bytes(), sc.clientNonce.bytes(), &sc.serverSPK, &sc.clientSSK) + apct := box.Seal(nil, appBuf, sc.clientNonce.bytes(), &sc.serverSPK, &sc.clientSSK) if len(apct) != 144 { panic("error encrypting payload") } copy(ap.Ciphertext[:], apct[0:144]) - buf := serializeAuthPkt(ap) + buf, _ := ap.MarshalBinary() writeHelper(wr, buf) } @@ -91,72 +95,72 @@ func (sc *SessionContext) dispatchAckMsg(wr io.Writer, mp messagePacket) { ackP := ackPacket{ PktType: clientAck, SenderID: mp.Sender, - MsgID: mp.ID} - serializedAckPkt := serializeAckPkt(ackP) + MsgID: mp.ID, + } + serializedAckPkt, _ := ackP.MarshalBinary() sc.clientNonce.increaseCounter() - ackpCipherText := box.Seal(nil, serializedAckPkt.Bytes(), sc.clientNonce.bytes(), &sc.serverSPK, &sc.clientSSK) + ackpCipherText := box.Seal(nil, serializedAckPkt, sc.clientNonce.bytes(), &sc.serverSPK, &sc.clientSSK) buf := new(bytes.Buffer) binary.Write(buf, binary.LittleEndian, uint16(len(ackpCipherText))) binary.Write(buf, binary.LittleEndian, ackpCipherText) - writeHelper(wr, buf) + writeBufferHelper(wr, buf) } func (sc *SessionContext) dispatchEchoMsg(wr io.Writer, oldEchoPacket echoPacket) { - ep := echoPacket{ - Counter: oldEchoPacket.Counter + 1} - serializedEchoPkt := serializeEchoPkt(ep) + ep := echoPacket{Counter: oldEchoPacket.Counter + 1} + serializedEchoPkt, _ := ep.MarshalBinary() sc.clientNonce.increaseCounter() - epCipherText := box.Seal(nil, serializedEchoPkt.Bytes(), sc.clientNonce.bytes(), &sc.serverSPK, &sc.clientSSK) + epCipherText := box.Seal(nil, serializedEchoPkt, sc.clientNonce.bytes(), &sc.serverSPK, &sc.clientSSK) buf := new(bytes.Buffer) binary.Write(buf, binary.LittleEndian, uint16(len(epCipherText))) binary.Write(buf, binary.LittleEndian, epCipherText) - writeHelper(wr, buf) + writeBufferHelper(wr, buf) } func (sc *SessionContext) dispatchMessage(wr io.Writer, m Message) { - mh := m.header() - + mh := m.Header() randNonce := newRandomNonce() - recipient, ok := sc.ID.Contacts.Get(mh.recipient.String()) + recipient, ok := sc.ID.Contacts.Get(mh.Recipient.String()) if !ok { var tr ThreemaRest var err error - recipient, err = tr.GetContactByID(mh.recipient) + recipient, err = tr.GetContactByID(mh.Recipient) if err != nil { panic("Recipient's PublicKey could not be found!") } sc.ID.Contacts.Add(recipient) } - msgCipherText := box.Seal(nil, m.Serialize(), randNonce.bytes(), &recipient.LPK, &sc.ID.LSK) + data, _ := m.MarshalBinary() + msgCipherText := box.Seal(nil, data, randNonce.bytes(), &recipient.LPK, &sc.ID.LSK) messagePkt := messagePacket{ + Sender: mh.Sender, + Recipient: mh.Recipient, + ID: mh.ID, + Time: mh.Time, + PubNick: mh.PubNick, PktType: sendingMsg, - Sender: mh.sender, - Recipient: mh.recipient, - ID: mh.id, - Time: mh.time, Flags: msgFlags{PushMessage: true}, - PubNick: mh.pubNick, Nonce: randNonce, Ciphertext: msgCipherText, } - serializedMsgPkt := serializeMsgPkt(messagePkt) + serializedMsgPkt, _ := messagePkt.MarshalBinary() sc.clientNonce.increaseCounter() - serializedMsgPktCipherText := box.Seal(nil, serializedMsgPkt.Bytes(), sc.clientNonce.bytes(), &sc.serverSPK, &sc.clientSSK) + serializedMsgPktCipherText := box.Seal(nil, serializedMsgPkt, sc.clientNonce.bytes(), &sc.serverSPK, &sc.clientSSK) buf := new(bytes.Buffer) binary.Write(buf, binary.LittleEndian, uint16(len(serializedMsgPktCipherText))) binary.Write(buf, binary.LittleEndian, serializedMsgPktCipherText) - writeHelper(wr, buf) + writeBufferHelper(wr, buf) } diff --git a/packethandler.go b/packethandler.go index 08e8640..f699340 100644 --- a/packethandler.go +++ b/packethandler.go @@ -11,12 +11,13 @@ import ( "encoding/binary" "fmt" - "encoding/hex" - "golang.org/x/crypto/nacl/box" ) func handlerPanicHandler(context string, i interface{}) error { + if err, ok := i.(error); ok { + return fmt.Errorf("%s: handling error occurred: %s", context, err.Error()) + } if _, ok := i.(string); ok { return fmt.Errorf("%s: error occurred handling %s", context, i) } @@ -32,7 +33,8 @@ func (sc *SessionContext) handleServerHello(buf *bytes.Buffer) { panic(handlerPanicHandler("server hello", r)) } }() - sh := parseServerHello(buf) + sh := serverHelloPacket{} + sh.UnmarshalBinary(buf.Bytes()) sc.serverNonce.initialize(sh.NoncePrefix, 1) @@ -42,13 +44,15 @@ func (sc *SessionContext) handleServerHello(buf *bytes.Buffer) { } rdr := bytes.NewBuffer(plaintext) - serverSPK, clientNP := parseServerHelloPayload(rdr) + var serverSPK [32]byte + var clientNP [16]byte + bufUnmarshal("server spk", rdr, &serverSPK) + bufUnmarshal("client np", rdr, &clientNP) sc.serverSPK = serverSPK if clientNP != sc.clientNonce.prefix() { panic("client nonce check failed") } - } func (sc *SessionContext) handleHandshakeAck(buf *bytes.Buffer) { @@ -73,7 +77,7 @@ func (sc *SessionContext) handleAckMsg(buf *bytes.Buffer) {} func (sc *SessionContext) handleClientServerMsg(buf *bytes.Buffer) interface{} { defer func() { if r := recover(); r != nil { - panic(handlerPanicHandler("handleDataMsg", r)) + panic(handlerPanicHandler("handleClientServerMsg", r)) } }() @@ -91,7 +95,8 @@ func (sc *SessionContext) handleClientServerMsg(buf *bytes.Buffer) interface{} { switch pt { case deliveringMsg: // It is an e2e message! - msgPkt := parseMsgPkt(bytes.NewBuffer(plaintext)) + msgPkt := messagePacket{} + msgPkt.UnmarshalBinary(plaintext) // Find the sender in our contacts, because we need their public key sender, ok := sc.ID.Contacts.Get(msgPkt.Sender.String()) if !ok { @@ -113,14 +118,20 @@ func (sc *SessionContext) handleClientServerMsg(buf *bytes.Buffer) interface{} { return msgPkt case serverAck: // It is an ACK for a message we sent - return parseAckPkt(bytes.NewBuffer(plaintext)) + pkg := ackPacket{} + pkg.UnmarshalBinary(plaintext) + return pkg case echoMsg: // It is an echo reply - return parseEchoPkt(bytes.NewBuffer(plaintext)) + pkg := echoPacket{} + pkg.UnmarshalBinary(plaintext) + return pkg case connEstablished: // We have received all enqueued messages - return parseConnEstPkt(bytes.NewBuffer(plaintext)) - case douplicateConnectionError: + pkg := connEstPacket{} + pkg.UnmarshalBinary(plaintext) + return pkg + case duplicateConnectionError: return errDuplicateConn default: fmt.Printf("Unknown PktType: %.2x", plaintext) @@ -133,75 +144,15 @@ func (sc *SessionContext) handleMessagePacket(mp messagePacket) (Message, error) // DEBUG //fmt.Print(hex.Dump(mp.Plaintext)) - buf := bytes.NewBuffer(mp.Plaintext) - - var message Message - mt := parseMessageType(buf) - switch mt { - case TEXTMESSAGE: - message = TextMessage{ - messageHeader: newMsgHdrFromPkt(mp), - textMessageBody: parseTextMessage(buf)} - case IMAGEMESSAGE: - message = ImageMessage{ - messageHeader: newMsgHdrFromPkt(mp), - imageMessageBody: parseImageMessage(buf)} - case AUDIOMESSAGE: - message = AudioMessage{ - messageHeader: newMsgHdrFromPkt(mp), - audioMessageBody: parseAudioMessage(buf)} - case GROUPTEXTMESSAGE: - message = GroupTextMessage{ - groupMessageHeader: parseGroupMessageHeader(buf), - TextMessage: TextMessage{ - messageHeader: newMsgHdrFromPkt(mp), - textMessageBody: parseTextMessage(buf)}} - case GROUPIMAGEMESSAGE: - message = GroupImageMessage{ - groupMessageHeader: parseGroupMessageHeader(buf), - messageHeader: newMsgHdrFromPkt(mp), - groupImageMessageBody: parseGroupImageMessage(buf)} - case GROUPSETNAMEMESSAGE: - message = GroupManageSetNameMessage{ - groupManageMessageHeader: parseGroupManageMessageHeader(buf), - messageHeader: newMsgHdrFromPkt(mp), - groupManageSetNameMessageBody: parseGroupManageSetNameMessage(buf)} - case GROUPSETIMAGEMESSAGE: - message = GroupManageSetImageMessage{ - groupManageMessageHeader: parseGroupManageMessageHeader(buf), - messageHeader: newMsgHdrFromPkt(mp), - groupImageMessageBody: parseGroupImageMessage(buf)} - case GROUPSETMEMEBERSMESSAGE: - message = GroupManageSetMembersMessage{ - groupManageMessageHeader: parseGroupManageMessageHeader(buf), - messageHeader: newMsgHdrFromPkt(mp), - groupManageSetMembersMessageBody: parseGroupManageSetMembersMessage(buf)} - case GROUPMEMBERLEFTMESSAGE: - fmt.Println(hex.Dump(buf.Bytes())) - message = GroupMemberLeftMessage{ - messageHeader: newMsgHdrFromPkt(mp), - groupMessageHeader: parseGroupMessageHeader(buf)} - case DELIVERYRECEIPT: - message = DeliveryReceiptMessage{ - messageHeader: newMsgHdrFromPkt(mp), - deliveryReceiptMessageBody: parseDeliveryReceipt(buf)} - case TYPINGNOTIFICATION: - message = TypingNotificationMessage{ - messageHeader: newMsgHdrFromPkt(mp), - typingNotificationBody: parseTypingNotification(buf)} - default: - fmt.Printf("\n%2x\n", buf) - fmt.Printf("\n%s\n", buf) - return nil, fmt.Errorf("o3: unknown MessageType: %d", mt) - } - return message, nil -} + mt := mp.GetMessageType() -func newMsgHdrFromPkt(mp messagePacket) messageHeader { - return messageHeader{ - sender: mp.Sender, - recipient: mp.Recipient, - id: mp.ID, - time: mp.Time, - pubNick: mp.PubNick} + if msgGen, ok := messageUnmarshal[mt]; ok { + mh := mp.header() + msg, err := msgGen(&mh, mp.Plaintext) + + return msg, err + } + fmt.Printf("\n%2x\n", mp.Plaintext) + fmt.Printf("\n%s\n", mp.Plaintext) + return nil, fmt.Errorf("o3: unknown MessageType: %d", mt) } diff --git a/packetparser.go b/packetparser.go deleted file mode 100644 index fd6b911..0000000 --- a/packetparser.go +++ /dev/null @@ -1,337 +0,0 @@ -// Package o3 functions to convert packets from byte buffers to go structs. -// These functions are called from packethandler only and their -// task is only conversion. Errors are bubbled up the chain as -// panics and will be converted to go errors further up the chain. -package o3 - -import ( - "bytes" - "encoding/binary" - "fmt" - "time" -) - -func parseMsgPkt(buf *bytes.Buffer) (mp messagePacket) { - - mp.PktType = parsePktType(buf) - mp.Sender = parseIDString(buf) - mp.Recipient = parseIDString(buf) - mp.ID = parseUint64(buf) - mp.Time = parseTime(buf) - mp.PubNick = parsePubNick(buf) - mp.Nonce = parseNonce(buf) - mp.Ciphertext = parseMessage(buf) - - return -} - -func parseAckPkt(buf *bytes.Buffer) (ap ackPacket) { - - ap.PktType = parsePktType(buf) - ap.SenderID = parseIDString(buf) - ap.MsgID = parseUint64(buf) - - return -} - -func parseEchoPkt(buf *bytes.Buffer) (ep echoPacket) { - - ep.PktType = parsePktType(buf) - ep.Counter = parseUint64(buf) - - return -} - -func parseDeliveryReceipt(buf *bytes.Buffer) deliveryReceiptMessageBody { - stripPadding(buf) - - dm := deliveryReceiptMessageBody{ - status: MsgStatus(parseByte(buf)), - msgID: parseUint64(buf)} - return dm -} - -func parseConnEstPkt(buf *bytes.Buffer) (cep connEstPacket) { - - cep.PktType = parsePktType(buf) - - return -} - -func parseClientHello(buf *bytes.Buffer) (ch clientHelloPacket) { - - ch.ClientSPK = parseKey(buf) - ch.NoncePrefix = parseNoncePrefix(buf) - - return -} - -func parseServerHello(buf *bytes.Buffer) (sh serverHelloPacket) { - - sh.NoncePrefix = parseNoncePrefix(buf) - sh.Ciphertext = parse64bytes(buf) - - return -} - -func parseServerHelloPayload(buf *bytes.Buffer) (serverSPK [32]byte, clientNP [16]byte) { - - serverSPK = parseKey(buf) - clientNP = parseNoncePrefix(buf) - return -} - -func parseAuthPkt(buf *bytes.Buffer) (ap authPacket) { - - ap.Ciphertext = parse144bytes(buf) - return -} - -func stripPadding(buf *bytes.Buffer) { - paddingLen := int(buf.Bytes()[buf.Len()-1]) - buf.Truncate(buf.Len() - paddingLen) -} - -func parseUint8(buf *bytes.Buffer) uint8 { - var ret uint8 - - err := binary.Read(buf, binary.LittleEndian, &ret) - if err != nil { - panic("uint8") - } - return ret -} - -func parseUint16(buf *bytes.Buffer) uint16 { - var ret uint16 - - err := binary.Read(buf, binary.LittleEndian, &ret) - if err != nil { - panic("uint16") - } - return ret -} - -func parseUint32(buf *bytes.Buffer) uint32 { - var ret uint32 - - err := binary.Read(buf, binary.LittleEndian, &ret) - if err != nil { - panic("uint32") - } - return ret -} - -func parseInt64(buf *bytes.Buffer) int64 { - var ret int64 - - err := binary.Read(buf, binary.LittleEndian, &ret) - if err != nil { - panic("int64") - } - return ret -} - -func parseUint64(buf *bytes.Buffer) uint64 { - var ret uint64 - - err := binary.Read(buf, binary.LittleEndian, &ret) - if err != nil { - panic("uint64") - } - return ret -} - -func parseMessageType(buf *bytes.Buffer) MsgType { - msgT := parseUint8(buf) - //TODO check valid range - return MsgType(msgT) -} - -func parsePktType(buf *bytes.Buffer) pktType { - pktT := parseUint32(buf) - //TODO check valid range - return pktType(pktT) -} - -func parseIDString(buf *bytes.Buffer) IDString { - var id [8]byte - err := binary.Read(buf, binary.LittleEndian, &id) - //TODO check valild characters - if err != nil { - panic("Threema ID") - } - return IDString(id) -} - -func parsePubNick(buf *bytes.Buffer) PubNick { - var pn PubNick - err := binary.Read(buf, binary.LittleEndian, &pn) - //TODO check valild characters - if err != nil { - panic("PubNick") - } - return pn -} - -func parseTime(buf *bytes.Buffer) time.Time { - return time.Unix(parseInt64(buf), 0) -} - -func parseNonce(buf *bytes.Buffer) (n nonce) { - rawNonce := make([]byte, 24) - err := binary.Read(buf, binary.LittleEndian, rawNonce) - if err != nil { - panic("nonce") - } - copy(n.nonce[:], rawNonce) - return -} - -func parseNoncePrefix(buf *bytes.Buffer) (np [16]byte) { - rawNP := make([]byte, 16) - err := binary.Read(buf, binary.LittleEndian, rawNP) - if err != nil { - panic("nonce prefix") - } - copy(np[:], rawNP) - return -} - -func parseMessage(buf *bytes.Buffer) []byte { - return buf.Bytes() -} - -func parseTextMessage(buf *bytes.Buffer) textMessageBody { - stripPadding(buf) - - return textMessageBody{text: string(buf.Bytes())} -} - -func parseImageMessage(buf *bytes.Buffer) imageMessageBody { - stripPadding(buf) - - im := imageMessageBody{ - BlobID: parseBlobID(buf), - Size: parseUint32(buf), - Nonce: parseNonce(buf)} - im.ServerID = im.BlobID[0] - return im -} - -func parseTypingNotification(buf *bytes.Buffer) (tn typingNotificationBody) { - tn.OnOff = parseByte(buf) - return -} - -func parseAudioMessage(buf *bytes.Buffer) audioMessageBody { - stripPadding(buf) - - am := audioMessageBody{ - Duration: parseUint16(buf), - BlobID: parseBlobID(buf), - Size: parseUint32(buf), - Key: parseKey(buf)} - am.ServerID = am.BlobID[0] - return am -} - -func parseGroupImageMessage(buf *bytes.Buffer) groupImageMessageBody { - stripPadding(buf) - - gim := groupImageMessageBody{ - BlobID: parseBlobID(buf), - Size: parseUint32(buf), - Key: parseKey(buf)} - gim.ServerID = gim.BlobID[0] - return gim -} - -func parseGroupMessageHeader(buf *bytes.Buffer) groupMessageHeader { - return groupMessageHeader{ - creatorID: parseIDString(buf), - groupID: parseGroupID(buf), - } -} - -func parseGroupManageSetNameMessage(buf *bytes.Buffer) groupManageSetNameMessageBody { - stripPadding(buf) - - return groupManageSetNameMessageBody{groupName: string(buf.Bytes())} -} - -func parseGroupManageSetMembersMessage(buf *bytes.Buffer) groupManageSetMembersMessageBody { - stripPadding(buf) - - if (buf.Len() % 8) != 0 { - panic("List of group members corrupt. Length is no multiple of 8.") - } - - memberCount := buf.Len() / 8 - gmm := groupManageSetMembersMessageBody{ - groupMembers: make([]IDString, memberCount)} - - for i := 0; i < memberCount; i++ { - gmm.groupMembers[i] = parseIDString(buf) - } - - return gmm -} - -func parseGroupManageMessageHeader(buf *bytes.Buffer) groupManageMessageHeader { - return groupManageMessageHeader{ - groupID: parseGroupID(buf), - } -} - -func parseKey(buf *bytes.Buffer) (key [32]byte) { - rawKey := make([]byte, 32) - n, err := buf.Read(rawKey) - if n != 32 || err != nil { - panic("32-byte key") - } - copy(key[:], rawKey) - return -} - -func parseBlobID(buf *bytes.Buffer) (bytes [16]byte) { - bytebuf := parsenbytes(buf, 16) - copy(bytes[:], bytebuf) - return -} - -func parseGroupID(buf *bytes.Buffer) (bytes [8]byte) { - bytebuf := parsenbytes(buf, 8) - copy(bytes[:], bytebuf) - return -} - -func parse64bytes(buf *bytes.Buffer) (bytes [64]byte) { - bytebuf := parsenbytes(buf, 64) - copy(bytes[:], bytebuf) - return -} - -func parse144bytes(buf *bytes.Buffer) (bytes [144]byte) { - bytebuf := parsenbytes(buf, 144) - copy(bytes[:], bytebuf) - return -} - -func parseByte(buf *bytes.Buffer) byte { - b, err := buf.ReadByte() - if err != nil { - panic(err) - } - return b -} - -//helper function: not to be called directly -func parsenbytes(buf *bytes.Buffer, size int) []byte { - bytes := make([]byte, size) - n, err := buf.Read(bytes) - if n != size || err != nil { - panic(fmt.Sprintf("%d bytes of data", size)) - } - return bytes -} diff --git a/packetserializer.go b/packetserializer.go deleted file mode 100644 index eb0513b..0000000 --- a/packetserializer.go +++ /dev/null @@ -1,410 +0,0 @@ -/*Functions to covert packets from go structs to byte buffers. - *These functions will only be called from packetdispatcher and - *their task is only conversion (inversion of the parser). - *Errors are passed up the chain in the form of panics and will - *be converted to go errors further up the chain. - */ - -package o3 - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "fmt" - "math/big" - "time" -) - -func serializeMsgPkt(mp messagePacket) *bytes.Buffer { - - buf := new(bytes.Buffer) - serializePktType(buf, mp.PktType) - serializeIDString(buf, mp.Sender) - serializeIDString(buf, mp.Recipient) - serializeMsgID(buf, mp.ID) - serializeTime(buf, mp.Time) - serializeMsgFlags(buf, mp.Flags) - // The three following bytes are unused - serializeUnusedBytes(buf) - serializePubNick(buf, mp.PubNick) - serializeNonce(buf, mp.Nonce) - serializeCiphertext(buf, mp.Ciphertext) - - return buf -} - -func serializeTextMsg(tm TextMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - serializeMsgType(buf, TEXTMESSAGE) - serializeText(buf, tm.text) - serializePadding(buf) - - return buf -} - -func serializeImageMsg(im ImageMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - serializeMsgType(buf, IMAGEMESSAGE) - serializeBlobID(buf, im.BlobID) - serializeUint32(buf, im.Size) - serializeNonce(buf, im.Nonce) - serializePadding(buf) - return buf -} - -func serializeAudioMsg(am AudioMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - serializeMsgType(buf, AUDIOMESSAGE) - // AudioClip duration - serializeUint16(buf, 0xFFFF) - serializeBlobID(buf, am.BlobID) - serializeUint32(buf, am.Size) - serializeKey(buf, am.Key) - serializePadding(buf) - - return buf -} - -func serializeGroupTextMsg(gtm GroupTextMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializeMsgType(buf, GROUPTEXTMESSAGE) - serializeGroupHeader(buf, gtm.groupMessageHeader) - serializeText(buf, gtm.text) - serializePadding(buf) - - return buf -} - -func serializeGroupImageMsg(gim GroupImageMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - serializeMsgType(buf, GROUPIMAGEMESSAGE) - serializeGroupHeader(buf, gim.groupMessageHeader) - serializeBlobID(buf, gim.BlobID) - serializeUint32(buf, gim.Size) - serializeKey(buf, gim.Key) - serializePadding(buf) - - return buf -} - -// func serializeGroupAudioMsg(gam GroupAudioMessage) *bytes.Buffer { -// defer func() { -// if r := recover(); r != nil { -// panic(serializerPanicHandler("AudioMsg", r)) -// } -// }() -// buf := new(bytes.Buffer) - -// serializeMsgType(buf, AUDIOMESSAGE) - -// // AudioClip duration, fixed value for now -// serializeUint16(buf, 0x0000) - -// serializeBlobID(buf, gam.BlobID) -// serializeUint32(buf, gam.Size) -// serializeKey(buf, gam.Key) -// serializePadding(buf) -// return buf -// } - -func serializeGroupMemberLeftMessage(glm GroupMemberLeftMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializeMsgType(buf, GROUPMEMBERLEFTMESSAGE) - serializeGroupHeader(buf, glm.groupMessageHeader) - serializePadding(buf) - - return buf -} - -func serializeGroupManageSetNameMessage(gmm GroupManageSetNameMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializeMsgType(buf, GROUPSETNAMEMESSAGE) - serializeGroupID(buf, gmm.GroupID()) - serializeText(buf, gmm.Name()) - serializePadding(buf) - - return buf -} - -func serializeGroupManageSetMembersMessage(gmm GroupManageSetMembersMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializeMsgType(buf, GROUPSETMEMEBERSMESSAGE) - serializeGroupID(buf, gmm.GroupID()) - for _, member := range gmm.Members() { - serializeIDString(buf, member) - } - serializePadding(buf) - - return buf -} - -func serializeGroupManageSetImageMessage(gim GroupManageSetImageMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializeMsgType(buf, GROUPSETIMAGEMESSAGE) - serializeGroupID(buf, gim.GroupID()) - serializeBlobID(buf, gim.BlobID) - serializeUint32(buf, gim.Size) - serializeKey(buf, gim.Key) - serializePadding(buf) - - return buf -} - -func serializeAckPkt(ap ackPacket) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializePktType(buf, ap.PktType) - serializeIDString(buf, ap.SenderID) - serializeMsgID(buf, ap.MsgID) - - return buf -} - -func serializeEchoPkt(ep echoPacket) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializePktType(buf, ep.PktType) - serializeUint64(buf, ep.Counter) - - return buf -} - -func serializeDeliveryReceiptMsg(dm DeliveryReceiptMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - serializeMsgType(buf, DELIVERYRECEIPT) - serializeMsgStatus(buf, dm.status) - serializeMsgID(buf, dm.msgID) - serializePadding(buf) - - return buf -} - -func serializeClientHelloPkt(ch clientHelloPacket) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializeKey(buf, ch.ClientSPK) - serializeNoncePrefix(buf, ch.NoncePrefix) - - return buf -} - -func serializeServerHelloPkt(sh serverHelloPacket) *bytes.Buffer { - - buf := new(bytes.Buffer) - - //TODO finish - - return buf -} - -func serializeAuthPktPayload(app authPacketPayload) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializeIDString(buf, app.Username) - serializeSysData(buf, app.SysData) - serializeNoncePrefix(buf, app.ServerNoncePrefix) - serializeNonce(buf, app.RandomNonce) - serializeArbitraryData(buf, app.Ciphertext) - - return buf -} - -func serializeAuthPkt(ap authPacket) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializeCiphertext(buf, ap.Ciphertext[:]) - - return buf -} - -func serializeTypingNotification(tn TypingNotificationMessage) *bytes.Buffer { - - buf := new(bytes.Buffer) - - serializeByte(buf, tn.OnOff) - - return buf -} - -func serializerPanicHandler(context string, i interface{}) error { - if _, ok := i.(string); ok { - return fmt.Errorf("%s: error occurred serializing %s", context, i) - } - return fmt.Errorf("%s: unknown serializing error occurred: %#v", context, i) -} - -func serializeHelper(buf *bytes.Buffer, i interface{}) *bytes.Buffer { - return contextualSerializeHelper(fmt.Sprintf("%T", i), buf, i) -} - -func contextualSerializeHelper(context string, buf *bytes.Buffer, i interface{}) *bytes.Buffer { - err := binary.Write(buf, binary.LittleEndian, i) - if err != nil { - panic(context) - } - return buf -} - -// serializePadding returns a byte slice filled with n repetitions of the byte value n -func serializePadding(buf *bytes.Buffer) { - paddingValueBig, err := rand.Int(rand.Reader, big.NewInt(255)) - if err != nil { - panic(err) - } - paddingValue := byte(paddingValueBig.Int64()) - padding := make([]byte, paddingValue) - for i := range padding { - padding[i] = paddingValue - } - serializeHelper(buf, padding) -} - -// TODO: clean this up! -func serializeUint8(num uint8, buf *bytes.Buffer) *bytes.Buffer { - return serializeHelper(buf, num) -} - -func serializeUint16(buf *bytes.Buffer, num uint16) *bytes.Buffer { - return serializeHelper(buf, num) -} - -func serializeUint32(buf *bytes.Buffer, num uint32) *bytes.Buffer { - return serializeHelper(buf, num) -} - -func serializeInt64(buf *bytes.Buffer, num int64) *bytes.Buffer { - return serializeHelper(buf, num) -} - -func serializeUint64(buf *bytes.Buffer, num uint64) *bytes.Buffer { - return serializeHelper(buf, num) -} - -func serializePktType(buf *bytes.Buffer, pktT pktType) *bytes.Buffer { - return serializeUint32(buf, uint32(pktT)) -} - -func serializeMsgType(buf *bytes.Buffer, msgT MsgType) *bytes.Buffer { - return serializeUint8(uint8(msgT), buf) -} - -func serializeByte(buf *bytes.Buffer, b byte) *bytes.Buffer { - err := buf.WriteByte(b) - if err != nil { - panic(err) - } - return buf -} - -func serializeMsgFlags(buf *bytes.Buffer, flags msgFlags) *bytes.Buffer { - var flagsByte byte - if flags.PushMessage { - flagsByte |= (1 << 0) - } - if flags.NoQueuing { - flagsByte |= (1 << 1) - } - if flags.NoAckExpected { - flagsByte |= (1 << 2) - } - if flags.MessageHasAlreadyBeenDelivered { - flagsByte |= (1 << 3) - } - if flags.GroupMessage { - flagsByte |= (1 << 4) - } - serializeUint8(flagsByte, buf) - return buf -} - -func serializeUnusedBytes(buf *bytes.Buffer) *bytes.Buffer { - return serializeArbitraryData(buf, []byte{0x00, 0x00, 0x00}) -} - -func serializeKey(buf *bytes.Buffer, key [32]byte) *bytes.Buffer { - return contextualSerializeHelper("key", buf, key) -} - -func serializeNoncePrefix(buf *bytes.Buffer, np [16]byte) *bytes.Buffer { - return contextualSerializeHelper("nonce prefix", buf, np) -} - -func serializeIDString(buf *bytes.Buffer, is IDString) *bytes.Buffer { - return contextualSerializeHelper("id string", buf, is) -} - -func serializePubNick(buf *bytes.Buffer, pn PubNick) *bytes.Buffer { - return contextualSerializeHelper("public nickname", buf, pn) -} - -func serializeMsgStatus(buf *bytes.Buffer, msgStatus MsgStatus) *bytes.Buffer { - return serializeByte(buf, byte(msgStatus)) -} - -func serializeMsgID(buf *bytes.Buffer, id uint64) *bytes.Buffer { - return serializeUint64(buf, id) -} - -func serializeTime(buf *bytes.Buffer, t time.Time) *bytes.Buffer { - //TODO time sanity checks - return contextualSerializeHelper("time", buf, uint32(t.Unix())) -} - -func serializeNonce(buf *bytes.Buffer, n nonce) *bytes.Buffer { - return contextualSerializeHelper("nonce", buf, n.nonce) -} - -func serializeCiphertext(buf *bytes.Buffer, bts []byte) *bytes.Buffer { - //TODO error handling written bytes vs. len(bts)? - return contextualSerializeHelper("ciphertext", buf, bts) -} - -func serializeSysData(buf *bytes.Buffer, sysData [32]byte) *bytes.Buffer { - return contextualSerializeHelper("system data", buf, sysData) -} - -func serializeText(buf *bytes.Buffer, text string) *bytes.Buffer { - // TODO: sanatize? - return serializeHelper(buf, []byte(text)) -} - -func serializeBlobID(buf *bytes.Buffer, blobID [16]byte) *bytes.Buffer { - return serializeHelper(buf, []byte(blobID[:])) -} - -func serializeArbitraryData(buf *bytes.Buffer, i interface{}) *bytes.Buffer { - //TODO type assertions for error handling? - //TODO what to do about context? - return serializeHelper(buf, i) -} - -func serializeGroupID(buf *bytes.Buffer, groupID [8]byte) *bytes.Buffer { - return serializeHelper(buf, []byte(groupID[:])) -} - -func serializeGroupHeader(buf *bytes.Buffer, gh groupMessageHeader) *bytes.Buffer { - serializeIDString(buf, gh.creatorID) - serializeGroupID(buf, gh.groupID) - return buf -} diff --git a/packettypes.go b/packettypes.go index ac9d42e..18593e4 100644 --- a/packettypes.go +++ b/packettypes.go @@ -1,6 +1,10 @@ package o3 -import "time" +import ( + "bytes" + "encoding" + "time" +) type pktType uint32 @@ -17,14 +21,16 @@ const ( clientAck pktType = 0x82 // connEstablished is the packet type of a pkt send by the server when all MSGs have been delivered connEstablished pktType = 0xd0 - // douplicateConnectionError is sent whenever the connection is ursurped by another client - douplicateConnectionError pktType = 0xE0 + // duplicateConnectionError is sent whenever the connection is ursurped by another client + duplicateConnectionError pktType = 0xE0 // msgHeaderLength is the length of a ThreemaMessageHeader msgHeaderLength uint8 = 64 ) // ThreemaMessageHeader contains fields that every type of message needs type messagePacket struct { + encoding.BinaryMarshaler + PktType pktType Sender IDString Recipient IDString @@ -37,35 +43,176 @@ type messagePacket struct { Plaintext []byte } +func (mp *messagePacket) header() MessageHeader { + return MessageHeader{ + Sender: mp.Sender, + Recipient: mp.Recipient, + ID: mp.ID, + Time: mp.Time, + PubNick: mp.PubNick, + } +} + +func (mp *messagePacket) GetMessageType() (mt MsgType) { + buf := bytes.NewBuffer(mp.Plaintext) + bufUnmarshal("msg-type", buf, &mt) + return +} + +func (mp *messagePacket) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + + bufMarshal("pkg-type", buf, uint32(mp.PktType)) + bufMarshal("sender", buf, mp.Sender) + bufMarshal("recipient", buf, mp.Recipient) + bufMarshal("id", buf, mp.ID) + bufMarshal("time", buf, uint32(mp.Time.Unix())) + bufMarshal("flags", buf, mp.Flags) + // The three following bytes are unused + bufMarshal("unused", buf, []byte{0x00, 0x00, 0x00}) + bufMarshal("public nick", buf, mp.PubNick) + bufMarshal("nonce", buf, mp.Nonce.nonce) + bufMarshal("ciphertext", buf, mp.Ciphertext) + + return buf.Bytes(), nil +} + +func (mp *messagePacket) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + + bufUnmarshal("pkg-type", buf, &mp.PktType) + bufUnmarshal("sender", buf, &mp.Sender) + bufUnmarshal("recipient", buf, &mp.Recipient) + bufUnmarshal("id", buf, &mp.ID) + + var t int32 + bufUnmarshal("time", buf, &t) + mp.Time = time.Unix(int64(t), 0) + + // handle int64 date - on wrong way + bufUnmarshal("fake-time", buf, &t) + + // 1 byte for flags + 3 bytes unused + unused := make([]byte, 4) + bufUnmarshal("unused", buf, &unused) + + bufUnmarshal("public nick", buf, &mp.PubNick) + bufUnmarshal("nonce", buf, &mp.Nonce.nonce) + mp.Ciphertext = buf.Bytes() + + return nil +} + type ackPacket struct { PktType pktType SenderID IDString MsgID uint64 } +func (ap *ackPacket) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + + bufMarshal("pkg-type", buf, ap.PktType) + bufMarshal("sender", buf, ap.SenderID) + bufMarshal("msg id", buf, ap.MsgID) + + return buf.Bytes(), nil +} + +func (ap *ackPacket) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + + bufUnmarshal("pkg-type", buf, &ap.PktType) + bufUnmarshal("sender", buf, &ap.SenderID) + bufUnmarshal("msg id", buf, &ap.MsgID) + + return nil +} + type echoPacket struct { PktType pktType Counter uint64 } +func (ep *echoPacket) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + + bufMarshal("pkg type", buf, ep.PktType) + bufMarshal("counter", buf, ep.Counter) + + return buf.Bytes(), nil +} + +func (ep *echoPacket) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + + bufUnmarshal("pkg type", buf, &ep.PktType) + bufUnmarshal("counter", buf, &ep.Counter) + + return nil +} + type connEstPacket struct { PktType pktType } +func (pkg *connEstPacket) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + + bufUnmarshal("pkg type", buf, &pkg.PktType) + return nil +} + type clientHelloPacket struct { ClientSPK [32]byte NoncePrefix [16]byte } +func (ch clientHelloPacket) MarshalBinary() ([]byte, error) { + + buf := new(bytes.Buffer) + + bufMarshal("client spk", buf, ch.ClientSPK) + bufMarshal("nonce prefix", buf, ch.NoncePrefix) + + return buf.Bytes(), nil +} + type serverHelloPacket struct { NoncePrefix [16]byte Ciphertext [64]byte } +func (sh serverHelloPacket) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + + //TODO finish + return buf.Bytes(), nil +} + +func (sh *serverHelloPacket) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + + bytebuf := bufUnmarshalBytes(buf, 16) + copy(sh.NoncePrefix[:], bytebuf) + + bytebuf = bufUnmarshalBytes(buf, 64) + copy(sh.Ciphertext[:], bytebuf) + + return nil +} + type authPacket struct { Ciphertext [144]byte } +func (ap *authPacket) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + + bufMarshal("ciphertext", buf, ap.Ciphertext) + return buf.Bytes(), nil +} + //plain content of an auth packet type authPacketPayload struct { Username IDString @@ -74,3 +221,15 @@ type authPacketPayload struct { RandomNonce nonce Ciphertext [48]byte } + +func (app *authPacketPayload) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + + bufMarshal("username", buf, app.Username) + bufMarshal("sys-data", buf, app.SysData) + bufMarshal("server nonce prefix", buf, app.ServerNoncePrefix) + bufMarshal("random nonce", buf, app.RandomNonce) + bufMarshal("ciphertext", buf, app.Ciphertext) + + return buf.Bytes(), nil +} From 02491c4b7dddb0cdb468ff647502deb025d3702e Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Fri, 6 Sep 2019 01:58:27 +0200 Subject: [PATCH 05/10] some fixes --- message_typingnotification.go | 12 +++++++++--- packetdispatcher.go | 5 ++++- packethandler.go | 9 +++++++-- packettypes.go | 19 ++++++++++++++----- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/message_typingnotification.go b/message_typingnotification.go index 2c93e12..9aeeb6f 100644 --- a/message_typingnotification.go +++ b/message_typingnotification.go @@ -3,6 +3,7 @@ package o3 import ( "bytes" "errors" + "fmt" ) // MsgType mock enum @@ -18,14 +19,13 @@ type TypingNotificationMessage struct { func (msg TypingNotificationMessage) MarshalBinary() ([]byte, error) { buf := new(bytes.Buffer) - bufMarshal("msg-type", buf, uint8(MessageTypeText)) + bufMarshal("msg-type", buf, uint8(MessageTypeTypingNotification)) bufMarshal("body", buf, msg.OnOff) bufMarshalPadding(buf) return buf.Bytes(), nil } -//TODO untested func (msg *TypingNotificationMessage) UnmarshalBinary(data []byte) error { buf := bytes.NewBuffer(data) var t MsgType @@ -34,10 +34,16 @@ func (msg *TypingNotificationMessage) UnmarshalBinary(data []byte) error { return errors.New("not correct type") } stripPadding(buf) - bufMarshal("typing?", buf, &msg.OnOff) + bufUnmarshal("typing?", buf, &msg.OnOff) return nil } +func (msg *TypingNotificationMessage) String() string { + if msg.OnOff != 0 { + return fmt.Sprintf("%s is composing", msg.Sender) + } + return fmt.Sprintf("%s is inactive", msg.Sender) +} func init() { messageUnmarshal[MessageTypeTypingNotification] = func(mh *MessageHeader, data []byte) (Message, error) { diff --git a/packetdispatcher.go b/packetdispatcher.go index 6858ccc..5671184 100644 --- a/packetdispatcher.go +++ b/packetdispatcher.go @@ -13,6 +13,7 @@ import ( "encoding/binary" "fmt" "io" + "time" "golang.org/x/crypto/nacl/box" ) @@ -152,7 +153,9 @@ func (sc *SessionContext) dispatchMessage(wr io.Writer, m Message) { Nonce: randNonce, Ciphertext: msgCipherText, } - + if messagePkt.Time.IsZero() { + messagePkt.Time = time.Now() + } serializedMsgPkt, _ := messagePkt.MarshalBinary() sc.clientNonce.increaseCounter() diff --git a/packethandler.go b/packethandler.go index f699340..e63d25d 100644 --- a/packethandler.go +++ b/packethandler.go @@ -112,6 +112,8 @@ func (sc *SessionContext) handleClientServerMsg(buf *bytes.Buffer) interface{} { // Decrypt using our private and their public key msgPkt.Plaintext, ok = box.Open(nil, msgPkt.Ciphertext, msgPkt.Nonce.bytes(), &sender.LPK, &sc.ID.LSK) if !ok { + fmt.Println("===== format ====") + fmt.Println(msgPkt.String()) panic("Cannot decrypt e2e MSG!") } @@ -147,11 +149,14 @@ func (sc *SessionContext) handleMessagePacket(mp messagePacket) (Message, error) mt := mp.GetMessageType() if msgGen, ok := messageUnmarshal[mt]; ok { - mh := mp.header() + mh := mp.Header() msg, err := msgGen(&mh, mp.Plaintext) - + if err != nil { + fmt.Println(mp) + } return msg, err } + fmt.Println(mp.String()) fmt.Printf("\n%2x\n", mp.Plaintext) fmt.Printf("\n%s\n", mp.Plaintext) return nil, fmt.Errorf("o3: unknown MessageType: %d", mt) diff --git a/packettypes.go b/packettypes.go index 18593e4..e76d188 100644 --- a/packettypes.go +++ b/packettypes.go @@ -3,6 +3,7 @@ package o3 import ( "bytes" "encoding" + "fmt" "time" ) @@ -43,7 +44,7 @@ type messagePacket struct { Plaintext []byte } -func (mp *messagePacket) header() MessageHeader { +func (mp *messagePacket) Header() MessageHeader { return MessageHeader{ Sender: mp.Sender, Recipient: mp.Recipient, @@ -62,7 +63,7 @@ func (mp *messagePacket) GetMessageType() (mt MsgType) { func (mp *messagePacket) MarshalBinary() ([]byte, error) { buf := new(bytes.Buffer) - bufMarshal("pkg-type", buf, uint32(mp.PktType)) + bufMarshal("pkg-type", buf, mp.PktType) bufMarshal("sender", buf, mp.Sender) bufMarshal("recipient", buf, mp.Recipient) bufMarshal("id", buf, mp.ID) @@ -89,9 +90,6 @@ func (mp *messagePacket) UnmarshalBinary(data []byte) error { bufUnmarshal("time", buf, &t) mp.Time = time.Unix(int64(t), 0) - // handle int64 date - on wrong way - bufUnmarshal("fake-time", buf, &t) - // 1 byte for flags + 3 bytes unused unused := make([]byte, 4) bufUnmarshal("unused", buf, &unused) @@ -102,6 +100,17 @@ func (mp *messagePacket) UnmarshalBinary(data []byte) error { return nil } +func (mp *messagePacket) String() string { + return fmt.Sprintf( + `type: %v +sender:%s +recipient:%s +public nick:%s`, + mp.PktType, + mp.Sender, + mp.Recipient, + mp.PubNick) +} type ackPacket struct { PktType pktType From b0d11096c694fa58af0598de8ec25a4e7a863db5 Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Fri, 6 Sep 2019 02:00:16 +0200 Subject: [PATCH 06/10] group message support - WIP --- messagegroup_text.go | 71 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 messagegroup_text.go diff --git a/messagegroup_text.go b/messagegroup_text.go new file mode 100644 index 0000000..785265c --- /dev/null +++ b/messagegroup_text.go @@ -0,0 +1,71 @@ +package o3 + +import ( + "bytes" + "errors" +) + +// MsgType mock enum +const MessageTypeGroupText MsgType = 0x41 + +type GroupMessageHeader struct { + CreatorID IDString + GroupID [8]byte +} + +const GroupMessageHeaderLenght = 16 + +func (msg *GroupMessageHeader) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + bufUnmarshal("read group creator", buf, &msg.CreatorID) + bufUnmarshal("read group id", buf, &msg.GroupID) + + return nil +} + +//GroupTextMessage represents a text message as sent e2e encrypted to other threema users +type GroupTextMessage struct { + *GroupMessageHeader + *TextMessage +} + +//Serialize returns a fully serialized byte slice of a TextMessage +func (msg GroupTextMessage) MarshalBinary() ([]byte, error) { + + buf := new(bytes.Buffer) + bufMarshal("msg-type", buf, uint8(MessageTypeGroupText)) + data, err := msg.TextMessage.MarshalBinary() + if err != nil { + return nil, err + } + bufMarshal("text-msg", buf, data) + + return buf.Bytes(), nil +} + +func (msg *GroupTextMessage) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + var t MsgType + bufUnmarshal("read message type", buf, &t) + if t != MessageTypeGroupText { + return errors.New("not correct type") + } + + return nil +} + +func init() { + messageUnmarshal[MessageTypeGroupText] = func(mh *MessageHeader, data []byte) (Message, error) { + tm := &GroupTextMessage{ + GroupMessageHeader: &GroupMessageHeader{}, + TextMessage: &TextMessage{ + MessageHeader: mh, + }, + } + data[0] = byte(MessageTypeText) + tm.GroupMessageHeader.UnmarshalBinary(data[1 : GroupMessageHeaderLenght+1]) + data = append(data[:1], data[GroupMessageHeaderLenght+1:]...) + tm.TextMessage.UnmarshalBinary(data) + return tm, nil + } +} From cd1e591a7b54b42d21ac1a27cab09a8733c7a603 Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Fri, 6 Sep 2019 03:56:14 +0200 Subject: [PATCH 07/10] a few fixes --- communicationhandler.go | 6 ++++-- message.go | 20 ++++++++++++++++++++ message_deliveryreceipt.go | 4 ++-- message_text.go | 3 +-- message_typingnotification.go | 2 +- packettypes.go | 3 ++- session.go | 3 ++- 7 files changed, 32 insertions(+), 9 deletions(-) diff --git a/communicationhandler.go b/communicationhandler.go index 8d34066..dbd7ce9 100644 --- a/communicationhandler.go +++ b/communicationhandler.go @@ -158,8 +158,10 @@ func (sc *SessionContext) sendLoop() { go func() { timeChan := time.Tick(3 * time.Minute) for range timeChan { - ep := echoPacket{PktType: echoMsg, - Counter: sc.echoCounter} + ep := echoPacket{ + PktType: echoMsg, + Counter: sc.echoCounter, + } echoPktChan <- ep } }() diff --git a/message.go b/message.go index d0a513e..e374296 100644 --- a/message.go +++ b/message.go @@ -19,6 +19,26 @@ type msgFlags struct { GroupMessage bool } +func (flags msgFlags) MarshalBinary() ([]byte, error) { + var flagsByte byte + if flags.PushMessage { + flagsByte |= (1 << 0) + } + if flags.NoQueuing { + flagsByte |= (1 << 1) + } + if flags.NoAckExpected { + flagsByte |= (1 << 2) + } + if flags.MessageHasAlreadyBeenDelivered { + flagsByte |= (1 << 3) + } + if flags.GroupMessage { + flagsByte |= (1 << 4) + } + return []byte{flagsByte}, nil +} + // NewMsgID returns a randomly generated message ID (not cryptographically secure!) // TODO: Why mrand? func NewMsgID() uint64 { diff --git a/message_deliveryreceipt.go b/message_deliveryreceipt.go index 2539523..b008074 100644 --- a/message_deliveryreceipt.go +++ b/message_deliveryreceipt.go @@ -30,8 +30,8 @@ func (msg DeliveryReceiptMessage) MarshalBinary() ([]byte, error) { buf := new(bytes.Buffer) bufMarshal("msg-type", buf, uint8(MessageTypeDeliveryReceipt)) - bufUnmarshal("message status", buf, msg.Status) - bufUnmarshal("message id", buf, msg.MessageID) + bufMarshal("message status", buf, msg.Status) + bufMarshal("message id", buf, msg.MessageID) bufMarshalPadding(buf) return buf.Bytes(), nil diff --git a/message_text.go b/message_text.go index 0f7948e..3bc4541 100644 --- a/message_text.go +++ b/message_text.go @@ -29,9 +29,8 @@ func (tm TextMessage) String() string { //Serialize returns a fully serialized byte slice of a TextMessage func (tm TextMessage) MarshalBinary() ([]byte, error) { - buf := new(bytes.Buffer) - bufMarshal("msg-type", buf, uint8(MessageTypeText)) + bufMarshal("msg-type", buf, MessageTypeText) bufMarshal("body", buf, []byte(tm.Body)) bufMarshalPadding(buf) diff --git a/message_typingnotification.go b/message_typingnotification.go index 9aeeb6f..e798618 100644 --- a/message_typingnotification.go +++ b/message_typingnotification.go @@ -19,7 +19,7 @@ type TypingNotificationMessage struct { func (msg TypingNotificationMessage) MarshalBinary() ([]byte, error) { buf := new(bytes.Buffer) - bufMarshal("msg-type", buf, uint8(MessageTypeTypingNotification)) + bufMarshal("msg-type", buf, MessageTypeTypingNotification) bufMarshal("body", buf, msg.OnOff) bufMarshalPadding(buf) diff --git a/packettypes.go b/packettypes.go index e76d188..223131e 100644 --- a/packettypes.go +++ b/packettypes.go @@ -68,7 +68,8 @@ func (mp *messagePacket) MarshalBinary() ([]byte, error) { bufMarshal("recipient", buf, mp.Recipient) bufMarshal("id", buf, mp.ID) bufMarshal("time", buf, uint32(mp.Time.Unix())) - bufMarshal("flags", buf, mp.Flags) + flags, _ := mp.Flags.MarshalBinary() + bufMarshal("flags", buf, flags) // The three following bytes are unused bufMarshal("unused", buf, []byte{0x00, 0x00, 0x00}) bufMarshal("public nick", buf, mp.PubNick) diff --git a/session.go b/session.go index c6989de..ec7955a 100644 --- a/session.go +++ b/session.go @@ -32,7 +32,8 @@ type SessionContext struct { func NewSessionContext(ID ThreemaID) SessionContext { sc := SessionContext{ serverLPK: [32]byte{69, 11, 151, 87, 53, 39, 159, 222, 203, 51, 19, 100, 143, 95, 198, 238, 159, 244, 54, 14, 169, 42, 140, 23, 81, 198, 97, 228, 192, 216, 201, 9}, - ID: ID} + ID: ID, + } // New Session means new ephemeral keys and nonce sc.clientNonce = newNonce() From ebd3b6c101aa4fe631957d29225e3047137cbc00 Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Fri, 6 Sep 2019 04:33:32 +0200 Subject: [PATCH 08/10] group message support - text finish --- messagegroup_text.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/messagegroup_text.go b/messagegroup_text.go index 785265c..8a28503 100644 --- a/messagegroup_text.go +++ b/messagegroup_text.go @@ -15,6 +15,12 @@ type GroupMessageHeader struct { const GroupMessageHeaderLenght = 16 +func (msg GroupMessageHeader) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + bufMarshal("gh-creator id", buf, msg.CreatorID) + bufMarshal("gh-group id", buf, msg.GroupID) + return buf.Bytes(), nil +} func (msg *GroupMessageHeader) UnmarshalBinary(data []byte) error { buf := bytes.NewBuffer(data) bufUnmarshal("read group creator", buf, &msg.CreatorID) @@ -34,12 +40,16 @@ func (msg GroupTextMessage) MarshalBinary() ([]byte, error) { buf := new(bytes.Buffer) bufMarshal("msg-type", buf, uint8(MessageTypeGroupText)) - data, err := msg.TextMessage.MarshalBinary() + data, err := msg.GroupMessageHeader.MarshalBinary() + bufMarshal("msg-group-header", buf, data) if err != nil { return nil, err } - bufMarshal("text-msg", buf, data) - + data, err = msg.TextMessage.MarshalBinary() + if err != nil { + return nil, err + } + bufMarshal("text-msg", buf, data[1:]) return buf.Bytes(), nil } From 92e57d759c59cb0c52343c55ee056c32b2f12ab2 Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Mon, 9 Sep 2019 13:14:38 +0200 Subject: [PATCH 09/10] add image+audio support + refactory group --- blob.go | 6 +-- message_audio.go | 92 ++++++++++++++++++++++++++++++++++++++++++++ message_image.go | 87 +++++++++++++++++++++++++++++++++++++++++ message_text.go | 46 +++++++++++++++++----- messagegroup.go | 27 +++++++++++++ messagegroup_text.go | 81 -------------------------------------- 6 files changed, 244 insertions(+), 95 deletions(-) create mode 100644 message_audio.go create mode 100644 message_image.go create mode 100644 messagegroup.go delete mode 100644 messagegroup_text.go diff --git a/blob.go b/blob.go index 30fe1dc..b7d1492 100644 --- a/blob.go +++ b/blob.go @@ -74,9 +74,8 @@ func uploadBlob(blob []byte) ([16]byte, error) { } // encryptAsymAndUpload encrypts a blob with recipients PK and the sc owners SK -func encryptAsymAndUpload(sc SessionContext, plainImage []byte, recipientName string) (blobNonce nonce, ServerID byte, size uint32, blobID [16]byte, err error) { +func encryptAsymAndUpload(threemaID *ThreemaID, plainImage []byte, recipientName string) (blobNonce nonce, ServerID byte, size uint32, blobID [16]byte, err error) { // Get contact public key - threemaID := sc.ID recipient, inContacts := threemaID.Contacts.Get(recipientName) if !inContacts { var tr ThreemaRest @@ -157,14 +156,13 @@ func downloadBlob(blobID [16]byte) ([]byte, error) { return ciphertext, nil } -func downloadAndDecryptAsym(sc SessionContext, blobID [16]byte, senderName string, blobNonce nonce) (plaintext []byte, err error) { +func downloadAndDecryptAsym(threemaID *ThreemaID, blobID [16]byte, senderName string, blobNonce nonce) (plaintext []byte, err error) { ciphertext, err := downloadBlob(blobID) if err != nil { return []byte{}, err } var sender ThreemaContact - threemaID := sc.ID sender, inContacts := threemaID.Contacts.Get(senderName) if !inContacts { var tr ThreemaRest diff --git a/message_audio.go b/message_audio.go new file mode 100644 index 0000000..007f41d --- /dev/null +++ b/message_audio.go @@ -0,0 +1,92 @@ +package o3 + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" +) + +// MsgType mock enum +const MessageTypeAudio MsgType = 0x14 + +//AudioMessage represents a image message as sent e2e encrypted to other threema users +type AudioMessage struct { + *MessageHeader + Duration uint16 + BlobID [16]byte + ServerID byte + Size uint32 + Key [32]byte +} + +// String returns the message text as string +func (m AudioMessage) String() string { + return fmt.Sprintf("AudioMSG: https://%2x.blob.threema.ch/%16x, Size: %d, Key: %24x", m.ServerID, m.BlobID, m.Size, m.Key) +} + +// GetData return the decrypted Audio needs the recipients secret key +func (m AudioMessage) GetData() ([]byte, error) { + return downloadAndDecryptSym(m.BlobID, m.Key) +} + +// SetAudio encrypts and uploads the image by file. Sets the blob info in the AudioMessage. Needs the recipients public key. +func (m *AudioMessage) SetDataByFile(filename string) error { + data, err := ioutil.ReadFile(filename) + if err != nil { + return errors.New("could not load image") + } + return m.SetData(data) +} + +// SetAudioData encrypts and uploads the image. Sets the blob info in the AudioMessage. Needs the recipients public key. +func (m *AudioMessage) SetData(data []byte) (err error) { + // TODO: Should we have a whole media lib as dependency just to set this to the proper value? + m.Duration = 0xFF + m.Key, m.ServerID, m.Size, m.BlobID, err = encryptSymAndUpload(data) + return +} + +//Serialize returns a fully serialized byte slice of a TextMessage +func (m AudioMessage) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + bufMarshal("msg-type", buf, MessageTypeAudio) + bufMarshal("duration", buf, 0xFFFF) + bufMarshal("blob-id", buf, m.BlobID) + bufMarshal("size", buf, m.Size) + bufMarshal("nonce", buf, m.Key) + bufMarshalPadding(buf) + + return buf.Bytes(), nil +} + +func (m *AudioMessage) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + var t MsgType + bufUnmarshal("read message type", buf, &t) + if t != MessageTypeAudio { + return errors.New("not correct type") + } + stripPadding(buf) + + bufUnmarshal("duration", buf, &m.Duration) + bufUnmarshal("blob-id", buf, &m.BlobID) + bufUnmarshal("size", buf, &m.Size) + bufUnmarshal("key", buf, &m.Key) + + m.ServerID = m.BlobID[0] + + return nil +} + +func init() { + messageUnmarshal[MessageTypeAudio] = func(mh *MessageHeader, data []byte) (Message, error) { + m := &AudioMessage{ + MessageHeader: mh, + } + if err := m.UnmarshalBinary(data); err != nil { + return nil, err + } + return m, nil + } +} diff --git a/message_image.go b/message_image.go new file mode 100644 index 0000000..e1fc99b --- /dev/null +++ b/message_image.go @@ -0,0 +1,87 @@ +package o3 + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" +) + +// MsgType mock enum +const MessageTypeImage MsgType = 0x2 + +//ImageMessage represents a image message as sent e2e encrypted to other threema users +type ImageMessage struct { + *MessageHeader + BlobID [16]byte + ServerID byte + Size uint32 + Nonce nonce +} + +// String returns the message text as string +func (m ImageMessage) String() string { + return fmt.Sprintf("ImageMSG: https://%2x.blob.threema.ch/%16x, Size: %d, Nonce: %24x", m.ServerID, m.BlobID, m.Size, m.Nonce.nonce) +} + +// GetImageData return the decrypted Image needs the recipients secret key +func (m ImageMessage) GetData(threemaID *ThreemaID) ([]byte, error) { + return downloadAndDecryptAsym(threemaID, m.BlobID, m.Sender.String(), m.Nonce) +} + +// SetDataByFile encrypts and uploads the image by file. Sets the blob info in the ImageMessage. Needs the recipients public key. +func (m *ImageMessage) SetDataByFile(threemaID *ThreemaID, filename string) error { + data, err := ioutil.ReadFile(filename) + if err != nil { + return errors.New("could not load image") + } + return m.SetData(threemaID, data) +} + +// SetData encrypts and uploads the image. Sets the blob info in the ImageMessage. Needs the recipients public key. +func (m *ImageMessage) SetData(threemaID *ThreemaID, data []byte) (err error) { + m.Nonce, m.ServerID, m.Size, m.BlobID, err = encryptAsymAndUpload(threemaID, data, m.Recipient.String()) + return +} + +//Serialize returns a fully serialized byte slice of a TextMessage +func (m ImageMessage) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + bufMarshal("msg-type", buf, MessageTypeImage) + bufMarshal("blob-id", buf, m.BlobID) + bufMarshal("size", buf, m.Size) + bufMarshal("nonce", buf, m.Nonce.nonce) + bufMarshalPadding(buf) + + return buf.Bytes(), nil +} + +func (m *ImageMessage) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + var t MsgType + bufUnmarshal("read message type", buf, &t) + if t != MessageTypeImage { + return errors.New("not correct type") + } + stripPadding(buf) + + bufUnmarshal("blob-id", buf, &m.BlobID) + bufUnmarshal("size", buf, &m.Size) + bufUnmarshal("nonce", buf, &m.Nonce.nonce) + + m.ServerID = m.BlobID[0] + + return nil +} + +func init() { + messageUnmarshal[MessageTypeImage] = func(mh *MessageHeader, data []byte) (Message, error) { + m := &ImageMessage{ + MessageHeader: mh, + } + if err := m.UnmarshalBinary(data); err != nil { + return nil, err + } + return m, nil + } +} diff --git a/message_text.go b/message_text.go index 3bc4541..dbacbe4 100644 --- a/message_text.go +++ b/message_text.go @@ -6,11 +6,15 @@ import ( ) // MsgType mock enum -const MessageTypeText MsgType = 0x1 +const ( + MessageTypeText MsgType = 0x1 + MessageTypeGroupText MsgType = 0x41 +) //TextMessage represents a text message as sent e2e encrypted to other threema users type TextMessage struct { *MessageHeader + *GroupMessageHeader Body string } @@ -28,36 +32,58 @@ func (tm TextMessage) String() string { } //Serialize returns a fully serialized byte slice of a TextMessage -func (tm TextMessage) MarshalBinary() ([]byte, error) { +func (m TextMessage) MarshalBinary() ([]byte, error) { buf := new(bytes.Buffer) - bufMarshal("msg-type", buf, MessageTypeText) - bufMarshal("body", buf, []byte(tm.Body)) + if m.GroupMessageHeader == nil { + bufMarshal("msg-type", buf, MessageTypeText) + } else { + bufMarshal("msg-type", buf, uint8(MessageTypeGroupText)) + data, err := m.GroupMessageHeader.MarshalBinary() + bufMarshal("msg-group-header", buf, data) + if err != nil { + return nil, err + } + } + bufMarshal("body", buf, []byte(m.Body)) bufMarshalPadding(buf) return buf.Bytes(), nil } -func (tm *TextMessage) UnmarshalBinary(data []byte) error { +func (m *TextMessage) UnmarshalBinary(data []byte) error { buf := bytes.NewBuffer(data) var t MsgType bufUnmarshal("read message type", buf, &t) - if t != MessageTypeText { + if t == MessageTypeGroupText { + m.GroupMessageHeader.UnmarshalBinary(data[1 : GroupMessageHeaderLenght+1]) + } else if t != MessageTypeText { return errors.New("not correct type") } stripPadding(buf) - tm.Body = string(buf.Bytes()) + m.Body = string(buf.Bytes()) return nil } func init() { messageUnmarshal[MessageTypeText] = func(mh *MessageHeader, data []byte) (Message, error) { - tm := &TextMessage{ + m := &TextMessage{ MessageHeader: mh, } - if err := tm.UnmarshalBinary(data); err != nil { + if err := m.UnmarshalBinary(data); err != nil { + return nil, err + } + return m, nil + } + messageUnmarshal[MessageTypeGroupText] = func(mh *MessageHeader, data []byte) (Message, error) { + m := &TextMessage{ + MessageHeader: mh, + GroupMessageHeader: &GroupMessageHeader{}, + } + + if err := m.UnmarshalBinary(data); err != nil { return nil, err } - return tm, nil + return m, nil } } diff --git a/messagegroup.go b/messagegroup.go new file mode 100644 index 0000000..21312a1 --- /dev/null +++ b/messagegroup.go @@ -0,0 +1,27 @@ +package o3 + +import ( + "bytes" +) + +type GroupMessageHeader struct { + CreatorID IDString + GroupID [8]byte +} + +const GroupMessageHeaderLenght = 16 + +func (msg GroupMessageHeader) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + bufMarshal("gh-creator id", buf, msg.CreatorID) + bufMarshal("gh-group id", buf, msg.GroupID) + return buf.Bytes(), nil +} + +func (msg *GroupMessageHeader) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + bufUnmarshal("read group creator", buf, &msg.CreatorID) + bufUnmarshal("read group id", buf, &msg.GroupID) + + return nil +} diff --git a/messagegroup_text.go b/messagegroup_text.go deleted file mode 100644 index 8a28503..0000000 --- a/messagegroup_text.go +++ /dev/null @@ -1,81 +0,0 @@ -package o3 - -import ( - "bytes" - "errors" -) - -// MsgType mock enum -const MessageTypeGroupText MsgType = 0x41 - -type GroupMessageHeader struct { - CreatorID IDString - GroupID [8]byte -} - -const GroupMessageHeaderLenght = 16 - -func (msg GroupMessageHeader) MarshalBinary() ([]byte, error) { - buf := new(bytes.Buffer) - bufMarshal("gh-creator id", buf, msg.CreatorID) - bufMarshal("gh-group id", buf, msg.GroupID) - return buf.Bytes(), nil -} -func (msg *GroupMessageHeader) UnmarshalBinary(data []byte) error { - buf := bytes.NewBuffer(data) - bufUnmarshal("read group creator", buf, &msg.CreatorID) - bufUnmarshal("read group id", buf, &msg.GroupID) - - return nil -} - -//GroupTextMessage represents a text message as sent e2e encrypted to other threema users -type GroupTextMessage struct { - *GroupMessageHeader - *TextMessage -} - -//Serialize returns a fully serialized byte slice of a TextMessage -func (msg GroupTextMessage) MarshalBinary() ([]byte, error) { - - buf := new(bytes.Buffer) - bufMarshal("msg-type", buf, uint8(MessageTypeGroupText)) - data, err := msg.GroupMessageHeader.MarshalBinary() - bufMarshal("msg-group-header", buf, data) - if err != nil { - return nil, err - } - data, err = msg.TextMessage.MarshalBinary() - if err != nil { - return nil, err - } - bufMarshal("text-msg", buf, data[1:]) - return buf.Bytes(), nil -} - -func (msg *GroupTextMessage) UnmarshalBinary(data []byte) error { - buf := bytes.NewBuffer(data) - var t MsgType - bufUnmarshal("read message type", buf, &t) - if t != MessageTypeGroupText { - return errors.New("not correct type") - } - - return nil -} - -func init() { - messageUnmarshal[MessageTypeGroupText] = func(mh *MessageHeader, data []byte) (Message, error) { - tm := &GroupTextMessage{ - GroupMessageHeader: &GroupMessageHeader{}, - TextMessage: &TextMessage{ - MessageHeader: mh, - }, - } - data[0] = byte(MessageTypeText) - tm.GroupMessageHeader.UnmarshalBinary(data[1 : GroupMessageHeaderLenght+1]) - data = append(data[:1], data[GroupMessageHeaderLenght+1:]...) - tm.TextMessage.UnmarshalBinary(data) - return tm, nil - } -} From 2f9fc2c25d42892f10eff4c22e8b14fa12f7c39e Mon Sep 17 00:00:00 2001 From: Martin/Geno Date: Thu, 3 Oct 2019 23:40:14 +0200 Subject: [PATCH 10/10] first group inline model --- message_image.go | 29 +++++++++++++++++++++++++++-- message_text.go | 4 +++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/message_image.go b/message_image.go index e1fc99b..65341e2 100644 --- a/message_image.go +++ b/message_image.go @@ -9,10 +9,12 @@ import ( // MsgType mock enum const MessageTypeImage MsgType = 0x2 +const MessageTypeGroupImage MsgType = 0x42 //ImageMessage represents a image message as sent e2e encrypted to other threema users type ImageMessage struct { *MessageHeader + *GroupMessageHeader BlobID [16]byte ServerID byte Size uint32 @@ -47,7 +49,16 @@ func (m *ImageMessage) SetData(threemaID *ThreemaID, data []byte) (err error) { //Serialize returns a fully serialized byte slice of a TextMessage func (m ImageMessage) MarshalBinary() ([]byte, error) { buf := new(bytes.Buffer) - bufMarshal("msg-type", buf, MessageTypeImage) + if m.GroupMessageHeader == nil { + bufMarshal("msg-type", buf, MessageTypeImage) + } else { + bufMarshal("msg-type", buf, uint8(MessageTypeGroupImage)) + data, err := m.GroupMessageHeader.MarshalBinary() + bufMarshal("msg-group-header", buf, data) + if err != nil { + return nil, err + } + } bufMarshal("blob-id", buf, m.BlobID) bufMarshal("size", buf, m.Size) bufMarshal("nonce", buf, m.Nonce.nonce) @@ -60,7 +71,11 @@ func (m *ImageMessage) UnmarshalBinary(data []byte) error { buf := bytes.NewBuffer(data) var t MsgType bufUnmarshal("read message type", buf, &t) - if t != MessageTypeImage { + if t == MessageTypeGroupText { + if err := m.GroupMessageHeader.UnmarshalBinary(data[1 : GroupMessageHeaderLenght+1]); err != nil { + return err + } + } else if t != MessageTypeImage { return errors.New("not correct type") } stripPadding(buf) @@ -84,4 +99,14 @@ func init() { } return m, nil } + messageUnmarshal[MessageTypeGroupImage] = func(mh *MessageHeader, data []byte) (Message, error) { + m := &ImageMessage{ + MessageHeader: mh, + GroupMessageHeader: &GroupMessageHeader{}, + } + if err := m.UnmarshalBinary(data); err != nil { + return nil, err + } + return m, nil + } } diff --git a/message_text.go b/message_text.go index dbacbe4..b0af3a6 100644 --- a/message_text.go +++ b/message_text.go @@ -55,7 +55,9 @@ func (m *TextMessage) UnmarshalBinary(data []byte) error { var t MsgType bufUnmarshal("read message type", buf, &t) if t == MessageTypeGroupText { - m.GroupMessageHeader.UnmarshalBinary(data[1 : GroupMessageHeaderLenght+1]) + if err := m.GroupMessageHeader.UnmarshalBinary(data[1 : GroupMessageHeaderLenght+1]); err != nil { + return err + } } else if t != MessageTypeText { return errors.New("not correct type") }