001 package edu.harvard.deas.hyperenc.gui; 002 003 import java.awt.Color; 004 import java.awt.Component; 005 import java.awt.Container; 006 import java.awt.Font; 007 import java.awt.GridBagConstraints; 008 import java.awt.GridBagLayout; 009 import java.awt.GridLayout; 010 import java.awt.event.ActionEvent; 011 import java.awt.event.ActionListener; 012 import java.awt.event.KeyEvent; 013 import java.awt.event.MouseAdapter; 014 import java.awt.event.MouseEvent; 015 import java.awt.event.MouseListener; 016 import java.awt.event.WindowAdapter; 017 import java.awt.event.WindowEvent; 018 import java.text.DateFormat; 019 import java.util.ArrayList; 020 import java.util.Collections; 021 import java.util.Date; 022 import java.util.List; 023 import java.util.concurrent.ExecutionException; 024 025 import javax.mail.Address; 026 import javax.swing.JFrame; 027 import javax.swing.JLabel; 028 import javax.swing.JList; 029 import javax.swing.JMenu; 030 import javax.swing.JMenuBar; 031 import javax.swing.JMenuItem; 032 import javax.swing.JOptionPane; 033 import javax.swing.JPanel; 034 import javax.swing.JScrollPane; 035 import javax.swing.JTable; 036 import javax.swing.KeyStroke; 037 import javax.swing.ListSelectionModel; 038 import javax.swing.SwingWorker; 039 import javax.swing.event.ListSelectionEvent; 040 import javax.swing.event.ListSelectionListener; 041 import javax.swing.table.DefaultTableCellRenderer; 042 043 import org.apache.log4j.Logger; 044 045 import edu.harvard.deas.hyperenc.BlockMissingException; 046 import edu.harvard.deas.hyperenc.Contact; 047 import edu.harvard.deas.hyperenc.ContactList; 048 import edu.harvard.deas.hyperenc.Direction; 049 import edu.harvard.deas.hyperenc.EncryptionException; 050 import edu.harvard.deas.hyperenc.HyperCollector; 051 import edu.harvard.deas.hyperenc.HyperCommunicator; 052 import edu.harvard.deas.hyperenc.HyperEncryptionEvent; 053 import edu.harvard.deas.hyperenc.HyperEncryptionEventType; 054 import edu.harvard.deas.hyperenc.HyperEncryptionListener; 055 import edu.harvard.deas.hyperenc.HyperMessage; 056 import edu.harvard.deas.hyperenc.HyperMessageType; 057 import edu.harvard.deas.hyperenc.MessageParseException; 058 import edu.harvard.deas.hyperenc.PageException; 059 import edu.harvard.deas.hyperenc.PlainTextMessageParser; 060 import edu.harvard.deas.hyperenc.util.UUCoder; 061 062 /** 063 * A graphical front-end for hyper-encryption. 064 */ 065 public class HyperGui 066 extends JFrame 067 implements ActionListener, HyperEncryptionListener, ComposerListener { 068 private static final long serialVersionUID = 1L; 069 070 private static final Logger logger = Logger.getLogger(HyperGui.class); 071 072 private JList contactsListBox; 073 private JTable messagesListBox; 074 private MessageTableModel messageTableModel; 075 private JPanel statusPanel; 076 private Contact currentContact; 077 private ContactList currentContacts; 078 private HyperCommunicator communicator; 079 private HyperCollector collector; 080 private Contact myOwner; 081 082 private StatusPanel masterPanel; 083 private StatusPanel slavePanel; 084 085 /** 086 * Constructs a GUI. 087 * 088 * @param communicator 089 * a HyperCommunicator. Used for sending and receiving messages. 090 * @param collector 091 * a HyperCollector that holds a master and slave storage for each 092 * contact. 093 * @param storage 094 * a MessageStorage. Messages stored in this MessageStorage will be 095 * viewable by the user, and newly-received messages will be stored in 096 * this MessageStorage. 097 * @param contacts 098 * the people we're talking with; each contact should have a master and 099 * slave storage in <code>collector</code> 100 * @param myOwner 101 * Contact representing the user, with the user's e-mail address/name 102 * @throws NullPointerException 103 * if any argument is <code>null</code> 104 */ 105 public HyperGui(HyperCommunicator communicator, HyperCollector collector, 106 MessageStorage storage, ContactList contacts, Contact myOwner) { 107 if (communicator == null) 108 throw new NullPointerException("communicator may not be null"); 109 if (collector == null) 110 throw new NullPointerException("collector may not be null"); 111 if (storage == null) 112 throw new NullPointerException("storage may not be null"); 113 if (contacts == null) 114 throw new NullPointerException("contacts may not be null"); 115 if (myOwner == null) 116 throw new NullPointerException("myOwner may not be null"); 117 118 this.communicator = communicator; 119 this.collector = collector; 120 this.messageTableModel = new MessageTableModel(storage); 121 this.myOwner = myOwner; 122 123 setTitle("Hyper-encryption -- " + myOwner.toString()); 124 125 setSize(850, 400); 126 setResizable(true); 127 setLocation(100, 100); 128 129 // Handle closing of the window 130 addWindowListener(new WindowAdapter() { 131 public void windowClosing(WindowEvent e) { 132 System.exit(0); 133 } 134 }); 135 136 // Creates and initializes the main menu bar 137 JMenuBar mb = createMenu(); 138 setJMenuBar(mb); 139 140 // Creates and initializes the tool bar 141 JPanel toolbar = new Toolbar(this); 142 143 // A simple header to let the user know what's going on 144 JLabel contactHeader = new JLabel("Contacts"); 145 contactHeader.setVerticalAlignment(JLabel.CENTER); 146 contactHeader.setHorizontalAlignment(JLabel.CENTER); 147 148 149 150 // ****** CONTACT LIST BOX ****** 151 152 // Grabs the current list of contactsListBox, loads it into a display list 153 // and wraps it in a scroll pane for ease of use 154 currentContacts = contacts; 155 String [] contactList = getContactNames(); 156 157 contactsListBox = new JList(contactList); 158 contactsListBox.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 159 // Fixes the size of the listing 160 contactsListBox.setPrototypeCellValue("Michael O. Rabin"); 161 162 assert(contactList != null); 163 if (contactList.length > 0) { 164 currentContact = currentContacts.get(0); 165 contactsListBox.setSelectedIndex(0); 166 } 167 168 logger.debug("contactsListBox created, 1st is " + currentContact); 169 170 // When a different contact is selected, update the currentContact and 171 // change the message listing 172 contactsListBox.addListSelectionListener(new ListSelectionListener() { 173 public void valueChanged(ListSelectionEvent e) { 174 175 int index = contactsListBox.getSelectedIndex(); 176 if (index != -1) { 177 currentContact = currentContacts.get(index); 178 messageTableModel.selectSender(currentContact); 179 } 180 181 updateStatusPanels(); 182 } 183 }); 184 185 JScrollPane contactListScrollPane = new JScrollPane(); 186 contactListScrollPane.getViewport().setView(contactsListBox); 187 188 189 190 // ****** MESSAGE LIST BOX ****** 191 192 // Create the list to display messagesListBox. Initialize it to display those 193 // for the first contact, if there is one 194 String [] messageList = null; 195 messagesListBox = new JTable(messageTableModel); 196 //messagesListBox.setCellRenderer(new MessageCellRenderer()); 197 messagesListBox.setSelectionBackground(Color.CYAN); 198 messagesListBox.setAutoCreateRowSorter(true); 199 200 messagesListBox.setDefaultRenderer(Date.class, 201 // from http://java.sun.com/docs/books/tutorial/uiswing/components/table.html 202 new DefaultTableCellRenderer() { 203 private static final long serialVersionUID = 1L; // hurrr 204 DateFormat formatter; 205 public void setValue(Object value) { 206 if (formatter == null) { 207 formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, 208 DateFormat.MEDIUM); 209 } 210 setText((value == null) ? "" : formatter.format(value)); 211 } 212 } 213 ); 214 215 if (currentContact != null) { 216 messageTableModel.selectSender(currentContact); 217 } 218 logger.debug("messagesListBox populated,"); 219 220 // Register for double clicks on messages -- open message when double-clicked 221 MouseListener mouseListener = new MouseAdapter() { 222 public void mouseClicked(MouseEvent e) { 223 if (e.getClickCount() == 2) { // Register double clicks 224 int index = messagesListBox.rowAtPoint(e.getPoint()); 225 if (index < 0) 226 return; 227 int modelIndex = messagesListBox.convertRowIndexToModel(index); 228 logger.debug("Double clicked on Item " + index + " (model " + modelIndex + ")"); 229 openMessage(modelIndex); 230 } 231 } 232 }; 233 messagesListBox.addMouseListener(mouseListener); 234 235 236 JScrollPane msgBoxScrollPane = new JScrollPane(); 237 messagesListBox.setFillsViewportHeight(true); 238 msgBoxScrollPane.getViewport().setView(messagesListBox); 239 240 241 242 243 // ****** STATUS PANEL ****** 244 245 statusPanel = new JPanel(); 246 GridLayout gl = new GridLayout(4, 1); 247 statusPanel.setLayout(gl); 248 249 Font labelFont = new Font("Serif", Font.BOLD, 18); 250 251 JLabel master = new JLabel("Sending Status"); 252 master.setFont(labelFont); 253 master.setVerticalAlignment(JLabel.CENTER); 254 master.setHorizontalAlignment(JLabel.CENTER); 255 256 JLabel slave = new JLabel("Receiving Status"); 257 slave.setFont(labelFont); 258 slave.setVerticalAlignment(JLabel.CENTER); 259 slave.setHorizontalAlignment(JLabel.CENTER); 260 261 masterPanel = new StatusPanel(); 262 slavePanel = new StatusPanel(); 263 264 statusPanel.add(master); 265 statusPanel.add(masterPanel); 266 statusPanel.add(slave); 267 statusPanel.add(slavePanel); 268 269 updateStatusPanels(); 270 271 272 273 // Finally, add all the components to the frame 274 275 Container pane = this.getContentPane(); 276 pane.setLayout(new GridBagLayout()); 277 GridBagConstraints gridConstraints = new GridBagConstraints(); 278 279 addItem(toolbar, gridConstraints, 0, 0, 5, 1, GridBagConstraints.HORIZONTAL, 280 GridBagConstraints.CENTER, 0, 0); 281 addItem(contactHeader, gridConstraints, 0, 1, 1, 1, GridBagConstraints.NONE, 282 GridBagConstraints.CENTER, 10, 0); 283 addItem(contactListScrollPane, gridConstraints, 0, 2, 1, 28/*GridBagConstraints.REMAINDER*/, 284 GridBagConstraints.BOTH, GridBagConstraints.CENTER, 10, 10); 285 addItem(msgBoxScrollPane, gridConstraints, 1, 1, 4, 29, 286 GridBagConstraints.BOTH, GridBagConstraints.CENTER, 50, 50); 287 addItem(statusPanel, gridConstraints, 0, 30, 7, 4, 288 GridBagConstraints.BOTH, GridBagConstraints.CENTER, 5, 5); 289 } 290 291 292 /** 293 * Create the menu bar. 294 * @return a JMenuBar for the menu bar that appears at the top of the window 295 */ 296 private JMenuBar createMenu() { 297 298 // Create the top-level menus 299 JMenu menuFile = new JMenu("File"); 300 JMenu menuEdit = new JMenu("Edit"); 301 JMenu menuAction = new JMenu("Action"); 302 JMenu menuHelp = new JMenu("Help"); 303 304 // Create the individual menu items. Use the Hyper Encrypter as the action listener 305 JMenuItem item = new JMenuItem("New Message", KeyEvent.VK_N); 306 item.addActionListener(this); 307 menuFile.add(item); 308 309 item = new JMenuItem("Initiate Reconciliation", KeyEvent.VK_R); 310 item.addActionListener(this); 311 menuFile.add(item); 312 313 item = new JMenuItem("Retrieve a page", KeyEvent.VK_P); 314 item.addActionListener(this); 315 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK)); 316 menuFile.add(item); 317 318 item = new JMenuItem("Make blocks from pages", KeyEvent.VK_P); 319 item.addActionListener(this); 320 menuFile.add(item); 321 322 item = new JMenuItem("Add Contact", KeyEvent.VK_C); 323 item.addActionListener(this); 324 menuFile.add(item); 325 326 item = new JMenuItem("Remove Contact", KeyEvent.VK_X); 327 item.addActionListener(this); 328 menuFile.add(item); 329 330 item = new JMenuItem("Exit"); 331 item.addActionListener(this); 332 menuFile.add(item); 333 334 item = new JMenuItem("Delete", KeyEvent.VK_DELETE); 335 item.addActionListener(this); 336 menuEdit.add(item); 337 338 item = new JMenuItem("Mark Read", KeyEvent.VK_M); 339 item.addActionListener(this); 340 menuEdit.add(item); 341 342 item = new JMenuItem("Mark Unread", KeyEvent.VK_U); 343 item.addActionListener(this); 344 menuEdit.add(item); 345 346 item = new JMenuItem("Get Mail", KeyEvent.VK_F5); 347 item.addActionListener(this); 348 menuAction.add(item); 349 350 item = new JMenuItem("Reply", KeyEvent.VK_R); 351 item.addActionListener(this); 352 menuAction.add(item); 353 354 item = new JMenuItem("Forward", KeyEvent.VK_F); 355 item.addActionListener(this); 356 menuAction.add(item); 357 358 item = new JMenuItem("Help"); 359 item.addActionListener(this); 360 menuHelp.add(item); 361 362 item = new JMenuItem("About"); 363 item.addActionListener(this); 364 menuHelp.add(item); 365 366 // Finally, add all menus to a menu bar and return it 367 JMenuBar mb = new JMenuBar(); 368 JMenu[] menus = { menuFile, menuEdit, menuAction, menuHelp }; 369 for (int i = 0; i < menus.length; i++) { 370 mb.add(menus[i]); 371 } 372 373 return mb; 374 } 375 376 private String[] getContactNames() { 377 List<String> clst = new ArrayList<String>(); 378 int i = 0; 379 for (Contact c : currentContacts) { 380 clst.add(c.getDisplayName()); 381 } 382 Collections.sort(clst); 383 384 String[] ret = new String[clst.size()]; 385 clst.toArray(ret); 386 return ret; 387 } 388 389 390 /* Helper function for adding items to the grid bag layout */ 391 // XXX order of arguments is impossible to remember, making this method prone 392 // to error; how can we improve it? 393 private void addItem(Component item, GridBagConstraints gc, 394 int xPosition, int yPosition, int width, int height, 395 int fill, int anchor, int wx, int wy) { 396 gc.gridx = xPosition; 397 gc.gridy = yPosition; 398 gc.gridwidth = width; 399 gc.gridheight = height; 400 gc.fill = fill; 401 gc.anchor = anchor; 402 gc.weightx = wx; 403 gc.weighty = wy; 404 405 getContentPane().add(item, gc); 406 } 407 408 /** Dispatches and/or handles menu and toolbar commands */ 409 @Override 410 public void actionPerformed(ActionEvent e) { 411 // TODO hard-coding these strings is fragile and unwise -- fix it, probably 412 // by creating private listener classes 413 if (e.getActionCommand().equals("Exit")) { 414 System.exit(0); 415 } else if (e.getActionCommand().equals("New Message") || e.getActionCommand().equals("New")) { 416 newMessage(); 417 } else if (e.getActionCommand().equals("Initiate Reconciliation")) { 418 startReconcile(); 419 } else if (e.getActionCommand().equals("Retrieve a page")) { 420 retrievePage(); 421 } else if (e.getActionCommand().equals("Make blocks from pages")) { 422 makeBlocksFromPages(); 423 } else if (e.getActionCommand().equals("Add Contact")) { 424 addContact(null); 425 } else if (e.getActionCommand().equals("Remove Contact")) { 426 removeContact(); 427 } else if (e.getActionCommand().equals("Delete")) { 428 delete(); 429 } else if (e.getActionCommand().equals("Mark Read")) { 430 for (int rowNum : messagesListBox.getSelectedRows()) 431 markRowAsRead(rowNum, true); 432 } else if (e.getActionCommand().equals("Mark Unread")) { 433 for (int rowNum : messagesListBox.getSelectedRows()) 434 markRowAsRead(rowNum, false); 435 } else if (e.getActionCommand().equals("Get Mail")) { 436 getMail(); 437 } else if (e.getActionCommand().equals("Decrypt")) { 438 decrypt(); 439 } else if (e.getActionCommand().equals("Reply")) { 440 reply(); 441 } else if (e.getActionCommand().equals("Forward")) { 442 forward(); 443 } else if (e.getActionCommand().equals("Help")) { 444 JOptionPane.showMessageDialog( 445 null, 446 "For help, please consult the manual included with this program.", 447 "Help", 448 JOptionPane.INFORMATION_MESSAGE); 449 } else if (e.getActionCommand().equals("About")) { 450 JOptionPane.showMessageDialog( 451 null, 452 "For more information about hyper-encryption, please consult the " + 453 "documentation included with this program.", 454 "About Hyper Encryption", 455 JOptionPane.INFORMATION_MESSAGE); 456 } 457 458 } 459 460 /** Creates a new message window */ 461 private void newMessage() { 462 Composer comp = new Composer(myOwner, currentContact, "", ""); 463 comp.addComposerListener(this); 464 } 465 466 /** Sends a master reconciliation message */ 467 private void startReconcile() { 468 // TODO need to NOT send this message if there's already a pending rec msg 469 SwingWorker<Boolean,Void> worker = new SwingWorker<Boolean,Void>() { 470 @Override protected Boolean doInBackground() { 471 HyperMessage mrec; 472 try { 473 mrec = collector.sendRec(currentContact, new PlainTextMessageParser()); 474 } catch (EncryptionException e) { 475 return false; 476 } 477 communicator.send(mrec); 478 return true; 479 } 480 481 @Override protected void done() { 482 boolean result; 483 try { 484 result = get(); 485 } catch (InterruptedException e) { 486 throw new RuntimeException( 487 "Unexpected InterruptedException in reconciliation thread", e); 488 } catch (ExecutionException e) { 489 throw new RuntimeException( 490 "Unexpected exception thrown from reconciliation thread", e); 491 } 492 if (!result) { 493 JOptionPane.showMessageDialog(null, 494 "Could not reconcile pages: there were not enough encryption " 495 + "blocks available to send the message.", 496 "Could not start reconciliation", 497 JOptionPane.ERROR_MESSAGE); 498 } 499 } 500 }; 501 502 worker.execute(); 503 } 504 505 /** Tries to generate a page for the selected contact in each direction. */ 506 private void retrievePage() { 507 SwingWorker<List<String>,Void> worker = new SwingWorker<List<String>,Void>() { 508 @Override 509 protected List<String> doInBackground() { 510 List<String> errors = new ArrayList<String>(); 511 512 try { 513 collector.collectNext(currentContact, Direction.MASTER); 514 } catch (EncryptionException e) { 515 errors.add("Could not retrieve an outgoing page: there are no " + 516 "system blocks available."); 517 } catch (PageException e) { 518 errors.add("Could not retrieve an outgoing page: all PSNs " + 519 "contacted were unavailable. Please try again later."); 520 } 521 522 try { 523 collector.collectNext(currentContact, Direction.SLAVE); 524 } catch (EncryptionException e) { 525 errors.add("Could not retrieve an incoming page: there are no " + 526 "system blocks available."); 527 } catch (PageException e) { 528 errors.add("Could not retrieve an incoming page: all PSNs " + 529 "contacted were unavailable. Please try again later."); 530 } 531 532 return errors; 533 } 534 535 @Override 536 protected void done() { 537 List<String> errList; 538 try { 539 errList = get(); 540 } catch (InterruptedException e) { 541 throw new RuntimeException( 542 "Unexpected InterruptedException in page retrieval thread", e); 543 } catch (ExecutionException e) { 544 // really shouldn't happen 545 throw new RuntimeException("Unexpected exception", e); 546 } 547 548 for (String err : errList) { 549 JOptionPane.showMessageDialog(null, 550 err, 551 "Could not download pages", 552 JOptionPane.ERROR_MESSAGE); 553 } 554 } 555 }; 556 557 worker.execute(); 558 } 559 560 /** 561 * Tries to create system/encryption blocks from reconciled pages, in each 562 * direction. 563 */ 564 private void makeBlocksFromPages() { 565 try { 566 collector.makeBlocks(currentContact, Direction.MASTER); 567 } catch (EncryptionException e) { 568 JOptionPane.showMessageDialog(null, 569 "There are not enough reconciled pages with which to create " + 570 "outgoing blocks. (" + HyperCollector.PAGES_PER_PAD + " are " + 571 "required, but only " + 572 collector.numRPages(currentContact, Direction.MASTER) + 573 " are available.)", 574 "Could not download outgoing pages", 575 JOptionPane.ERROR_MESSAGE); 576 } 577 578 try { 579 collector.makeBlocks(currentContact, Direction.SLAVE); 580 } catch (EncryptionException e) { 581 JOptionPane.showMessageDialog(null, 582 "There are not enough reconciled pages with which to create " + 583 "incoming blocks. (" + HyperCollector.PAGES_PER_PAD + " are" + 584 "required, but only " + 585 collector.numRPages(currentContact, Direction.MASTER) + 586 " are available.)", 587 "Could not download incoming pages", 588 JOptionPane.ERROR_MESSAGE); 589 } 590 } 591 592 /** Handles new contact creation. */ 593 private void addContact(Contact contact) { 594 // Open the add contact window 595 AddContact ac; 596 if (contact == null) 597 ac = new AddContact(this); 598 else 599 ac = new AddContact(this, contact.getEmail().toString(), 600 contact.getFullName(), contact.getDisplayName()); 601 602 // Grab the contact that was created 603 Contact c = ac.getNewContact(); 604 605 if (c != null) { 606 currentContacts.addContact(c); // Add him to our collection 607 608 //communicator.getContacter().sendContact(c.email); // send keyrequest 609 //c.status = Contact.STATUS_WE_WAIT; 610 611 collector.newContact(c, 612 ac.getOutgoingSecret(), 613 ac.getIncomingSecret()); 614 615 contactsListBox.setListData(getContactNames()); // Update the display 616 int index = contactsListBox.getSelectedIndex(); 617 if (index != -1) { 618 currentContact = currentContacts.get(index); 619 } else { 620 contactsListBox.setSelectedIndex(0); 621 currentContact = currentContacts.get(0); 622 } 623 624 updateStatusPanels(); 625 } 626 } 627 628 /** Remove a contact */ 629 private void removeContact() { 630 Contact c; 631 int index = contactsListBox.getSelectedIndex(); 632 633 if (index != -1) { 634 c = currentContacts.get(index); 635 currentContacts.removeContact(c); 636 contactsListBox.setListData(getContactNames()); 637 if (index > 0) { 638 contactsListBox.setSelectedIndex(index-1); 639 } else if (currentContacts.size() > 0) { 640 contactsListBox.setSelectedIndex(0); 641 } 642 } 643 644 // TODO remove contact's HyperStorages from the collector 645 } 646 647 648 /** 649 * Decrypt the selected messages. 650 */ 651 private void decrypt() { 652 // Build list of model row numbers (converting from table row numbers) 653 List<Integer> modelIndices = new ArrayList<Integer>(); 654 for (int tableRow : messagesListBox.getSelectedRows()) 655 modelIndices.add(messagesListBox.convertRowIndexToModel(tableRow)); 656 657 for (int modelRow : modelIndices) { 658 GuiMessage gmsg = messageTableModel.getMessageAtRow(modelRow); 659 660 if (gmsg.getHyperMessage().getType() != HyperMessageType.ENCRYPTED) { 661 JOptionPane.showMessageDialog(null, 662 "Message is not encrypted!", 663 "Error", 664 JOptionPane.ERROR_MESSAGE); 665 return; 666 } 667 668 boolean validMac; 669 try { 670 validMac = collector.verifyHEMAC( 671 UUCoder.decode(gmsg.getHyperMessage().getContent()), 672 gmsg.getHyperMessage().getPadsUsed(), 673 gmsg.getHyperMessage().getMac(), 674 gmsg.getSender() 675 ); 676 } catch (BlockMissingException e) { 677 String message = ""; 678 message += "MAC blocks missing: "; 679 for (int id : e.getMissingIDList()) 680 message += id + " "; 681 logger.warn(message); 682 validMac = false; 683 } 684 685 if (!validMac) { 686 JOptionPane.showMessageDialog(null, 687 "The message authentication code (MAC) could not be verified for " 688 + "the message with subject \"" + gmsg.getSubject() + "\". " 689 + "The message may not be authentic.", 690 "Error", 691 JOptionPane.ERROR_MESSAGE); 692 return; 693 } 694 695 HyperMessage decrypted; 696 try { 697 decrypted = collector.decrypt(gmsg.getHyperMessage()); 698 } catch (EncryptionException ex) { 699 JOptionPane.showMessageDialog(null, 700 "An encryption block needed to decrypt this message is missing.", 701 "Error", 702 JOptionPane.ERROR_MESSAGE); 703 return; 704 } 705 706 // Replace the encrypted message with the decrypted one. 707 messageTableModel.replaceMessageAtRow(modelRow, 708 new GuiMessage(decrypted, gmsg.isRead())); 709 } 710 } 711 712 713 /** 714 * Creates a new message window, prepped for a reply. If multiple messages 715 * are selected, all but the first are ignored. 716 */ 717 private void reply() { 718 int index = messagesListBox.getSelectedRow(); 719 if (index == -1) { 720 JOptionPane.showMessageDialog(null, 721 "Please select a message to reply to.", 722 "Select a message", 723 JOptionPane.INFORMATION_MESSAGE); 724 return; 725 } 726 727 index = messagesListBox.convertRowIndexToModel(index); 728 GuiMessage cur = messageTableModel.getMessageAtRow(index); 729 markRowAsRead(index, true); 730 731 // Create a new composition window based on the currently selected message 732 Composer comp = new Composer(myOwner, currentContact, 733 "Re: " + cur.getSubject(), cur.getContent()); 734 comp.addComposerListener(this); 735 } 736 737 /** 738 * Creates a new message window, prepped for forwarding. If multiple messages 739 * are selected, all but the first are ignored. 740 */ 741 private void forward() { 742 int index = messagesListBox.getSelectedRow(); 743 if (index == -1) { 744 JOptionPane.showMessageDialog(null, 745 "Please select a message to forward.", 746 "Select a message", 747 JOptionPane.INFORMATION_MESSAGE); 748 return; 749 } 750 751 index = messagesListBox.convertColumnIndexToModel(index); 752 GuiMessage cur = messageTableModel.getMessageAtRow(index); 753 markRowAsRead(index, true); 754 755 // Create a new composition window based on the currently selected message 756 Composer comp = new Composer(myOwner, currentContact, 757 "Fwd: " + cur.getSubject(), cur.getContent()); 758 comp.addComposerListener(this); 759 } 760 761 /** Deletes all currently selected messages. */ 762 private void delete() { 763 int[] indices = messagesListBox.getSelectedRows(); 764 for (int index : indices) { 765 int modelIndex = messagesListBox.convertColumnIndexToModel(index); 766 GuiMessage cur = messageTableModel.getMessageAtRow(modelIndex); 767 messageTableModel.removeMessage(cur.getID()); 768 } 769 } 770 771 /** 772 * Update the read flag of the message in the given row of the message list. 773 * If <code>val</code> is <code>true</code>, mark the message read, otherwise 774 * unread. 775 * <p> 776 * The row number provided should be in <i>table coordinates</i>, such as that 777 * returned by {@link<code>JTable#getSelectedRow()</code>}. 778 */ 779 private void markRowAsRead(int tableRowNum, boolean val) { 780 int modelRowNum = messagesListBox.convertRowIndexToModel(tableRowNum); 781 if (val) { 782 messageTableModel.markRead(modelRowNum); 783 } else { 784 messageTableModel.markUnread(modelRowNum); 785 } 786 } 787 788 /** 789 * Retrieve new mail and process each incoming message. If the message is a 790 * master or slave reconciliation message, the appropriate reconciliation 791 * method of the collector is called to perform the reconciliation. Other 792 * messages are placed in the MessageStorage to be processed at the user's 793 * discretion. 794 */ 795 private void getMail() { 796 SwingWorker<List<GuiMessage>,Void> worker = 797 new SwingWorker<List<GuiMessage>,Void>() { 798 @Override protected List<GuiMessage> doInBackground() { 799 // All incoming messages 800 List<HyperMessage> newMessages = communicator.receive(); 801 // Incoming messages that aren't processed automatically, and will be 802 // saved and seen by the user 803 List<GuiMessage> userMessages = new ArrayList<GuiMessage>(); 804 805 for (HyperMessage hm : newMessages) { 806 if (hm.getType() == HyperMessageType.MASTER_REC) { 807 // massage all whitespace 808 String body = hm.getContent().trim().replaceAll("\\s+", "."); 809 boolean validMac; 810 try { 811 validMac = collector.verifyHEMAC( 812 body.getBytes(), hm.getPadsUsed(), 813 hm.getMac(), hm.getSender()); 814 } catch (BlockMissingException e) { 815 String message = ""; 816 message += "MAC blocks missing: "; 817 for (int id : e.getMissingIDList()) 818 message += id + " "; 819 logger.warn(message); 820 validMac = false; 821 } 822 823 if (!validMac) { 824 JOptionPane.showMessageDialog(null, 825 "The message authentication code (MAC) could not be " 826 + "verified for a reconciliation initiation message (\"" 827 + hm.getSubject() + "\"). The message may not be authentic.", 828 "Error", 829 JOptionPane.ERROR_MESSAGE); 830 continue; 831 } 832 833 HyperMessage response; 834 try { 835 response = collector.reconcile(hm.getSender(), hm, new PlainTextMessageParser()); 836 } catch (MessageParseException e) { 837 logger.warn("Malformed master rec message: " + hm.getContent()); 838 continue; 839 } 840 841 communicator.send(response); 842 logger.info("Responded to master reconciliation message"); 843 } else if (hm.getType() == HyperMessageType.SLAVE_REC) { 844 // massage all whitespace 845 String body = hm.getContent().trim().replaceAll("\\s+", "."); 846 boolean validMac; 847 try { 848 validMac = collector.verifyHEMAC( 849 body.getBytes(), hm.getPadsUsed(), 850 hm.getMac(), hm.getSender()); 851 } catch (BlockMissingException e) { 852 String message = ""; 853 message += "MAC blocks missing: "; 854 for (int id : e.getMissingIDList()) 855 message += id + " "; 856 logger.warn(message); 857 validMac = false; 858 } 859 860 if (!validMac) { 861 JOptionPane.showMessageDialog(null, 862 "The message authentication code (MAC) could not be " 863 + "verified for a reconciliation response message (\"" 864 + hm.getSubject() + "\"). The message may not be authentic.", 865 "Error", 866 JOptionPane.ERROR_MESSAGE); 867 continue; 868 } 869 870 try { 871 collector.receiveRec(hm.getSender(), hm, new PlainTextMessageParser()); 872 } catch (MessageParseException e) { 873 logger.warn("Malformed slave rec message: " + hm.getContent()); 874 continue; 875 } 876 877 logger.info("Processed slave reconciliation message"); 878 } else { 879 userMessages.add(new GuiMessage(hm, ReadStatus.UNREAD)); 880 logger.debug("Stored message (type " + hm.getType() + ")"); 881 } 882 } 883 884 return userMessages; 885 } 886 887 @Override protected void done() { 888 List<GuiMessage> userMessages; 889 try { 890 userMessages = get(); 891 } catch (InterruptedException e) { 892 throw new RuntimeException( 893 "Unexpected InterruptedException in mail retrieval thread", e); 894 } catch (ExecutionException e) { 895 // really shouldn't happen 896 throw new RuntimeException( 897 "Unexpected exception thrown from mail retrieval thread", e); 898 } 899 900 // Put all user-visible messages in the message storage 901 for (GuiMessage gm : userMessages) 902 messageTableModel.addMessage(gm); 903 904 // Reload the message list for the selected contact 905 int index = contactsListBox.getSelectedIndex(); 906 if (index != -1) { 907 currentContact = currentContacts.get(index); 908 messageTableModel.selectSender(currentContact); 909 } 910 } 911 }; 912 913 worker.execute(); 914 } 915 916 private void openMessage(int rowNum) { 917 GuiMessage msg = messageTableModel.getMessageAtRow(rowNum); 918 markRowAsRead(rowNum, true); 919 Composer comp = new Composer(myOwner, currentContact, 920 msg.getSubject(), msg.getContent()); 921 comp.addComposerListener(this); 922 } 923 924 /* 925 private void showMaster() { 926 Contact c; 927 int index = contactsListBox.getSelectedIndex(); 928 929 if (index != -1) { 930 c = currentContacts.get(index); 931 //showDisplay(c.masterDisplay, "Sending Status for " + c.fullName); 932 } 933 } 934 935 private void showSlave() { 936 Contact c; 937 int index = contactsListBox.getSelectedIndex(); 938 939 if (index != -1) { 940 c = currentContacts.get(index); 941 //showDisplay(c.slaveDisplay , "Receiving Status for " + c.fullName); 942 } 943 } 944 945 946 private void showDisplay(final Display d, String str) { 947 JFrame f = new JFrame(str); 948 f.setSize(Display.WIDTH, Display.HEIGHT); 949 f.setLocation(100, 100); 950 951 d.animate(); 952 f.setContentPane(d); 953 954 f.addWindowListener(new WindowAdapter() { 955 public void windowClosing(WindowEvent e) { 956 d.stopAnimation(); 957 } 958 }); 959 960 f.setVisible(true); 961 } 962 */ 963 964 965 private void updateStatusPanels() { 966 if (currentContact == null) 967 return; 968 969 if (!collector.hasContact(currentContact)) { 970 // oops! 971 throw new IllegalStateException("currentContact does not exist in the collector"); 972 } 973 974 masterPanel.setNumSysBlocks(collector.numSysBlocks(currentContact, Direction.MASTER)); 975 masterPanel.setNumEncBlocks(collector.numEncBlocks(currentContact, Direction.MASTER)); 976 masterPanel.setNumUnrecPages(collector.numUPages(currentContact, Direction.MASTER)); 977 masterPanel.setNumRecPages(collector.numRPages(currentContact, Direction.MASTER)); 978 979 slavePanel.setNumSysBlocks(collector.numSysBlocks(currentContact, Direction.SLAVE)); 980 slavePanel.setNumEncBlocks(collector.numEncBlocks(currentContact, Direction.SLAVE)); 981 slavePanel.setNumUnrecPages(collector.numUPages(currentContact, Direction.SLAVE)); 982 slavePanel.setNumRecPages(collector.numRPages(currentContact, Direction.SLAVE)); 983 } 984 985 986 /** 987 * When a new outgoing message is ready for processing, encrypts it and sends it. 988 */ 989 @Override 990 public void messageComposed(ComposerEvent e) { 991 HyperMessage hm = e.getMessage(); 992 993 if (hm == null) // user closed the Composer window without sending the message 994 return; 995 996 HyperMessage encryptedMsg; 997 try { 998 encryptedMsg = collector.encrypt(hm); 999 } catch (EncryptionException ex) { 1000 JOptionPane.showMessageDialog(null, 1001 ex.getMessage(), 1002 "Not enough encryption blocks", 1003 JOptionPane.ERROR_MESSAGE); 1004 return; 1005 } 1006 communicator.send(encryptedMsg); 1007 } 1008 1009 1010 /** Something happened in the HyperEncryption world - update our info */ 1011 @Override 1012 public void eventFired(HyperEncryptionEvent e) { 1013 Address a = e.getContact().getEmail(); 1014 Contact c = currentContacts.getContact(a); 1015 1016 if (c == null) { 1017 logger.warn("Message received and ignored for non-existent contact " 1018 + a.toString()); 1019 1020 // XXX at the moment, this is not used... 1021 // this isn't useful for now, anyway -- if you don't have 1022 // sys/enc blocks for the contact, adding the contact to your list doesn't 1023 // accomplish anything 1024 if (e.getType() == HyperEncryptionEventType.CONTACT_REQUEST) { 1025 int ans=JOptionPane.showConfirmDialog(this, 1026 a.toString()+ 1027 " has requested to concact you."+ 1028 "Do you want to add him/her as a "+ 1029 "contact now?", 1030 "Contact Initiation Request", 1031 JOptionPane.YES_NO_OPTION, 1032 JOptionPane.QUESTION_MESSAGE); 1033 if (ans == JOptionPane.YES_OPTION) { 1034 c = new Contact(a.toString(), a, a.toString()); 1035 addContact(c); 1036 } 1037 } 1038 return; 1039 } 1040 1041 // Grab the appropriate status panel 1042 StatusPanel sp = (e.getDirection() == Direction.MASTER) ? masterPanel : slavePanel; 1043 1044 // Update numbers appropriately 1045 switch (e.getType()) { 1046 case BLOCK_STORAGE_UPDATED: 1047 updateStatusPanels(); 1048 logger.debug("Updated status panels"); 1049 break; 1050 1051 // Deal with contact statuses 1052 case CONTACT_REQUEST: // fall through 1053 case CONTACT_ESTABLISHED: 1054 logger.debug("Contacter event ignored"); 1055 break; 1056 } 1057 } 1058 }