From f3cf9cfc40d2a42a431b1b2703df08d0f92e3a6a Mon Sep 17 00:00:00 2001 From: afonso Date: Sat, 20 Apr 2024 17:16:52 +0100 Subject: [PATCH] [PD1] Cleaned up protocol,database and receiving multiple messageInfos --- Projs/PD1/internal/client/client.go | 164 +++++++++-------- Projs/PD1/internal/client/interface.go | 65 ++++++- Projs/PD1/internal/protocol/protocol.go | 227 +++++++++++++++--------- Projs/PD1/internal/server/datastore.go | 92 +++++----- Projs/PD1/internal/server/server.go | 30 ++-- Projs/PD1/server.db | Bin 32768 -> 40960 bytes 6 files changed, 347 insertions(+), 231 deletions(-) diff --git a/Projs/PD1/internal/client/client.go b/Projs/PD1/internal/client/client.go index 3afbda7..3769cb2 100644 --- a/Projs/PD1/internal/client/client.go +++ b/Projs/PD1/internal/client/client.go @@ -30,52 +30,46 @@ func Run() { panic("Insufficient arguments for 'send' command. Usage: send ") } uid := flag.Arg(1) - subject := flag.Arg(2) - messageBody := readMessageBody() + plainSubject := flag.Arg(2) + plainBody := readStdin("Enter message content (limited to 1000 bytes):") //Turn content to bytes - marshaledSubject := Marshal(subject) - marshaledBody := Marshal(messageBody) + plainSubjectBytes := Marshal(plainSubject) + plainBodyBytes := Marshal(plainBody) cl := networking.NewClient[protocol.Packet](&clientKeyStore) defer cl.Connection.Conn.Close() - uidCert := getUserCert(cl, uid) - if uidCert == nil { + receiverCert := getUserCert(cl, uid) + if receiverCert == nil { return } - encryptedSubject := clientKeyStore.EncryptMessageContent(uidCert, marshaledSubject) - encryptedBody := clientKeyStore.EncryptMessageContent(uidCert, marshaledBody) - submitMessage := protocol.NewSubmitMessagePacket(uid, encryptedSubject, encryptedBody) - if !cl.Connection.Send(submitMessage) { + subject := clientKeyStore.EncryptMessageContent(receiverCert, plainSubjectBytes) + body := clientKeyStore.EncryptMessageContent(receiverCert, plainBodyBytes) + sendMsgPacket := protocol.NewSendMsgPacket(uid, subject, body) + if !cl.Connection.Send(sendMsgPacket) { return } cl.Connection.Conn.Close() case "askqueue": - cl := networking.NewClient[protocol.Packet](&clientKeyStore) - defer cl.Connection.Conn.Close() - - requestUnreadMsgsQueuePacket := protocol.NewRequestUnreadMsgsQueuePacket() - if !cl.Connection.Send(requestUnreadMsgsQueuePacket) { - return - } - serverMessagePackets, certificates := getManyMessagesInfo(cl) - var clientMessages []ClientMessageInfo - for _, message := range serverMessagePackets { - senderCert, ok := certificates[message.FromUID] - if ok { - decryptedSubjectBytes := clientKeyStore.DecryptMessageContent(senderCert, message.Subject) - subject := Unmarshal(decryptedSubjectBytes) - clientMessage := newClientMessageInfo(message.Num, message.FromUID, subject, message.Timestamp) - clientMessages = append(clientMessages, clientMessage) + pageInput := flag.Arg(1) + page := 1 + if pageInput != "" { + if val, err := strconv.Atoi(pageInput); err == nil { + page = max(1, val) + } + } + pageSizeInput := flag.Arg(2) + pageSize := 5 + if pageSizeInput != "" { + if val, err := strconv.Atoi(pageSizeInput); err == nil { + pageSize = max(1, val) } } - //Sort the messages - sort.Slice(clientMessages, func(i, j int) bool { - return clientMessages[i].Num > clientMessages[j].Num - }) - showMessagesInfo(clientMessages) + cl := networking.NewClient[protocol.Packet](&clientKeyStore) + defer cl.Connection.Conn.Close() + askQueue(cl,clientKeyStore, page, pageSize) case "getmsg": if flag.NArg() < 2 { @@ -84,26 +78,26 @@ func Run() { numString := flag.Arg(1) cl := networking.NewClient[protocol.Packet](&clientKeyStore) defer cl.Connection.Conn.Close() - num,err :=strconv.Atoi(numString) - if err!=nil{ - log.Panicln("NUM argument provided is not a number") - } - packet := protocol.NewRequestMsgPacket(num) - cl.Connection.Send(packet) + num, err := strconv.Atoi(numString) + if err != nil { + log.Panicln("NUM argument provided is not a number") + } + packet := protocol.NewGetMsgPacket(num) + cl.Connection.Send(packet) + + receivedMsgPacket, active := cl.Connection.Receive() + if !active { + return + } + answerGetMsg := protocol.UnmarshalAnswerGetMsg(receivedMsgPacket.Body) + senderCert := getUserCert(cl, answerGetMsg.FromUID) + decSubjectBytes := clientKeyStore.DecryptMessageContent(senderCert, answerGetMsg.Subject) + decBodyBytes := clientKeyStore.DecryptMessageContent(senderCert, answerGetMsg.Body) + subject := Unmarshal(decSubjectBytes) + body := Unmarshal(decBodyBytes) + message := newClientMessage(answerGetMsg.FromUID, answerGetMsg.ToUID, subject, body, answerGetMsg.Timestamp) + showMessage(message) - receivedMsgPacket,active := cl.Connection.Receive() - if !active{ - return - } - serverMessagePacket := protocol.UnmarshalServerMessagePacket(receivedMsgPacket.Body) - senderCert := getUserCert(cl, serverMessagePacket.FromUID) - decryptedSubjectBytes := clientKeyStore.DecryptMessageContent(senderCert, serverMessagePacket.Subject) - decryptedBodyBytes := clientKeyStore.DecryptMessageContent(senderCert, serverMessagePacket.Body) - subject := Unmarshal(decryptedSubjectBytes) - body := Unmarshal(decryptedBodyBytes) - message := newClientMessage(serverMessagePacket.FromUID, serverMessagePacket.ToUID, subject, body, serverMessagePacket.Timestamp) - showMessage(message) - case "help": showHelp() @@ -114,43 +108,33 @@ func Run() { } func getUserCert(cl networking.Client[protocol.Packet], uid string) *x509.Certificate { - certRequestPacket := protocol.NewRequestUserCertPacket(uid) - if !cl.Connection.Send(certRequestPacket) { + getUserCertPacket := protocol.NewGetUserCertPacket(uid) + if !cl.Connection.Send(getUserCertPacket) { return nil } - var certPacket *protocol.Packet - certPacket, active := cl.Connection.Receive() + var answerGetUserCertPacket *protocol.Packet + answerGetUserCertPacket, active := cl.Connection.Receive() if !active { return nil } - uidCertInBytes := protocol.UnmarshalSendUserCertPacket(certPacket.Body) - uidCert, err := x509.ParseCertificate(uidCertInBytes.Certificate) + answerGetUserCert := protocol.UnmarshalAnswerGetUserCert(answerGetUserCertPacket.Body) + userCert, err := x509.ParseCertificate(answerGetUserCert.Certificate) if err != nil { return nil } - return uidCert + return userCert } -func getManyMessagesInfo(cl networking.Client[protocol.Packet]) ([]protocol.ServerMessageInfoPacket, map[string]*x509.Certificate) { - //Create the slice to hold the incoming messages before decrypting - //Create the map to hold the sender certificates - //Create sync mutexes - serverMessageInfoPackets := []protocol.ServerMessageInfoPacket{} - //Run while message isn't the last one - msg := protocol.ServerMessageInfoPacket{} - for !msg.Last { - sendMsgPacket, active := cl.Connection.Receive() - if !active { - return nil, nil - } - msg = protocol.UnmarshalServerMessageInfoPacket(sendMsgPacket.Body) - //Lock and append - serverMessageInfoPackets = append(serverMessageInfoPackets, msg) +func getManyMessagesInfo(cl networking.Client[protocol.Packet]) (protocol.AnswerGetUnreadMsgsInfo, map[string]*x509.Certificate) { + answerGetUnreadMsgsInfoPacket, active := cl.Connection.Receive() + if !active { + return protocol.NewAnswerGetUnreadMsgsInfo(0, 0, nil), nil } + answerGetUnreadMsgsInfo := protocol.UnmarshalAnswerGetUnreadMsgsInfo(answerGetUnreadMsgsInfoPacket.Body) //Create Set of needed certificates senderSet := map[string]bool{} - for _, messageInfo := range serverMessageInfoPackets { + for _, messageInfo := range answerGetUnreadMsgsInfo.MessagesInfo { senderSet[messageInfo.FromUID] = true } certificatesMap := map[string]*x509.Certificate{} @@ -159,5 +143,37 @@ func getManyMessagesInfo(cl networking.Client[protocol.Packet]) ([]protocol.Serv senderCert := getUserCert(cl, senderUID) certificatesMap[senderUID] = senderCert } - return serverMessageInfoPackets, certificatesMap + return answerGetUnreadMsgsInfo, certificatesMap +} + +func askQueue(cl networking.Client[protocol.Packet],clientKeyStore cryptoUtils.KeyStore, page int, pageSize int) { + requestUnreadMsgsQueuePacket := protocol.NewGetUnreadMsgsInfoPacket(page, pageSize) + if !cl.Connection.Send(requestUnreadMsgsQueuePacket) { + return + } + unreadMsgsInfo, certificates := getManyMessagesInfo(cl) + var clientMessages []ClientMessageInfo + for _, message := range unreadMsgsInfo.MessagesInfo { + senderCert, ok := certificates[message.FromUID] + if ok { + decryptedSubjectBytes := clientKeyStore.DecryptMessageContent(senderCert, message.Subject) + subject := Unmarshal(decryptedSubjectBytes) + clientMessage := newClientMessageInfo(message.Num, message.FromUID, subject, message.Timestamp) + clientMessages = append(clientMessages, clientMessage) + } + } + //Sort the messages + sort.Slice(clientMessages, func(i, j int) bool { + return clientMessages[i].Num > clientMessages[j].Num + }) + + action := showMessagesInfo(unreadMsgsInfo.Page, unreadMsgsInfo.NumPages, clientMessages) + switch action { + case -1: + askQueue(cl, clientKeyStore , max(1,unreadMsgsInfo.Page-1) , pageSize) + case 0: + return + case 1: + askQueue(cl, clientKeyStore , max(1,unreadMsgsInfo.Page+1) , pageSize) + } } diff --git a/Projs/PD1/internal/client/interface.go b/Projs/PD1/internal/client/interface.go index 8d49bb6..82f2d8a 100644 --- a/Projs/PD1/internal/client/interface.go +++ b/Projs/PD1/internal/client/interface.go @@ -4,10 +4,11 @@ import ( "bufio" "fmt" "os" + "strings" ) -func readMessageBody() string { - fmt.Println("Enter message content (limited to 1000 bytes):") +func readStdin(message string) string { + fmt.Println(message) scanner := bufio.NewScanner(os.Stdin) scanner.Scan() // FIX: make sure this doesnt die @@ -36,15 +37,63 @@ func showHelp() { fmt.Println("help: Imprime instruções de uso do programa.") } -func showMessagesInfo(messages []ClientMessageInfo) { +func showMessagesInfo(page int, numPages int, messages []ClientMessageInfo) int { + if messages == nil { + fmt.Println("No unread messages in the queue") + return 0 + } for _, message := range messages { fmt.Printf("%v:%v:%v:%v\n", message.Num, message.FromUID, message.Timestamp, message.Subject) } + fmt.Printf("Page %v/%v\n",page,numPages) + return messagesInfoPageNavigation(page, numPages) } -func showMessage(message ClientMessage) { - fmt.Printf("From:%v\n", message.FromUID) - fmt.Printf("To:%v\n", message.ToUID) - fmt.Printf("Subject:%v\n", message.Subject) - fmt.Printf("Body:%v\n", message.Body) +func messagesInfoPageNavigation(page int, numPages int) int { + var action string + + switch page { + case 1: + if page == numPages { + action = readStdin("Actions: quit") + } else { + action = readStdin("Actions: quit/next") + } + case numPages: + action = readStdin("Actions: prev/quit") + default: + action = readStdin("prev/quit/next") + } + + switch strings.ToLower(action) { + case "prev": + if page == 1 { + fmt.Println("Unavailable action: Already in first page") + messagesInfoPageNavigation(page, numPages) + } else { + return -1 + } + case "quit": + return 0 + case "next": + if page == numPages { + fmt.Println("Unavailable action: Already in last page") + messagesInfoPageNavigation(page, numPages) + } else { + return 1 + } + default: + fmt.Println("Unknown action") + messagesInfoPageNavigation(page, numPages) + } + return 0 } + + +func showMessage(message ClientMessage) { + fmt.Printf("From: %s\n", message.FromUID) + fmt.Printf("To: %s\n", message.ToUID) + fmt.Printf("Subject: %s\n", message.Subject) + fmt.Printf("Body: %s\n", message.Body) +} + diff --git a/Projs/PD1/internal/protocol/protocol.go b/Projs/PD1/internal/protocol/protocol.go index 6aeecec..680c2b5 100644 --- a/Projs/PD1/internal/protocol/protocol.go +++ b/Projs/PD1/internal/protocol/protocol.go @@ -9,46 +9,67 @@ import ( type PacketType int const ( - ReqUserCertPkt PacketType = iota - ReqMsgsQueue - ReqMsgPkt - SubmitMsgPkt - SendUserCertPkt - ServerMsgInfoPkt - ServerMsgPkt + // Client requests user certificate + FlagGetUserCert PacketType = iota + + // Client requests unread message info + FlagGetUnreadMsgsInfo + + // Client requests a message from the queue + FlagGetMsg + + // Client sends a message + FlagSendMsg + + // Server sends user certificate + FlagAnswerGetUserCert + + // Server sends list of unread messages + FlagAnswerGetUnreadMsgsInfo + + // Server sends requested message + FlagAnswerGetMsg ) type ( - RequestUserCertPacket struct { + GetUserCert struct { UID string `json:"uid"` } - RequestMsgsQueuePacket struct { + GetUnreadMsgsInfo struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` } - RequestMsgPacket struct { + GetMsg struct { Num int `json:"num"` } - SubmitMessagePacket struct { + SendMsg struct { ToUID string `json:"to_uid"` Subject []byte `json:"subject"` Body []byte `json:"body"` } - SendUserCertPacket struct { + AnswerGetUserCert struct { UID string `json:"uid"` Certificate []byte `json:"certificate"` } - ServerMessageInfoPacket struct { + AnswerGetUnreadMsgsInfo struct { + Page int `json:"page"` + NumPages int `json:"num_pages"` + MessagesInfo []MsgInfo `json:"messages_info"` + } + + MsgInfo struct { Num int `json:"num"` FromUID string `json:"from_uid"` Subject []byte `json:"subject"` Timestamp time.Time `json:"timestamp"` - Last bool `json:"last"` } - ServerMessagePacket struct { + + AnswerGetMsg struct { FromUID string `json:"from_uid"` ToUID string `json:"to_uid"` Subject []byte `json:"subject"` @@ -64,156 +85,188 @@ type Packet struct { Body PacketBody `json:"body"` } -func NewRequestUserCertPacket(UID string) Packet { +func NewPacket(fl PacketType, body PacketBody) Packet { return Packet{ - Flag: ReqUserCertPkt, - Body: RequestUserCertPacket{ - UID: UID, - }, + Flag: fl, + Body: body, + } + +} + +func NewGetUserCert(UID string) GetUserCert { + return GetUserCert{ + UID: UID, } } -func NewRequestUnreadMsgsQueuePacket() Packet { - return Packet{ - Flag: ReqMsgsQueue, - Body: RequestMsgsQueuePacket{}, +func NewGetUnreadMsgsInfo(page int, pageSize int) GetUnreadMsgsInfo { + return GetUnreadMsgsInfo{ + Page: page, + PageSize: pageSize} +} + +func NewGetMsg(num int) GetMsg { + return GetMsg{ + Num: num, } } -func NewRequestMsgPacket(num int) Packet { - return Packet{ - Flag: ReqMsgPkt, - Body: RequestMsgPacket{ - Num: num, - }, +func NewSendMsg(toUID string, subject []byte, body []byte) SendMsg { + return SendMsg{ + ToUID: toUID, + Subject: subject, + Body: body, } } -func NewSubmitMessagePacket(toUID string, subject []byte, body []byte) Packet { - return Packet{ - Flag: SubmitMsgPkt, - Body: SubmitMessagePacket{ - ToUID: toUID, - Subject: subject, - Body: body, - }, +func NewAnswerGetUserCert(uid string, certificate []byte) AnswerGetUserCert { + return AnswerGetUserCert{ + UID: uid, + Certificate: certificate, } } -func NewSendUserCertPacket(uid string, certificate []byte) Packet { - return Packet{ - Flag: SendUserCertPkt, - Body: SendUserCertPacket{ - UID: uid, - Certificate: certificate, - }, - } +func NewAnswerGetUnreadMsgsInfo(page int, numPages int, messagesInfo []MsgInfo) AnswerGetUnreadMsgsInfo { + return AnswerGetUnreadMsgsInfo{Page:page,NumPages:numPages,MessagesInfo: messagesInfo} } -func NewServerMessageInfoPacket(num int, fromUID string, subject []byte, timestamp time.Time, last bool) Packet { - return Packet{ - Flag: ServerMsgInfoPkt, - Body: ServerMessageInfoPacket{ - Num: num, - FromUID: fromUID, - Subject: subject, - Timestamp: timestamp, - Last: last, - }, +func NewMsgInfo(num int, fromUID string, subject []byte, timestamp time.Time) MsgInfo { + return MsgInfo{ + Num: num, + FromUID: fromUID, + Subject: subject, + Timestamp: timestamp, } } -func NewServerMessagePacket(fromUID, toUID string, subject []byte, body []byte, timestamp time.Time, last bool) Packet { - return Packet{ - Flag: ServerMsgPkt, - Body: ServerMessagePacket{ - FromUID: fromUID, - ToUID: toUID, - Subject: subject, - Body: body, - Timestamp: timestamp, - }, +func NewAnswerGetMsg(fromUID, toUID string, subject []byte, body []byte, timestamp time.Time, last bool) AnswerGetMsg { + return AnswerGetMsg{ + FromUID: fromUID, + ToUID: toUID, + Subject: subject, + Body: body, + Timestamp: timestamp, } } -func UnmarshalRequestUserCertPacket(data PacketBody) RequestUserCertPacket { +func NewGetUserCertPacket(UID string) Packet { + return NewPacket(FlagGetUserCert, NewGetUserCert(UID)) +} + +func NewGetUnreadMsgsInfoPacket(page int, pageSize int) Packet { + return NewPacket(FlagGetUnreadMsgsInfo, NewGetUnreadMsgsInfo(page, pageSize)) +} + +func NewGetMsgPacket(num int) Packet { + return NewPacket(FlagGetMsg, NewGetMsg(num)) +} + +func NewSendMsgPacket(toUID string, subject []byte, body []byte) Packet { + return NewPacket(FlagSendMsg, NewSendMsg(toUID, subject, body)) +} + +func NewAnswerGetUserCertPacket(uid string, certificate []byte) Packet { + return NewPacket(FlagAnswerGetUserCert, NewAnswerGetUserCert(uid, certificate)) +} + +func NewAnswerGetUnreadMsgsInfoPacket(page int, numPages int, messagesInfo []MsgInfo) Packet { + return NewPacket(FlagAnswerGetUnreadMsgsInfo, NewAnswerGetUnreadMsgsInfo(page,numPages,messagesInfo)) +} + +func NewAnswerGetMsgPacket(fromUID, toUID string, subject []byte, body []byte, timestamp time.Time, last bool) Packet { + return NewPacket(FlagAnswerGetMsg, NewAnswerGetMsg(fromUID, toUID, subject, body, timestamp, last)) +} + +func UnmarshalGetUserCert(data PacketBody) GetUserCert { jsonData, err := json.Marshal(data) if err != nil { panic(fmt.Errorf("failed to marshal data: %v", err)) } - var packet RequestUserCertPacket + var packet GetUserCert if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into RequestUserCertPacket: %v", err)) + panic(fmt.Errorf("failed to unmarshal into GetUserCert: %v", err)) } return packet } -func UnmarshalRequestMsgsQueuePacket(data PacketBody) RequestMsgsQueuePacket { +func UnmarshalGetUnreadMsgsInfo(data PacketBody) GetUnreadMsgsInfo { jsonData, err := json.Marshal(data) if err != nil { panic(fmt.Errorf("failed to marshal data: %v", err)) } - var packet RequestMsgsQueuePacket + var packet GetUnreadMsgsInfo if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into RequestMsgsQueuePacket: %v", err)) + panic(fmt.Errorf("failed to unmarshal into GetUnreadMsgsInfo: %v", err)) } return packet } -func UnmarshalRequestMsgPacket(data PacketBody) RequestMsgPacket { +func UnmarshalGetMsg(data PacketBody) GetMsg { jsonData, err := json.Marshal(data) if err != nil { panic(fmt.Errorf("failed to marshal data: %v", err)) } - var packet RequestMsgPacket + var packet GetMsg if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into RequestMsgPacket: %v", err)) + panic(fmt.Errorf("failed to unmarshal into GetMsg: %v", err)) } return packet } -func UnmarshalSubmitMessagePacket(data PacketBody) SubmitMessagePacket { +func UnmarshalSendMsg(data PacketBody) SendMsg { jsonData, err := json.Marshal(data) if err != nil { panic(fmt.Errorf("failed to marshal data: %v", err)) } - var packet SubmitMessagePacket + var packet SendMsg if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into SubmitMessagePacket: %v", err)) + panic(fmt.Errorf("failed to unmarshal into SendMsg: %v", err)) } return packet } -func UnmarshalSendUserCertPacket(data PacketBody) SendUserCertPacket { +func UnmarshalAnswerGetUserCert(data PacketBody) AnswerGetUserCert { jsonData, err := json.Marshal(data) if err != nil { panic(fmt.Errorf("failed to marshal data: %v", err)) } - var packet SendUserCertPacket + var packet AnswerGetUserCert if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into SendUserCertPacket: %v", err)) + panic(fmt.Errorf("failed to unmarshal into AnswerGetUserCert: %v", err)) } return packet } -func UnmarshalServerMessageInfoPacket(data PacketBody) ServerMessageInfoPacket { +func UnmarshalUnreadMsgInfo(data PacketBody) MsgInfo { jsonData, err := json.Marshal(data) if err != nil { panic(fmt.Errorf("failed to marshal data: %v", err)) } - var packet ServerMessageInfoPacket + var packet MsgInfo if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into ServerMessageInfoPacket: %v", err)) + panic(fmt.Errorf("failed to unmarshal into UnreadMsgInfo: %v", err)) } return packet } -func UnmarshalServerMessagePacket(data PacketBody) ServerMessagePacket { +func UnmarshalAnswerGetUnreadMsgsInfo(data PacketBody) AnswerGetUnreadMsgsInfo { jsonData, err := json.Marshal(data) if err != nil { panic(fmt.Errorf("failed to marshal data: %v", err)) } - var packet ServerMessagePacket + var packet AnswerGetUnreadMsgsInfo if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into ServerMessagePacket: %v", err)) + panic(fmt.Errorf("failed to unmarshal into AnswerGetUnreadMsgsInfo: %v", err)) + } + return packet +} + +func UnmarshalAnswerGetMsg(data PacketBody) AnswerGetMsg { + jsonData, err := json.Marshal(data) + if err != nil { + panic(fmt.Errorf("failed to marshal data: %v", err)) + } + var packet AnswerGetMsg + if err := json.Unmarshal(jsonData, &packet); err != nil { + panic(fmt.Errorf("failed to unmarshal into AnswerGetMsg: %v", err)) } return packet } diff --git a/Projs/PD1/internal/server/datastore.go b/Projs/PD1/internal/server/datastore.go index 9229077..c2466c1 100644 --- a/Projs/PD1/internal/server/datastore.go +++ b/Projs/PD1/internal/server/datastore.go @@ -41,6 +41,7 @@ func (ds DataStore) CreateTables() error { fromUID TEXT, toUID TEXT, timestamp TIMESTAMP, + queue_position INT DEFAULT 0, subject BLOB, body BLOB, status INT CHECK (status IN (0,1)), @@ -53,18 +54,36 @@ func (ds DataStore) CreateTables() error { return err } + // Define a trigger to automatically assign numbers for each message of each user starting from 1 + _, err = ds.db.Exec(` + CREATE TRIGGER IF NOT EXISTS assign_queue_position + AFTER INSERT ON messages + FOR EACH ROW + BEGIN + UPDATE messages + SET queue_position = ( + SELECT COUNT(*) + FROM messages + WHERE toUID = NEW.toUID + ) + WHERE toUID = NEW.toUID AND rowid = NEW.rowid; + END; + `) + if err != nil { + fmt.Println("Error creating trigger", err) + return err + } + return nil } func (ds DataStore) GetMessage(toUID string, position int) protocol.Packet { - var serverMessage protocol.ServerMessagePacket + var serverMessage protocol.AnswerGetMsg query := ` SELECT fromUID, toUID, subject, body, timestamp FROM messages - WHERE toUID = ? - ORDER BY timestamp - LIMIT 1 OFFSET ? + WHERE toUID = ? AND queue_position = ? ` // Execute the query row := ds.db.QueryRow(query, toUID, position) @@ -73,7 +92,7 @@ func (ds DataStore) GetMessage(toUID string, position int) protocol.Packet { log.Printf("Error getting the message in position %v from UID %v: %v", position, toUID, err) } - return protocol.NewServerMessagePacket(serverMessage.FromUID, serverMessage.ToUID, serverMessage.Subject, serverMessage.Body, serverMessage.Timestamp, true) + return protocol.NewAnswerGetMsgPacket(serverMessage.FromUID, serverMessage.ToUID, serverMessage.Subject, serverMessage.Body, serverMessage.Timestamp, true) } @@ -84,9 +103,7 @@ func (ds DataStore) MarkMessageInQueueAsRead(toUID string, position int) { WHERE (fromUID,toUID,timestamp) = ( SELECT fromUID,toUID,timestamp FROM messages - WHERE toUID = ? - ORDER BY timestamp - LIMIT 1 OFFSET ? + WHERE toUID = ? AND queue_position = ? ) ` @@ -97,8 +114,14 @@ func (ds DataStore) MarkMessageInQueueAsRead(toUID string, position int) { } } -func (ds DataStore) GetUnreadMessagesInfoQueue(toUID string) []protocol.Packet { - var messageInfoPackets []protocol.Packet +func (ds DataStore) GetUnreadMsgsInfo(toUID string, page int, pageSize int) protocol.Packet { + + // Retrieve the total count of unread messages + var totalCount int + err := ds.db.QueryRow("SELECT COUNT(*) FROM messages WHERE toUID = ? AND status = 0", toUID).Scan(&totalCount) + if err != nil { + log.Printf("Error getting total count of unread messages for UID %v: %v", toUID, err) + } // Query to retrieve all messages from the user's queue query := ` @@ -109,38 +132,23 @@ func (ds DataStore) GetUnreadMessagesInfoQueue(toUID string) []protocol.Packet { queue_position, subject, status - FROM ( - SELECT - fromUID, - toUID, - timestamp, - ROW_NUMBER() OVER (PARTITION BY toUID ORDER BY timestamp) - 1 AS queue_position, - subject, - status - FROM - messages - WHERE - toUID = ? - ) AS ranked_messages + FROM messages WHERE - status = 0 + toUID = ? AND status = 0 ORDER BY - timestamp; + queue_position DESC + LIMIT ? OFFSET ?; ` // Execute the query - rows, err := ds.db.Query(query, toUID) + rows, err := ds.db.Query(query, toUID, pageSize, (page-1)*pageSize) if err != nil { log.Printf("Error getting all messages for UID %v: %v", toUID, err) } defer rows.Close() - // Iterate through the result set and scan each row into a ServerMessage struct - //First row - if !rows.Next() { - return []protocol.Packet{} - } - for { + messageInfoPackets := []protocol.MsgInfo{} + for rows.Next() { var fromUID string var subject []byte var timestamp time.Time @@ -148,25 +156,19 @@ func (ds DataStore) GetUnreadMessagesInfoQueue(toUID string) []protocol.Packet { if err := rows.Scan(&fromUID, &toUID, ×tamp, &queuePosition, &subject, &status); err != nil { panic(err) } - var message protocol.Packet - hasNext := rows.Next() - if !hasNext { - message = protocol.NewServerMessageInfoPacket(queuePosition, fromUID, subject, timestamp, true) - messageInfoPackets = append(messageInfoPackets, message) - break - } else { - message = protocol.NewServerMessageInfoPacket(queuePosition, fromUID, subject, timestamp, false) - messageInfoPackets = append(messageInfoPackets, message) - } + answerGetUnreadMsgsInfo := protocol.NewMsgInfo(queuePosition, fromUID, subject, timestamp) + messageInfoPackets = append(messageInfoPackets, answerGetUnreadMsgsInfo) } if err := rows.Err(); err != nil { log.Printf("Error when getting messages for UID %v: %v", toUID, err) } - return messageInfoPackets + numberOfPages := (totalCount + pageSize - 1) / pageSize + currentPage := min(numberOfPages, page) + return protocol.NewAnswerGetUnreadMsgsInfoPacket(currentPage, numberOfPages, messageInfoPackets) } -func (ds DataStore) AddMessageToQueue(fromUID string, message protocol.SubmitMessagePacket) { +func (ds DataStore) AddMessageToQueue(fromUID string, message protocol.SendMsg) { query := ` INSERT INTO messages (fromUID, toUID, subject, body, timestamp, status) VALUES (?, ?, ?, ?, ?, 0) @@ -197,7 +199,7 @@ func (ds DataStore) GetUserCertificate(uid string) protocol.Packet { //if err!=nil { // log.Panicf("Error parsing certificate for UID %v",uid) //} - return protocol.NewSendUserCertPacket(uid, userCertBytes) + return protocol.NewAnswerGetUserCertPacket(uid, userCertBytes) } func (ds DataStore) userExists(uid string) bool { diff --git a/Projs/PD1/internal/server/server.go b/Projs/PD1/internal/server/server.go index 1f2863a..e4d6474 100644 --- a/Projs/PD1/internal/server/server.go +++ b/Projs/PD1/internal/server/server.go @@ -4,7 +4,6 @@ import ( "PD1/internal/protocol" "PD1/internal/utils/cryptoUtils" "PD1/internal/utils/networking" - "fmt" ) func clientHandler(connection networking.Connection[protocol.Packet], dataStore DataStore) { @@ -24,33 +23,30 @@ F: for { pac, active := connection.Receive() if !active { - break F + break } switch pac.Flag { - case protocol.ReqUserCertPkt: - reqUserCert := protocol.UnmarshalRequestUserCertPacket(pac.Body) + case protocol.FlagGetUserCert: + reqUserCert := protocol.UnmarshalGetUserCert(pac.Body) userCertPacket := dataStore.GetUserCertificate(reqUserCert.UID) if active := connection.Send(userCertPacket); !active { break F } - case protocol.ReqMsgsQueue: - _ = protocol.UnmarshalRequestMsgsQueuePacket(pac.Body) - messages := dataStore.GetUnreadMessagesInfoQueue(UID) - fmt.Printf("Number of unread messages by user %v is %v\n",UID,len(messages)) - for _, message := range messages { - if !connection.Send(message) { - break - } + case protocol.FlagGetUnreadMsgsInfo: + getUnreadMsgsInfo := protocol.UnmarshalGetUnreadMsgsInfo(pac.Body) + messages := dataStore.GetUnreadMsgsInfo(UID,getUnreadMsgsInfo.Page,getUnreadMsgsInfo.PageSize) + if !connection.Send(messages) { + break F } - case protocol.ReqMsgPkt: - reqMsg := protocol.UnmarshalRequestMsgPacket(pac.Body) + case protocol.FlagGetMsg: + reqMsg := protocol.UnmarshalGetMsg(pac.Body) message := dataStore.GetMessage(UID, reqMsg.Num) if active := connection.Send(message); !active { break F } - dataStore.MarkMessageInQueueAsRead(UID, reqMsg.Num) - case protocol.SubmitMsgPkt: - submitMsg := protocol.UnmarshalSubmitMessagePacket(pac.Body) + dataStore.MarkMessageInQueueAsRead(UID, reqMsg.Num) + case protocol.FlagSendMsg: + submitMsg := protocol.UnmarshalSendMsg(pac.Body) if submitMsg.ToUID != UID && dataStore.userExists(submitMsg.ToUID) { dataStore.AddMessageToQueue(UID, submitMsg) } diff --git a/Projs/PD1/server.db b/Projs/PD1/server.db index b5ed3b148273dff98bb8afe342732e7866c2a5a9..3784ec98eb700789457acedb5eadc3cf839188db 100644 GIT binary patch delta 14032 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 z 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 delta 9074 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 q