From b9182117369c4eb32a950741dec523fe898028ac Mon Sep 17 00:00:00 2001 From: afonso Date: Sun, 28 Apr 2024 22:02:13 +0100 Subject: [PATCH] [PD1] Error handling project-wide --- Projs/PD1/cmd/server/server.go | 2 +- Projs/PD1/internal/client/client.go | 313 ++++++++++++------ Projs/PD1/internal/client/datastore.go | 27 +- Projs/PD1/internal/client/interface.go | 10 +- Projs/PD1/internal/protocol/protocol.go | 83 +++-- Projs/PD1/internal/server/datastore.go | 44 ++- Projs/PD1/internal/server/interface.go | 5 - Projs/PD1/internal/server/server.go | 79 +++-- Projs/PD1/internal/utils/networking/client.go | 7 +- .../internal/utils/networking/connection.go | 24 +- Projs/PD1/internal/utils/networking/server.go | 13 +- Projs/PD1/server.db | Bin 61440 -> 28672 bytes Projs/PD1/tokefile.toml | 2 +- 13 files changed, 364 insertions(+), 245 deletions(-) diff --git a/Projs/PD1/cmd/server/server.go b/Projs/PD1/cmd/server/server.go index 0526a10..c93ba9b 100644 --- a/Projs/PD1/cmd/server/server.go +++ b/Projs/PD1/cmd/server/server.go @@ -5,5 +5,5 @@ import ( ) func main(){ - server.Run(8080) + server.Run() } diff --git a/Projs/PD1/internal/client/client.go b/Projs/PD1/internal/client/client.go index d4d2535..82a1a7f 100644 --- a/Projs/PD1/internal/client/client.go +++ b/Projs/PD1/internal/client/client.go @@ -5,6 +5,7 @@ import ( "PD1/internal/utils/cryptoUtils" "PD1/internal/utils/networking" "crypto/x509" + "errors" "flag" "log" "sort" @@ -17,45 +18,27 @@ func Run() { flag.Parse() if flag.NArg() == 0 { - panic("No command provided. Use 'help' for instructions.") + log.Fatalln("No command provided. Use 'help' for instructions.") } //Get user KeyStore password := readStdin("Insert keystore passphrase") - clientKeyStore := cryptoUtils.LoadKeyStore(userFile, password) + clientKeyStore, err := cryptoUtils.LoadKeyStore(userFile, password) + if err != nil { + log.Fatalln(err) + } command := flag.Arg(0) switch command { case "send": if flag.NArg() < 3 { - panic("Insufficient arguments for 'send' command. Usage: send ") + log.Fatalln("Insufficient arguments for 'send' command. Usage: send ") } uid := flag.Arg(1) plainSubject := flag.Arg(2) plainBody := readStdin("Enter message content (limited to 1000 bytes):") - //Turn content to bytes - plainSubjectBytes := Marshal(plainSubject) - plainBodyBytes := Marshal(plainBody) - - cl := networking.NewClient[protocol.Packet](&clientKeyStore) - defer cl.Connection.Conn.Close() - - receiverCert := getUserCert(cl, clientKeyStore, uid) - if receiverCert == nil { - return - } - subject := clientKeyStore.EncryptMessageContent(receiverCert, plainSubjectBytes) - body := clientKeyStore.EncryptMessageContent(receiverCert, plainBodyBytes) - sendMsgPacket := protocol.NewSendMsgPacket(uid, subject, body) - if !cl.Connection.Send(sendMsgPacket) { - return - } - answerSendMsg, active := cl.Connection.Receive() - if !active { - return - } - if answerSendMsg.Flag == protocol.FlagReportError { - reportError := protocol.UnmarshalReportError(answerSendMsg.Body) - log.Println(reportError.ErrorMessage) + err := sendCommand(clientKeyStore, plainSubject, plainBody, uid) + if err != nil { + log.Fatalln(err) } case "askqueue": @@ -74,41 +57,24 @@ func Run() { } } - cl := networking.NewClient[protocol.Packet](&clientKeyStore) - defer cl.Connection.Conn.Close() - askQueue(cl, clientKeyStore, page, pageSize) + err := askQueueCommand(clientKeyStore, page, pageSize) + if err != nil { + log.Fatalln(err) + } case "getmsg": if flag.NArg() < 2 { - panic("Insufficient arguments for 'getmsg' command. Usage: getmsg ") + log.Fatalln("Insufficient arguments for 'getmsg' command. Usage: getmsg ") } 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") + log.Fatalln(err) } - packet := protocol.NewGetMsgPacket(num) - cl.Connection.Send(packet) - - receivedMsgPacket, active := cl.Connection.Receive() - if !active { - return + err = getMsgCommand(clientKeyStore, num) + if err != nil { + log.Fatalln(err) } - if receivedMsgPacket.Flag == protocol.FlagReportError { - reportError := protocol.UnmarshalReportError(receivedMsgPacket.Body) - log.Println(reportError.ErrorMessage) - return - } - answerGetMsg := protocol.UnmarshalAnswerGetMsg(receivedMsgPacket.Body) - senderCert := getUserCert(cl, clientKeyStore, 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) case "help": showHelp() @@ -119,43 +85,152 @@ func Run() { } -func getUserCert(cl networking.Client[protocol.Packet], keyStore cryptoUtils.KeyStore, uid string) *x509.Certificate { - getUserCertPacket := protocol.NewGetUserCertPacket(uid) - if !cl.Connection.Send(getUserCertPacket) { - return nil - } - var answerGetUserCertPacket *protocol.Packet - answerGetUserCertPacket, active := cl.Connection.Receive() - if !active { - return nil - } - if answerGetUserCertPacket.Flag == protocol.FlagReportError { - reportError := protocol.UnmarshalReportError(answerGetUserCertPacket.Body) - log.Println(reportError.ErrorMessage) - return nil - } - answerGetUserCert := protocol.UnmarshalAnswerGetUserCert(answerGetUserCertPacket.Body) - userCert, err := x509.ParseCertificate(answerGetUserCert.Certificate) +func sendCommand(clientKeyStore cryptoUtils.KeyStore, plainSubject, plainBody, uid string) error { + //Turn content to bytes + plainSubjectBytes, err := Marshal(plainSubject) if err != nil { - return nil + return err } - if !keyStore.CheckCert(userCert, uid){ - return nil - } - return userCert + plainBodyBytes, err := Marshal(plainBody) + if err != nil { + return err + } + + cl, err := networking.NewClient[protocol.Packet](&clientKeyStore) + if err != nil { + return err + } + defer cl.Connection.Conn.Close() + + receiverCert, err := getUserCert(cl, clientKeyStore, uid) + if err != nil { + return err + } + subject, err := clientKeyStore.EncryptMessageContent(receiverCert, plainSubjectBytes) + if err != nil { + return err + } + body, err := clientKeyStore.EncryptMessageContent(receiverCert, plainBodyBytes) + if err != nil { + return err + } + sendMsgPacket := protocol.NewSendMsgPacket(uid, subject, body) + if err := cl.Connection.Send(sendMsgPacket); err != nil { + return err + } + answerSendMsg, err := cl.Connection.Receive() + if err != nil { + return err + } + if answerSendMsg.Flag == protocol.FlagReportError { + reportError, err := protocol.UnmarshalReportError(answerSendMsg.Body) + if err != nil { + return err + } + return errors.New(reportError.ErrorMessage) + } + return nil + } -func getManyMessagesInfo(cl networking.Client[protocol.Packet], keyStore cryptoUtils.KeyStore) (protocol.AnswerGetUnreadMsgsInfo, map[string]*x509.Certificate) { - answerGetUnreadMsgsInfoPacket, active := cl.Connection.Receive() - if !active { - return protocol.NewAnswerGetUnreadMsgsInfo(0, 0, nil), nil +func getMsgCommand(clientKeyStore cryptoUtils.KeyStore, num int) error { + cl, err := networking.NewClient[protocol.Packet](&clientKeyStore) + if err != nil { + return err + } + defer cl.Connection.Conn.Close() + packet := protocol.NewGetMsgPacket(num) + if err := cl.Connection.Send(packet); err != nil { + return err + } + + receivedMsgPacket, err := cl.Connection.Receive() + if err != nil { + return err + } + if receivedMsgPacket.Flag == protocol.FlagReportError { + reportError, err := protocol.UnmarshalReportError(receivedMsgPacket.Body) + if err != nil { + return err + } + return errors.New(reportError.ErrorMessage) + } + answerGetMsg, err := protocol.UnmarshalAnswerGetMsg(receivedMsgPacket.Body) + if err != nil { + return err + } + senderCert, err := getUserCert(cl, clientKeyStore, answerGetMsg.FromUID) + if err != nil { + return err + } + decSubjectBytes, err := clientKeyStore.DecryptMessageContent(senderCert, answerGetMsg.Subject) + if err != nil { + return err + } + decBodyBytes, err := clientKeyStore.DecryptMessageContent(senderCert, answerGetMsg.Body) + if err != nil { + return err + } + subject, err := Unmarshal(decSubjectBytes) + if err != nil { + return err + } + body, err := Unmarshal(decBodyBytes) + if err != nil { + return err + } + message := newClientMessage(answerGetMsg.FromUID, answerGetMsg.ToUID, subject, body, answerGetMsg.Timestamp) + showMessage(message) + return nil +} + +func getUserCert(cl networking.Client[protocol.Packet], keyStore cryptoUtils.KeyStore, uid string) (*x509.Certificate, error) { + getUserCertPacket := protocol.NewGetUserCertPacket(uid) + if err := cl.Connection.Send(getUserCertPacket); err != nil { + return nil, err + } + var answerGetUserCertPacket *protocol.Packet + answerGetUserCertPacket, err := cl.Connection.Receive() + if err != nil { + return nil, err + } + if answerGetUserCertPacket.Flag == protocol.FlagReportError { + reportError, err := protocol.UnmarshalReportError(answerGetUserCertPacket.Body) + if err != nil { + return nil, err + } + return nil, errors.New(reportError.ErrorMessage) + } + answerGetUserCert, err := protocol.UnmarshalAnswerGetUserCert(answerGetUserCertPacket.Body) + if err != nil { + return nil, err + } + userCert, err := x509.ParseCertificate(answerGetUserCert.Certificate) + if err != nil { + return nil, err + } + if err := keyStore.CheckCert(userCert, uid); err != nil { + return nil, err + } + return userCert, nil +} + +func getManyMessagesInfo(cl networking.Client[protocol.Packet], keyStore cryptoUtils.KeyStore) (protocol.AnswerGetUnreadMsgsInfo, map[string]*x509.Certificate, error) { + answerGetUnreadMsgsInfoPacket, err := cl.Connection.Receive() + if err != nil { + return protocol.NewAnswerGetUnreadMsgsInfo(0, 0, nil), nil, err } if answerGetUnreadMsgsInfoPacket.Flag == protocol.FlagReportError { - reportError := protocol.UnmarshalReportError(answerGetUnreadMsgsInfoPacket.Body) - log.Println(reportError.ErrorMessage) - return protocol.NewAnswerGetUnreadMsgsInfo(0, 0, nil), nil + reportError, err := protocol.UnmarshalReportError(answerGetUnreadMsgsInfoPacket.Body) + if err != nil { + return protocol.AnswerGetUnreadMsgsInfo{}, nil, err + } + return protocol.NewAnswerGetUnreadMsgsInfo(0, 0, nil), nil, errors.New(reportError.ErrorMessage) + } + answerGetUnreadMsgsInfo, err := protocol.UnmarshalAnswerGetUnreadMsgsInfo(answerGetUnreadMsgsInfoPacket.Body) + if err != nil { + return protocol.AnswerGetUnreadMsgsInfo{}, nil, err } - answerGetUnreadMsgsInfo := protocol.UnmarshalAnswerGetUnreadMsgsInfo(answerGetUnreadMsgsInfoPacket.Body) //Create Set of needed certificates senderSet := map[string]bool{} @@ -165,32 +240,60 @@ func getManyMessagesInfo(cl networking.Client[protocol.Packet], keyStore cryptoU certificatesMap := map[string]*x509.Certificate{} //Get senders' certificates for senderUID := range senderSet { - senderCert := getUserCert(cl, keyStore, senderUID) - certificatesMap[senderUID] = senderCert + senderCert, err := getUserCert(cl, keyStore, senderUID) + if err == nil { + certificatesMap[senderUID] = senderCert + } } - return answerGetUnreadMsgsInfo, certificatesMap + return answerGetUnreadMsgsInfo, certificatesMap, nil } -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 +func askQueueCommand(clientKeyStore cryptoUtils.KeyStore, page int, pageSize int) error { + cl, err := networking.NewClient[protocol.Packet](&clientKeyStore) + if err != nil { + return err + } + defer cl.Connection.Conn.Close() + return askQueueRec(cl, clientKeyStore, page, pageSize) +} + +func askQueueRec(cl networking.Client[protocol.Packet], clientKeyStore cryptoUtils.KeyStore, page int, pageSize int) error { + + requestUnreadMsgsQueuePacket := protocol.NewGetUnreadMsgsInfoPacket(page, pageSize) + if err := cl.Connection.Send(requestUnreadMsgsQueuePacket); err != nil { + return err + } + unreadMsgsInfo, certificates, err := getManyMessagesInfo(cl, clientKeyStore) + if err != nil { + return err } - unreadMsgsInfo, certificates := getManyMessagesInfo(cl, clientKeyStore) var clientMessages []ClientMessageInfo for _, message := range unreadMsgsInfo.MessagesInfo { + var clientMessageInfo ClientMessageInfo senderCert, ok := certificates[message.FromUID] - if ok { - var subject string - if senderCert != nil { - decryptedSubjectBytes := clientKeyStore.DecryptMessageContent(senderCert, message.Subject) - subject = Unmarshal(decryptedSubjectBytes) - } else { - subject = "" - } - clientMessage := newClientMessageInfo(message.Num, message.FromUID, subject, message.Timestamp) - clientMessages = append(clientMessages, clientMessage) + if !ok { + clientMessageInfo = newClientMessageInfo(message.Num, + message.FromUID, + "", + message.Timestamp, + errors.New("certificate needed to decrypt not received")) + clientMessages = append(clientMessages, clientMessageInfo) + continue } + decryptedSubjectBytes, err := clientKeyStore.DecryptMessageContent(senderCert, message.Subject) + if err != nil { + clientMessageInfo = newClientMessageInfo(message.Num, message.FromUID, "", message.Timestamp, err) + clientMessages = append(clientMessages, clientMessageInfo) + continue + } + subject, err := Unmarshal(decryptedSubjectBytes) + if err != nil { + clientMessageInfo = newClientMessageInfo(message.Num, message.FromUID, "", message.Timestamp, err) + clientMessages = append(clientMessages, clientMessageInfo) + continue + } + clientMessageInfo = newClientMessageInfo(message.Num, message.FromUID, subject, message.Timestamp, nil) + clientMessages = append(clientMessages, clientMessageInfo) } //Sort the messages sort.Slice(clientMessages, func(i, j int) bool { @@ -200,10 +303,10 @@ func askQueue(cl networking.Client[protocol.Packet], clientKeyStore cryptoUtils. action := showMessagesInfo(unreadMsgsInfo.Page, unreadMsgsInfo.NumPages, clientMessages) switch action { case -1: - askQueue(cl, clientKeyStore, max(1, unreadMsgsInfo.Page-1), pageSize) - case 0: - return + return askQueueRec(cl, clientKeyStore, max(1, unreadMsgsInfo.Page-1), pageSize) case 1: - askQueue(cl, clientKeyStore, max(1, unreadMsgsInfo.Page+1), pageSize) + return askQueueRec(cl, clientKeyStore, max(1, unreadMsgsInfo.Page+1), pageSize) + default: + return nil } } diff --git a/Projs/PD1/internal/client/datastore.go b/Projs/PD1/internal/client/datastore.go index a2d115c..7036d9e 100644 --- a/Projs/PD1/internal/client/datastore.go +++ b/Projs/PD1/internal/client/datastore.go @@ -1,7 +1,7 @@ package client import ( - "log" + "encoding/json" "time" ) @@ -14,33 +14,34 @@ type ClientMessage struct { } type ClientMessageInfo struct { - Num int - FromUID string - Timestamp time.Time - Subject string + Num int + FromUID string + Timestamp time.Time + Subject string + decryptError error } func newClientMessage(fromUID string, toUID string, subject string, body string, timestamp time.Time) ClientMessage { return ClientMessage{FromUID: fromUID, ToUID: toUID, Subject: subject, Body: body, Timestamp: timestamp} } -func newClientMessageInfo(num int, fromUID string, subject string, timestamp time.Time) ClientMessageInfo { - return ClientMessageInfo{Num:num,FromUID: fromUID,Subject: subject,Timestamp: timestamp} +func newClientMessageInfo(num int, fromUID string, subject string, timestamp time.Time, err error) ClientMessageInfo { + return ClientMessageInfo{Num: num, FromUID: fromUID, Subject: subject, Timestamp: timestamp, decryptError: err} } -func Marshal(data any) []byte { +func Marshal(data any) ([]byte, error) { subject, err := json.Marshal(data) if err != nil { - log.Panicf("Error when marshalling message: %v", err) + return nil, err } - return subject + return subject, nil } -func Unmarshal(data []byte) string { +func Unmarshal(data []byte) (string, error) { var c string err := json.Unmarshal(data, &c) if err != nil { - log.Panicln("Could not unmarshal data") + return "", err } - return c + return c, nil } diff --git a/Projs/PD1/internal/client/interface.go b/Projs/PD1/internal/client/interface.go index 21551c1..0a5817b 100644 --- a/Projs/PD1/internal/client/interface.go +++ b/Projs/PD1/internal/client/interface.go @@ -3,6 +3,7 @@ package client import ( "bufio" "fmt" + "log" "os" "strings" ) @@ -34,11 +35,12 @@ func showMessagesInfo(page int, numPages int, messages []ClientMessageInfo) int return 0 } for _, message := range messages { - if message.Subject == "" { - fmt.Printf("ERROR DECRYPTING MESSAGE %v IN QUEUE FROM UID %v\n", message.Num, message.FromUID) - continue + if message.decryptError != nil { + fmt.Printf("ERROR: %v:%v:%v:", message.Num, message.FromUID, message.Timestamp) + log.Println(message.decryptError) + } else { + fmt.Printf("%v:%v:%v:%v\n", message.Num, message.FromUID, message.Timestamp, message.Subject) } - 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) diff --git a/Projs/PD1/internal/protocol/protocol.go b/Projs/PD1/internal/protocol/protocol.go index 0e57d08..3aefe0e 100644 --- a/Projs/PD1/internal/protocol/protocol.go +++ b/Projs/PD1/internal/protocol/protocol.go @@ -2,7 +2,6 @@ package protocol import ( "encoding/json" - "fmt" "time" ) @@ -30,8 +29,8 @@ const ( // Server sends requested message FlagAnswerGetMsg - // Server tells the client that the message was successfully sent - FlagAnswerSendMsg + // Server tells the client that the message was successfully sent + FlagAnswerSendMsg // Report an error FlagReportError @@ -192,118 +191,118 @@ func NewAnswerGetMsgPacket(fromUID, toUID string, subject []byte, body []byte, t return NewPacket(FlagAnswerGetMsg, NewAnswerGetMsg(fromUID, toUID, subject, body, timestamp, last)) } -func NewAnswerSendMsgPacket() Packet{ - //This packet has no body - return NewPacket(FlagAnswerSendMsg,nil) +func NewAnswerSendMsgPacket() Packet { + //This packet has no body + return NewPacket(FlagAnswerSendMsg, nil) } func NewReportErrorPacket(errorMessage string) Packet { return NewPacket(FlagReportError, NewReportError(errorMessage)) } -func UnmarshalGetUserCert(data PacketBody) GetUserCert { +func UnmarshalGetUserCert(data PacketBody) (GetUserCert, error) { jsonData, err := json.Marshal(data) if err != nil { - panic(fmt.Errorf("failed to marshal data: %v", err)) + return GetUserCert{}, err } var packet GetUserCert if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into GetUserCert: %v", err)) + return GetUserCert{}, err } - return packet + return packet, nil } -func UnmarshalGetUnreadMsgsInfo(data PacketBody) GetUnreadMsgsInfo { +func UnmarshalGetUnreadMsgsInfo(data PacketBody) (GetUnreadMsgsInfo, error) { jsonData, err := json.Marshal(data) if err != nil { - panic(fmt.Errorf("failed to marshal data: %v", err)) + return GetUnreadMsgsInfo{}, err } var packet GetUnreadMsgsInfo if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into GetUnreadMsgsInfo: %v", err)) + return GetUnreadMsgsInfo{}, err } - return packet + return packet, nil } -func UnmarshalGetMsg(data PacketBody) GetMsg { +func UnmarshalGetMsg(data PacketBody) (GetMsg, error) { jsonData, err := json.Marshal(data) if err != nil { - panic(fmt.Errorf("failed to marshal data: %v", err)) + return GetMsg{}, err } var packet GetMsg if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into GetMsg: %v", err)) + return GetMsg{}, err } - return packet + return packet, nil } -func UnmarshalSendMsg(data PacketBody) SendMsg { +func UnmarshalSendMsg(data PacketBody) (SendMsg, error) { jsonData, err := json.Marshal(data) if err != nil { - panic(fmt.Errorf("failed to marshal data: %v", err)) + return SendMsg{}, err } var packet SendMsg if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into SendMsg: %v", err)) + return SendMsg{}, err } - return packet + return packet, nil } -func UnmarshalAnswerGetUserCert(data PacketBody) AnswerGetUserCert { +func UnmarshalAnswerGetUserCert(data PacketBody) (AnswerGetUserCert, error) { jsonData, err := json.Marshal(data) if err != nil { - panic(fmt.Errorf("failed to marshal data: %v", err)) + return AnswerGetUserCert{}, err } var packet AnswerGetUserCert if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into AnswerGetUserCert: %v", err)) + return AnswerGetUserCert{}, err } - return packet + return packet, nil } -func UnmarshalUnreadMsgInfo(data PacketBody) MsgInfo { +func UnmarshalUnreadMsgInfo(data PacketBody) (MsgInfo, error) { jsonData, err := json.Marshal(data) if err != nil { - panic(fmt.Errorf("failed to marshal data: %v", err)) + return MsgInfo{}, err } var packet MsgInfo if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into UnreadMsgInfo: %v", err)) + return MsgInfo{}, err } - return packet + return packet, nil } -func UnmarshalAnswerGetUnreadMsgsInfo(data PacketBody) AnswerGetUnreadMsgsInfo { +func UnmarshalAnswerGetUnreadMsgsInfo(data PacketBody) (AnswerGetUnreadMsgsInfo, error) { jsonData, err := json.Marshal(data) if err != nil { - panic(fmt.Errorf("failed to marshal data: %v", err)) + return AnswerGetUnreadMsgsInfo{}, err } var packet AnswerGetUnreadMsgsInfo if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into AnswerGetUnreadMsgsInfo: %v", err)) + return AnswerGetUnreadMsgsInfo{}, err } - return packet + return packet, nil } -func UnmarshalAnswerGetMsg(data PacketBody) AnswerGetMsg { +func UnmarshalAnswerGetMsg(data PacketBody) (AnswerGetMsg, error) { jsonData, err := json.Marshal(data) if err != nil { - panic(fmt.Errorf("failed to marshal data: %v", err)) + return AnswerGetMsg{}, err } var packet AnswerGetMsg if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into AnswerGetMsg: %v", err)) + return AnswerGetMsg{}, err } - return packet + return packet, nil } -func UnmarshalReportError(data PacketBody) ReportError { +func UnmarshalReportError(data PacketBody) (ReportError, error) { jsonData, err := json.Marshal(data) if err != nil { - panic(fmt.Errorf("failed to marshal data: %v", err)) + return ReportError{}, err } var packet ReportError if err := json.Unmarshal(jsonData, &packet); err != nil { - panic(fmt.Errorf("failed to unmarshal into AnswerGetMsg: %v", err)) + return ReportError{}, err } - return packet + return packet, nil } diff --git a/Projs/PD1/internal/server/datastore.go b/Projs/PD1/internal/server/datastore.go index b578630..f55ef1d 100644 --- a/Projs/PD1/internal/server/datastore.go +++ b/Projs/PD1/internal/server/datastore.go @@ -4,6 +4,7 @@ import ( "PD1/internal/protocol" "crypto/x509" "database/sql" + "errors" "fmt" "log" "time" @@ -15,14 +16,17 @@ type DataStore struct { db *sql.DB } -func OpenDB() DataStore { +func OpenDB() (DataStore, error) { db, err := sql.Open("sqlite3", "server.db") if err != nil { - log.Fatalln("Error opening db file") + return DataStore{}, err } ds := DataStore{db: db} - ds.CreateTables() - return ds + err = ds.CreateTables() + if err != nil { + return DataStore{}, err + } + return ds, nil } func (ds DataStore) CreateTables() error { @@ -32,7 +36,6 @@ func (ds DataStore) CreateTables() error { userCert BLOB )`) if err != nil { - fmt.Println("Error creating users table", err) return err } @@ -50,7 +53,6 @@ func (ds DataStore) CreateTables() error { FOREIGN KEY(toUID) REFERENCES users(UID) )`) if err != nil { - fmt.Println("Error creating messages table", err) return err } @@ -70,7 +72,6 @@ func (ds DataStore) CreateTables() error { END; `) if err != nil { - fmt.Println("Error creating trigger", err) return err } @@ -91,7 +92,7 @@ func (ds DataStore) GetMessage(toUID string, position int) protocol.Packet { if err == sql.ErrNoRows { log.Printf("No message with NUM %v for UID %v\n", position, toUID) errorMessage := fmt.Sprintf("No message with NUM %v", position) - return protocol.NewReportErrorPacket(errorMessage) + return protocol.NewReportErrorPacket(errorMessage) } return protocol.NewAnswerGetMsgPacket(serverMessage.FromUID, serverMessage.ToUID, serverMessage.Subject, serverMessage.Body, serverMessage.Timestamp, true) @@ -116,14 +117,13 @@ func (ds DataStore) MarkMessageInQueueAsRead(toUID string, position int) { } } -func (ds DataStore) GetUnreadMsgsInfo(toUID string, page int, pageSize int) protocol.Packet { +func (ds DataStore) GetUnreadMsgsInfo(toUID string, page int, pageSize int) (protocol.Packet, error) { // 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 == sql.ErrNoRows { - log.Printf("No unread messages for UID %v: %v", toUID, err) - return protocol.NewAnswerGetUnreadMsgsInfoPacket(0, 0, []protocol.MsgInfo{}) + return protocol.NewAnswerGetUnreadMsgsInfoPacket(0, 0, []protocol.MsgInfo{}), nil } // Query to retrieve all messages from the user's queue @@ -157,19 +157,18 @@ func (ds DataStore) GetUnreadMsgsInfo(toUID string, page int, pageSize int) prot var timestamp time.Time var queuePosition, status int if err := rows.Scan(&fromUID, &toUID, ×tamp, &queuePosition, &subject, &status); err != nil { - panic(err) + return protocol.Packet{}, err } 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 protocol.NewReportErrorPacket(err.Error()) + return protocol.Packet{}, err } - numberOfPages := (totalCount + pageSize - 1) / pageSize currentPage := min(numberOfPages, page) - return protocol.NewAnswerGetUnreadMsgsInfoPacket(currentPage, numberOfPages, messageInfoPackets) + return protocol.NewAnswerGetUnreadMsgsInfoPacket(currentPage, numberOfPages, messageInfoPackets), nil } func (ds DataStore) AddMessageToQueue(fromUID string, message protocol.SendMsg) protocol.Packet { @@ -218,17 +217,16 @@ func (ds DataStore) userExists(uid string) bool { // Execute the SQL query err := ds.db.QueryRow(query, uid).Scan(&count) if err == sql.ErrNoRows { - log.Printf("User with UID %v does not exist", uid) - return false - } else { - return true + log.Println("user with UID %v does not exist", uid) + return false } + return true } -func (ds DataStore) storeUserCertIfNotExists(uid string, cert x509.Certificate) { +func (ds DataStore) storeUserCertIfNotExists(uid string, cert x509.Certificate) error { // Check if the user already exists if ds.userExists(uid) { - return + return nil } // Insert the user certificate @@ -238,8 +236,8 @@ func (ds DataStore) storeUserCertIfNotExists(uid string, cert x509.Certificate) ` _, err := ds.db.Exec(insertQuery, uid, cert.Raw) if err != nil { - log.Printf("Error storing user certificate for UID %s: %v\n", uid, err) - return + return errors.New(fmt.Sprintf("Error storing user certificate for UID %s: %v\n", uid, err)) } log.Printf("User certificate for UID %s stored successfully.\n", uid) + return nil } diff --git a/Projs/PD1/internal/server/interface.go b/Projs/PD1/internal/server/interface.go index b2eec56..40e156f 100644 --- a/Projs/PD1/internal/server/interface.go +++ b/Projs/PD1/internal/server/interface.go @@ -3,7 +3,6 @@ package server import ( "bufio" "fmt" - "log" "os" ) @@ -13,7 +12,3 @@ func readStdin(message string) string { scanner.Scan() return scanner.Text() } - -func LogFatal(err error) { - log.Fatalln(err) -} diff --git a/Projs/PD1/internal/server/server.go b/Projs/PD1/internal/server/server.go index 37652b9..54b62da 100644 --- a/Projs/PD1/internal/server/server.go +++ b/Projs/PD1/internal/server/server.go @@ -19,84 +19,111 @@ func clientHandler(connection networking.Connection[protocol.Packet], dataStore //Check if certificate usage is MSG SERVICE usage := oidMap["2.5.4.11"] if usage == "" { - log.Println("User certificate does not have the correct usage") - return + log.Fatalln("User certificate does not have the correct usage") } //Get the UID of this user UID := oidMap["2.5.4.65"] if UID == "" { - log.Println("User certificate does not specify it's PSEUDONYM") + log.Fatalln("User certificate does not specify it's PSEUDONYM") + } + err := dataStore.storeUserCertIfNotExists(UID, *clientCert) + if err != nil { + log.Fatalln(err) } - dataStore.storeUserCertIfNotExists(UID, *clientCert) F: for { - pac, active := connection.Receive() - if !active { + pac, err := connection.Receive() + if err != nil { break } switch pac.Flag { case protocol.FlagGetUserCert: - reqUserCert := protocol.UnmarshalGetUserCert(pac.Body) + reqUserCert, err := protocol.UnmarshalGetUserCert(pac.Body) + if err != nil { + log.Fatalln(err) + } userCertPacket := dataStore.GetUserCertificate(reqUserCert.UID) - if !connection.Send(userCertPacket) { + if err := connection.Send(userCertPacket); err != nil { + log.Fatalln(err) break F } + case protocol.FlagGetUnreadMsgsInfo: - getUnreadMsgsInfo := protocol.UnmarshalGetUnreadMsgsInfo(pac.Body) + getUnreadMsgsInfo, err := protocol.UnmarshalGetUnreadMsgsInfo(pac.Body) + if err != nil { + log.Fatalln(err) + } var messages protocol.Packet if getUnreadMsgsInfo.Page <= 0 || getUnreadMsgsInfo.PageSize <= 0 { messages = protocol.NewReportErrorPacket("Page and PageSize need to be >= 1") } else { - messages = dataStore.GetUnreadMsgsInfo(UID, getUnreadMsgsInfo.Page, getUnreadMsgsInfo.PageSize) + messages, err = dataStore.GetUnreadMsgsInfo(UID, getUnreadMsgsInfo.Page, getUnreadMsgsInfo.PageSize) + if err != nil { + log.Fatalln(err) + } } - if !connection.Send(messages) { - break F + if err := connection.Send(messages); err != nil { + log.Fatalln(err) } + case protocol.FlagGetMsg: - reqMsg := protocol.UnmarshalGetMsg(pac.Body) + reqMsg, err := protocol.UnmarshalGetMsg(pac.Body) + if err != nil { + log.Fatalln(err) + } var message protocol.Packet if reqMsg.Num <= 0 { message = protocol.NewReportErrorPacket("Message NUM needs to be >= 1") } else { message = dataStore.GetMessage(UID, reqMsg.Num) } - if !connection.Send(message) { + if err := connection.Send(message); err != nil { + log.Fatalln(err) break F } dataStore.MarkMessageInQueueAsRead(UID, reqMsg.Num) + case protocol.FlagSendMsg: - submitMsg := protocol.UnmarshalSendMsg(pac.Body) + submitMsg, err := protocol.UnmarshalSendMsg(pac.Body) + if err != nil { + log.Fatalln(err) + } var answerSendMsgPacket protocol.Packet if submitMsg.ToUID == UID { - answerSendMsgPacket = protocol.NewReportErrorPacket("Cannot message yourself") + answerSendMsgPacket = protocol.NewReportErrorPacket("Message sender and receiver cannot be the same user") } else if !dataStore.userExists(submitMsg.ToUID) { - answerSendMsgPacket = protocol.NewReportErrorPacket("Message receiver does not exist in database") + answerSendMsgPacket = protocol.NewReportErrorPacket("Message receiver does not exist") } else { answerSendMsgPacket = dataStore.AddMessageToQueue(UID, submitMsg) } - if !connection.Send(answerSendMsgPacket) { + if err := connection.Send(answerSendMsgPacket); err != nil { + log.Fatalln(err) break F } } } } -func Run(port int) { +func Run() { //Open connection to DB - dataStore := OpenDB() + dataStore, err := OpenDB() + if err != nil { + log.Fatalln(err) + } defer dataStore.db.Close() - //FIX: Get the server's keystore path instead of hardcoding it - //Read server keystore - password := readStdin("Insert keystore passphrase") - serverKeyStore, err := cryptoUtils.LoadKeyStore("certs/server/server.p12", password) + keystorePassphrase := readStdin("Insert keystore passphrase") + serverKeyStore, err := cryptoUtils.LoadKeyStore("certs/server/server.p12", keystorePassphrase) if err != nil { - LogFatal(err) + log.Fatalln(err) } //Create server listener - server := networking.NewServer[protocol.Packet](&serverKeyStore, port) + server, err := networking.NewServer[protocol.Packet](&serverKeyStore) + if err != nil { + log.Fatalln(err) + } go server.ListenLoop() for { diff --git a/Projs/PD1/internal/utils/networking/client.go b/Projs/PD1/internal/utils/networking/client.go index 19ebb05..cb49c4c 100644 --- a/Projs/PD1/internal/utils/networking/client.go +++ b/Projs/PD1/internal/utils/networking/client.go @@ -2,7 +2,6 @@ package networking import ( "crypto/tls" - "log" ) @@ -14,11 +13,11 @@ type Client[T any] struct { Connection Connection[T] } -func NewClient[T any](clientTLSConfigProvider ClientTLSConfigProvider) Client[T] { +func NewClient[T any](clientTLSConfigProvider ClientTLSConfigProvider) (Client[T],error) { dialConn, err := tls.Dial("tcp", "localhost:8080", clientTLSConfigProvider.GetClientTLSConfig()) if err != nil { - log.Panicln("Server connection error:\n",err) + return Client[T]{},err } conn := NewConnection[T](dialConn) - return Client[T]{Connection: conn} + return Client[T]{Connection: conn},nil } diff --git a/Projs/PD1/internal/utils/networking/connection.go b/Projs/PD1/internal/utils/networking/connection.go index 1e40f9b..82efba6 100644 --- a/Projs/PD1/internal/utils/networking/connection.go +++ b/Projs/PD1/internal/utils/networking/connection.go @@ -22,33 +22,27 @@ func NewConnection[T any](netConn *tls.Conn) Connection[T] { } } -func (c Connection[T]) Send(obj T) bool { +func (c Connection[T]) Send(obj T) error { if err := c.encoder.Encode(&obj); err!=nil { if err == io.EOF { log.Println("Connection closed by peer") - //Return false as connection not active - return false - } else { - log.Panic(err) - } + } + return err } //Return true as connection active - return true + return nil } -func (c Connection[T]) Receive() (*T, bool) { +func (c Connection[T]) Receive() (*T, error) { var obj T if err := c.decoder.Decode(&obj); err != nil { if err == io.EOF { log.Println("Connection closed by peer") - //Return false as connection not active - return nil,false - } else { - log.Panic(err) - } - } + } + return nil,err + } //Return true as connection active - return &obj, true + return &obj, nil } func (c Connection[T]) GetPeerCertificate() *x509.Certificate { diff --git a/Projs/PD1/internal/utils/networking/server.go b/Projs/PD1/internal/utils/networking/server.go index 16bf4a7..5a960b4 100644 --- a/Projs/PD1/internal/utils/networking/server.go +++ b/Projs/PD1/internal/utils/networking/server.go @@ -2,7 +2,6 @@ package networking import ( "crypto/tls" - "fmt" "log" "net" ) @@ -16,16 +15,16 @@ type Server[T any] struct { C chan Connection[T] } -func NewServer[T any](serverTLSConfigProvider ServerTLSConfigProvider, port int) Server[T] { +func NewServer[T any](serverTLSConfigProvider ServerTLSConfigProvider) (Server[T], error) { - listener, err := tls.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port), serverTLSConfigProvider.GetServerTLSConfig()) + listener, err := tls.Listen("tcp", "127.0.0.1:8080", serverTLSConfigProvider.GetServerTLSConfig()) if err != nil { - log.Fatalln("Server could not bind to address") + return Server[T]{}, err } return Server[T]{ listener: listener, C: make(chan Connection[T]), - } + }, nil } func (s *Server[T]) ListenLoop() { @@ -39,7 +38,9 @@ func (s *Server[T]) ListenLoop() { if !ok { log.Fatalln("Connection is not a TLS connection") } - tlsConn.Handshake() + if err := tlsConn.Handshake(); err != nil { + log.Fatalln(err) + } state := tlsConn.ConnectionState() if len(state.PeerCertificates) == 0 { diff --git a/Projs/PD1/server.db b/Projs/PD1/server.db index 9e8a1251a6fa8d7496a9a01c9ca4c1d6ecf2749c..e587c746604bb4b9b2070729760902f7b536a2bb 100644 GIT binary patch delta 6721 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 Yt28w-c&Q&xhOSDm26TOV delta 24982 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