From 79a4bdc9eca1635ff036191bb50670f6a51654dc Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 22 Sep 2023 15:39:48 +0200 Subject: [client] Add config option to set transfer type (SSL or plain) --- .../src/main/java/org/openslx/dozmod/Config.java | 40 +++++++++++- .../openslx/dozmod/filetransfer/DownloadTask.java | 73 +++++++++++++++++++--- .../openslx/dozmod/filetransfer/UploadTask.java | 72 ++++++++++++++++++--- .../openslx/dozmod/gui/window/ConfigWindow.java | 27 ++++++++ .../org/openslx/dozmod/gui/window/LoginWindow.java | 4 +- .../gui/window/layout/ConfigWindowLayout.java | 20 ++++++ .../org/openslx/dozmod/thrift/ThriftActions.java | 12 +++- .../org/openslx/dozmod/thrift/UploadInitiator.java | 4 +- .../main/properties/i18n/window_layout.properties | 4 ++ .../properties/i18n/window_layout_de_DE.properties | 4 ++ .../properties/i18n/window_layout_tr_TR.properties | 6 +- 11 files changed, 239 insertions(+), 27 deletions(-) (limited to 'dozentenmodul/src') diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/Config.java b/dozentenmodul/src/main/java/org/openslx/dozmod/Config.java index 6ca1d195..176a5742 100755 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/Config.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/Config.java @@ -422,7 +422,7 @@ public class Config { * @param mode */ public static void setProxyMode(ProxyMode mode) { - setString("proxy.mode", mode.toString()); + setString("proxy.mode", mode.name()); } /** @@ -438,6 +438,28 @@ public class Config { } } + /** + * Set the mode in which the ft server is configured. + * + * @param string + */ + public static void setFileTransferMode(String string) { + setString("transfer.type", string); + } + + /** + * Get the mode in which the ft server is configured + * + * @return the saved ft mode, FileTransferMode.AUTO otherwise + */ + public static FileTransferMode getFileTransferMode() { + try { + return FileTransferMode.valueOf(getString("transfer.type", FileTransferMode.PLAIN.name())); + } catch (Exception e) { + return FileTransferMode.PLAIN; + } + } + /** * Set the LookAndFeel which should be used * @@ -473,6 +495,10 @@ public class Config { public static String getPreferredLanguage() { return getString("gui.language", null); } + + /* + * Generic helpers + */ /** * Gets the boolean from the given key. @@ -546,8 +572,7 @@ public class Config { /** * Helper class to represent a saved session composed of the satellite - * adress, - * the satellite and master tokens + * address, the satellite and master tokens */ public static class SavedSession { public final String address; @@ -570,5 +595,14 @@ public class Config { SOCKS, HTTP_CONNECT } + + /** + * Enum representing the desired image/VM transfer mode + */ + public enum FileTransferMode { + PLAIN, + SSL, + SSL_ONLY, + } } diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/DownloadTask.java b/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/DownloadTask.java index 3b57222f..3951d630 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/DownloadTask.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/DownloadTask.java @@ -6,8 +6,11 @@ import java.io.RandomAccessFile; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.SSLContext; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.openslx.bwlp.thrift.iface.TransferInformation; import org.openslx.bwlp.thrift.iface.TransferState; import org.openslx.dozmod.Config; import org.openslx.filetransfer.DataReceivedCallback; @@ -33,19 +36,23 @@ public class DownloadTask extends TransferTask { private static final AtomicInteger THREAD_ID = new AtomicInteger(); private final String host; - private final int port; + private final int portPlain; + private final int portSsl; + private final SSLContext sslCtx; private final String downloadToken; private final RandomAccessFile fileHandle; private final ChunkList chunks; private final long startTime; private boolean fileWritable = true; - public DownloadTask(String host, int port, String downloadToken, File destinationFile, long fileSize, + public DownloadTask(String host, TransferInformation ti, SSLContext ctx, File destinationFile, long fileSize, List sha1Sums) throws FileNotFoundException { super(destinationFile, fileSize); this.host = host; - this.port = port; - this.downloadToken = downloadToken; + this.portPlain = ti.plainPort; + this.portSsl = ti.sslPort; + this.downloadToken = ti.token; + this.sslCtx = ctx; this.fileHandle = new RandomAccessFile(destinationFile, "rw"); this.chunks = new ChunkList(fileSize, sha1Sums); this.startTime = System.currentTimeMillis(); @@ -129,17 +136,65 @@ public class DownloadTask extends TransferTask { public DownloadThread() { super("UpConn#" + THREAD_ID.incrementAndGet()); } + + private Exception initPlain(Exception ex) { + if (portPlain <= 0 || portPlain > 65535) + return ex; + LOGGER.info("Establishing plain download connection to " + host + ":" + portPlain); + try { + downloader = new Downloader(host, portPlain, Config.TRANSFER_TIMEOUT, null, downloadToken); + } catch (Exception e) { + LOGGER.info("Connection failed"); + return e; + } + return null; + } + + private Exception initSsl(Exception ex) { + if (portSsl <= 0 || portSsl > 65535 || sslCtx == null) + return ex; + LOGGER.info("Establishing SSL download connection to " + host + ":" + portSsl); + try { + downloader = new Downloader(host, portSsl, Config.TRANSFER_TIMEOUT, sslCtx, downloadToken); + } catch (Exception e) { + LOGGER.info("Connection failed"); + return e; + } + return null; + } @Override public void run() { - try { - downloader = new Downloader(host, port, Config.TRANSFER_TIMEOUT, null, downloadToken); - } catch (Exception e) { - LOGGER.warn("Could not initialize new uploader", e); + Exception ex = null; + switch (Config.getFileTransferMode()) { + case SSL: + ex = initSsl(ex); + if (downloader == null) { + ex = initPlain(ex); + } + break; + case SSL_ONLY: + ex = initSsl(ex); + break; + case PLAIN: + default: + ex = initPlain(ex); + if (downloader == null) { + ex = initSsl(ex); + } + break; + } + if (downloader == null) { + if (ex == null) { + LOGGER.warn("Could not initialize new downloader because neither plain" + + " nor SSL transfer data is given"); + } else { + LOGGER.warn("Could not initialize new downloader, all connection methods failed", ex); + } consecutiveInitFails.incrementAndGet(); connectFailed(this); return; - } // TODO: SSL + } connectSucceeded(this); boolean ret = downloader.download(cb, cb); diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/UploadTask.java b/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/UploadTask.java index fc13f113..06736619 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/UploadTask.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/filetransfer/UploadTask.java @@ -4,9 +4,12 @@ import java.io.File; import java.io.FileNotFoundException; import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.SSLContext; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openslx.bwlp.thrift.iface.TInvalidTokenException; +import org.openslx.bwlp.thrift.iface.TransferInformation; import org.openslx.bwlp.thrift.iface.TransferState; import org.openslx.bwlp.thrift.iface.TransferStatus; import org.openslx.dozmod.Config; @@ -35,8 +38,10 @@ public class UploadTask extends TransferTask { private static final int THRIFT_INTERVAL_MS = (int) (THRIFT_INTERVAL_SECONDS * 1000); private final String host; - private final int port; private final String uploadToken; + private final int portPlain; + private final int portSsl; + private final SSLContext sslCtx; private final long startTime; private String transferConnectionError = null; @@ -54,15 +59,16 @@ public class UploadTask extends TransferTask { return numConnections.get(); } - public UploadTask(String host, int port, String uploadToken, File uploadFile) + public UploadTask(String host, TransferInformation ti, SSLContext ctx, File uploadFile) throws FileNotFoundException { super(uploadFile, uploadFile.length()); if (!uploadFile.canRead()) throw new FileNotFoundException(); - // TODO: SSL + this.sslCtx = ctx; this.host = host; - this.port = port; - this.uploadToken = uploadToken; + this.portPlain = ti.plainPort; + this.portSsl = ti.sslPort; + this.uploadToken = ti.token; this.startTime = System.currentTimeMillis(); } @@ -86,15 +92,63 @@ public class UploadTask extends TransferTask { } } - public void run2() { + private Exception initPlain(Exception ex) { + if (portPlain <= 0 || portPlain > 65535) + return ex; + LOGGER.info("Establishing plain upload connection to " + host + ":" + portPlain); + try { + uploader = new Uploader(host, portPlain, Config.TRANSFER_TIMEOUT, null, uploadToken); + } catch (Exception e) { + LOGGER.info("Connection failed"); + return e; + } + return null; + } + + private Exception initSsl(Exception ex) { + if (portSsl <= 0 || portSsl > 65535 || sslCtx == null) + return ex; + LOGGER.info("Establishing SSL upload connection to " + host + ":" + portSsl); try { - uploader = new Uploader(host, port, Config.TRANSFER_TIMEOUT, null, uploadToken); + uploader = new Uploader(host, portSsl, Config.TRANSFER_TIMEOUT, sslCtx, uploadToken); } catch (Exception e) { - LOGGER.warn("Could not initialize new uploader", e); + LOGGER.info("Connection failed"); + return e; + } + return null; + } + + public void run2() { + Exception ex = null; + switch (Config.getFileTransferMode()) { + case SSL: + ex = initSsl(ex); + if (uploader == null) { + ex = initPlain(ex); + } + break; + case SSL_ONLY: + ex = initSsl(ex); + break; + case PLAIN: + default: + ex = initPlain(ex); + if (uploader == null) { + ex = initSsl(ex); + } + break; + } + if (uploader == null) { + if (ex == null) { + LOGGER.warn("Could not initialize new uploader because neither plain" + + " nor SSL transfer data is given"); + } else { + LOGGER.warn("Could not initialize new uploader, all connection methods failed", ex); + } consecutiveInitFails.incrementAndGet(); connectFailed(this); return; - } // TODO: SSL + } connectSucceeded(this); final UploadThread thread = this; diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ConfigWindow.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ConfigWindow.java index c64fbfca..3003624c 100755 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ConfigWindow.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ConfigWindow.java @@ -38,6 +38,7 @@ public class ConfigWindow extends ConfigWindowLayout implements UiFeedback, Acti private final static Logger LOGGER = LogManager.getLogger(ConfigWindow.class); private SatelliteUserConfig userConfig = null; private JRadioButton selectedLookAndFeel = null; + private JRadioButton selectedTransferEnc = null; public ConfigWindow(Window modalParent) { super(modalParent); @@ -114,6 +115,26 @@ public class ConfigWindow extends ConfigWindowLayout implements UiFeedback, Acti // non-critical but log it anyways LOGGER.error("Failed to detect the current look & feel theme."); } + + // -- Transfer connection encryption -- + for (Enumeration btn = btnGroupTransferEnc.getElements(); btn.hasMoreElements();) { + final JRadioButton b = (JRadioButton) btn.nextElement(); + if (Config.getFileTransferMode().name().equals(b.getToolTipText())) { + selectedTransferEnc = b; + b.setSelected(true); + } + b.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + selectedTransferEnc = b; + reactToInput(); + } + }); + } + if (selectedTransferEnc == null) { + selectedTransferEnc = (JRadioButton) btnGroupTransferEnc.getElements().nextElement(); + selectedTransferEnc.setSelected(true); + } // Transfer connection count sldConnections.setValue(Config.getTransferConnectionCount()); @@ -171,6 +192,8 @@ public class ConfigWindow extends ConfigWindowLayout implements UiFeedback, Acti changed = true; } else if (!selectedLookAndFeel.getToolTipText().equals(Config.getLookAndFeel())) { changed = true; + } else if (!selectedTransferEnc.getToolTipText().equals(Config.getFileTransferMode().name())) { + changed = true; } else if (!newLanguage.value.equals(Config.getPreferredLanguage())) { changed = true; } @@ -239,6 +262,10 @@ public class ConfigWindow extends ConfigWindowLayout implements UiFeedback, Acti restartRequired = restartRequired || !selectedLookAndFeel.getToolTipText().equals(Config.getLookAndFeel()); Config.setLookAndFeel(selectedLookAndFeel.getToolTipText()); } + // save TransferEnc + if (selectedTransferEnc != null) { + Config.setFileTransferMode(selectedTransferEnc.getToolTipText()); + } // save language Language newLanguage = (Language)cboLanguage.getSelectedItem(); diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/LoginWindow.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/LoginWindow.java index 0f2e1f35..b7448a94 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/LoginWindow.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/LoginWindow.java @@ -53,6 +53,7 @@ import org.openslx.dozmod.util.ClientVersion; import org.openslx.dozmod.util.DesktopEnvironment; import org.openslx.dozmod.util.DesktopEnvironment.Link; import org.openslx.util.QuickTimer; +import org.openslx.util.Util; import org.openslx.util.QuickTimer.Task; import edu.kit.scc.dei.ecplean.ECPAuthenticationException; @@ -169,8 +170,9 @@ public class LoginWindow extends LoginWindowLayout { Iterator iterator = orgs.iterator(); while (iterator.hasNext()) { Organization current = iterator.next(); - if (current == null || !current.isSetEcpUrl() || current.getEcpUrl().isEmpty()) + if (current == null || !current.isSetEcpUrl() || Util.isEmptyString(current.getEcpUrl())) { iterator.remove(); + } } // now send the organisations back to the LoginWindow // through populateIdpCombo() diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/layout/ConfigWindowLayout.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/layout/ConfigWindowLayout.java index 8254cda2..432654f7 100755 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/layout/ConfigWindowLayout.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/layout/ConfigWindowLayout.java @@ -9,6 +9,7 @@ import javax.swing.UIManager.LookAndFeelInfo; import org.openslx.dozmod.Branding; import org.openslx.dozmod.Config; +import org.openslx.dozmod.Config.FileTransferMode; import org.openslx.dozmod.gui.control.ComboBox; import org.openslx.dozmod.gui.control.QLabel; import org.openslx.dozmod.gui.control.WordWrapLabel; @@ -32,6 +33,7 @@ public class ConfigWindowLayout extends JDialog { protected final JSlider sldFontSize; protected final JSlider sldConnections; protected ButtonGroup btnGroupLookAndFeel = null; + protected ButtonGroup btnGroupTransferEnc = null; protected final ComboBox cboLanguage; @@ -141,6 +143,24 @@ public class ConfigWindowLayout extends JDialog { sldConnections.setPaintTicks(true); sldConnections.setPaintLabels(true); grid.add(sldConnections).expand(true, false).fill(true, false); + + // Transfer SSL or plain + grid.add(new WordWrapLabel(I18n.WINDOW_LAYOUT.getString("Config.Label.transferEncryption.text"), + true, false)) + .insets(headingInset) + .fill(true, false) + .expand(true, false); + JPanel encGroupPanel = new JPanel(); + encGroupPanel.setLayout(new BoxLayout(encGroupPanel, BoxLayout.LINE_AXIS)); + btnGroupTransferEnc = new ButtonGroup(); + + for (FileTransferMode mode : Config.FileTransferMode.values()) { + JRadioButton btn = new JRadioButton(I18n.WINDOW_LAYOUT.getString("Config.Button.transfermode." + mode.name())); + btn.setToolTipText(mode.name()); + btnGroupTransferEnc.add(btn); + encGroupPanel.add(btn); + } + grid.add(encGroupPanel).expand(true, false).fill(true, false); // Language grid.add(new WordWrapLabel(I18n.WINDOW_LAYOUT.getString("Config.Label.language.text"), diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/thrift/ThriftActions.java b/dozentenmodul/src/main/java/org/openslx/dozmod/thrift/ThriftActions.java index 34995b41..40c198ac 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/thrift/ThriftActions.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/thrift/ThriftActions.java @@ -4,12 +4,14 @@ import java.awt.Frame; import java.awt.Window; import java.io.File; import java.io.FileNotFoundException; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import javax.net.ssl.SSLContext; import javax.swing.JFileChooser; import org.apache.logging.log4j.LogManager; @@ -406,10 +408,12 @@ public class ThriftActions { TException transEx = null; String transHost = null; TransferInformation transInf = null; + SSLContext sslContext = null; try { transInf = ThriftManager.getSatClient().requestDownload(Session.getSatelliteToken(), imageVersionId); transHost = Session.getSatelliteAddress(); + sslContext = ThriftManager.getSatelliteSslContext(); } catch (TException e) { transEx = e; } @@ -420,6 +424,10 @@ public class ThriftActions { transInf = ThriftManager.getMasterClient().downloadImage(Session.getSatelliteToken(), imageVersionId); transHost = App.getMasterServerAddress(); + try { + sslContext = SSLContext.getDefault(); + } catch (NoSuchAlgorithmException e) { + } } catch (TException e) { transEx = e; } @@ -436,8 +444,8 @@ public class ThriftActions { final TransferInformation fTransInf = transInf; final DownloadTask dlTask; try { - dlTask = new DownloadTask(fTransHost, transInf.getPlainPort(), - transInf.getToken(), tmpDiskFile, imageSize, null); + dlTask = new DownloadTask(fTransHost, transInf, + sslContext, tmpDiskFile, imageSize, null); } catch (FileNotFoundException e) { Gui.asyncMessageBox(I18n.THRIFT.getString("ThriftActions.Message.error.destinationNotWritable"), MessageType.ERROR, LOGGER, e); diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/thrift/UploadInitiator.java b/dozentenmodul/src/main/java/org/openslx/dozmod/thrift/UploadInitiator.java index 78e41d05..5aa36bb1 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/thrift/UploadInitiator.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/thrift/UploadInitiator.java @@ -165,8 +165,8 @@ public class UploadInitiator { // do actually start the upload now LOGGER.debug("Starting upload for: " + diskFile.getName()); try { - uploadTask = new UploadTask(Session.getSatelliteAddress(), - transferInformation.getPlainPort(), transferInformation.getToken(), diskFile); + uploadTask = new UploadTask(Session.getSatelliteAddress(), transferInformation, + ThriftManager.getSatelliteSslContext(), diskFile); } catch (FileNotFoundException e) { cancelWithGuiErrorMessage("Kann VM nicht hochladen: Datei nicht gefunden\n\n" + diskFile.getAbsolutePath()); diff --git a/dozentenmodul/src/main/properties/i18n/window_layout.properties b/dozentenmodul/src/main/properties/i18n/window_layout.properties index 5d05010d..eb78a489 100644 --- a/dozentenmodul/src/main/properties/i18n/window_layout.properties +++ b/dozentenmodul/src/main/properties/i18n/window_layout.properties @@ -26,6 +26,10 @@ Config.Label.concurrentConnectionsInfo.text=Normally, best results are achieved If the transfer speed does not overload your network connection, try the next higher value.\n \ Too high values can have a negative effect on the \ transfer speed and put a higher load on the satellite server. +Config.Label.transferEncryption.text=Connection type for VM up-/downloads +Config.Button.transfermode.PLAIN=Prefer plain, fallback to SSL +Config.Button.transfermode.SSL=Prefer SSL, fallback to plain +Config.Button.transfermode.SSL_ONLY=Use SSL, abort otherwise Config.Label.language.text=Language Config.Label.languageInfo.text=Here you can switch between the languages. Config.Button.close.text=Close diff --git a/dozentenmodul/src/main/properties/i18n/window_layout_de_DE.properties b/dozentenmodul/src/main/properties/i18n/window_layout_de_DE.properties index f352eed4..8d544eea 100644 --- a/dozentenmodul/src/main/properties/i18n/window_layout_de_DE.properties +++ b/dozentenmodul/src/main/properties/i18n/window_layout_de_DE.properties @@ -26,6 +26,10 @@ Config.Label.concurrentConnectionsInfo.text=Im Normalfall werden beste Ergebniss Falls die Übertragungsgeschwindigkeit Ihre Netzwerkanbindung nicht auslastet,\n \ probieren Sie den nächsthöheren Wert. Zu hohe Werte können einen negativen Effekt auf die\n\ Übertragungsgeschwindigkeit haben und belasten den Satellitenserver stärker. +Config.Label.transferEncryption.text=Verbindungstyp für VM Up-/Downloads +Config.Button.transfermode.PLAIN=Unverschlüsselt, SSL als Fallback +Config.Button.transfermode.SSL=SSL, unverschlüsselt als Fallback +Config.Button.transfermode.SSL_ONLY=Nur SSL Config.Label.language.text=Sprache Config.Label.languageInfo.text=Hier können Sie zwischen den Sprachen wechseln. Config.Button.close.text=Schließen diff --git a/dozentenmodul/src/main/properties/i18n/window_layout_tr_TR.properties b/dozentenmodul/src/main/properties/i18n/window_layout_tr_TR.properties index fe5b7627..d144bf1d 100644 --- a/dozentenmodul/src/main/properties/i18n/window_layout_tr_TR.properties +++ b/dozentenmodul/src/main/properties/i18n/window_layout_tr_TR.properties @@ -26,6 +26,10 @@ Config.Label.concurrentConnectionsInfo.text=En iyi sonuçlar genellikle bu ayar ağ bağlantı kapasitenizi aşmıyorsa, bir sonraki yüksek değeri \ deneyebilirsiniz. Çok yüksek değerler aktarım hızını olumsuz etkileyebilir \ ve satelit sunucusunun aşırı yüklenmesine sebep olabilir. +Config.Label.transferEncryption.text=VM yükleme/indirme işlemleri için bağlantı türü +Config.Button.transfermode.PLAIN=Düz tercih, SSL'ye geri dönüş +Config.Button.transfermode.SSL=SSL'yi tercih edin, düz sürüme geri dönün +Config.Button.transfermode.SSL_ONLY=Yalnızca SSL kullanın Config.Label.language.text=Dil Config.Label.languageInfo.text=Burada diller arasında geçiş yapabilirsiniz. Config.Button.close.text=Kapat @@ -241,4 +245,4 @@ VirtDropDownConfigEditor.Label.E0VirtDev.text=Ethernet kartı VirtDropDownConfigEditor.Label.maxUSBSpeed.text=USB VirtDropDownConfigEditor.Button.more.text=Uzman modu VirtDropDownConfigEditor.Button.cancel.text=İptal -VirtDropDownConfigEditor.Button.save.text=Kaydet \ No newline at end of file +VirtDropDownConfigEditor.Button.save.text=Kaydet -- cgit v1.2.3-55-g7522