Skip to content

OF-1927: Show TCP port for server-to-server (S2S) connections in admin console#3233

Open
MilanTyagi2004 wants to merge 2 commits into
igniterealtime:mainfrom
MilanTyagi2004:OF-1927-Show-TCP-port-used-for-server-to-server-connection
Open

OF-1927: Show TCP port for server-to-server (S2S) connections in admin console#3233
MilanTyagi2004 wants to merge 2 commits into
igniterealtime:mainfrom
MilanTyagi2004:OF-1927-Show-TCP-port-used-for-server-to-server-connection

Conversation

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator

Summary

Enhances the Openfire administrative console by displaying the TCP port used for server-to-server (S2S) connections on the "Remote Server Connections Details" page. This allows administrators to distinguish between standard S2S connections (typically port 5269) and direct TLS connections (typically port 5270).


Changes

Backend (Connection & Session Layers)

  • Added default getRemotePort() method to the Connection interface
  • Implemented getRemotePort() in:
    • NettyConnection (extracts port from Netty Channel)
    • SocketConnection (delegates to existing getPort() method)
  • Added default getHostPort() method to Session interface
  • Updated LocalSession to delegate port retrieval to the underlying Connection
  • Extended RemoteSession and RemoteSessionTask to support port retrieval in clustered environments

Administrative UI

  • Updated server-session-details.jsp to include a new "Port" column
    • Applies to both Incoming and Outgoing session tables
  • Reused existing i18n key ports.port for column header

Verification

Build

  • Command:
    mvn clean install -DskipTests -pl xmppserver
  • Result: BUILD SUCCESS

Manual Testing

  • Full end-to-end S2S verification requires a publicly reachable domain with proper DNS (A + SRV) and open port 5269.
  • This setup is not available in the current development environment, so a live S2S connection could not be established.

Functional Validation

  • Code paths verified to correctly retrieve and expose the remote port from:
    • NettyConnection (via Channel)
    • SocketConnection (via existing port method)
  • UI rendering verified to correctly display the Port column when session data includes port values.

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator Author

hey @guusdk manual test won't happen because to build connection btw servers, need hosting that's why i am not able to check

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances Openfire’s admin console “Remote Server Connections Details” page by exposing and displaying the TCP port used for S2S connections, enabling admins to distinguish standard S2S vs direct TLS connections.

