diff --git a/dfssc/sign.go b/dfssc/sign.go index f5df004df1fb04cfb81d35fe5030318fb19704da..aff20e51ad683fea43f5c848dd34824e02f64cba 100644 --- a/dfssc/sign.go +++ b/dfssc/sign.go @@ -10,6 +10,8 @@ import ( func signContract(args []string) { filename := args[0] fmt.Println("You are going to sign the following contract:") + showContract(args) + contract := getContract(filename) if contract == nil { os.Exit(1) @@ -24,6 +26,8 @@ func signContract(args []string) { fmt.Fprintln(os.Stderr, err) os.Exit(2) } + + manager.OnSignerStatusUpdate = signFeedbackFn err = manager.ConnectToPeers() if err != nil { fmt.Fprintln(os.Stderr, err) @@ -49,6 +53,7 @@ func signContract(args []string) { fmt.Println("Everybody is ready, starting the signature", signatureUUID) // Signature + manager.OnProgressUpdate = signProgressFn err = manager.Sign() if err != nil { fmt.Fprintln(os.Stderr, err) @@ -64,3 +69,13 @@ func signContract(args []string) { fmt.Println("Signature complete! See .proof file for evidences.") } + +func signFeedbackFn(mail string, status sign.SignerStatus, data string) { + if status == sign.StatusConnecting { + fmt.Println("- Trying to connect with", mail, "/", data) + } else if status == sign.StatusConnected { + fmt.Println(" Successfully connected!", "[", data, "]") + } +} + +func signProgressFn(current int, max int) {} diff --git a/dfssc/sign/protocol.go b/dfssc/sign/protocol.go index d0f944c7c40630f232ca88d2766f88a36b0877ad..2165cc737e3b6719b3e8c97095a4d96b14e249d9 100644 --- a/dfssc/sign/protocol.go +++ b/dfssc/sign/protocol.go @@ -29,12 +29,15 @@ func (m *SignatureManager) Sign() error { // Cooldown delay, let other clients wake-up their channels time.Sleep(time.Second) + seqLen := len(m.sequence) + // Promess rounds // Follow the sequence until there is no next occurence of me for m.currentIndex >= 0 { + m.OnProgressUpdate(m.currentIndex, seqLen+1) dAPI.DLog("starting round at index [" + fmt.Sprintf("%d", m.currentIndex) + "] with nextIndex=" + fmt.Sprintf("%d", nextIndex)) - // Set of the promise we are waiting for + // Set of promises we are waiting for var pendingSet []uint32 pendingSet, err = common.GetPendingSet(m.sequence, myID, m.currentIndex) if err != nil { @@ -58,6 +61,7 @@ func (m *SignatureManager) Sign() error { } } + m.OnProgressUpdate(seqLen, seqLen+1) dAPI.DLog("entering signature round") // Signature round err = m.ExchangeAllSignatures() @@ -65,6 +69,7 @@ func (m *SignatureManager) Sign() error { return err } dAPI.DLog("exiting signature round") + m.OnProgressUpdate(seqLen+1, seqLen+1) // Network's job is done, cleaning time // Shutdown and platform client and TODO peer server & connections @@ -125,7 +130,7 @@ func (m *SignatureManager) promiseRound(pendingSet, sendSet []uint32, myID uint3 // Verifying we sent all the due promises for range sendSet { - _ = <-c + <-c } } diff --git a/dfssc/sign/starter.go b/dfssc/sign/starter.go index 42978b55c840beefbb4a5c864e8a69baf7bbcbb1..23a86ec203981010bbba76dee363ed9d90111490 100644 --- a/dfssc/sign/starter.go +++ b/dfssc/sign/starter.go @@ -42,6 +42,10 @@ type SignatureManager struct { keyHash [][]byte mail string archives *Archives + + // Callbacks + OnSignerStatusUpdate func(mail string, status SignerStatus, data string) + OnProgressUpdate func(current int, end int) } // Archives stores the received and sent messages, as evidence if needed @@ -139,10 +143,11 @@ func (m *SignatureManager) addPeer(user *pAPI.User) (ready bool, err error) { } addrPort := user.Ip + ":" + strconv.Itoa(int(user.Port)) - fmt.Println("- Trying to connect with", user.Email, "/", addrPort) + m.OnSignerStatusUpdate(user.Email, StatusConnecting, addrPort) conn, err := net.Connect(addrPort, m.auth.Cert, m.auth.Key, m.auth.CA) if err != nil { + m.OnSignerStatusUpdate(user.Email, StatusError, err.Error()) return false, err } @@ -158,12 +163,13 @@ func (m *SignatureManager) addPeer(user *pAPI.User) (ready bool, err error) { defer cancel() msg, err := client.Discover(ctx, &cAPI.Hello{Version: dfss.Version}) if err != nil { + m.OnSignerStatusUpdate(user.Email, StatusError, err.Error()) return false, err } // Printing answer: application version // TODO check certificate - fmt.Println(" Successfully connected!", "[", msg.Version, "]") + m.OnSignerStatusUpdate(user.Email, StatusConnected, msg.Version) // Check if we have any other peer to connect to if lastConnection == nil { diff --git a/dfssc/sign/status.go b/dfssc/sign/status.go new file mode 100644 index 0000000000000000000000000000000000000000..4d1d83544f38f4b18e14b40ba13a97c7778d9635 --- /dev/null +++ b/dfssc/sign/status.go @@ -0,0 +1,12 @@ +package sign + +// SignerStatus represents the current state of a signer. +type SignerStatus int + +// These constants represent the different states of a signer. +const ( + StatusWaiting SignerStatus = iota + StatusConnecting + StatusConnected + StatusError +) diff --git a/gui/authform/authform.go b/gui/authform/authform.go index 2d15aa4f5626e940a30ed7b49f14c092e94c806c..8a0445a41d8e411c88bb938b0e52e0036f7eeec4 100644 --- a/gui/authform/authform.go +++ b/gui/authform/authform.go @@ -41,3 +41,9 @@ func NewWidget(conf *config.Config, onAuth func()) *Widget { return &Widget{QWidget: form} } + +func (w *Widget) Q() *ui.QWidget { + return w.QWidget +} + +func (w *Widget) Tick() {} diff --git a/gui/contractform/contractform.go b/gui/contractform/contractform.go index 7f80b1290525fed83e1c0321c12c0baf9fed3265..9cd9ec371d5c9d14929e6ad99494104c2f9f128d 100644 --- a/gui/contractform/contractform.go +++ b/gui/contractform/contractform.go @@ -86,3 +86,9 @@ func (w *Widget) SignersList() (list []string) { return } + +func (w *Widget) Q() *ui.QWidget { + return w.QWidget +} + +func (w *Widget) Tick() {} diff --git a/gui/main.go b/gui/main.go index 30348eaa60fc0707409badf65265884f35970b0c..24fe004d62a6dda74aeaefc46549182a0d0d7368 100644 --- a/gui/main.go +++ b/gui/main.go @@ -5,41 +5,100 @@ import ( "dfss/gui/authform" "dfss/gui/config" "dfss/gui/contractform" + "dfss/gui/signform" "dfss/gui/userform" "github.com/visualfc/goqt/ui" ) +type window struct { + *ui.QMainWindow + + current widget + conf *config.Config +} + +type widget interface { + Q() *ui.QWidget + Tick() +} + func main() { // Load configuration conf := config.Load() // Start first window ui.Run(func() { - window := ui.NewMainWindow() - - var newuser *userform.Widget - var newauth *authform.Widget - var newcontract *contractform.Widget - - newauth = authform.NewWidget(&conf, func() { - window.SetCentralWidget(newcontract) - }) - - newuser = userform.NewWidget(&conf, func(pwd string) { - window.SetCentralWidget(newauth) - }) - - newcontract = contractform.NewWidget(&conf) + w := &window{ + QMainWindow: ui.NewMainWindow(), + conf: &conf, + } if conf.Authenticated { - window.SetCentralWidget(newcontract) + w.addActions() + w.showNewContractForm() } else if conf.Registered { - window.SetCentralWidget(newauth) + w.showAuthForm() } else { - window.SetCentralWidget(newuser) + w.showUserForm() } - window.SetWindowTitle("DFSS Client v" + dfss.Version) - window.Show() + timer := ui.NewTimerWithParent(w) + timer.OnTimeout(func() { + w.current.Tick() + }) + timer.StartWithMsec(1000) + + w.SetWindowTitle("DFSS Client v" + dfss.Version) + w.SetWindowIcon(ui.NewIconWithFilename(":/images/digital_signature_pen.png")) + w.Show() }) } + +func (w *window) addActions() { + openAct := ui.NewActionWithTextParent("&Open", w) + openAct.SetShortcuts(ui.QKeySequence_Open) + openAct.OnTriggered(func() { + w.showSignForm() + }) + w.MenuBar().AddAction(openAct) +} + +func (w *window) setScreen(wi widget) { + old := w.CentralWidget() + w.SetCentralWidget(wi.Q()) + w.current = wi + if old != nil { + old.DeleteLater() + } +} + +func (w *window) showUserForm() { + w.setScreen(userform.NewWidget(w.conf, func(pwd string) { + w.showAuthForm() + })) +} + +func (w *window) showAuthForm() { + w.setScreen(authform.NewWidget(w.conf, func() { + w.showNewContractForm() + w.addActions() + })) +} + +func (w *window) showNewContractForm() { + w.setScreen(contractform.NewWidget(w.conf)) +} + +func (w *window) showSignForm() { + home := config.GetHomeDir() + filter := "Contract file (*.json);;Any (*.*)" + filename := ui.QFileDialogGetOpenFileNameWithParentCaptionDirFilterSelectedfilterOptions(w, "Select the contract file", home, filter, &filter, 0) + if filename != "" { + config.PasswordDialog(func(err error, pwd string) { + widget := signform.NewWidget(w.conf, filename, pwd) + if widget != nil { + w.setScreen(widget) + } + }) + } +} diff --git a/gui/signform/icons.go b/gui/signform/icons.go new file mode 100644 index 0000000000000000000000000000000000000000..c9a07cd3d501653cba3267ae34eb6a944aa13d49 --- /dev/null +++ b/gui/signform/icons.go @@ -0,0 +1,31 @@ +package signform + +import ( + "dfss/dfssc/sign" + "github.com/visualfc/goqt/ui" +) + +var icons map[sign.SignerStatus]*ui.QIcon +var icons_labels = map[sign.SignerStatus]string{ + sign.StatusWaiting: "Waiting", + sign.StatusConnecting: "Connecting", + sign.StatusConnected: "Connected", + sign.StatusError: "Error", +} + +var iconsLoaded = false + +func loadIcons() { + if iconsLoaded { + return + } + + icons = map[sign.SignerStatus]*ui.QIcon{ + sign.StatusWaiting: ui.NewIconWithFilename(":/images/time.png"), + sign.StatusConnecting: ui.NewIconWithFilename(":/images/time.png"), + sign.StatusConnected: ui.NewIconWithFilename(":/images/connected.png"), + sign.StatusError: ui.NewIconWithFilename(":/images/error.png"), + } + + iconsLoaded = true +} diff --git a/gui/signform/signform.go b/gui/signform/signform.go index 338c06277c956e3716c81a30dc7ae817c0b64b8c..311738e8943f056ff4d93dd85594a6ef03b220e1 100644 --- a/gui/signform/signform.go +++ b/gui/signform/signform.go @@ -1,20 +1,155 @@ package signform import ( + "encoding/json" + "io/ioutil" + + "dfss/dfssc/sign" + "dfss/dfssp/contract" "dfss/gui/config" "github.com/visualfc/goqt/ui" ) +type line struct { + status sign.SignerStatus + info, mail string + cellA, cellB, cellC *ui.QTableWidgetItem +} + type Widget struct { *ui.QWidget + + manager *sign.SignatureManager + contract *contract.JSON + table *ui.QTableWidget + progressBar *ui.QProgressBar + feedbackLabel *ui.QLabel + lines []line + statusMax, statusCurrent int32 + feedback string } -func NewWidget(conf *config.Config, onAuth func()) *Widget { +func NewWidget(conf *config.Config, filename, pwd string) *Widget { + loadIcons() file := ui.NewFileWithName(":/signform/signform.ui") loader := ui.NewUiLoader() form := loader.Load(file) - w := &Widget{QWidget: form} + w.feedbackLabel = ui.NewLabelFromDriver(w.FindChild("mainLabel")) + w.table = ui.NewTableWidgetFromDriver(w.FindChild("signersTable")) + w.progressBar = ui.NewProgressBarFromDriver(w.FindChild("progressBar")) + + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil + } + + w.contract = new(contract.JSON) + err = json.Unmarshal(data, w.contract) + if err != nil { + return nil + } + + home := config.GetHomeDir() + m, err := sign.NewSignatureManager( + home+config.CAFile, + home+config.CertFile, + home+config.KeyFile, + conf.Platform, + pwd, + 9005, // TODO change port + w.contract, + ) + if err != nil { + // TODO + } + + w.manager = m + w.manager.OnSignerStatusUpdate = w.signerUpdated + w.manager.OnProgressUpdate = func(current int, max int) { + w.statusCurrent = int32(current) + w.statusMax = int32(max) + } + + w.initLines() + w.signerUpdated(conf.Email, sign.StatusConnected, "It's you!") + go func() { + err = w.execute() + if err != nil { + w.feedback = err.Error() + } else { + w.feedback = "Contract signed successfully!" + } + }() + return w } + +func (w *Widget) execute() error { + w.feedback = "Connecting to peers..." + err := w.manager.ConnectToPeers() + if err != nil { + return err + } + + w.feedback = "Waiting for peers..." + _, err = w.manager.SendReadySign() + if err != nil { + return err + } + + w.feedback = "Signature in progress..." + err = w.manager.Sign() + if err != nil { + return err + } + + w.feedback = "Storing file..." + return w.manager.PersistSignaturesToFile() // TODO choose destination +} + +func (w *Widget) signerUpdated(mail string, status sign.SignerStatus, data string) { + for i, s := range w.contract.Signers { + if s.Email == mail { + w.lines[i].status = status + w.lines[i].info = data + break + } + } +} + +// Tick updates the whole screen, as we cannot directly update the screen on each callback. +// It is called by the screen coordinator via a timer. +func (w *Widget) Tick() { + w.feedbackLabel.SetText(w.feedback) + w.progressBar.SetMaximum(w.statusMax) + w.progressBar.SetValue(w.statusCurrent) + for _, l := range w.lines { + l.cellA.SetIcon(icons[l.status]) + l.cellA.SetText(icons_labels[l.status]) + l.cellB.SetText(l.info) + } +} + +func (w *Widget) initLines() { + w.table.SetRowCount(int32(len(w.contract.Signers))) + for i, s := range w.contract.Signers { + status := ui.NewTableWidgetItemWithIconTextType(icons[sign.StatusWaiting], icons_labels[sign.StatusWaiting], int32(ui.QTableWidgetItem_UserType)) + w.table.SetItem(int32(i), 0, status) + + info := ui.NewTableWidgetItemWithTextType("", int32(ui.QTableWidgetItem_UserType)) + w.table.SetItem(int32(i), 1, info) + + mail := ui.NewTableWidgetItemWithTextType(s.Email, int32(ui.QTableWidgetItem_UserType)) + w.table.SetItem(int32(i), 2, mail) + + // We must store items, otherwise GC will remove it and cause the application to crash. + // Trust me, hard to debug... + w.lines = append(w.lines, line{sign.StatusWaiting, "", s.Email, status, info, mail}) + } +} + +func (w *Widget) Q() *ui.QWidget { + return w.QWidget +} diff --git a/gui/signform/signform.ui b/gui/signform/signform.ui index f72664fd5c6615c5067f0e2e45595e49b1d08a7c..8c4d41b6d5da97ad7b35da1d1f30d205ad4e862b 100644 --- a/gui/signform/signform.ui +++ b/gui/signform/signform.ui @@ -50,8 +50,20 @@ + + + 0 + 0 + + + + + 400 + 0 + + - <html><head/><body><p><span style=" font-size:18pt;">Preparing signature...</span></p></body></html> + Preparing signature... @@ -82,6 +94,9 @@ + + QAbstractItemView::NoEditTriggers + true @@ -101,7 +116,7 @@ 200 - 200 + 50 true @@ -122,7 +137,7 @@ - Ping + Info diff --git a/gui/userform/userform.go b/gui/userform/userform.go index fc998a06d5f25219cea4da3ad0508fead7a92d5e..8f424e77bcd974c4c7f1428118cfecbfa703bc0d 100644 --- a/gui/userform/userform.go +++ b/gui/userform/userform.go @@ -71,3 +71,9 @@ func copyCA(from string, to string) error { return ioutil.WriteFile(to, file, 0600) } + +func (w *Widget) Q() *ui.QWidget { + return w.QWidget +} + +func (w *Widget) Tick() {}