RemotePeerInfo.java
/*
* junixsocket
*
* Copyright 2009-2024 Christian Kohlschütter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.newsclub.net.unix.rmi;
import java.io.IOException;
import java.io.ObjectOutput;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.server.RMISocketFactory;
import java.rmi.server.RemoteObject;
import java.rmi.server.RemoteObjectInvocationHandler;
import org.newsclub.net.unix.AFUNIXSocketCredentials;
import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
/**
* Information about the remote connection.
*
* @author Christian Kohlschütter
*/
public final class RemotePeerInfo {
private RMISocketFactory socketFactory;
String host;
int port;
private AFUNIXSocketCredentials peerCredentials;
RemotePeerInfo() {
}
/**
* The socket factory used to establish connections.
*
* @return The socket factory.
*/
public RMISocketFactory getSocketFactory() {
return socketFactory;
}
/**
* The hostname.
*
* @return The hostname
*/
public String getHost() {
return host;
}
/**
* The port.
*
* @return The port
*/
public int getPort() {
return port;
}
/**
* The remote socket credentials, or {@code null} if they could not be retrieved.
*
* @return The peer credentials, or {@code null}.
*/
@SuppressFBWarnings("EI_EXPOSE_REP")
public AFUNIXSocketCredentials getPeerCredentials() {
return peerCredentials;
}
/**
* Returns the {@link AFUNIXSocketCredentials} for the currently active remote session
* (RemoteServer session during a remote method invocation), or {@code null} if it was not
* possible to retrieve these credentials.
*
* @return The credentials, or {@code null} if unable to retrieve.
*/
public static AFUNIXSocketCredentials remotePeerCredentials() {
return AFUNIXSocketCredentials.remotePeerCredentials();
}
/**
* Returns the {@link AFUNIXSocketCredentials} for the peer (server) of the given {@link Remote}
* instance, or {@code null} if it was not possible to retrieve these credentials.
*
* @param obj The remote object.
* @return The credentials, or {@code null} if unable to retrieve.
* @throws IOException if an exception occurs.
*/
public static AFUNIXSocketCredentials remotePeerCredentials(Remote obj) throws IOException {
return getConnectionInfo(obj).getPeerCredentials();
}
/**
* Returns the connection information ({@link RMISocketFactory}, hostname and port) used for the
* given {@link Remote} object, or {@code null} if no custom {@link RMISocketFactory} was
* specified.
*
* An {@link IOException} may be thrown if we couldn't determine the socket factory.
*
* @param obj The remote object.
* @return The factory, or {@code null}
* @throws IOException if the operation fails.
*/
public static RemotePeerInfo getConnectionInfo(Remote obj) throws IOException {
try (RemotePeerInfo.ExtractingObjectOutput eoo = new RemotePeerInfo.ExtractingObjectOutput()) {
RemoteObjectInvocationHandler roih = (RemoteObjectInvocationHandler) Proxy
.getInvocationHandler(RemoteObject.toStub(obj));
roih.getRef().writeExternal(eoo);
if (!eoo.validate()) {
throw new IOException("Unexpected data format for " + obj.getClass());
}
RemotePeerInfo data = eoo.data;
if ((data.socketFactory) instanceof AFUNIXRMISocketFactory) {
AFUNIXRMISocketFactory sf = ((AFUNIXRMISocketFactory) (data.socketFactory));
data.peerCredentials = sf.peerCredentialsFor(data);
if (data.peerCredentials == null) {
if (sf.isLocalServer(data.port)) {
data.peerCredentials = AFUNIXSocketCredentials.SAME_PROCESS;
}
}
} else {
data.peerCredentials = null;
}
return data;
}
}
/**
* Mimics a Serializer for RemoteRef.writeExternal, so we can extract the information we need
* about the remote reference without having to break-open internal Java classes.
*
* NOTE: The format for the data we extract is assumed to be stable across JVM implementations,
* otherwise RMI would probably not work.
*
* @author Christian Kohlschütter
*/
static final class ExtractingObjectOutput implements ObjectOutput {
private int callId = 0;
private boolean done = false;
private boolean invalid = false;
private int format = -1;
final RemotePeerInfo data = new RemotePeerInfo();
public ExtractingObjectOutput() {
}
private void setInvalid() {
invalid = done = true;
}
private void call(int id, Object v) {
switch (id) {
case 1:
if (v instanceof Integer) {
// see sun.rmi.transport.tcp.TCPEndpoint
switch ((int) v) {
case 0:
// FORMAT_HOST_PORT (= no socket factory)
format = 0;
return;
case 1:
// FORMAT_HOST_PORT_FACTORY
format = 1;
return;
default:
}
}
break;
case 2:
if (v instanceof String) {
this.data.host = (String) v;
return;
}
break;
case 3:
if (format == 0) {
done = true;
}
if (v instanceof Integer) {
this.data.port = (int) v;
if (this.data.port <= 0) {
setInvalid();
}
return;
}
break;
case 4:
if (v instanceof RMISocketFactory && format == 1) {
this.data.socketFactory = (RMISocketFactory) v;
return;
}
break;
default:
// no need to read any further
done = true;
return;
}
// otherwise:
setInvalid();
}
@Override
public void writeBoolean(boolean v) {
if (done) {
return;
}
call(++callId, v);
}
@Override
public void writeByte(int v) {
if (done) {
return;
}
call(++callId, v);
}
@Override
public void writeShort(int v) {
if (done) {
return;
}
call(++callId, v);
}
@Override
public void writeChar(int v) {
if (done) {
return;
}
call(++callId, v);
}
@Override
public void writeInt(int v) {
if (done) {
return;
}
call(++callId, v);
}
@Override
public void writeLong(long v) {
if (done) {
return;
}
call(++callId, v);
}
@Override
public void writeFloat(float v) {
if (done) {
return;
}
call(++callId, v);
}
@Override
public void writeDouble(double v) {
if (done) {
return;
}
call(++callId, v);
}
@Override
public void writeBytes(String s) {
if (done) {
return;
}
call(++callId, s);
}
@Override
public void writeChars(String s) {
if (done) {
return;
}
call(++callId, s);
}
@Override
public void writeUTF(String s) {
if (done) {
return;
}
call(++callId, s);
}
@Override
public void writeObject(Object obj) {
if (done) {
return;
}
call(++callId, obj);
}
@Override
public void write(int b) {
if (done) {
return;
}
call(++callId, b);
}
@Override
public void write(byte[] b) {
if (done) {
return;
}
call(++callId, b);
}
@Override
public void write(byte[] b, int off, int len) {
if (done) {
return;
}
setInvalid();
// NOTE: not yet needed
// byte[] copy = Arrays.copyOfRange(b, off, off + len);
// call(++callId, copy);
}
@Override
public void flush() {
}
public boolean validate() {
this.done = true;
if (callId < 3) {
setInvalid();
}
return !invalid;
}
@Override
public void close() {
}
}
@Override
public String toString() {
return getClass().getName() + "[" + host + ":" + port + ";socketFactory=" + socketFactory
+ ";peerCredentials=" + peerCredentials + "]";
}
}