Changes:

  • Adds port accessors (Connection#getRemotePort() and Session#getHostPort()) with implementations for Socket- and Netty-based connections.
  • Extends clustered session plumbing (RemoteSession/RemoteSessionTask) to retrieve the port from remote cluster nodes.
  • Updates server-session-details.jsp to display a new “Port” column for incoming and outgoing server sessions.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
xmppserver/src/main/webapp/server-session-details.jsp Adds a Port column and renders ${session.hostPort} in incoming/outgoing S2S tables.
xmppserver/src/main/java/org/jivesoftware/openfire/session/Session.java Introduces default getHostPort() API.
xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalSession.java Implements getHostPort() by delegating to the underlying connection.
xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSession.java Caches and fetches host port via cluster task.
xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSessionTask.java Adds a new clustered operation to fetch host port.
xmppserver/src/main/java/org/jivesoftware/openfire/session/IncomingServerSessionTask.java Adds a default case to the operation switch.
xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyConnection.java Implements getRemotePort() using Netty channel remote address.
xmppserver/src/main/java/org/jivesoftware/openfire/net/SocketConnection.java Implements getRemotePort() by delegating to existing getPort().
xmppserver/src/main/java/org/jivesoftware/openfire/Connection.java Introduces default getRemotePort() API.
Comments suppressed due to low confidence (1)

xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSessionTask.java:226

  • RemoteSessionTask serializes the operation using operation.ordinal() and deserializes using Operation.values()[...]. Adding the new enum constant getHostPort in the middle of the enum changes the ordinal values of all subsequent constants (e.g., validate/removeDetached/markNonResumable), which can cause nodes on different versions (or persisted/queued tasks) to execute the wrong operation. To keep cluster serialization stable, append new enum constants to the end (or switch serialization to a stable identifier like the enum name with backwards-compatible parsing).
    public void writeExternal(ObjectOutput out) throws IOException {
        ExternalizableUtil.getInstance().writeBoolean(out, operation != null);
        if (operation != null) {
            ExternalizableUtil.getInstance().writeInt(out, operation.ordinal());
        }
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        if (ExternalizableUtil.getInstance().readBoolean(in)) {
            operation = Operation.values()[ExternalizableUtil.getInstance().readInt(in)];
        }
    }

    public enum Operation {
        /**
         * Basic session operations
         */
        getStreamID,
        getServerName,
        getCreationDate,
        getLastActiveDate,
        getNumClientPackets,
        getNumServerPackets,
        getTLSProtocolName,
        getCipherSuiteName,
        getPeerCertificates,
        getSoftwareVersion,
        close,
        isClosed,
        isDetached,
        isEncrypted,
        getHostAddress,
        getHostName,
        getHostPort,
        validate,
        removeDetached,
        markNonResumable,
        
        /**

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

</c:choose>
<td><c:out value="${session.TLSProtocolName}"/></td>
<td><c:out value="${session.cipherSuiteName}"/></td>
<td><c:out value="${session.hostPort}"/></td>
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UI will render the integer returned by session.hostPort even when the backend uses 0 to mean "not available" (e.g., detached session / unknown remote address). This will show a confusing port value of 0 in the table. Consider rendering an empty value or placeholder when hostPort <= 0 to avoid implying a real TCP port.

Suggested change
<td><c:out value="${session.hostPort}"/></td>
<td>
<c:choose>
<c:when test="${session.hostPort gt 0}">
<c:out value="${session.hostPort}"/>
</c:when>
<c:otherwise>&nbsp;</c:otherwise>
</c:choose>
</td>

Copilot uses AI. Check for mistakes.
Comment thread xmppserver/src/main/java/org/jivesoftware/openfire/Connection.java Outdated
@guusdk
Copy link
Copy Markdown
Member

guusdk commented Apr 3, 2026

@MilanTyagi2004 thanks for doing this! Copilot found some small but good things to improve. If this relates to a JIRA issue, can you please mention the JIRA number in the commit message? That way, automation will link the ticket to the code change.

@MilanTyagi2004 MilanTyagi2004 changed the title Show TCP port for server-to-server (S2S) connections in admin console OF-1927: Show TCP port for server-to-server (S2S) connections in admin console Apr 4, 2026
@MilanTyagi2004
Copy link
Copy Markdown
Collaborator Author

@guusdk changes are done.

Copy link
Copy Markdown
Member

@guusdk guusdk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty good, apart from the Copilot comment that you can address, I'd also like you to see if you can settle on either remotePort or hostPort to identify the port used by the peer (I think I prefer 'remote' as it clearly signals that we're talking about the post used by the other party). Having two different names for basically the same thing is a bit confusing.

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator Author

sure, Guus

@guusdk
Copy link
Copy Markdown
Member

guusdk commented May 8, 2026

Hi @MilanTyagi2004 - am I right to assume that you are still planning to add improvements to this PR? The changes look promising, it would be good to be able to finish this!

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator Author

sure i will look into this and finish this.

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator Author

@coderabbitai
check this pr

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

@MilanTyagi2004 Sure! I'll review the PR now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1fe847cb-4c66-4a17-ad6b-b5a7831b4896

📥 Commits

Reviewing files that changed from the base of the PR and between 0f6f185 and f814706.

📒 Files selected for processing (7)
  • xmppserver/src/main/java/org/jivesoftware/openfire/net/SocketConnection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/IncomingServerSessionTask.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSessionTask.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/Session.java
  • xmppserver/src/main/webapp/server-session-details.jsp
✅ Files skipped from review due to trivial changes (2)
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/Session.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/IncomingServerSessionTask.java
🚧 Files skipped from review as they are similar to previous changes (5)
  • xmppserver/src/main/java/org/jivesoftware/openfire/net/SocketConnection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalSession.java
  • xmppserver/src/main/webapp/server-session-details.jsp
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSessionTask.java

📝 Walkthrough

Walkthrough

This PR exposes remote TCP peer port information end-to-end. Connection and Session interfaces gain a default getRemotePort(); SocketConnection and NettyConnection implement it. LocalSession delegates to its Connection; RemoteSession lazily queries and caches the port via a new RemoteSessionTask.Operation.getRemotePort remote operation. IncomingServerSessionTask adds a defensive default switch case. The admin UI adds a ports.column to incoming and outgoing server session tables and conditionally displays session.remotePort.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description accurately describes the changes made across backend connection/session layers and the administrative UI, aligning well with the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSessionTask.java`:
- Around line 221-224: The enum ordering change (in Operation inside
RemoteSessionTask) breaks clustered serialization because code uses
operation.ordinal() and Operation.values()[ordinal]; to fix, preserve existing
ordinals by not inserting new enum constants in the middle—move the newly added
getHostPort constant to the end of the Operation enum (after markNonResumable)
so existing values like getHostName, validate, removeDetached, markNonResumable
keep their original ordinals; update only the enum declaration in
RemoteSessionTask and do not change places that use operation.ordinal() or
Operation.values()[ordinal].

In `@xmppserver/src/main/webapp/server-session-details.jsp`:
- Line 391: Replace the direct output of session.hostPort (the <c:out
value="${session.hostPort}"/> tag) with a conditional that emits a blank when
the port is 0 to match the incoming-table behavior; e.g. render an empty string
if session.hostPort == 0 otherwise the port (use EL ternary or a
<c:choose>/<c:when> around session.hostPort) so unknown outgoing ports are shown
as blank instead of "0".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4abb7bea-518f-417b-8999-839bce523146

📥 Commits

Reviewing files that changed from the base of the PR and between dcf6173 and 6a06fd0.

📒 Files selected for processing (9)
  • xmppserver/src/main/java/org/jivesoftware/openfire/Connection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/net/SocketConnection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyConnection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/IncomingServerSessionTask.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSessionTask.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/Session.java
  • xmppserver/src/main/webapp/server-session-details.jsp

Comment thread xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSessionTask.java Outdated
Comment thread xmppserver/src/main/webapp/server-session-details.jsp Outdated
…n console

Display the remote TCP port in the incoming and outgoing S2S session
tables on the admin console, allowing administrators to distinguish
standard S2S (port 5269) from direct TLS (port 5270) connections.

- Added Session#getRemotePort() delegating to Connection#getRemotePort()
- Implemented in LocalSession, RemoteSession, and RemoteSessionTask
- Appended new enum constant at the end of RemoteSessionTask.Operation
  to preserve ordinal compatibility in mixed-version clusters
- Standardized naming from hostPort to remotePort across all layers
- Added port column to S2S session detail tables (reusing ports.port i18n key)
- Render blank instead of 0 when port is unavailable
- Improved Javadocs to document the 0-when-unavailable contract
@MilanTyagi2004 MilanTyagi2004 force-pushed the OF-1927-Show-TCP-port-used-for-server-to-server-connection branch from 6a06fd0 to 0f6f185 Compare May 9, 2026 01:33
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSession.java`:
- Around line 186-191: The code currently converts a null cluster lookup result
into 0 and caches it in the remotePort field, preventing future retries; change
the logic in RemoteSession (the remotePort initialization block that calls
getRemoteSessionTask and doSynchronousClusterTask) so that you only assign to
remotePort when result != null (e.g., remotePort = (Integer) result); if result
is null leave remotePort as the original sentinel (e.g., -1) so subsequent calls
will retry the cluster lookup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e333a952-0ec4-489c-be2a-fa61ceae08df

📥 Commits

Reviewing files that changed from the base of the PR and between 6a06fd0 and 0f6f185.

📒 Files selected for processing (9)
  • xmppserver/src/main/java/org/jivesoftware/openfire/Connection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/net/SocketConnection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyConnection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/IncomingServerSessionTask.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSessionTask.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/Session.java
  • xmppserver/src/main/webapp/server-session-details.jsp
✅ Files skipped from review due to trivial changes (2)
  • xmppserver/src/main/java/org/jivesoftware/openfire/net/SocketConnection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalSession.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • xmppserver/src/main/webapp/server-session-details.jsp
  • xmppserver/src/main/java/org/jivesoftware/openfire/Connection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyConnection.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/IncomingServerSessionTask.java

Comment on lines +186 to +191
if (remotePort == -1) {
ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getRemotePort);
Object result = doSynchronousClusterTask(task);
remotePort = result == null ? 0 : (Integer) result;
}
return remotePort;
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don’t permanently cache an unavailable remote port.

At Line 189, null is converted to 0 and cached forever. If the lookup fails transiently (for example while detached), later calls won’t retry and can keep showing no port even when it becomes available.

Proposed minimal fix
 `@Override`
 public int getRemotePort() {
     if (remotePort == -1) {
         ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getRemotePort);
         Object result = doSynchronousClusterTask(task);
-        remotePort = result == null ? 0 : (Integer) result;
+        if (result != null) {
+            remotePort = (Integer) result;
+        }
     }
-    return remotePort;
+    return remotePort == -1 ? 0 : remotePort;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xmppserver/src/main/java/org/jivesoftware/openfire/session/RemoteSession.java`
around lines 186 - 191, The code currently converts a null cluster lookup result
into 0 and caches it in the remotePort field, preventing future retries; change
the logic in RemoteSession (the remotePort initialization block that calls
getRemoteSessionTask and doSynchronousClusterTask) so that you only assign to
remotePort when result != null (e.g., remotePort = (Integer) result); if result
is null leave remotePort as the original sentinel (e.g., -1) so subsequent calls
will retry the cluster lookup.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MilanTyagi2004 I suggest that you apply the proposed minimal fix.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator Author

hi @guusdk,
i have implement all the change, please review the changes when you have time.

Copy link
Copy Markdown
Member

@guusdk guusdk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this Milan! I have a couple of requests, which I left as review comments. Also, don't forget to update the year in the copyright header in every file that you change.

Comment on lines +186 to +191
if (remotePort == -1) {
ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getRemotePort);
Object result = doSynchronousClusterTask(task);
remotePort = result == null ? 0 : (Integer) result;
}
return remotePort;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MilanTyagi2004 I suggest that you apply the proposed minimal fix.

* @return the remote port, or 0 when unavailable.
*/
default int getRemotePort() {
return 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throughout the code, there are two different values used to represent a 'unknown' remote port: 0 and -1. Unless it is important to have different values (I cannot immediately see a reason) I would use the same value everywhere.

result = ((IncomingServerSession) getSession()).getValidatedDomains();
break;
default:
break;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this default added? I'm not saying it is wrong, I'm just surprised to see it.

*
* @return the port that the connection uses.
* @return the remote port, or 0 when unavailable.
*/
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you're doing here is basically replacing getPort() (which is specific to this class) with getRemotePort() (which you have now defined for all Connections.

Ideally, we do not keep two duplicate methods around forever, but we cannot simply remove the old method either: some plugins (maybe even created by other people) may be using it. Removing it will immediately break those plugins.

What we typically do, is first send a 'signal' to warn people that this method is going to be removed. Then, in a future version, we remove the method. That gives people some time to adjust.

This 'signal' is performed by marking the method as being deprecated. You can do that like this:

  /**
     * Returns the remote port used by the connection.
     *
     * @return the remote port, or 0 when unavailable.
     * @deprecated This method is replaced by {@link #getRemotePort()}
     */
    @Deprecated(forRemoval = true, since = "5.1.0") // Remove in or after Openfire 5.2.0.
    public int getPort() {
        return getRemotePort();
    }

    @Override
    public int getRemotePort() {
        return socket.getPort();
    }

After doing that, you should make sure that all code in Openfire itself that uses the deprecated method is modified to use the new method instead.

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator Author

Hey @guusdk,

No code in Openfire calls .getPort() on a SocketConnection instance, the only usages are new SocketConnection(...), SocketConnection.getInstances(), SocketConnection.TLSPolicy, etc. The getPort() method was only called internally by getRemotePort() within the same class, which I've already reversed.

No additional callers need updating. The deprecation is purely for external plugin consumers who might be using SocketConnection.getPort() directly. All Openfire internal code already uses getRemotePort() (via the Connection or Session interface), not the concrete SocketConnection.getPort().

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator Author

could you please review these new changes.
and apology for increasing you work load by reviewing again n again.
really sorry

@MilanTyagi2004
Copy link
Copy Markdown
Collaborator Author

once you review and approve i will squash the commits into one

@Fishbowler Fishbowler requested a review from guusdk May 10, 2026 20:12
Copy link
Copy Markdown
Member

@Fishbowler Fishbowler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks reasonable to me. You haven't been able to test this at all?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants