View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2026 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.newsclub.net.unix.rmi;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.ObjectInput;
23  import java.io.ObjectOutput;
24  import java.rmi.server.RMIClientSocketFactory;
25  import java.rmi.server.RMIServerSocketFactory;
26  import java.rmi.server.RMISocketFactory;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.Objects;
30  
31  import org.newsclub.net.unix.AFSocket;
32  import org.newsclub.net.unix.AFSocketAddress;
33  import org.newsclub.net.unix.AFUNIXSocket;
34  import org.newsclub.net.unix.AFUNIXSocketAddress;
35  import org.newsclub.net.unix.AFUNIXSocketCredentials;
36  import org.newsclub.net.unix.HostAndPort;
37  
38  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
39  
40  /**
41   * An {@link RMISocketFactory} that supports {@link AFUNIXSocket}s.
42   *
43   * @author Christian Kohlschütter
44   */
45  public class AFUNIXRMISocketFactory extends AFRMISocketFactory {
46    private static final long serialVersionUID = 1L;
47  
48    static final String DEFAULT_SOCKET_FILE_PREFIX = "";
49    static final String DEFAULT_SOCKET_FILE_SUFFIX = ".rmi";
50  
51    private File socketDir;
52    private String socketPrefix;
53    private String socketSuffix;
54  
55    private final transient Map<HostAndPort, AFUNIXSocketCredentials> credentials = new HashMap<>();
56  
57    /**
58     * Constructor required per definition.
59     *
60     * @see RMISocketFactory
61     */
62    public AFUNIXRMISocketFactory() {
63      super();
64    }
65  
66    /**
67     * Creates a new socket factory.
68     *
69     * @param naming The {@link AFNaming} instance to use.
70     * @param socketDir The directory to store the sockets in.
71     * @param defaultClientFactory The default {@link RMIClientSocketFactory}.
72     * @param defaultServerFactory The default {@link RMIServerSocketFactory}.
73     * @param socketPrefix A string that will be inserted at the beginning of each socket filename, or
74     *          {@code null}.
75     * @param socketSuffix A string that will be added to the end of each socket filename, or
76     *          {@code null}.
77     */
78    @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
79    public AFUNIXRMISocketFactory(final AFNaming naming, final File socketDir,
80        final RMIClientSocketFactory defaultClientFactory,
81        final RMIServerSocketFactory defaultServerFactory, final String socketPrefix,
82        final String socketSuffix) {
83      super(naming, defaultClientFactory, defaultServerFactory);
84      Objects.requireNonNull(socketDir);
85      this.socketDir = socketDir;
86      this.socketPrefix = socketPrefix == null ? DEFAULT_SOCKET_FILE_PREFIX : socketPrefix;
87      this.socketSuffix = socketSuffix == null ? DEFAULT_SOCKET_FILE_SUFFIX : socketSuffix;
88    }
89  
90    /**
91     * Creates a new socket factory.
92     *
93     * @param naming The {@link AFNaming} instance to use.
94     * @param socketDir The directory to store the sockets in.
95     * @param defaultClientFactory The default {@link RMIClientSocketFactory}.
96     * @param defaultServerFactory The default {@link RMIServerSocketFactory}.
97     */
98    @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
99    public AFUNIXRMISocketFactory(AFNaming naming, File socketDir,
100       RMIClientSocketFactory defaultClientFactory, RMIServerSocketFactory defaultServerFactory) {
101     this(naming, socketDir, defaultClientFactory, defaultServerFactory, null, null);
102   }
103 
104   /**
105    * Creates a new socket factory.
106    *
107    * @param naming The {@link AFNaming} instance to use.
108    * @param socketDir The directory to store the sockets in.
109    */
110   @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
111   public AFUNIXRMISocketFactory(AFNaming naming, File socketDir) {
112     this(naming, socketDir, DefaultRMIClientSocketFactory.getInstance(),
113         DefaultRMIServerSocketFactory.getInstance());
114   }
115 
116   @Override
117   @SuppressFBWarnings("PATH_TRAVERSAL_IN")
118   protected AFNaming readNamingInstance(ObjectInput in) throws IOException {
119     socketDir = new File(in.readUTF());
120     int port = in.readInt();
121     return AFUNIXNaming.getInstance(socketDir, port);
122   }
123 
124   @Override
125   protected void writeNamingInstance(ObjectOutput out, AFNaming naming) throws IOException {
126     out.writeUTF(socketDir.getAbsolutePath());
127     out.writeInt(naming.getRegistryPort());
128   }
129 
130   @Override
131   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
132     super.readExternal(in);
133 
134     socketPrefix = in.readUTF();
135     socketSuffix = in.readUTF();
136   }
137 
138   @Override
139   public void writeExternal(ObjectOutput out) throws IOException {
140     super.writeExternal(out);
141 
142     out.writeUTF(socketPrefix);
143     out.writeUTF(socketSuffix);
144   }
145 
146   @Override
147   public int hashCode() {
148     return socketDir == null ? System.identityHashCode(this) : socketDir.hashCode();
149   }
150 
151   @Override
152   public boolean equals(Object other) {
153     if (!(other instanceof AFUNIXRMISocketFactory)) {
154       return false;
155     }
156     AFUNIXRMISocketFactory sf = (AFUNIXRMISocketFactory) other;
157     if (socketDir == null) {
158       return sf == this;
159     } else {
160       return socketDir.equals(sf.socketDir);
161     }
162   }
163 
164   /**
165    * The directory in which socket files are stored.
166    *
167    * @return The directory.
168    */
169   public File getSocketDir() {
170     return socketDir;
171   }
172 
173   @SuppressFBWarnings("PATH_TRAVERSAL_IN")
174   File getFile(int port) {
175     if (isPlainFileSocket()) {
176       return getSocketDir();
177     } else {
178       Objects.requireNonNull(socketDir);
179       return new File(socketDir, socketPrefix + port + socketSuffix);
180     }
181   }
182 
183   boolean hasSocketFile(int port) {
184     return getFile(port).exists();
185   }
186 
187   private boolean isPlainFileSocket() {
188     return (getNaming().getRegistryPort() == RMIPorts.PLAIN_FILE_SOCKET);
189   }
190 
191   @Override
192   protected AFUNIXSocketAddress newSocketAddress(int port) throws IOException {
193     return AFUNIXSocketAddress.of(getFile(port), port);
194   }
195 
196   @Override
197   protected final AFSocket<?> newConnectedSocket(AFSocketAddress addr) throws IOException {
198     final AFUNIXSocket socket = ((AFUNIXSocketAddress) addr).newConnectedSocket();
199     AFUNIXSocketCredentials creds = socket.getPeerCredentials();
200 
201     final HostAndPort hap = new HostAndPort(addr.getHostString(), addr.getPort());
202     synchronized (credentials) {
203       if (credentials.put(hap, creds) != null) {
204         // unexpected
205       }
206     }
207     socket.addCloseable(() -> {
208       synchronized (credentials) {
209         credentials.remove(hap);
210       }
211     });
212     return socket;
213   }
214 
215   @Override
216   public String toString() {
217     return super.toString() + //
218         "[path=" + socketDir + //
219         (isPlainFileSocket() ? "" : //
220             ";prefix=" + socketPrefix + ";suffix=" + socketSuffix) + "]";
221   }
222 
223   @Override
224   public void close() throws IOException {
225     synchronized (credentials) {
226       credentials.clear();
227     }
228     super.close();
229   }
230 
231   AFUNIXSocketCredentials peerCredentialsFor(RemotePeerInfo data) {
232     synchronized (credentials) {
233       return credentials.get(new HostAndPort(data.host, data.port));
234     }
235   }
236 
237   @Override
238   boolean hasRegisteredPort(int port) {
239     return hasSocketFile(port);
240   }
241 }