From 63cdcff18540d0a2c8184057d2ed3a6f0986ad43 Mon Sep 17 00:00:00 2001 From: Werner Dittmann Date: Wed, 26 Aug 2009 11:04:48 +0000 Subject: [PATCH] Integrate the second big patch for OTR. With this patch all the required GSoC functions are available. Second commit. --- .../otr/OtrBuddyAuthenticationDialog.java | 132 +++++ .../plugin/otr/OtrConfigurationPanel.java | 497 ++++++++++++++++ .../plugin/otr/OtrContactMenu.java | 265 +++++++++ .../plugin/otr/OtrMetaContactButton.java | 172 ++++++ .../plugin/otr/OtrMetaContactMenu.java | 87 +++ .../communicator/plugin/otr/ScOtrEngine.java | 54 ++ .../plugin/otr/ScOtrEngineImpl.java | 530 ++++++++++++++++++ .../plugin/otr/ScOtrEngineListener.java | 12 + .../protocol/generic/ImEventCollector.java | 98 ++++ .../generic/PredictableTransformLayer.java | 83 +++ ...nSetInstantMessageTransformJabberImpl.java | 274 +++++++++ ...tionSetInstantMessageTransformMsnImpl.java | 273 +++++++++ 12 files changed, 2477 insertions(+) create mode 100644 src/net/java/sip/communicator/plugin/otr/OtrBuddyAuthenticationDialog.java create mode 100644 src/net/java/sip/communicator/plugin/otr/OtrConfigurationPanel.java create mode 100644 src/net/java/sip/communicator/plugin/otr/OtrContactMenu.java create mode 100644 src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java create mode 100644 src/net/java/sip/communicator/plugin/otr/OtrMetaContactMenu.java create mode 100644 src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java create mode 100644 src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java create mode 100644 src/net/java/sip/communicator/plugin/otr/ScOtrEngineListener.java create mode 100644 test/net/java/sip/communicator/slick/protocol/generic/ImEventCollector.java create mode 100644 test/net/java/sip/communicator/slick/protocol/generic/PredictableTransformLayer.java create mode 100644 test/net/java/sip/communicator/slick/protocol/jabber/TestOperationSetInstantMessageTransformJabberImpl.java create mode 100644 test/net/java/sip/communicator/slick/protocol/msn/TestOperationSetInstantMessageTransformMsnImpl.java diff --git a/src/net/java/sip/communicator/plugin/otr/OtrBuddyAuthenticationDialog.java b/src/net/java/sip/communicator/plugin/otr/OtrBuddyAuthenticationDialog.java new file mode 100644 index 000000000..d2ebd59ea --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/OtrBuddyAuthenticationDialog.java @@ -0,0 +1,132 @@ +package net.java.sip.communicator.plugin.otr; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +import net.java.sip.communicator.service.protocol.*; + +@SuppressWarnings("serial") +public class OtrBuddyAuthenticationDialog + extends JDialog +{ + private Contact contact; + + public OtrBuddyAuthenticationDialog(Contact contact) + { + this.contact = contact; + + initComponents(); + loadContact(); + } + + JTextArea txtLocalFingerprint; + + JTextArea txtRemoteFingerprint; + + private void loadContact() + { + // Local fingerprint. + String account = + contact.getProtocolProvider().getAccountID().getDisplayName(); + String localFingerprint = + OtrActivator.scOtrEngine.getLocalFingerprint(contact + .getProtocolProvider().getAccountID()); + txtLocalFingerprint.setText(OtrActivator.resourceService.getI18NString( + "plugin.otr.authbuddydialog.LOCAL_FINGERPRINT", new String[] + { account, localFingerprint })); + + // Remote fingerprint. + String user = contact.getDisplayName(); + String remoteFingerprint = + OtrActivator.scOtrEngine.getRemoteFingerprint(contact); + txtRemoteFingerprint.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.authbuddydialog.REMOTE_FINGERPRINT", + new String[] + { user, remoteFingerprint })); + } + + private void initComponents() + { + this.setTitle(OtrActivator.resourceService + .getI18NString("plugin.otr.authbuddydialog.TITLE")); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + mainPanel.setPreferredSize(new Dimension(400, 300)); + + JTextArea generalInformation = new JTextArea(); + generalInformation.setBackground(new java.awt.Color(212, 208, 200)); + generalInformation.setColumns(20); + generalInformation.setEditable(false); + generalInformation.setLineWrap(true); + generalInformation.setWrapStyleWord(true); + generalInformation.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.authbuddydialog.AUTHENTICATION_INFO")); + mainPanel.add(generalInformation); + + txtLocalFingerprint = new JTextArea(); + txtLocalFingerprint.setBackground(new java.awt.Color(212, 208, 200)); + txtLocalFingerprint.setColumns(20); + txtLocalFingerprint.setEditable(false); + txtLocalFingerprint.setLineWrap(true); + generalInformation.setWrapStyleWord(true); + + mainPanel.add(txtLocalFingerprint); + + txtRemoteFingerprint = new JTextArea(); + txtRemoteFingerprint.setBackground(new java.awt.Color(212, 208, 200)); + txtRemoteFingerprint.setColumns(20); + txtRemoteFingerprint.setEditable(false); + txtRemoteFingerprint.setLineWrap(true); + generalInformation.setWrapStyleWord(true); + + mainPanel.add(txtRemoteFingerprint); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + + JButton helpButton = + new JButton(OtrActivator.resourceService + .getI18NString("plugin.otr.authbuddydialog.HELP")); + helpButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent arg0) + { + OtrActivator.scOtrEngine.launchHelp(); + } + }); + buttonPanel.add(helpButton); + + JButton cancelButton = + new JButton(OtrActivator.resourceService + .getI18NString("plugin.otr.authbuddydialog.CANCEL")); + cancelButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + dispose(); + } + }); + buttonPanel.add(cancelButton); + + JButton authenticateButton = + new JButton(OtrActivator.resourceService + .getI18NString("plugin.otr.authbuddydialog.AUTHENTICATE_BUDDY")); + authenticateButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + OtrActivator.scOtrEngine.verifyContactFingerprint(contact); + dispose(); + } + }); + buttonPanel.add(authenticateButton); + + mainPanel.add(buttonPanel); + + this.getContentPane().add(mainPanel); + this.pack(); + } +} diff --git a/src/net/java/sip/communicator/plugin/otr/OtrConfigurationPanel.java b/src/net/java/sip/communicator/plugin/otr/OtrConfigurationPanel.java new file mode 100644 index 000000000..542d79fa4 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/OtrConfigurationPanel.java @@ -0,0 +1,497 @@ +package net.java.sip.communicator.plugin.otr; + +import java.awt.*; +import java.awt.event.*; +import java.util.Iterator; +import java.util.Vector; + +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.AbstractTableModel; + +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +import net.java.otr4j.OtrPolicy; +import net.java.sip.communicator.service.contactlist.MetaContact; +import net.java.sip.communicator.service.contactlist.MetaContactListService; +import net.java.sip.communicator.service.protocol.AccountID; +import net.java.sip.communicator.service.protocol.Contact; +import net.java.sip.communicator.service.protocol.ProtocolProviderFactory; +import net.java.sip.communicator.service.protocol.ProtocolProviderService; +import net.java.sip.communicator.util.swing.*; + +@SuppressWarnings("serial") +public class OtrConfigurationPanel + extends TransparentPanel +{ + + class PrivateKeysPanel + extends TransparentPanel + { + class AccountsComboBox + extends JComboBox + { + + class AccountsComboBoxItem + { + public AccountID accountID; + + public AccountsComboBoxItem(AccountID accountID) + { + this.accountID = accountID; + } + + public String toString() + { + return accountID.getDisplayName(); + } + } + + public AccountsComboBox() + { + for (ProtocolProviderFactory providerFactory : OtrActivator + .getProtocolProviderFactories().values()) + { + for (AccountID accountID : providerFactory + .getRegisteredAccounts()) + { + this.addItem(new AccountsComboBoxItem(accountID)); + } + } + } + + public AccountID getSelectedAccountID() + { + return ((AccountsComboBoxItem) this.getSelectedItem()).accountID; + } + } + + private AccountsComboBox cbAccounts; + + private JLabel lblFingerprint; + + private JButton btnGenerate; + + public PrivateKeysPanel() + { + this.initComponents(); + + this.openAccount(cbAccounts.getSelectedAccountID()); + } + + private void openAccount(AccountID account) + { + String fingerprint = + OtrActivator.scOtrEngine.getLocalFingerprint(account); + + if (fingerprint == null || fingerprint.length() < 1) + { + lblFingerprint.setText("No key present"); + btnGenerate.setText("Generate"); + } + else + { + lblFingerprint.setText(fingerprint); + btnGenerate.setText("Re-generate"); + } + } + + private void initComponents() + { + this.setBorder(BorderFactory.createTitledBorder(BorderFactory + .createEtchedBorder(EtchedBorder.LOWERED), + OtrActivator.resourceService + .getI18NString("plugin.otr.configform.MY_PRIVATE_KEYS"))); + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + JPanel pnlAccounts = new TransparentPanel(); + this.add(pnlAccounts); + + pnlAccounts.add(new JLabel("Account: ")); + + cbAccounts = new AccountsComboBox(); + cbAccounts.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + openAccount(((AccountsComboBox) e.getSource()) + .getSelectedAccountID()); + } + }); + pnlAccounts.add(cbAccounts); + + JPanel pnlFingerprint = new TransparentPanel(); + this.add(pnlFingerprint); + + pnlFingerprint.add(new JLabel("Fingerprint: ")); + + lblFingerprint = new JLabel(); + pnlFingerprint.add(lblFingerprint); + + btnGenerate = new JButton(); + btnGenerate.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + AccountID account = cbAccounts.getSelectedAccountID(); + OtrActivator.scOtrEngine.generateKeyPair(account + .getAccountUniqueID()); + openAccount(account); + } + }); + pnlFingerprint.add(btnGenerate); + } + } + + class KnownFingerprintsPanel + extends TransparentPanel + { + + class ContactsTableModel + extends AbstractTableModel + { + public java.util.List allContacts = new Vector(); + + public ContactsTableModel() + { + // Get the protocolproviders + ServiceReference[] protocolProviderRefs = null; + try + { + protocolProviderRefs = + OtrActivator.bundleContext.getServiceReferences( + ProtocolProviderService.class.getName(), null); + } + catch (InvalidSyntaxException ex) + { + return; + } + + if (protocolProviderRefs == null + || protocolProviderRefs.length < 1) + return; + + // Get the metacontactlist service. + ServiceReference ref = + OtrActivator.bundleContext + .getServiceReference(MetaContactListService.class + .getName()); + + MetaContactListService service = + (MetaContactListService) OtrActivator.bundleContext + .getService(ref); + + // Populate contacts. + for (int i = 0; i < protocolProviderRefs.length; i++) + { + ProtocolProviderService provider = + (ProtocolProviderService) OtrActivator.bundleContext + .getService(protocolProviderRefs[i]); + + Iterator metaContacts = + service.findAllMetaContactsForProvider(provider); + while (metaContacts.hasNext()) + { + MetaContact metaContact = metaContacts.next(); + Iterator contacts = metaContact.getContacts(); + while (contacts.hasNext()) + { + allContacts.add(contacts.next()); + } + } + } + } + + public static final int CONTACTNAME_INDEX = 0; + + public static final int VERIFIED_INDEX = 1; + + public static final int FINGERPRINT_INDEX = 2; + + public String getColumnName(int column) + { + switch (column) + { + case CONTACTNAME_INDEX: + return "Contact"; + case VERIFIED_INDEX: + return "Verified"; + case FINGERPRINT_INDEX: + return "Fingerprint"; + default: + return null; + } + } + + public Object getValueAt(int row, int column) + { + if (row < 0) + return null; + + Contact contact = allContacts.get(row); + switch (column) + { + case CONTACTNAME_INDEX: + return contact.getDisplayName(); + case VERIFIED_INDEX: + return (OtrActivator.scOtrEngine.isContactVerified(contact)) ? "Yes" + : "No"; + case FINGERPRINT_INDEX: + return OtrActivator.scOtrEngine + .getRemoteFingerprint(contact); + default: + return null; + } + } + + public int getRowCount() + { + return allContacts.size(); + } + + public int getColumnCount() + { + return 3; + } + } + + public KnownFingerprintsPanel() + { + this.initComponents(); + + openContact(getSelectedContact()); + } + + private Contact getSelectedContact() + { + ContactsTableModel model = + (ContactsTableModel) contactsTable.getModel(); + int index = contactsTable.getSelectedRow(); + if (index < 0 || index > model.allContacts.size()) + return null; + + return model.allContacts.get(index); + } + + private void openContact(Contact contact) + { + if (contact == null) + { + btnForgetFingerprint.setEnabled(false); + btnVerifyFingerprint.setEnabled(false); + } + else + { + boolean verified = + OtrActivator.scOtrEngine.isContactVerified(contact); + + btnForgetFingerprint.setEnabled(verified); + btnVerifyFingerprint.setEnabled(!verified); + } + } + + JButton btnVerifyFingerprint; + + JButton btnForgetFingerprint; + + JTable contactsTable; + + private void initComponents() + { + this + .setBorder(BorderFactory + .createTitledBorder( + BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), + OtrActivator.resourceService + .getI18NString("plugin.otr.configform.KNOWN_FINGERPRINTS"))); + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + + + contactsTable = new JTable(); + contactsTable.setModel(new ContactsTableModel()); + contactsTable + .setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + contactsTable.setCellSelectionEnabled(false); + contactsTable.setColumnSelectionAllowed(false); + contactsTable.setRowSelectionAllowed(true); + contactsTable.getSelectionModel().addListSelectionListener( + new ListSelectionListener() + { + public void valueChanged(ListSelectionEvent e) + { + if (e.getValueIsAdjusting()) + return; + + openContact(getSelectedContact()); + + } + }); + + JScrollPane pnlContacts = new JScrollPane(contactsTable); + this.add(pnlContacts); + + + JPanel pnlButtons = new TransparentPanel(); + this.add(pnlButtons); + + btnVerifyFingerprint = new JButton(); + btnVerifyFingerprint.setText("Verify fingerprint"); + btnVerifyFingerprint.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent arg0) + { + OtrActivator.scOtrEngine + .verifyContactFingerprint(getSelectedContact()); + + } + }); + pnlButtons.add(btnVerifyFingerprint); + + btnForgetFingerprint = new JButton(); + btnForgetFingerprint.setText("Forget fingerprint"); + btnForgetFingerprint.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent arg0) + { + OtrActivator.scOtrEngine + .forgetContactFingerprint(getSelectedContact()); + } + }); + pnlButtons.add(btnForgetFingerprint); + } + } + + // TODO We should listen for configuration value changes. + class DefaultOtrPolicyPanel + extends TransparentPanel + { + public DefaultOtrPolicyPanel() + { + this.initComponents(); + this.loadPolicy(); + } + + public void loadPolicy() + { + OtrPolicy otrPolicy = OtrActivator.scOtrEngine.getGlobalPolicy(); + + boolean otrEnabled = otrPolicy.getEnableManual(); + cbEnable.setSelected(otrEnabled); + cbAutoInitiate.setEnabled(otrEnabled); + cbRequireOtr.setEnabled(otrEnabled); + cbAutoInitiate.setSelected(otrPolicy.getEnableAlways()); + cbRequireOtr.setSelected(otrPolicy.getRequireEncryption()); + } + + private SIPCommCheckBox cbEnable; + + private SIPCommCheckBox cbAutoInitiate; + + private SIPCommCheckBox cbRequireOtr; + + private void initComponents() + { + this.setBorder(BorderFactory.createTitledBorder(BorderFactory + .createEtchedBorder(EtchedBorder.LOWERED), + OtrActivator.resourceService + .getI18NString("plugin.otr.configform.DEFAULT_SETTINGS"))); + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + cbEnable = + new SIPCommCheckBox(OtrActivator.resourceService + .getI18NString("plugin.otr.configform.CB_ENABLE")); + cbEnable.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + OtrPolicy otrPolicy = + OtrActivator.scOtrEngine.getGlobalPolicy(); + + otrPolicy.setEnableManual(((JCheckBox) e.getSource()) + .isSelected()); + + OtrActivator.scOtrEngine.setGlobalPolicy(otrPolicy); + + DefaultOtrPolicyPanel.this.loadPolicy(); + } + }); + this.add(cbEnable); + + cbAutoInitiate = + new SIPCommCheckBox(OtrActivator.resourceService + .getI18NString("plugin.otr.configform.CB_AUTO")); + cbAutoInitiate.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + OtrPolicy otrPolicy = + OtrActivator.scOtrEngine.getGlobalPolicy(); + + otrPolicy.setEnableAlways(((JCheckBox) e.getSource()) + .isSelected()); + + OtrActivator.scOtrEngine.setGlobalPolicy(otrPolicy); + + DefaultOtrPolicyPanel.this.loadPolicy(); + + } + }); + + this.add(cbAutoInitiate); + + cbRequireOtr = + new SIPCommCheckBox(OtrActivator.resourceService + .getI18NString("plugin.otr.configform.CB_REQUIRE")); + cbRequireOtr.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + OtrPolicy otrPolicy = + OtrActivator.scOtrEngine.getGlobalPolicy(); + + otrPolicy.setRequireEncryption(((JCheckBox) e.getSource()) + .isSelected()); + + OtrActivator.scOtrEngine.setGlobalPolicy(otrPolicy); + + DefaultOtrPolicyPanel.this.loadPolicy(); + + } + }); + this.add(cbRequireOtr); + } + } + + public OtrConfigurationPanel() + { + this.initComponents(); + } + + private void initComponents() + { + this.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1.0; + c.anchor = GridBagConstraints.PAGE_START; + + JPanel pnlPrivateKeys = new PrivateKeysPanel(); + c.gridy = 0; + this.add(pnlPrivateKeys, c); + + JPanel pnlPolicy = new DefaultOtrPolicyPanel(); + c.gridy = 1; + this.add(pnlPolicy, c); + + JPanel pnlFingerprints = new KnownFingerprintsPanel(); + pnlFingerprints.setMinimumSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)); + c.weighty = 1.0; + c.gridy = 2; + this.add(pnlFingerprints, c); + } +} \ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/otr/OtrContactMenu.java b/src/net/java/sip/communicator/plugin/otr/OtrContactMenu.java new file mode 100644 index 000000000..619eda7e4 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/OtrContactMenu.java @@ -0,0 +1,265 @@ +package net.java.sip.communicator.plugin.otr; + +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenu; +import javax.swing.JMenuItem; + +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.session.SessionStatus; +import net.java.sip.communicator.service.protocol.Contact; + +@SuppressWarnings("serial") +class OtrContactMenu + extends JMenu +{ + public OtrContactMenu(Contact contact) + { + this.contact = contact; + this.setText(contact.getDisplayName()); + + OtrActivator.scOtrEngine.addListener(new ScOtrEngineListener() + { + public void sessionStatusChanged(Contact contact) + { + SessionStatus status = + OtrActivator.scOtrEngine.getSessionStatus(contact); + + if (contact.equals(OtrContactMenu.this.contact)) + setSessionStatus(status); + } + + public void contactPolicyChanged(Contact contact) + { + // Update the corresponding to the contact menu. + OtrPolicy policy = + OtrActivator.scOtrEngine.getContactPolicy(contact); + + if (contact.equals(OtrContactMenu.this.contact)) + setOtrPolicy(policy); + } + + public void globalPolicyChanged() + { + OtrPolicy policy = + OtrActivator.scOtrEngine + .getContactPolicy(OtrContactMenu.this.contact); + + setOtrPolicy(policy); + } + }); + + setSessionStatus(OtrActivator.scOtrEngine.getSessionStatus(contact)); + setOtrPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact)); + } + + private SessionStatus sessionStatus; + + private OtrPolicy otrPolicy; + + public void rebuildMenu() + { + this.removeAll(); + + OtrPolicy policy = OtrActivator.scOtrEngine.getContactPolicy(contact); + + JMenuItem endOtr = new JMenuItem(); + endOtr.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.menu.END_OTR")); + endOtr.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + // End session. + OtrActivator.scOtrEngine.endSession(contact); + } + }); + + JMenuItem startOtr = new JMenuItem(); + startOtr.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.menu.START_OTR")); + startOtr.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + // Start session. + OtrActivator.scOtrEngine.startSession(contact); + } + }); + startOtr.setEnabled(policy.getEnableManual()); + + switch (this.getSessionStatus()) + { + case ENCRYPTED: + this.setIcon(OtrActivator.resourceService + .getImage("plugin.otr.ENCRYPTED_ICON_15x15")); + + this.add(endOtr); + + JMenuItem refreshOtr = new JMenuItem(); + refreshOtr.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.menu.REFRESH_OTR")); + refreshOtr.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + // Refresh session. + OtrActivator.scOtrEngine.refreshSession(contact); + } + }); + refreshOtr.setEnabled(policy.getEnableManual()); + this.add(refreshOtr); + + if (!OtrActivator.scOtrEngine.isContactVerified(contact)) + { + JMenuItem authBuddy = new JMenuItem(); + authBuddy.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.menu.AUTHENTICATE_BUDDY")); + authBuddy.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + // Launch auth buddy dialog. + OtrBuddyAuthenticationDialog authenticateBuddyDialog = + new OtrBuddyAuthenticationDialog(contact); + + authenticateBuddyDialog.setLocation(Toolkit + .getDefaultToolkit().getScreenSize().width + / 2 - authenticateBuddyDialog.getWidth() / 2, + Toolkit.getDefaultToolkit().getScreenSize().height + / 2 - authenticateBuddyDialog.getHeight() / 2); + + authenticateBuddyDialog.setVisible(true); + } + }); + this.add(authBuddy); + } + break; + case FINISHED: + this.setIcon(OtrActivator.resourceService + .getImage("plugin.otr.FINISHED_ICON_15x15")); + + this.add(endOtr); + this.add(startOtr); + break; + case PLAINTEXT: + this.setIcon(OtrActivator.resourceService + .getImage("plugin.otr.PLAINTEXT_ICON_15x15")); + + this.add(startOtr); + break; + } + + this.addSeparator(); + + JCheckBoxMenuItem cbEnable = new JCheckBoxMenuItem(); + cbEnable.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.menu.CB_ENABLE")); + + cbEnable.setState(policy.getEnableManual()); + + cbEnable.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + OtrPolicy policy = + OtrActivator.scOtrEngine.getContactPolicy(contact); + + boolean state = ((JCheckBoxMenuItem) e.getSource()).getState(); + policy.setEnableManual(state); + OtrActivator.scOtrEngine.setContactPolicy(contact, policy); + } + }); + this.add(cbEnable); + + JCheckBoxMenuItem cbAlways = new JCheckBoxMenuItem(); + cbAlways.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.menu.CB_AUTO")); + + cbAlways.setEnabled(policy.getEnableManual()); + cbAlways.setState(policy.getEnableAlways()); + + cbAlways.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + OtrPolicy policy = + OtrActivator.scOtrEngine.getContactPolicy(contact); + + boolean state = ((JCheckBoxMenuItem) e.getSource()).getState(); + policy.setEnableAlways(state); + OtrActivator.scOtrEngine.setContactPolicy(contact, policy); + } + }); + this.add(cbAlways); + + JCheckBoxMenuItem cbRequire = new JCheckBoxMenuItem(); + cbRequire.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.menu.CB_REQUIRE")); + + cbRequire.setEnabled(policy.getEnableManual()); + cbRequire.setState(policy.getRequireEncryption()); + + cbRequire.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + OtrPolicy policy = + OtrActivator.scOtrEngine.getContactPolicy(contact); + + boolean state = ((JCheckBoxMenuItem) e.getSource()).getState(); + policy.setEnableAlways(state); + OtrActivator.scOtrEngine.setContactPolicy(contact, policy); + } + }); + this.add(cbRequire); + + this.addSeparator(); + + JCheckBoxMenuItem cbReset = new JCheckBoxMenuItem(); + cbReset.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.menu.CB_RESET")); + + cbReset.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + OtrActivator.scOtrEngine.setContactPolicy(contact, null); + } + }); + this.add(cbReset); + } + + public void setSessionStatus(SessionStatus sessionStatus) + { + if (sessionStatus == this.sessionStatus) + return; + + this.sessionStatus = sessionStatus; + this.rebuildMenu(); + } + + public SessionStatus getSessionStatus() + { + return sessionStatus; + } + + public void setOtrPolicy(OtrPolicy otrPolicy) + { + if (otrPolicy.equals(this.otrPolicy)) + return; + + this.otrPolicy = otrPolicy; + this.rebuildMenu(); + } + + public OtrPolicy getOtrPolicy() + { + return otrPolicy; + } + + public Contact contact; +} diff --git a/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java new file mode 100644 index 000000000..1b7fec343 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactButton.java @@ -0,0 +1,172 @@ +package net.java.sip.communicator.plugin.otr; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; + +import javax.imageio.*; + +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.session.*; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.gui.Container; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.swing.*; + +@SuppressWarnings("serial") +public class OtrMetaContactButton + extends SIPCommButton + implements PluginComponent +{ + + private Container container; + + public OtrMetaContactButton(Container container) + { + super(null, null); + this.setEnabled(false); + this.setPreferredSize(new Dimension(25, 25)); + this.container = container; + + OtrActivator.scOtrEngine.addListener(new ScOtrEngineListener() + { + public void sessionStatusChanged(Contact contact) + { + // OtrMetaContactButton.this.contact can be null. + if (contact.equals(OtrMetaContactButton.this.contact)) + { + setStatus(OtrActivator.scOtrEngine + .getSessionStatus(contact)); + } + } + + public void contactPolicyChanged(Contact contact) + { + // OtrMetaContactButton.this.contact can be null. + if (contact.equals(OtrMetaContactButton.this.contact)) + { + setPolicy(OtrActivator.scOtrEngine + .getContactPolicy(contact)); + } + } + + public void globalPolicyChanged() + { + if (OtrMetaContactButton.this.contact != null) + setPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact)); + } + }); + + this.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + if (contact == null) + return; + + switch (OtrActivator.scOtrEngine.getSessionStatus(contact)) + { + case ENCRYPTED: + // Default action for encrypted session is end session. + OtrActivator.scOtrEngine.endSession(contact); + break; + case FINISHED: + case PLAINTEXT: + // Default action for finished and plaintext sessions is + // start session. + OtrActivator.scOtrEngine.startSession(contact); + break; + } + } + }); + } + + public Object getComponent() + { + return this; + } + + public String getConstraints() + { + return null; + } + + public Container getContainer() + { + return this.container; + } + + public int getPositionIndex() + { + return -1; + } + + public boolean isNativeComponent() + { + return false; + } + + public void setCurrentContact(MetaContact metaContact) + { + contact = metaContact.getDefaultContact(); + this.setStatus(OtrActivator.scOtrEngine.getSessionStatus(contact)); + this.setPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact)); + } + + private void setPolicy(OtrPolicy contactPolicy) + { + this.setEnabled(contactPolicy.getEnableManual()); + } + + private Contact contact; + + public void setCurrentContactGroup(MetaContactGroup metaGroup) + { + + } + + private void setStatus(SessionStatus status) + { + if (contact == null) + return; + + switch (status) + { + case ENCRYPTED: + try + { + this.setImage(ImageIO.read(OtrActivator.resourceService + .getImageURL("plugin.otr.ENCRYPTED_ICON_25x25"))); + } + catch (IOException e) + { + e.printStackTrace(); + } + break; + case FINISHED: + try + { + this.setImage(ImageIO.read(OtrActivator.resourceService + .getImageURL("plugin.otr.FINISHED_ICON_25x25"))); + } + catch (IOException e) + { + e.printStackTrace(); + } + break; + case PLAINTEXT: + try + { + this.setImage(ImageIO.read(OtrActivator.resourceService + .getImageURL("plugin.otr.PLAINTEXT_ICON_25x25"))); + } + catch (IOException e) + { + e.printStackTrace(); + } + break; + } + } + +} diff --git a/src/net/java/sip/communicator/plugin/otr/OtrMetaContactMenu.java b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactMenu.java new file mode 100644 index 000000000..f66bc4f25 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/OtrMetaContactMenu.java @@ -0,0 +1,87 @@ +package net.java.sip.communicator.plugin.otr; + +import java.awt.Component; /* Explicit import required */ +import java.awt.event.*; +import java.util.Iterator; + +import javax.swing.*; + +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; + +@SuppressWarnings("serial") +public class OtrMetaContactMenu + extends JMenu + implements PluginComponent +{ + + private Container container; + + public OtrMetaContactMenu(Container container) + { + this.container = container; + this.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.menu.TITLE")); + } + + public String getConstraints() + { + return null; + } + + public Component getComponent() + { + return this; + } + + public Container getContainer() + { + return this.container; + } + + public int getPositionIndex() + { + return -1; + } + + public boolean isNativeComponent() + { + return false; + } + + public void setCurrentContact(MetaContact metaContact) + { + // Rebuild menu. + this.removeAll(); + + if (metaContact == null) + return; + + Iterator contacts = metaContact.getContacts(); + while (contacts.hasNext()) + { + this.add(new OtrContactMenu(contacts.next())); + } + + this.addSeparator(); + + JMenuItem whatsThis = new JMenuItem(); + whatsThis.setIcon(OtrActivator.resourceService + .getImage("plugin.otr.HELP_ICON_15x15")); + whatsThis.setText(OtrActivator.resourceService + .getI18NString("plugin.otr.menu.WHATS_THIS")); + whatsThis.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + OtrActivator.scOtrEngine.launchHelp(); + } + }); + this.add(whatsThis); + } + + public void setCurrentContactGroup(MetaContactGroup metaGroup) + { + } +} \ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java b/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java new file mode 100644 index 000000000..54b35f9d9 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrEngine.java @@ -0,0 +1,54 @@ +package net.java.sip.communicator.plugin.otr; + +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.session.SessionStatus; +import net.java.sip.communicator.service.protocol.AccountID; +import net.java.sip.communicator.service.protocol.Contact; + +public interface ScOtrEngine +{ + // Proxy methods OtrEngine. + public abstract String transformSending(Contact contact, String content); + + public abstract String transformReceiving(Contact contact, String content); + + public abstract void startSession(Contact contact); + + public abstract void endSession(Contact contact); + + public abstract void refreshSession(Contact contact); + + public abstract SessionStatus getSessionStatus(Contact contact); + + // New Methods (Key Management) + public abstract void generateKeyPair(String accountID); + + public abstract String getRemoteFingerprint(Contact contact); + + public abstract void verifyContactFingerprint(Contact contact); + + public abstract void forgetContactFingerprint(Contact contact); + + public abstract String getLocalFingerprint(AccountID account); + + public abstract boolean isContactVerified(Contact contact); + + // New Methods (Misc) + public abstract boolean isMessageUIDInjected(String messageUID); + + public abstract void addListener(ScOtrEngineListener listener); + + public abstract void removeListener(ScOtrEngineListener listener); + + // New Methods (Policy management) + public abstract OtrPolicy getGlobalPolicy(); + + public abstract OtrPolicy getContactPolicy(Contact contact); + + public abstract void setGlobalPolicy(OtrPolicy policy); + + public abstract void setContactPolicy(Contact contact, OtrPolicy policy); + + public abstract void launchHelp(); + +} diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java b/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java new file mode 100644 index 000000000..a11e512bf --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrEngineImpl.java @@ -0,0 +1,530 @@ +package net.java.sip.communicator.plugin.otr; + +import java.awt.Desktop; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.*; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import org.bouncycastle.util.encoders.Base64; + +import net.java.otr4j.OtrEngine; +import net.java.otr4j.OtrEngineImpl; +import net.java.otr4j.OtrEngineHost; +import net.java.otr4j.OtrKeyManager; +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.OtrPolicyImpl; +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.crypto.OtrCryptoException; +import net.java.otr4j.session.SessionID; +import net.java.otr4j.session.SessionStatus; +import net.java.sip.communicator.service.gui.PopupDialog; +import net.java.sip.communicator.service.protocol.AccountID; +import net.java.sip.communicator.service.protocol.Contact; +import net.java.sip.communicator.service.protocol.Message; +import net.java.sip.communicator.service.protocol.OperationSetBasicInstantMessaging; + +public class ScOtrEngineImpl + implements ScOtrEngine +{ + + private List listeners = + new Vector(); + + public void addListener(ScOtrEngineListener l) + { + listeners.add(l); + } + + public void removeListener(ScOtrEngineListener l) + { + listeners.remove(l); + } + + private List injectedMessageUIDs = new Vector(); + + public boolean isMessageUIDInjected(String mUID) + { + return injectedMessageUIDs.contains(mUID); + } + + class ScOtrKeyManager + implements OtrKeyManager + { + public KeyPair getKeyPair(SessionID sessionID) + { + String accountID = sessionID.getAccountID(); + KeyPair keyPair = loadKeyPair(accountID); + if (keyPair == null) + generateKeyPair(accountID); + + return loadKeyPair(accountID); + } + } + + class ScOtrEngineHost + implements OtrEngineHost + { + public void showWarning(SessionID sessionID, String warn) + { + // TODO Dialog usage is not that great. + OtrActivator.uiService.getPopupDialog().showMessagePopupDialog( + warn, "OTR warning", PopupDialog.WARNING_MESSAGE); + } + + public void showError(SessionID sessionID, String err) + { + // TODO Dialog usage is not that great. + OtrActivator.uiService.getPopupDialog().showMessagePopupDialog(err, + "OTR Error", PopupDialog.ERROR_MESSAGE); + } + + public void injectMessage(SessionID sessionID, String messageText) + { + Contact contact = contactsMap.get(sessionID); + OperationSetBasicInstantMessaging imOpSet = + (OperationSetBasicInstantMessaging) contact + .getProtocolProvider().getOperationSet( + OperationSetBasicInstantMessaging.class); + + Message message = imOpSet.createMessage(messageText); + injectedMessageUIDs.add(message.getMessageUID()); + imOpSet.sendInstantMessage(contact, message); + } + + public OtrPolicy getSessionPolicy(SessionID sessionID) + { + return getContactPolicy(contactsMap.get(sessionID)); + } + + public void sessionStatusChanged(SessionID sessionID) + { + Contact contact = contactsMap.get(sessionID); + if (contact == null) + return; + + switch (otrEngine.getSessionStatus(sessionID)) + { + case ENCRYPTED: + PublicKey remotePubKey = + otrEngine.getRemotePublicKey(sessionID); + + PublicKey storedPubKey = loadPublicKey(sessionID.getUserID()); + + if (!remotePubKey.equals(storedPubKey)) + savePublicKey(sessionID.getUserID(), remotePubKey); + break; + } + + for (ScOtrEngineListener l : listeners) + { + l.sessionStatusChanged(contact); + } + } + } + + private OtrEngine otrEngine = + new OtrEngineImpl(new ScOtrEngineHost(), new ScOtrKeyManager()); + + public boolean isContactVerified(Contact contact) + { + String id = getSessionNS(getSessionID(contact), "publicKey.verified"); + if (id == null || id.length() < 1) + return false; + + return OtrActivator.configService.getBoolean(id, false); + } + + Map contactsMap = new Hashtable(); + + public void endSession(Contact contact) + { + otrEngine.endSession(getSessionID(contact)); + } + + public SessionStatus getSessionStatus(Contact contact) + { + return otrEngine.getSessionStatus(getSessionID(contact)); + } + + public String transformReceiving(Contact contact, String msgText) + { + return otrEngine.transformReceiving(getSessionID(contact), msgText); + } + + public String transformSending(Contact contact, String msgText) + { + return otrEngine.transformSending(getSessionID(contact), msgText); + } + + public void refreshSession(Contact contact) + { + otrEngine.refreshSession(getSessionID(contact)); + } + + public void startSession(Contact contact) + { + otrEngine.startSession(getSessionID(contact)); + } + + private SessionID getSessionID(Contact contact) + { + SessionID sessionID = + new SessionID(contact.getProtocolProvider().getAccountID() + .getAccountUniqueID(), contact.getAddress(), contact + .getProtocolProvider().getProtocolName()); + + contactsMap.put(sessionID, contact); + return sessionID; + } + + public String getRemoteFingerprint(Contact contact) + { + PublicKey remotePublicKey = loadPublicKey(contact.getAddress()); + if (remotePublicKey == null) + return null; + try + { + return new OtrCryptoEngineImpl().getFingerprint(remotePublicKey); + } + catch (OtrCryptoException e) + { + e.printStackTrace(); + return null; + } + } + + public String getLocalFingerprint(AccountID account) + { + KeyPair keyPair = loadKeyPair(account.getAccountUniqueID()); + + if (keyPair == null) + return null; + + PublicKey pubKey = keyPair.getPublic(); + + try + { + return new OtrCryptoEngineImpl().getFingerprint(pubKey); + } + catch (OtrCryptoException e) + { + e.printStackTrace(); + return null; + } + } + + private String getSessionNS(SessionID sessionID, String function) + { + try + { + return "net.java.sip.comunicator.plugin.otr." + + URLEncoder.encode(sessionID.toString(), "UTF-8") + "." + + function; + } + catch (UnsupportedEncodingException e1) + { + e1.printStackTrace(); + return null; + } + } + + public void verifyContactFingerprint(Contact contact) + { + if (contact == null) + return; + + String id = getSessionNS(getSessionID(contact), "publicKey.verified"); + if (id == null || id.length() < 1) + return; // TODO provide error handling. + + OtrActivator.configService.setProperty(id, true); + } + + public void forgetContactFingerprint(Contact contact) + { + if (contact == null) + return; + + String id = getSessionNS(getSessionID(contact), "publicKey.verified"); + if (id == null || id.length() < 1) + return; // TODO provide error handling. + + OtrActivator.configService.removeProperty(id); + + } + + public OtrPolicy getGlobalPolicy() + { + return new OtrPolicyImpl(OtrActivator.configService.getInt( + "net.java.sip.comunicator.plugin.otr.POLICY", + OtrPolicy.OTRL_POLICY_DEFAULT)); + } + + public void setGlobalPolicy(OtrPolicy policy) + { + if (policy == null) + OtrActivator.configService + .removeProperty("net.java.sip.comunicator.plugin.otr.POLICY"); + else + OtrActivator.configService.setProperty( + "net.java.sip.comunicator.plugin.otr.POLICY", policy + .getPolicy()); + + for (ScOtrEngineListener l : listeners) + l.globalPolicyChanged(); + } + + public void launchHelp() + { + boolean fallback = false; + if (!Desktop.isDesktopSupported()) + { + fallback = true; + } + else + { + try + { + Desktop.getDesktop().browse( + new URI(OtrActivator.resourceService + .getI18NString("plugin.otr.authbuddydialog.HELP_URI"))); + } + catch (Exception ex) + { + // not possible. + fallback = true; + } + } + + if (fallback) + { + // TODO Either find another way to launch the URI or display + // a + // dialog, we need to discuss this first. + } + } + + public OtrPolicy getContactPolicy(Contact contact) + { + String id = getSessionNS(getSessionID(contact), "policy"); + if (id == null || id.length() < 1) + return getGlobalPolicy(); + + int policy = OtrActivator.configService.getInt(id, -1); + if (policy < 0) + return getGlobalPolicy(); + else + return new OtrPolicyImpl(policy); + } + + public void setContactPolicy(Contact contact, OtrPolicy policy) + { + String id = getSessionNS(getSessionID(contact), "policy"); + if (id == null || id.length() < 1) + return; + + if (policy == null) + OtrActivator.configService.removeProperty(id); + else + OtrActivator.configService.setProperty(id, policy.getPolicy()); + + for (ScOtrEngineListener l : listeners) + l.contactPolicyChanged(contact); + + } + + private KeyPair loadKeyPair(String accountID) + { + // Load Private Key. + String idPrivKey; + try + { + idPrivKey = + "net.java.sip.comunicator.plugin.otr." + + URLEncoder.encode(accountID, "UTF-8") + ".privateKey"; + } + catch (UnsupportedEncodingException e1) + { + e1.printStackTrace(); + return null; + } + Object b64PrivKey = OtrActivator.configService.getProperty(idPrivKey); + if (b64PrivKey == null) + return null; + + PKCS8EncodedKeySpec privateKeySpec = + new PKCS8EncodedKeySpec(Base64.decode((String) b64PrivKey)); + + // Load Public Key. + String idPubKey; + try + { + idPubKey = + "net.java.sip.comunicator.plugin.otr." + + URLEncoder.encode(accountID, "UTF-8") + ".publicKey"; + } + catch (UnsupportedEncodingException e1) + { + e1.printStackTrace(); + return null; + } + Object b64PubKey = OtrActivator.configService.getProperty(idPubKey); + if (b64PubKey == null) + return null; + + X509EncodedKeySpec publicKeySpec = + new X509EncodedKeySpec(Base64.decode((String) b64PubKey)); + + PublicKey publicKey; + PrivateKey privateKey; + + // Generate KeyPair. + KeyFactory keyFactory; + try + { + keyFactory = KeyFactory.getInstance("DSA"); + publicKey = keyFactory.generatePublic(publicKeySpec); + privateKey = keyFactory.generatePrivate(privateKeySpec); + } + catch (NoSuchAlgorithmException e) + { + e.printStackTrace(); + return null; + } + catch (InvalidKeySpecException e) + { + e.printStackTrace(); + return null; + } + + return new KeyPair(publicKey, privateKey); + } + + public void generateKeyPair(String accountID) + { + KeyPair keyPair; + try + { + keyPair = KeyPairGenerator.getInstance("DSA").genKeyPair(); + } + catch (NoSuchAlgorithmException e) + { + e.printStackTrace(); + return; + } + + // Store Public Key. + String idPubKey; + try + { + idPubKey = + "net.java.sip.comunicator.plugin.otr." + + URLEncoder.encode(accountID, "UTF-8") + ".publicKey"; + } + catch (UnsupportedEncodingException e1) + { + e1.printStackTrace(); + return; + } + PublicKey pubKey = keyPair.getPublic(); + X509EncodedKeySpec x509EncodedKeySpec = + new X509EncodedKeySpec(pubKey.getEncoded()); + OtrActivator.configService.setProperty(idPubKey, new String(Base64 + .encode(x509EncodedKeySpec.getEncoded()))); + + // Store Private Key. + String idPrivKey; + try + { + idPrivKey = + "net.java.sip.comunicator.plugin.otr." + + URLEncoder.encode(accountID, "UTF-8") + ".privateKey"; + } + catch (UnsupportedEncodingException e1) + { + e1.printStackTrace(); + return; + } + + PrivateKey privKey = keyPair.getPrivate(); + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = + new PKCS8EncodedKeySpec(privKey.getEncoded()); + OtrActivator.configService.setProperty(idPrivKey, new String(Base64 + .encode(pkcs8EncodedKeySpec.getEncoded()))); + } + + private void savePublicKey(String userID, PublicKey pubKey) + { + String idPubKey; + try + { + idPubKey = + "net.java.sip.comunicator.plugin.otr." + + URLEncoder.encode(userID, "UTF-8") + ".publicKey"; + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + return; + } + X509EncodedKeySpec x509EncodedKeySpec = + new X509EncodedKeySpec(pubKey.getEncoded()); + + OtrActivator.configService.setProperty(idPubKey, new String(Base64 + .encode(x509EncodedKeySpec.getEncoded()))); + + OtrActivator.configService.removeProperty(idPubKey + ".verified"); + } + + private PublicKey loadPublicKey(String userID) + { + String idPubKey; + try + { + idPubKey = + "net.java.sip.comunicator.plugin.otr." + + URLEncoder.encode(userID, "UTF-8") + ".publicKey"; + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + return null; + } + + Object b64PubKey = OtrActivator.configService.getProperty(idPubKey); + if (b64PubKey == null) + return null; + + X509EncodedKeySpec publicKeySpec = + new X509EncodedKeySpec(Base64.decode((String) b64PubKey)); + + // Generate KeyPair. + KeyFactory keyFactory; + try + { + keyFactory = KeyFactory.getInstance("DSA"); + return keyFactory.generatePublic(publicKeySpec); + } + catch (NoSuchAlgorithmException e) + { + e.printStackTrace(); + return null; + } + catch (InvalidKeySpecException e) + { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/net/java/sip/communicator/plugin/otr/ScOtrEngineListener.java b/src/net/java/sip/communicator/plugin/otr/ScOtrEngineListener.java new file mode 100644 index 000000000..366acab92 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/otr/ScOtrEngineListener.java @@ -0,0 +1,12 @@ +package net.java.sip.communicator.plugin.otr; + +import net.java.sip.communicator.service.protocol.Contact; + +public interface ScOtrEngineListener +{ + public abstract void sessionStatusChanged(Contact contact); + + public abstract void contactPolicyChanged(Contact contact); + + public abstract void globalPolicyChanged(); +} diff --git a/test/net/java/sip/communicator/slick/protocol/generic/ImEventCollector.java b/test/net/java/sip/communicator/slick/protocol/generic/ImEventCollector.java new file mode 100644 index 000000000..638624db7 --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/generic/ImEventCollector.java @@ -0,0 +1,98 @@ +package net.java.sip.communicator.slick.protocol.generic; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * Collects instant messaging events. + */ +public class ImEventCollector + implements MessageListener +{ + private static final Logger logger = + Logger.getLogger(ImEventCollector.class); + + public List collectedEvents = new LinkedList(); + + /** + * Called when a new incoming Message has been received. + * + * @param evt the MessageReceivedEvent containing the newly + * received message, its sender and other details. + */ + public void messageReceived(MessageReceivedEvent evt) + { + logger.debug("Received a MessageReceivedEvent: " + evt); + + synchronized (this) + { + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Called to indicated that delivery of a message sent earlier has failed. + * Reason code and phrase are contained by the MessageFailedEvent + * + * @param evt the MessageFailedEvent containing the ID of the + * message whose delivery has failed. + */ + public void messageDeliveryFailed(MessageDeliveryFailedEvent evt) + { + logger.debug("Received a MessageDeliveryFailedEvent: " + evt); + + synchronized (this) + { + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Called when the underlying implementation has received an indication that + * a message, sent earlier has been successfully received by the + * destination. + * + * @param evt the MessageDeliveredEvent containing the id of the message + * that has caused the event. + */ + public void messageDelivered(MessageDeliveredEvent evt) + { + logger.debug("Received a MessageDeliveredEvent: " + evt); + + synchronized (this) + { + collectedEvents.add(evt); + notifyAll(); + } + } + + /** + * Blocks until at least one event is received or until waitFor miliseconds + * pass (whichever happens first). + * + * @param waitFor the number of miliseconds that we should be waiting for an + * event before simply bailing out. + */ + public void waitForEvent(long waitFor) + { + synchronized (this) + { + + if (collectedEvents.size() > 0) + return; + + try + { + wait(waitFor); + } + catch (InterruptedException ex) + { + logger.debug("Interrupted while waiting for a message evt", ex); + } + } + } +} \ No newline at end of file diff --git a/test/net/java/sip/communicator/slick/protocol/generic/PredictableTransformLayer.java b/test/net/java/sip/communicator/slick/protocol/generic/PredictableTransformLayer.java new file mode 100644 index 000000000..02818741e --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/generic/PredictableTransformLayer.java @@ -0,0 +1,83 @@ +package net.java.sip.communicator.slick.protocol.generic; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + + +public class PredictableTransformLayer + implements TransformLayer +{ + private static final Logger logger = + Logger.getLogger(PredictableTransformLayer.class); + + public MessageDeliveredEvent messageDelivered(MessageDeliveredEvent evt) + { + logger + .debug("Message Delivered Transformation, transform a message after it has been sent."); + logger.debug("IN: " + evt.getSourceMessage().getContent()); + Message transformedMessage = + createMessage(evt.getDestinationContact(), evt.getSourceMessage(), + "DELIVERED"); + + logger.debug("OUT: " + transformedMessage.getContent()); + return new MessageDeliveredEvent(transformedMessage, evt + .getDestinationContact(), evt.getTimestamp()); + } + + public MessageDeliveryFailedEvent messageDeliveryFailed( + MessageDeliveryFailedEvent evt) + { + logger + .debug("Message Delivery Failed Transformation, transform a message after it has failed to be sent."); + logger.debug("IN: " + evt.getSourceMessage().getContent()); + Message transformedMessage = + createMessage(evt.getDestinationContact(), evt.getSourceMessage(), + "DELIVERY_FAILED"); + + logger.debug("OUT: " + transformedMessage.getContent()); + return new MessageDeliveryFailedEvent(transformedMessage, evt + .getDestinationContact(), evt.getErrorCode()); + } + + public MessageDeliveredEvent messageDeliveryPending( + MessageDeliveredEvent evt) + { + logger + .debug("Message Delivered Transformation, transform a message after it has failed to be sent."); + logger.debug("IN: " + evt.getSourceMessage().getContent()); + Message transformedMessage = + createMessage(evt.getDestinationContact(), evt.getSourceMessage(), + "DELIVERY_PENDING"); + + logger.debug("OUT: " + transformedMessage.getContent()); + return new MessageDeliveredEvent(transformedMessage, evt + .getDestinationContact(), evt.getTimestamp()); + } + + public MessageReceivedEvent messageReceived(MessageReceivedEvent evt) + { + logger + .debug("Message Received Transformation, transform a message after it has been received."); + logger.debug("IN: " + evt.getSourceMessage().getContent()); + Message transformedMessage = + createMessage(evt.getSourceContact(), evt.getSourceMessage(), + "RECEIVED"); + + logger.debug("OUT: " + transformedMessage.getContent()); + return new MessageReceivedEvent(transformedMessage, evt + .getSourceContact(), evt.getTimestamp()); + + } + + private Message createMessage(Contact contact, Message message, + String action) + { + OperationSetBasicInstantMessaging imOpSet = + (OperationSetBasicInstantMessaging) contact.getProtocolProvider() + .getOperationSet(OperationSetBasicInstantMessaging.class); + return imOpSet.createMessage("__" + action + "__" + + message.getContent()); + } + +} diff --git a/test/net/java/sip/communicator/slick/protocol/jabber/TestOperationSetInstantMessageTransformJabberImpl.java b/test/net/java/sip/communicator/slick/protocol/jabber/TestOperationSetInstantMessageTransformJabberImpl.java new file mode 100644 index 000000000..c578eb477 --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/jabber/TestOperationSetInstantMessageTransformJabberImpl.java @@ -0,0 +1,274 @@ +package net.java.sip.communicator.slick.protocol.jabber; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.slick.protocol.generic.*; +import net.java.sip.communicator.util.*; +import junit.framework.*; + +public class TestOperationSetInstantMessageTransformJabberImpl + extends TestCase +{ + + private static final Logger logger = + Logger + .getLogger(TestOperationSetInstantMessageTransformJabberImpl.class); + + private JabberSlickFixture fixture = new JabberSlickFixture(); + + private OperationSetBasicInstantMessaging opSetBasicIM1 = null; + + private OperationSetBasicInstantMessaging opSetBasicIM2 = null; + + private OperationSetInstantMessageTransform opSetTransform1 = null; + + private OperationSetInstantMessageTransform opSetTransform2 = null; + + private OperationSetPresence opSetPresence1 = null; + + private OperationSetPresence opSetPresence2 = null; + + public TestOperationSetInstantMessageTransformJabberImpl(String name) + { + super(name); + } + + protected void setUp() throws Exception + { + super.setUp(); + fixture.setUp(); + + Map supportedOperationSets1 = + fixture.provider1.getSupportedOperationSets(); + + if (supportedOperationSets1 == null + || supportedOperationSets1.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + + "this implementation. "); + + // get the operation set presence here. + opSetBasicIM1 = + (OperationSetBasicInstantMessaging) supportedOperationSets1 + .get(OperationSetBasicInstantMessaging.class.getName()); + + if (opSetBasicIM1 == null) + { + throw new NullPointerException( + "No implementation for basic IM was found"); + } + + // we also need the presence op set in order to retrieve contacts. + opSetTransform1 = + (OperationSetInstantMessageTransform) supportedOperationSets1 + .get(OperationSetInstantMessageTransform.class.getName()); + + // if the op set is null show that we're not happy. + if (opSetTransform1 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an OperationSetInstantMessageTransform implementation"); + } + + // we also need the presence op set in order to retrieve contacts. + opSetPresence1 = + (OperationSetPresence) supportedOperationSets1 + .get(OperationSetPresence.class.getName()); + + // if the op set is null show that we're not happy. + if (opSetPresence1 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an " + + "implementation of at least one of the PresenceOperationSets"); + } + + Map supportedOperationSets2 = + fixture.provider2.getSupportedOperationSets(); + + if (supportedOperationSets2 == null + || supportedOperationSets2.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + + "this implementation. "); + + // get the operation set presence here. + opSetBasicIM2 = + (OperationSetBasicInstantMessaging) supportedOperationSets2 + .get(OperationSetBasicInstantMessaging.class.getName()); + + if (opSetBasicIM2 == null) + { + throw new NullPointerException( + "No implementation for basic IM was found"); + } + + opSetTransform2 = + (OperationSetInstantMessageTransform) supportedOperationSets2 + .get(OperationSetInstantMessageTransform.class.getName()); + + // if the op set is null show that we're not happy. + if (opSetTransform2 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an OperationSetInstantMessageTransform implementation"); + } + + opSetPresence2 = + (OperationSetPresence) supportedOperationSets2 + .get(OperationSetPresence.class.getName()); + + // if the op set is null show that we're not happy. + if (opSetPresence2 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an " + + "implementation of at least one of the PresenceOperationSets"); + } + + } + + protected void tearDown() throws Exception + { + super.tearDown(); + + fixture.tearDown(); + } + + public static Test suite() + { + TestSuite suite = new TestSuite(); + + suite.addTest(new TestOperationSetInstantMessageTransformJabberImpl( + "firstTestTransformLayerInstallation")); + + suite + .addTestSuite(TestOperationSetInstantMessageTransformJabberImpl.class); + + return suite; + } + + public void firstTestTransformLayerInstallation() + { + PredictableTransformLayer transformLayer = + new PredictableTransformLayer(); + + opSetTransform1.addTransformLayer(transformLayer); + if (!opSetTransform1.containsLayer(transformLayer)) + fail("Transform layer did not install."); + + opSetTransform2.addTransformLayer(transformLayer); + if (!opSetTransform2.containsLayer(transformLayer)) + fail("Transform layer did not install."); + } + + public void testMessageReceivedTransform() + { + String body = + "This is an IM coming from the tester agent" + " on " + + new Date().toString(); + + // We expect out message to be transformed only from the + // MessageDelivered Event. + String expectedReceivedBody = "__RECEIVED____DELIVERY_PENDING__" + body; + + ImEventCollector receiversEventCollector = new ImEventCollector(); + + opSetBasicIM1.addMessageListener(receiversEventCollector); + + Contact contact1 = opSetPresence2.findContactByID(fixture.userID1); + + logger.debug("Will send message \"" + body + "\" to: \"" + contact1 + + "\". We expect to get back: \"" + expectedReceivedBody + "\""); + + opSetBasicIM2.sendInstantMessage(contact1, opSetBasicIM2 + .createMessage(body)); + + receiversEventCollector.waitForEvent(timeout); + + opSetBasicIM1.removeMessageListener(receiversEventCollector); + + // assert reception of a message event + assertTrue("No events delivered upon a received message", + receiversEventCollector.collectedEvents.size() > 0); + + // assert event instance of Message Received Evt + assertTrue( + "Received evt was not an instance of " + + MessageReceivedEvent.class.getName(), + receiversEventCollector.collectedEvents.get(0) instanceof MessageReceivedEvent); + + // assert source contact == testAgent.uin + MessageReceivedEvent evt = + (MessageReceivedEvent) receiversEventCollector.collectedEvents + .get(0); + assertEquals("message sender ", evt.getSourceContact().getAddress(), + fixture.userID2); + + logger.debug("We got back: \"" + evt.getSourceMessage().getContent() + + "\""); + + // assert messageBody == body + assertEquals("message body", expectedReceivedBody, evt + .getSourceMessage().getContent()); + + } + + private static final long timeout = 1000; + + public void testMessageDeliveredTransform() + { + String body = + "This is an IM coming from the tester agent" + " on " + + new Date().toString(); + + // The message will be transformed prior to sending and after being + // received, we expect two underscores to be prepended and two + // underscores to be appended. + String expectedReceivedBody = "__DELIVERED__" + body; + + ImEventCollector sendersEventCollector = new ImEventCollector(); + opSetBasicIM2.addMessageListener(sendersEventCollector); + + Contact contact1 = opSetPresence2.findContactByID(fixture.userID1); + + logger.debug("Will send message \"" + body + "\" to: \"" + contact1 + + "\". We expect to get back: \"" + expectedReceivedBody + "\""); + + opSetBasicIM2.sendInstantMessage(contact1, opSetBasicIM2 + .createMessage(body)); + + sendersEventCollector.waitForEvent(timeout); + opSetBasicIM2.removeMessageListener(sendersEventCollector); + + // assert reception of a message event + assertTrue("No events delivered upon a sent message", + sendersEventCollector.collectedEvents.size() > 0); + + // assert event instance of Message Received Evt + assertTrue( + "Received evt was not an instance of " + + MessageDeliveredEvent.class.getName(), + sendersEventCollector.collectedEvents.get(0) instanceof MessageDeliveredEvent); + + // assert source contact == testAgent.uin + MessageDeliveredEvent evtDelivered = + (MessageDeliveredEvent) sendersEventCollector.collectedEvents + .get(0); + assertEquals("message sender ", evtDelivered.getDestinationContact() + .getAddress(), fixture.userID1); + + logger.debug("We got back: \"" + + evtDelivered.getSourceMessage().getContent() + "\""); + // assert messageBody == body + assertEquals("message body", expectedReceivedBody, evtDelivered + .getSourceMessage().getContent()); + } + + public void testMessageDeliveryFailedTransform() + { + // TODO not sure how this can be implemented. + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/msn/TestOperationSetInstantMessageTransformMsnImpl.java b/test/net/java/sip/communicator/slick/protocol/msn/TestOperationSetInstantMessageTransformMsnImpl.java new file mode 100644 index 000000000..fd3fe45f9 --- /dev/null +++ b/test/net/java/sip/communicator/slick/protocol/msn/TestOperationSetInstantMessageTransformMsnImpl.java @@ -0,0 +1,273 @@ +package net.java.sip.communicator.slick.protocol.msn; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.slick.protocol.generic.*; +import net.java.sip.communicator.util.*; +import junit.framework.*; + +public class TestOperationSetInstantMessageTransformMsnImpl + extends TestCase +{ + + private static final Logger logger = + Logger.getLogger(TestOperationSetInstantMessageTransformMsnImpl.class); + + private MsnSlickFixture fixture = new MsnSlickFixture(); + + private OperationSetBasicInstantMessaging opSetBasicIM1 = null; + + private OperationSetBasicInstantMessaging opSetBasicIM2 = null; + + private OperationSetInstantMessageTransform opSetTransform1 = null; + + private OperationSetInstantMessageTransform opSetTransform2 = null; + + private OperationSetPresence opSetPresence1 = null; + + private OperationSetPresence opSetPresence2 = null; + + public TestOperationSetInstantMessageTransformMsnImpl(String name) + { + super(name); + } + + protected void setUp() throws Exception + { + super.setUp(); + fixture.setUp(); + + Map supportedOperationSets1 = + fixture.provider1.getSupportedOperationSets(); + + if (supportedOperationSets1 == null + || supportedOperationSets1.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + + "this implementation. "); + + // get the operation set presence here. + opSetBasicIM1 = + (OperationSetBasicInstantMessaging) supportedOperationSets1 + .get(OperationSetBasicInstantMessaging.class.getName()); + + if (opSetBasicIM1 == null) + { + throw new NullPointerException( + "No implementation for basic IM was found"); + } + + // we also need the presence op set in order to retrieve contacts. + opSetTransform1 = + (OperationSetInstantMessageTransform) supportedOperationSets1 + .get(OperationSetInstantMessageTransform.class.getName()); + + // if the op set is null show that we're not happy. + if (opSetTransform1 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an OperationSetInstantMessageTransform implementation"); + } + + // we also need the presence op set in order to retrieve contacts. + opSetPresence1 = + (OperationSetPresence) supportedOperationSets1 + .get(OperationSetPresence.class.getName()); + + // if the op set is null show that we're not happy. + if (opSetPresence1 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an " + + "implementation of at least one of the PresenceOperationSets"); + } + + Map supportedOperationSets2 = + fixture.provider2.getSupportedOperationSets(); + + if (supportedOperationSets2 == null + || supportedOperationSets2.size() < 1) + throw new NullPointerException( + "No OperationSet implementations are supported by " + + "this implementation. "); + + // get the operation set presence here. + opSetBasicIM2 = + (OperationSetBasicInstantMessaging) supportedOperationSets2 + .get(OperationSetBasicInstantMessaging.class.getName()); + + if (opSetBasicIM2 == null) + { + throw new NullPointerException( + "No implementation for basic IM was found"); + } + + opSetTransform2 = + (OperationSetInstantMessageTransform) supportedOperationSets2 + .get(OperationSetInstantMessageTransform.class.getName()); + + // if the op set is null show that we're not happy. + if (opSetTransform2 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an OperationSetInstantMessageTransform implementation"); + } + + opSetPresence2 = + (OperationSetPresence) supportedOperationSets2 + .get(OperationSetPresence.class.getName()); + + // if the op set is null show that we're not happy. + if (opSetPresence2 == null) + { + throw new NullPointerException( + "An implementation of the service must provide an " + + "implementation of at least one of the PresenceOperationSets"); + } + + } + + protected void tearDown() throws Exception + { + super.tearDown(); + + fixture.tearDown(); + } + + public static Test suite() + { + TestSuite suite = new TestSuite(); + + suite.addTest(new TestOperationSetInstantMessageTransformMsnImpl( + "firstTestTransformLayerInstallation")); + + suite + .addTestSuite(TestOperationSetInstantMessageTransformMsnImpl.class); + + return suite; + } + + public void firstTestTransformLayerInstallation() + { + PredictableTransformLayer transformLayer = + new PredictableTransformLayer(); + + opSetTransform1.addTransformLayer(transformLayer); + if (!opSetTransform1.containsLayer(transformLayer)) + fail("Transform layer did not install."); + + opSetTransform2.addTransformLayer(transformLayer); + if (!opSetTransform2.containsLayer(transformLayer)) + fail("Transform layer did not install."); + } + + public void testMessageReceivedTransform() + { + String body = + "This is an IM coming from the tester agent" + " on " + + new Date().toString(); + + // We expect out message to be transformed only from the + // MessageDelivered Event. + String expectedReceivedBody = "__RECEIVED____DELIVERY_PENDING__" + body; + + ImEventCollector receiversEventCollector = new ImEventCollector(); + + opSetBasicIM1.addMessageListener(receiversEventCollector); + + Contact contact1 = opSetPresence2.findContactByID(fixture.userID1); + + logger.debug("Will send message \"" + body + "\" to: \"" + contact1 + + "\". We expect to get back: \"" + expectedReceivedBody + "\""); + + opSetBasicIM2.sendInstantMessage(contact1, opSetBasicIM2 + .createMessage(body)); + + receiversEventCollector.waitForEvent(timeout); + + opSetBasicIM1.removeMessageListener(receiversEventCollector); + + // assert reception of a message event + assertTrue("No events delivered upon a received message", + receiversEventCollector.collectedEvents.size() > 0); + + // assert event instance of Message Received Evt + assertTrue( + "Received evt was not an instance of " + + MessageReceivedEvent.class.getName(), + receiversEventCollector.collectedEvents.get(0) instanceof MessageReceivedEvent); + + // assert source contact == testAgent.uin + MessageReceivedEvent evt = + (MessageReceivedEvent) receiversEventCollector.collectedEvents + .get(0); + assertEquals("message sender ", evt.getSourceContact().getAddress(), + fixture.userID2); + + logger.debug("We got back: \"" + evt.getSourceMessage().getContent() + + "\""); + + // assert messageBody == body + assertEquals("message body", expectedReceivedBody, evt + .getSourceMessage().getContent()); + + } + + private static final long timeout = 1000; + + public void testMessageDeliveredTransform() + { + String body = + "This is an IM coming from the tester agent" + " on " + + new Date().toString(); + + // The message will be transformed prior to sending and after being + // received, we expect two underscores to be prepended and two + // underscores to be appended. + String expectedReceivedBody = "__DELIVERED__" + body; + + ImEventCollector sendersEventCollector = new ImEventCollector(); + opSetBasicIM2.addMessageListener(sendersEventCollector); + + Contact contact1 = opSetPresence2.findContactByID(fixture.userID1); + + logger.debug("Will send message \"" + body + "\" to: \"" + contact1 + + "\". We expect to get back: \"" + expectedReceivedBody + "\""); + + opSetBasicIM2.sendInstantMessage(contact1, opSetBasicIM2 + .createMessage(body)); + + sendersEventCollector.waitForEvent(timeout); + opSetBasicIM2.removeMessageListener(sendersEventCollector); + + // assert reception of a message event + assertTrue("No events delivered upon a sent message", + sendersEventCollector.collectedEvents.size() > 0); + + // assert event instance of Message Received Evt + assertTrue( + "Received evt was not an instance of " + + MessageDeliveredEvent.class.getName(), + sendersEventCollector.collectedEvents.get(0) instanceof MessageDeliveredEvent); + + // assert source contact == testAgent.uin + MessageDeliveredEvent evtDelivered = + (MessageDeliveredEvent) sendersEventCollector.collectedEvents + .get(0); + assertEquals("message sender ", evtDelivered.getDestinationContact() + .getAddress(), fixture.userID1); + + logger.debug("We got back: \"" + + evtDelivered.getSourceMessage().getContent() + "\""); + // assert messageBody == body + assertEquals("message body", expectedReceivedBody, evtDelivered + .getSourceMessage().getContent()); + } + + public void testMessageDeliveryFailedTransform() + { + // TODO not sure how this can be implemented. + } +}