diff --git a/cmd/cfyne/actions.go b/cmd/cfyne/actions.go new file mode 100644 index 0000000..29d559b --- /dev/null +++ b/cmd/cfyne/actions.go @@ -0,0 +1,7 @@ +package main + +const ( + ACTION_SET_SCENE_NICKNAME = 1 + iota + ACTION_SET_SCENE_MAIN + ACTION_UPDATE_USER_OPTS_CONTS +) diff --git a/cmd/cfyne/conUsers.go b/cmd/cfyne/conUsers.go new file mode 100644 index 0000000..6abcf33 --- /dev/null +++ b/cmd/cfyne/conUsers.go @@ -0,0 +1,61 @@ +package main + +import ( + "log" + "sync" +) + +var ( + userCenter UserCenter +) + +type UserCenter struct { + UsersSTOI map[string]uint16 + UsersITOS map[uint16]string + Mu sync.Mutex +} + +func (u *UserCenter) Init() { + u.UsersSTOI = make(map[string]uint16) + u.UsersITOS = make(map[uint16]string) +} + +func (u *UserCenter) AddUser(name string, id uint16) error { + u.Mu.Lock() + defer u.Mu.Unlock() + _, alreadyHave := u.UsersSTOI[name] + if alreadyHave { + return ERROR_ALREADY_HAVE + } + log.Printf("Users: add %s with %d id\n", name, id) + u.UsersITOS[id] = name + u.UsersSTOI[name] = id + return nil +} + +func (u *UserCenter) DeleteIfHaveOne(id uint16) { + name, found := u.UsersITOS[id] + if !found { + log.Printf("User with %d id is not found; Can not delete\n", id) + return + } + delete(u.UsersITOS, id) + delete(u.UsersSTOI, name) + log.Printf("User with %s name and %d id was found; User is deleted\n", name, id) +} + +func (u *UserCenter) GetID(name string) (uint16, error) { + id, have := u.UsersSTOI[name] + if !have { + return 0, ERROR_DONT_HAVE + } + return id, nil +} + +func (u *UserCenter) GetName(id uint16) (string, error) { + name, have := u.UsersITOS[id] + if !have { + return "", ERROR_DONT_HAVE + } + return name, nil +} diff --git a/cmd/cfyne/funcs.go b/cmd/cfyne/funcs.go new file mode 100644 index 0000000..d19190c --- /dev/null +++ b/cmd/cfyne/funcs.go @@ -0,0 +1,15 @@ +package main + +import ( + // "fyne.io/fyne/v2/widget" + com "git.qowevisa.me/Qowevisa/gotell/communication" +) + +func getSendMessageFuncToIntercom(intercom chan *com.Message, id uint8, data []byte) func() { + return func() { + intercom <- &com.Message{ + ID: id, + Data: data, + } + } +} diff --git a/cmd/cfyne/linksmuar.go b/cmd/cfyne/linksmuar.go new file mode 100644 index 0000000..94e7512 --- /dev/null +++ b/cmd/cfyne/linksmuar.go @@ -0,0 +1,31 @@ +package main + +import ( + com "git.qowevisa.me/Qowevisa/gotell/communication" + "sync" +) + +// Non-Generic variant +type MutexLinksArray struct { + Ar []com.Link + Mu sync.RWMutex +} + +func CreateMutexLinksArray() *MutexLinksArray { + var tmp []com.Link + return &MutexLinksArray{ + Ar: tmp, + } +} + +func (ma *MutexLinksArray) Add(v com.Link) { + ma.Mu.Lock() + ma.Ar = append(ma.Ar, v) + ma.Mu.Unlock() +} + +func (ma *MutexLinksArray) GetArray() []com.Link { + ma.Mu.RLock() + defer ma.Mu.RUnlock() + return ma.Ar +} diff --git a/cmd/cfyne/main.go b/cmd/cfyne/main.go new file mode 100644 index 0000000..0add586 --- /dev/null +++ b/cmd/cfyne/main.go @@ -0,0 +1,648 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "encoding/binary" + "errors" + "fmt" + "log" + "net" + "os" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + + com "git.qowevisa.me/Qowevisa/gotell/communication" + "git.qowevisa.me/Qowevisa/gotell/env" + "git.qowevisa.me/Qowevisa/gotell/extfyne/widgets" +) + +func updateTime(clock *widget.Label) { + formatted := time.Now().Format("Time: 03:04:05") + clock.SetText(formatted) +} + +var globalApp *MutableApplication +var globalCfg *FyneConfig + +var ( + ecdhKeyReceived = false + ecdhKeySent = false +) + +var ( + globalClipboardChannel = make(chan string) +) + +func main() { + tlepCenter.Init() + userCenter.Init() + loadingFileName := env.ServerFullchainFileName + cert, err := os.ReadFile(loadingFileName) + if err != nil { + log.Fatalf("client: load root cert: %s", err) + } + log.Printf("Certificate %s loaded successfully!\n", loadingFileName) + // + roots := x509.NewCertPool() + if ok := roots.AppendCertsFromPEM(cert); !ok { + log.Fatalf("client: failed to parse root certificate") + } + + config := &tls.Config{ + RootCAs: roots, + } + host := "chat.qowevisa.click" + port := 3232 + service := fmt.Sprintf("%s:%d", host, port) + log.Printf("client: connecting to %s", service) + conn, err := tls.Dial("tcp", service, config) + if err != nil { + log.Fatalf("client: dial: %s", err) + } + defer conn.Close() + log.Printf("client: connected to %s", service) + // + + a := app.New() + w := a.NewWindow("gotell-fyne") + // w.Resize(fyne.NewSize(640, 400)) + + // dispatch clipboardChannel + go func(w fyne.Window) { + for val := range globalClipboardChannel { + w.Clipboard().SetContent(val) + } + }(w) + + clock := widget.NewLabel("") + go func() { + for range time.Tick(time.Second) { + updateTime(clock) + } + }() + + btn := widget.NewButtonWithIcon("testBtn", theme.AccountIcon(), func() { + fmt.Println("I'm pressed!") + }) + + mainContainer := container.New(layout.NewVBoxLayout(), clock, btn) + + w.SetContent(mainContainer) + // w.SetContent(widget.NewLabel("Hello World!")) + // w2 := a.NewWindow("Larger") + // w2.SetContent(widget.NewLabel("More content")) + // w2.Resize(fyne.NewSize(100, 100)) + // w2.Show() + intercomFromServer := make(chan *com.Message, 16) + intercomToServer := make(chan *com.Message, 16) + actionsChannel := make(chan int) + cfg := FyneConfig{ + App: a, + Window: w, + } + globalCfg = &cfg + + go readFromServer(conn, intercomFromServer) + go analyzeMessages(intercomFromServer, actionsChannel) + go actOnActions(cfg, actionsChannel, intercomToServer) + go readFromIntercom(intercomToServer, conn) + w.ShowAndRun() +} + +var r com.RegisteredUser +var tmpLink *com.Link +var tmpNick string + +func readFromServer(conn net.Conn, intercom chan *com.Message) { + buf := make([]byte, 70000) + for { + err := conn.SetDeadline(time.Now().Add(1 * time.Minute)) + if err != nil { + log.Printf("ERROR: conn.SetDeadline: %v", err) + continue + } + n, err := conn.Read(buf) + if err != nil { + log.Printf("ERROR: client: read: %s", err) + if errors.Is(err, net.ErrClosed) { + log.Printf("caught ErrClosed!") + } + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + log.Println("caught! Read timeout occurred") + continue + } + return + } + msg, err := com.Decode(buf[:n]) + if err != nil { + log.Printf("ERROR: %#v\n", err) + continue + } + if msg == nil { + continue + } + log.Printf("client: readServer: received message from server: %v", *msg) + switch msg.ID { + case com.ID_SERVER_APPROVE_CLIENT_NICKNAME: + newID := binary.BigEndian.Uint16(msg.Data) + msg.FromID = newID + msg.Data = []byte{} + r.ID = newID + if tmpNick != "" { + r.Name = tmpNick + } + r.IsRegistered = true + case com.ID_SERVER_APPROVE_CLIENT_LINK: + if tmpLink == nil { + continue + } + msg.ToID = tmpLink.UseCount + msg.Data = tmpLink.Data + // Crypto stuff + case com.ID_CLIENT_SEND_CLIENT_ECDH_PUBKEY: + t, err := tlepCenter.GetTLEP(msg.FromID) + if err != nil { + log.Printf("ERROR: tlep: GetTLEP: %v\n", err) + continue + } + err = t.ECDHApplyOtherKeyBytes(msg.Data) + if err != nil { + log.Printf("ERROR: tlep: ECDHApplyOtherKeyBytes: %v\n", err) + continue + } + fromName, err := userCenter.GetName(msg.FromID) + if err != nil { + log.Printf("ERROR: userCenter: GetName: %v\n", err) + } else { + msg.Data = []byte(fromName) + } + case com.ID_CLIENT_SEND_CLIENT_CBES_SPECS: + t, err := tlepCenter.GetTLEP(msg.FromID) + if err != nil { + log.Printf("ERROR: tlep: GetTLEP: %v\n", err) + continue + } + cbes, err := t.DecryptMessageEA(msg.Data) + if err != nil { + log.Printf("ERROR: tlep: DecryptMessageEA: %v\n", err) + continue + } + err = t.CBESSetFromBytes(cbes) + if err != nil { + log.Printf("ERROR: tlep: CBESSetFromBytes: %v\n", err) + continue + } + fromName, err := userCenter.GetName(msg.FromID) + if err != nil { + log.Printf("ERROR: userCenter: GetName: %v\n", err) + } else { + msg.Data = []byte(fromName) + } + // message + case com.ID_CLIENT_SEND_CLIENT_MESSAGE: + t, err := tlepCenter.GetTLEP(msg.FromID) + if err != nil { + log.Printf("ERROR: tlep: GetTLEP: %v\n", err) + continue + } + decrypedMsg, err := t.DecryptMessageAtMax(msg.Data) + if err != nil { + log.Printf("ERROR: tlep: DecryptMessageAtMax: %v\n", err) + continue + } + msg.Data = decrypedMsg + // switch + } + // user stuff + switch msg.ID { + case com.ID_CLIENT_ASK_CLIENT_HANDSHAKE, + com.ID_CLIENT_APPROVE_CLIENT_HANDSHAKE: + userCenter.AddUser(string(msg.Data), msg.FromID) + } + log.Printf("client: readServer: sending message to websocket: %v", *msg) + intercom <- msg + } +} + +func analyzeMessages(intercomFromServer chan *com.Message, actionsChannel chan int) { + for msg := range intercomFromServer { + if msg == nil { + log.Printf("ERROR: msg is nil") + continue + } + log.Printf("Handling %v from server", *msg) + switch msg.ID { + case com.ID_SERVER_ASK_CLIENT_NICKNAME: + actionsChannel <- ACTION_SET_SCENE_NICKNAME + case com.ID_SERVER_APPROVE_CLIENT_NICKNAME: + actionsChannel <- ACTION_SET_SCENE_MAIN + case com.ID_SERVER_DECLINE_CLIENT_NICKNAME: + // TODO + // Link stuff + case com.ID_SERVER_APPROVE_CLIENT_LINK: + globalApp.ArrayBundle.LinksMuAr.Add(*tmpLink) + globalApp.Tabs.LinkTab.Content.Refresh() + case com.ID_SERVER_SEND_CLIENT_ANOTHER_ID: + selTab := globalApp.Tabs.AppTabs.Selected() + id := msg.ToID + userStr := getStdUserName(id, string(msg.Data)) + globalApp.Widgets.UsersSelect.Options = append(globalApp.Widgets.UsersSelect.Options, userStr) + globalApp.Tabs.ConnsNoty++ + globalApp.ArrayBundle.UsersOpts[userStr] = &UserOptions{ + Status: USER_STATUS_VISIBLE, + ToID: id, + } + _, exists := globalApp.ArrayBundle.UserShortcuts[id] + if !exists { + globalApp.ArrayBundle.UserShortcuts[id] = userStr + globalApp.ArrayBundle.UserShortcutsRev[userStr] = id + } + if selTab != globalApp.Tabs.ConnectionTab { + updateTabsBasedOnNotyVals(&globalApp.Tabs) + } + actionsChannel <- ACTION_UPDATE_USER_OPTS_CONTS + case com.ID_CLIENT_ASK_CLIENT_HANDSHAKE: + selTab := globalApp.Tabs.AppTabs.Selected() + id := msg.FromID + userStr := getStdUserName(id, string(msg.Data)) + globalApp.Widgets.UsersSelect.Options = append(globalApp.Widgets.UsersSelect.Options, userStr) + globalApp.Tabs.ConnsNoty++ + globalApp.ArrayBundle.UsersOpts[userStr] = &UserOptions{ + Status: USER_STATUS_HANDSHAKE_INIT, + ToID: id, + } + _, exists := globalApp.ArrayBundle.UserShortcuts[id] + if !exists { + globalApp.ArrayBundle.UserShortcuts[id] = userStr + globalApp.ArrayBundle.UserShortcutsRev[userStr] = id + } + if selTab != globalApp.Tabs.ConnectionTab { + updateTabsBasedOnNotyVals(&globalApp.Tabs) + } + actionsChannel <- ACTION_UPDATE_USER_OPTS_CONTS + case com.ID_CLIENT_APPROVE_CLIENT_HANDSHAKE: + id := msg.FromID + userStr := getStdUserName(id, string(msg.Data)) + var shortcut string + val, exists := globalApp.ArrayBundle.UserShortcuts[id] + if !exists { + globalApp.ArrayBundle.UserShortcuts[id] = userStr + globalApp.ArrayBundle.UserShortcutsRev[userStr] = id + shortcut = userStr + } else { + shortcut = val + } + u, exists := globalApp.ArrayBundle.UsersOpts[shortcut] + if !exists { + log.Printf("WARNING!!: UserOpts[%s] NOT exists!", shortcut) + continue + } + u.Status = USER_STATUS_HANDSHAKE_ACCEPTED + actionsChannel <- ACTION_UPDATE_USER_OPTS_CONTS + case com.ID_CLIENT_SEND_CLIENT_ECDH_PUBKEY: + id := msg.FromID + if ecdhKeySent { + val, exists := globalApp.ArrayBundle.UserShortcuts[id] + if !exists { + log.Printf("WARNING!!: UserShortcuts[%d] NOT exists!", id) + continue + } + u, exists := globalApp.ArrayBundle.UsersOpts[val] + if !exists { + log.Printf("WARNING!!: UserOpts[%s] NOT exists!", val) + continue + } + u.Status = USER_STATUS_ECDH_ESTABLISHED + actionsChannel <- ACTION_UPDATE_USER_OPTS_CONTS + } + ecdhKeyReceived = true + case com.ID_CLIENT_SEND_CLIENT_CBES_SPECS: + id := msg.FromID + val, exists := globalApp.ArrayBundle.UserShortcuts[id] + if !exists { + log.Printf("WARNING!!: UserShortcuts[%d] NOT exists!", id) + continue + } + u, exists := globalApp.ArrayBundle.UsersOpts[val] + if !exists { + log.Printf("WARNING!!: UserOpts[%s] NOT exists!", val) + continue + } + u.Status = USER_STATUS_ECDH_CBES_ESTABLISHED + actionsChannel <- ACTION_UPDATE_USER_OPTS_CONTS + // + log.Printf("Checkin Messages tab") + userStr := getStdUserName(id, string(msg.Data)) + var shortcut string + if !exists { + globalApp.ArrayBundle.UserShortcuts[id] = userStr + shortcut = userStr + } else { + shortcut = val + } + // + haveUser := false + for _, v := range globalApp.Widgets.MessagesSelect.Options { + if v == shortcut { + haveUser = true + break + } + } + log.Printf("MessagesSelect.Options1 are %v\n", globalApp.Widgets.MessagesSelect.Options) + log.Printf("have user is : %t\n", haveUser) + if !haveUser { + log.Printf("MessagesSelect.Options1.5 shortcut is '%s'\n", shortcut) + // LOL wtf + // globalApp.Widgets.MessagesSelect.Options = append(globalApp.Widgets.UsersSelect.Options, shortcut) + globalApp.Widgets.MessagesSelect.Options = append(globalApp.Widgets.MessagesSelect.Options, shortcut) + log.Printf("MessagesSelect.Options2 are %v\n", globalApp.Widgets.MessagesSelect.Options) + globalApp.Tabs.UsersNoty++ + globalApp.Widgets.MessagesSelect.Refresh() + updateTabsBasedOnNotyVals(&globalApp.Tabs) + } + case com.ID_CLIENT_SEND_CLIENT_MESSAGE: + id := msg.FromID + shortcut, exists := globalApp.ArrayBundle.UserShortcuts[id] + if !exists { + log.Printf("WARNING: UserShortcuts[%d] NOT exists!", id) + continue + } + msgs, exists := globalApp.ArrayBundle.Messages[shortcut] + mBoardMsg := widgets.MBoardMessage{ + LeftAlign: true, + Data: widget.NewLabel(string(msg.Data)), + } + if !exists { + tmp := CreateMutexArray[widgets.MBoardMessage]() + tmp.Add(mBoardMsg) + globalApp.ArrayBundle.Messages[shortcut] = &tmp + globalApp.Widgets.MBoardCaretacker.Refresh() + globalApp.Tabs.UsersTab.Content.Refresh() + } else { + msgs.Add(mBoardMsg) + globalApp.Widgets.MBoardCaretacker.Refresh() + globalApp.Tabs.UsersTab.Content.Refresh() + } + haveUser := false + for _, v := range globalApp.Widgets.MessagesSelect.Options { + if v == shortcut { + haveUser = true + break + } + } + if !haveUser { + globalApp.Widgets.MessagesSelect.Options = append(globalApp.Widgets.MessagesSelect.Options, shortcut) + globalApp.Tabs.UsersNoty++ + updateTabsBasedOnNotyVals(&globalApp.Tabs) + globalApp.Widgets.MessagesSelect.Refresh() + } + // + mBoard, exists := globalApp.Widgets.MBoardMap[shortcut] + if exists { + mBoard.Add(mBoardMsg) + } + // TODO + default: + log.Printf("Can not handle msg with id %d", msg.ID) + log.Printf("Msg is different : %v", *msg) + } + } +} + +func actOnActions(cfg FyneConfig, actionsChannel chan int, intercomToServer chan *com.Message) { + for action := range actionsChannel { + log.Printf("Receive action %d", action) + switch action { + case ACTION_SET_SCENE_NICKNAME: + // cfg.Window.Content() + scene := GetNicknameScene(intercomToServer) + cfg.Window.Resize(fyne.NewSize(640, 400)) + cfg.Window.SetContent(scene) + case ACTION_SET_SCENE_MAIN: + scene, app := GetMainScene(intercomToServer, MainSceneConfig{ + UserNickcname: tmpNick, + UserNameSet: true, + UserId: r.ID, + UserIdSet: true, + }) + globalApp = app + globalApp.App.App = cfg.App + globalApp.App.Window = cfg.Window + // shortcuts + ctrl1 := &desktop.CustomShortcut{KeyName: fyne.Key1, Modifier: fyne.KeyModifierControl} + ctrl2 := &desktop.CustomShortcut{KeyName: fyne.Key2, Modifier: fyne.KeyModifierControl} + ctrl3 := &desktop.CustomShortcut{KeyName: fyne.Key3, Modifier: fyne.KeyModifierControl} + cfg.Window.Canvas().AddShortcut(ctrl1, func(shortcut fyne.Shortcut) { + app.Tabs.AppTabs.Select(app.Tabs.LinkTab) + }) + cfg.Window.Canvas().AddShortcut(ctrl2, func(shortcut fyne.Shortcut) { + app.Tabs.AppTabs.Select(app.Tabs.ConnectionTab) + }) + cfg.Window.Canvas().AddShortcut(ctrl3, func(shortcut fyne.Shortcut) { + app.Tabs.AppTabs.Select(app.Tabs.UsersTab) + }) + // cfg.Window.Resize(fyne.NewSize(640, 400)) + cfg.Window.SetContent(scene) + // ACTION_UPDATE_USER_OPTS_CONTS + case ACTION_UPDATE_USER_OPTS_CONTS: + for _, u := range globalApp.ArrayBundle.UsersOpts { + switch u.Status { + case USER_STATUS_VISIBLE: + btn := widget.NewButton("Init Handhake", func() {}) + btn.OnTapped = func() { + intercomToServer <- &com.Message{ + ID: com.ID_CLIENT_ASK_CLIENT_HANDSHAKE, + ToID: u.ToID, + } + btn.Hide() + } + u.OptsContainer = container.NewGridWithRows(1, btn) + cTaker := globalApp.Widgets.UserOptsCaretaker + cTaker.RemoveAll() + cTaker.Add(u.OptsContainer) + case USER_STATUS_HANDSHAKE_INIT: + btn := widget.NewButton("Accept Handhake", func() {}) + btn.OnTapped = func() { + intercomToServer <- &com.Message{ + ID: com.ID_CLIENT_APPROVE_CLIENT_HANDSHAKE, + ToID: u.ToID, + } + btn.Hide() + u.Status = USER_STATUS_HANDSHAKE_ACCEPTED + actionsChannel <- ACTION_UPDATE_USER_OPTS_CONTS + } + u.OptsContainer = container.NewGridWithRows(1, btn) + cTaker := globalApp.Widgets.UserOptsCaretaker + cTaker.RemoveAll() + cTaker.Add(u.OptsContainer) + case USER_STATUS_HANDSHAKE_ACCEPTED: + btn := widget.NewButton("Send ECDH PubKey", func() {}) + btn.OnTapped = func() { + intercomToServer <- &com.Message{ + ID: com.ID_CLIENT_SEND_CLIENT_ECDH_PUBKEY, + ToID: u.ToID, + } + btn.Hide() + if ecdhKeyReceived { + u.Status = USER_STATUS_ECDH_ESTABLISHED + actionsChannel <- ACTION_UPDATE_USER_OPTS_CONTS + } + ecdhKeySent = true + } + u.OptsContainer = container.NewGridWithRows(1, btn) + cTaker := globalApp.Widgets.UserOptsCaretaker + cTaker.RemoveAll() + cTaker.Add(u.OptsContainer) + case USER_STATUS_ECDH_ESTABLISHED: + btn := widget.NewButton("Send CBES Specs", func() {}) + btn.OnTapped = func() { + intercomToServer <- &com.Message{ + ID: com.ID_CLIENT_SEND_CLIENT_CBES_SPECS, + ToID: u.ToID, + } + btn.Hide() + } + u.OptsContainer = container.NewGridWithRows(1, btn) + cTaker := globalApp.Widgets.UserOptsCaretaker + cTaker.RemoveAll() + cTaker.Add(u.OptsContainer) + case USER_STATUS_ECDH_CBES_ESTABLISHED: + btn := widget.NewButton("TBC", func() {}) + btn.OnTapped = func() { + log.Println("TODO:: 1001") + } + u.OptsContainer = container.NewGridWithRows(1, btn) + cTaker := globalApp.Widgets.UserOptsCaretaker + cTaker.RemoveAll() + cTaker.Add(u.OptsContainer) + default: + } + } + // globalApp.Tabs.ConnectionTab.Content.Refresh() + // ACTION_UPDATE_USER_OPTS_CONTS + default: + } + } +} + +func readFromIntercom(intercom chan *com.Message, conn net.Conn) { + for msg := range intercom { + log.Printf("client: received message from Intercom: %v", msg) + msg.Version = com.V1 + switch msg.ID { + case com.ID_CLIENT_SEND_SERVER_NICKNAME: + tmpNick = string(msg.Data) + case com.ID_CLIENT_SEND_SERVER_LINK: + if !r.IsRegistered { + continue + } + l, err := r.GenerateLink(msg.ToID) + if err != nil { + log.Printf("Error: link: %v", err) + continue + } + tmpLink = &l + answ, err := com.ClientSendServerLink(r.ID, l) + if err != nil { + log.Printf("Error: com: %v", err) + continue + } + log.Printf("client: readWS: sending data to server: %v", answ) + conn.Write(answ) + continue + case com.ID_CLIENT_ASK_CLIENT_HANDSHAKE, + com.ID_CLIENT_APPROVE_CLIENT_HANDSHAKE, + com.ID_CLIENT_DECLINE_CLIENT_HANDSHAKE, + com.ID_CLIENT_SEND_CLIENT_ECDH_PUBKEY, + com.ID_CLIENT_SEND_CLIENT_CBES_SPECS, + com.ID_CLIENT_SEND_CLIENT_MKLG_FINGERPRINT, + com.ID_CLIENT_DECLINE_CLIENT_MKLG_FINGERPRINT, + com.ID_CLIENT_SEND_CLIENT_MESSAGE: + if !r.IsRegistered { + continue + } + msg.FromID = r.ID + // switch + } + // user stuff + switch msg.ID { + case com.ID_CLIENT_ASK_CLIENT_HANDSHAKE, + com.ID_CLIENT_APPROVE_CLIENT_HANDSHAKE: + if r.IsRegistered { + msg.Data = []byte(r.Name) + } + } + // Crypto stuff + switch msg.ID { + case com.ID_CLIENT_ASK_CLIENT_HANDSHAKE, + com.ID_CLIENT_APPROVE_CLIENT_HANDSHAKE: + err := tlepCenter.AddTLEP(msg.ToID, fmt.Sprintf("%s-%d", r.Name, msg.ToID)) + if err != nil { + log.Printf("ERROR: tlepCenter.AddUser: %v\n", err) + } + case com.ID_CLIENT_SEND_CLIENT_ECDH_PUBKEY: + t, err := tlepCenter.GetTLEP(msg.ToID) + if err != nil { + log.Printf("ERROR: tlep: GetTLEP: %v\n", err) + continue + } + key, err := t.ECDHGetPublicKey() + if err != nil { + log.Printf("ERROR: tlep: ECDHGetPublicKey: %v\n", err) + continue + } + msg.Data = key + case com.ID_CLIENT_SEND_CLIENT_CBES_SPECS: + t, err := tlepCenter.GetTLEP(msg.ToID) + if err != nil { + log.Printf("ERROR: tlep: GetTLEP: %v\n", err) + continue + } + err = t.CBESInitRandom() + if err != nil { + log.Printf("ERROR: tlep: CBESInitRandom: %v\n", err) + continue + } + cbes, err := t.CBESGetBytes() + if err != nil { + log.Printf("ERROR: tlep: ECDHGetPublicKey: %v\n", err) + continue + } + cbesEAEncr, err := t.EncryptMessageEA(cbes) + if err != nil { + log.Printf("ERROR: tlep: EncryptMessageEA: %v\n", err) + continue + } + msg.Data = cbesEAEncr + // message + case com.ID_CLIENT_SEND_CLIENT_MESSAGE: + t, err := tlepCenter.GetTLEP(msg.ToID) + if err != nil { + log.Printf("ERROR: tlep: GetTLEP: %v\n", err) + continue + } + encrypedMsg, err := t.EncryptMessageAtMax(msg.Data) + if err != nil { + log.Printf("ERROR: tlep: EncryptMessageAtMax: %v\n", err) + continue + } + msg.Data = encrypedMsg + // switch + } + encodedMsg, err := msg.Bytes() + if err != nil { + log.Printf("Encoding error: %s", err) + continue + } + log.Printf("client: readWS: sending data to server: %v", encodedMsg) + conn.Write(encodedMsg) + } +} diff --git a/cmd/cfyne/scenes.go b/cmd/cfyne/scenes.go new file mode 100644 index 0000000..f7fb00c --- /dev/null +++ b/cmd/cfyne/scenes.go @@ -0,0 +1,377 @@ +package main + +import ( + "errors" + "fmt" + "log" + "strconv" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + + // "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + com "git.qowevisa.me/Qowevisa/gotell/communication" + "git.qowevisa.me/Qowevisa/gotell/extfyne/layouts" + "git.qowevisa.me/Qowevisa/gotell/extfyne/widgets" +) + +func GetNicknameScene(intercom chan *com.Message) *fyne.Container { + // button := widget.NewButtonWithIcon( + // "Send", + // theme.ConfirmIcon(), + // getSendMessageFuncToIntercom(intercom, com.ID_CLIENT_SEND_SERVER_NICKNAME, []byte("test"))) + entry := widget.NewEntry() + entry.SetPlaceHolder("Enter Nickname:") + btn := widget.NewButtonWithIcon("Send", theme.MailSendIcon(), func() { + log.Println("Nickname submitted:", entry.Text) + intercom <- &com.Message{ + ID: com.ID_CLIENT_SEND_SERVER_NICKNAME, + Data: []byte(entry.Text), + } + }) + contentInner := container.New(layout.NewVBoxLayout(), entry, btn) + return container.NewGridWithRows(3, + layout.NewSpacer(), + container.NewGridWithColumns(3, + layout.NewSpacer(), + contentInner, + layout.NewSpacer(), + ), + layout.NewSpacer(), + ) +} + +func GetMainScene(intercom chan *com.Message, cfg MainSceneConfig) (*fyne.Container, *MutableApplication) { + headerWidget := widget.NewLabel(fmt.Sprintf("Hello, %s! Your ID: %d", cfg.UserNickcname, cfg.UserId)) + header := container.NewGridWithColumns(3, + layout.NewSpacer(), + headerWidget, + layout.NewSpacer(), + ) + + linksSS, linksArr := GetLinksSubScene(intercom, header) + linksTab := container.NewTabItemWithIcon("Links", theme.MailComposeIcon(), linksSS) + conSS, conBundle, conWidgets := GetConnectionsSubScene(intercom, header) + connsTab := container.NewTabItemWithIcon("Connections", theme.ComputerIcon(), conSS) + userSS, userBundle, userWidgets := GetUsersSubScene(intercom, header, conBundle.UserShortcutsRev) + usersTab := container.NewTabItemWithIcon("Users", theme.AccountIcon(), userSS) + tabs := container.NewAppTabs( + linksTab, + connsTab, + usersTab, + ) + tabs.SetTabLocation(container.TabLocationLeading) + + return container.NewGridWithColumns(1, + tabs), + &MutableApplication{ + Tabs: MutableStructAboutTabs{ + AppTabs: tabs, + LinkTab: linksTab, + LinksBT: "Links", + LinksNoty: 0, + ConnectionTab: connsTab, + ConnsBT: "Connections", + ConnsNoty: 0, + UsersTab: usersTab, + UsersBT: "Users", + UsersNoty: 0, + }, + ArrayBundle: BundleOfMutexArrays{ + LinksMuAr: linksArr, + UsersMuAr: conBundle.UsersMuAr, + UsersOpts: conBundle.UsersOpts, + UserShortcuts: conBundle.UserShortcuts, + UserShortcutsRev: conBundle.UserShortcutsRev, + Messages: userBundle.Messages, + }, + Widgets: MutableStructAboutWidgets{ + UsersSelect: conWidgets.UsersSelect, + UserOptsCaretaker: conWidgets.UserOptsCaretaker, + MessagesSelect: userWidgets.MessagesSelect, + MBoardCaretacker: userWidgets.MBoardCaretacker, + MsgButton: userWidgets.MsgButton, + MBoardMap: userWidgets.MBoardMap, + }, + } + +} + +func GetLinksSubScene(intercom chan *com.Message, header *fyne.Container) (*fyne.Container, *MutexLinksArray) { + entry := widget.NewEntry() + entry.SetPlaceHolder("Count:") + entry.Validator = func(s string) error { + val, err := strconv.ParseUint(s, 10, 16) + if err == nil && val == 0 { + return errors.New("Use count can't be 0") + } + return err + } + btn := widget.NewButtonWithIcon("Generate", theme.MailSendIcon(), func() { + count, err := strconv.ParseUint(entry.Text, 10, 16) + if err != nil || count == 0 { + log.Printf("dafuq: GetLinksSubScene: %v ; %d\n", err, count) + return + } + intercom <- &com.Message{ + ID: com.ID_CLIENT_SEND_SERVER_LINK, + ToID: uint16(count), + } + }) + entry.SetOnValidationChanged(func(err error) { + if err != nil { + btn.Hide() + } else { + if btn.Hidden { + btn.Show() + } + } + }) + contentInner := container.NewGridWithColumns(2, entry, btn) + // / + linksMutAt := CreateMutexLinksArray() + linksList := widget.NewList(func() int { + return len(linksMutAt.GetArray()) + }, func() fyne.CanvasObject { + return widget.NewLabel("template") + }, func(lii widget.ListItemID, co fyne.CanvasObject) { + link := linksMutAt.Ar[lii] + str := fmt.Sprintf("Count: %d ; %s", link.UseCount, link.Data) + co.(*widget.Label).SetText(str) + }, + ) + linksList.OnSelected = func(id widget.ListItemID) { + link := linksMutAt.Ar[id] + globalClipboardChannel <- string(link.Data) + } + // // + linksGetEntry := widget.NewEntry() + linksGetEntry.SetPlaceHolder("Link:") + linksGetEntry.Validator = func(s string) error { + validated, err := com.IsThisALinkData(s) + if err != nil { + return err + } + if !validated { + return errors.New("Link is not validated") + } + return nil + } + linksGetBtn := widget.NewButtonWithIcon("Get", theme.MailReplyIcon(), func() { + link := linksGetEntry.Text + validated, err := com.IsThisALinkData(link) + if err != nil { + log.Printf("linksGetBtn: Error: %v\n", err) + return + } + if !validated { + log.Printf("linksGetBtn: Error: link %v not validated\n", link) + return + } + intercom <- &com.Message{ + ID: com.ID_CLIENT_ASK_SERVER_LINK, + Data: []byte(link), + } + }) + linksGetEntry.SetOnValidationChanged(func(err error) { + if err != nil { + linksGetBtn.Hide() + } else { + if linksGetBtn.Hidden { + linksGetBtn.Show() + } + } + }) + contentInner2 := container.NewGridWithColumns(2, linksGetEntry, linksGetBtn) + // / + // // + return container.NewGridWithRows(3, + header, + container.New( + layouts.NewVariableGridWithColumns( + 6, []int{2, 1, 3}), + container.NewGridWithRows(3, + container.NewGridWithColumns(3, + layout.NewSpacer(), + widget.NewLabel("Create link:"), + layout.NewSpacer()), + contentInner, + layout.NewSpacer(), + ), + layout.NewSpacer(), + linksList, + ), + container.New( + layouts.NewVariableGridWithColumns( + 6, []int{2, 4}), + container.NewGridWithRows(3, + container.NewGridWithColumns(3, + layout.NewSpacer(), + widget.NewLabel("Get user from link:"), + layout.NewSpacer()), + contentInner2, + layout.NewSpacer(), + ), + layout.NewSpacer(), + ), + ), linksMutAt +} + +func GetConnectionsSubScene(intercom chan *com.Message, header *fyne.Container) (*fyne.Container, BundleOfMutexArrays, MutableStructAboutWidgets) { + optionsHeaderLabel := widget.NewLabel("Options for user: ") + usersMuAr := CreateMutexStringArray() + usersOptions := make(map[string]*UserOptions) + userShortcuts := make(map[uint16]string) + userShortcutsRev := make(map[string]uint16) + userOptsCaretaker := container.NewGridWithRows(1) + optsContainer := container.NewGridWithRows(2, + optionsHeaderLabel, + userOptsCaretaker, + ) + userSelect := widget.NewSelect(usersMuAr.Ar, func(s string) { + optionsHeaderLabel.SetText(fmt.Sprintf("Options for user: %s", s)) + uOpts, exists := usersOptions[s] + if !exists { + log.Printf("GetConnectionsSubScene::1 : TODO!!\n") + // TODO + return + } + userOptsCaretaker.RemoveAll() + userOptsCaretaker.Add(uOpts.OptsContainer) + optsContainer.Refresh() + }) + + contentInner := container.New(layout.NewVBoxLayout(), userSelect, optsContainer) + return container.New( + layout.NewVBoxLayout(), + header, + container.New( + layout.NewHBoxLayout(), + layout.NewSpacer(), + contentInner, + layout.NewSpacer(), + ), + ), BundleOfMutexArrays{ + UsersMuAr: usersMuAr, + UsersOpts: usersOptions, + UserShortcuts: userShortcuts, + UserShortcutsRev: userShortcutsRev, + }, MutableStructAboutWidgets{ + UsersSelect: userSelect, + UserOptsCaretaker: userOptsCaretaker, + } +} + +func GetUsersSubScene(intercom chan *com.Message, header *fyne.Container, userShortcutsRev map[string]uint16) (*fyne.Container, BundleOfMutexArrays, MutableStructAboutWidgets) { + msgHeader := widget.NewLabel("Messages With User: ") + usersMessages := make(map[string]*MutexArray[widgets.MBoardMessage]) + usersMBoards := make(map[string]*widgets.MessageBoard) + msgEntry := widget.NewEntry() + msgEntry.SetPlaceHolder("Message...") + userMsgSendBtn := widget.NewButtonWithIcon("Send", theme.MailSendIcon(), func() {}) + mBoardCareTaker := container.NewGridWithRows(1) + userMsgsSelect := widget.NewSelect([]string{}, func(s string) { + msgHeader.SetText(fmt.Sprintf("Messages With User: %s", s)) + msgsMuAr, exists := usersMessages[s] + if !exists { + tmp := CreateMutexArray[widgets.MBoardMessage]() + usersMessages[s] = &tmp + msgsMuAr = &tmp + return + } + mBoardCareTaker.RemoveAll() + mmBoard := widgets.NewMessageBoard(msgsMuAr.Ar) + usersMBoards[s] = mmBoard + mBoardCareTaker.Add(mmBoard) + mBoardCareTaker.Refresh() + id, exists := userShortcutsRev[s] + if !exists { + log.Printf("GetUsersSubScene::2 : TODO!!\n") + // TODO + return + } + userMsgSendBtn.Text = fmt.Sprintf("%s", s) + userMsgSendBtn.OnTapped = func() { + intercom <- &com.Message{ + ID: com.ID_CLIENT_SEND_CLIENT_MESSAGE, + ToID: id, + Data: []byte(msgEntry.Text), + } + msg := widgets.MBoardMessage{ + LeftAlign: false, + Data: widget.NewLabel(msgEntry.Text), + } + msgsMuAr.Add(msg) + mmBoard.Add(msg) + msgEntry.Text = "" + msgEntry.Refresh() + mBoardCareTaker.Refresh() + mmBoard.Refresh() + } + userMsgSendBtn.Refresh() + }) + + // entryWithButton := container.NewHBox( + // layout.NewSpacer(), + // container.NewPadded(msgEntry), + // userMsgSendBtn, + // ) + entryWithButton := container.New( + layouts.NewEntryBtn7030(), + msgEntry, + userMsgSendBtn, + ) + fs := globalCfg.Window.Canvas().Size() + + scene := container.New( + layout.NewVBoxLayout(), + header, + container.NewGridWithColumns(2, + container.New( + layout.NewVBoxLayout(), + layout.NewSpacer(), + container.New( + layout.NewHBoxLayout(), + userMsgsSelect, + msgHeader, + ), + ), + container.New( + layout.NewVBoxLayout(), + container.New( + layouts.NewFullWidthWithSize(fyne.NewSize(0, fs.Height*0.8)), + mBoardCareTaker, + ), + layout.NewSpacer(), + entryWithButton, + ), + ), + ) + // container.New( + // layouts.NewVariableGridWithRows(3, []int{1, 2}), + // header, + // container.New( + // layouts.NewVariableGridWithColumns(4, []int{1, 3}), + // container.NewGridWithColumns(3, + // layout.NewSpacer(), + // container.NewVBox(userMsgsSelect, msgHeader), + // layout.NewSpacer()), + // container.New( + // layouts.NewVariableGridWithRows(6, []int{5, 1}), + // mBoardCareTaker, + // container.NewHBox(msgEntry, userMsgSendBtn), + // ), + // ), + // ) + return scene, BundleOfMutexArrays{ + Messages: usersMessages, + }, MutableStructAboutWidgets{ + MessagesSelect: userMsgsSelect, + MBoardCaretacker: mBoardCareTaker, + MsgButton: userMsgSendBtn, + MBoardMap: usersMBoards, + } +} diff --git a/cmd/cfyne/tleps.go b/cmd/cfyne/tleps.go new file mode 100644 index 0000000..8f9ed76 --- /dev/null +++ b/cmd/cfyne/tleps.go @@ -0,0 +1,66 @@ +package main + +import ( + "errors" + "log" + "sync" + + "git.qowevisa.me/Qowevisa/gotell/gmyerr" + "git.qowevisa.me/Qowevisa/gotell/tlep" +) + +var ( + tlepCenter TlepCenter +) + +type TlepCenter struct { + TLEPs map[uint16]*tlep.TLEP + Mu sync.Mutex +} + +func (t *TlepCenter) Init() { + t.TLEPs = make(map[uint16]*tlep.TLEP) +} + +var ( + ERROR_ALREADY_HAVE = errors.New("Already taken") + ERROR_DONT_HAVE = errors.New("Not found") +) + +func (t *TlepCenter) AddTLEP(id uint16, name string) error { + t.Mu.Lock() + defer t.Mu.Unlock() + _, alreadyHave := t.TLEPs[id] + if alreadyHave { + return ERROR_ALREADY_HAVE + } + val, err := tlep.InitTLEP(name) + if err != nil { + return gmyerr.WrapPrefix("tlep.InitTLEP", err) + } + val.Debug = true + t.TLEPs[id] = val + log.Printf("TLEPs: add %p for %d id\n", val, id) + return nil +} + +func (t *TlepCenter) DeleteIfHaveOne(id uint16) { + t.Mu.Lock() + defer t.Mu.Unlock() + val, found := t.TLEPs[id] + if !found { + log.Printf("TLEP with %d id is not found; Can not delete\n", id) + return + } + delete(t.TLEPs, id) + log.Printf("TLEP with %v val and %d id was found; TLEP is deleted\n", val, id) +} + +func (t *TlepCenter) GetTLEP(id uint16) (*tlep.TLEP, error) { + log.Printf("Getting tlep by id = %d\n", id) + name, have := t.TLEPs[id] + if !have { + return nil, ERROR_DONT_HAVE + } + return name, nil +} diff --git a/cmd/cfyne/types.go b/cmd/cfyne/types.go new file mode 100644 index 0000000..9fe1d6c --- /dev/null +++ b/cmd/cfyne/types.go @@ -0,0 +1,125 @@ +package main + +import ( + "sync" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" + "git.qowevisa.me/Qowevisa/gotell/extfyne/widgets" + // com "git.qowevisa.me/Qowevisa/gotell/communication" +) + +type FyneConfig struct { + App fyne.App + Window fyne.Window +} + +type MainSceneConfig struct { + UserNickcname string + // Just for my calmness + UserNameSet bool + UserId uint16 + // Just for my calmness + UserIdSet bool +} + +type MutableStructAboutTabs struct { + AppTabs *container.AppTabs + LinkTab *container.TabItem + LinksBT string + LinksNoty uint + ConnectionTab *container.TabItem + ConnsBT string + ConnsNoty uint + UsersTab *container.TabItem + UsersBT string + UsersNoty uint +} + +type MutableStructAboutWidgets struct { + UsersSelect *widget.Select + UserOptsCaretaker *fyne.Container + MessagesSelect *widget.Select + MBoardCaretacker *fyne.Container + MsgButton *widget.Button + MBoardMap map[string]*widgets.MessageBoard +} + +type UserOptions struct { + OptsContainer *fyne.Container + Status int + ToID uint16 + Name string +} + +type BundleOfMutexArrays struct { + LinksMuAr *MutexLinksArray + UsersMuAr *MutexStringArray + UsersOpts map[string]*UserOptions + UserShortcuts map[uint16]string + UserShortcutsRev map[string]uint16 + Messages map[string]*MutexArray[widgets.MBoardMessage] +} + +type MutableApp struct { + App fyne.App + Window fyne.Window +} + +type MutableApplication struct { + Tabs MutableStructAboutTabs + Widgets MutableStructAboutWidgets + ArrayBundle BundleOfMutexArrays + App MutableApp +} + +// I don't know if it is a good idea +type MutexArray[T any] struct { + Ar []T + Mu sync.RWMutex +} + +func CreateMutexArray[T any]() MutexArray[T] { + var tmp []T + return MutexArray[T]{ + Ar: tmp, + } +} + +func (ma *MutexArray[T]) Add(v T) { + ma.Mu.Lock() + ma.Ar = append(ma.Ar, v) + ma.Mu.Unlock() +} + +func (ma *MutexArray[T]) GetArray() []T { + ma.Mu.RLock() + defer ma.Mu.RUnlock() + return ma.Ar +} + +// Non-Generic variant +type MutexStringArray struct { + Ar []string + Mu sync.RWMutex +} + +func CreateMutexStringArray() *MutexStringArray { + var tmp []string + return &MutexStringArray{ + Ar: tmp, + } +} + +func (ma *MutexStringArray) Add(v string) { + ma.Mu.Lock() + ma.Ar = append(ma.Ar, v) + ma.Mu.Unlock() +} + +func (ma *MutexStringArray) GetArray() []string { + ma.Mu.RLock() + defer ma.Mu.RUnlock() + return ma.Ar +} diff --git a/cmd/cfyne/uset_statuses.go b/cmd/cfyne/uset_statuses.go new file mode 100644 index 0000000..72fe9b8 --- /dev/null +++ b/cmd/cfyne/uset_statuses.go @@ -0,0 +1,36 @@ +package main + +import ( +// "log" +// +// "fyne.io/fyne/v2" +// "fyne.io/fyne/v2/container" +// "fyne.io/fyne/v2/widget" +// com "git.qowevisa.me/Qowevisa/gotell/communication" +) + +const ( + USER_STATUS_NOT_VISIBLE = 0 + iota + USER_STATUS_VISIBLE + USER_STATUS_HANDSHAKE_INIT + USER_STATUS_HANDSHAKE_ACCEPTED + USER_STATUS_ECDH_ESTABLISHED + USER_STATUS_ECDH_CBES_ESTABLISHED + USER_STATUS_ECDH_CBES_MKLG_ESTABLISHED +) + +// func getUserOptsContainer(userStatus int, intercom chan *com.Message) *fyne.Container { +// switch { +// case USER_STATUS_HANDSHAKE_INIT: +// btn := widget.NewButton("Init Hadnshake", func() { +// intercom <- &com.Message{ +// ID: ID_CLIENT_ASK_CLIENT_HANDSHAKE, +// ToID: , +// } +// }) +// return container.NewGridWithRows(1, ) +// default: +// log.Printf("ERROR: getUserOptsContainer: status %d was not handled!", userStatus) +// return container.NewGridWithRows(1, widget.NewLabel("ERROR: 500")) +// } +// } diff --git a/cmd/cfyne/util.go b/cmd/cfyne/util.go new file mode 100644 index 0000000..c78ec9f --- /dev/null +++ b/cmd/cfyne/util.go @@ -0,0 +1,18 @@ +package main + +import "fmt" + +func getStdUserName(userID uint16, userName string) string { + return fmt.Sprintf("User: %s; ID: %d", userName, userID) +} + +func getTabNameBasedOnBaseTextAndNoty(baseText string, noty uint) string { + return fmt.Sprintf("%s (%d)", baseText, noty) +} + +func updateTabsBasedOnNotyVals(tabs *MutableStructAboutTabs) { + tabs.LinkTab.Text = getTabNameBasedOnBaseTextAndNoty(tabs.LinksBT, tabs.LinksNoty) + tabs.ConnectionTab.Text = getTabNameBasedOnBaseTextAndNoty(tabs.ConnsBT, tabs.ConnsNoty) + tabs.UsersTab.Text = getTabNameBasedOnBaseTextAndNoty(tabs.UsersBT, tabs.UsersNoty) + tabs.AppTabs.Refresh() +}