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;
19  
20  import java.io.File;
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.Serializable;
24  import java.net.InetAddress;
25  import java.net.InetSocketAddress;
26  import java.net.SocketAddress;
27  import java.net.SocketException;
28  import java.net.URI;
29  import java.nio.ByteBuffer;
30  import java.util.Arrays;
31  import java.util.HashSet;
32  import java.util.Locale;
33  import java.util.Objects;
34  import java.util.Set;
35  import java.util.regex.Matcher;
36  import java.util.regex.Pattern;
37  
38  import org.eclipse.jdt.annotation.NonNull;
39  import org.eclipse.jdt.annotation.NonNullByDefault;
40  import org.newsclub.net.unix.pool.ObjectPool.Lease;
41  
42  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
43  
44  /**
45   * An {@link AFSocketAddress} for TIPC sockets.
46   *
47   * The TIPC socket API provides three different address types:
48   * <ul>
49   * <li><em>Service Address.</em>
50   * <p>
51   * This address type consists of a 32-bit service type identifier and a 32-bit service instance
52   * identifier.
53   * </p>
54   * <p>
55   * The type identifier is typically determined and hard-coded by the user application programmer,
56   * but its value may have to be coordinated with other applications which might be present in the
57   * same cluster. The instance identifier is often calculated by the program, based on application
58   * specific criteria.
59   * </p>
60   * <p>
61   * Typical invocation: {@link #ofService(int, int)}.
62   * </p>
63   * </li>
64   * <li><em>Service Range.</em>
65   * <p>
66   * This address type represents a range of service addresses of the same type and with instances
67   * between a lower and an upper range limit.
68   * </p>
69   * <p>
70   * By binding a socket to this address type one can make it represent many instances, something
71   * which has proved useful in many cases. This address type is also used as multicast address.
72   * </p>
73   * <p>
74   * Typical invocation: {@link #ofServiceRange(int, int, int)}.
75   * </p>
76   * </li>
77   * <li><em>Socket Address.</em>
78   * <p>
79   * This address is a reference to a specific socket in the cluster.
80   * </p>
81   * <p>
82   * It contains a 32-bit port number and a 32-bit node hash number. The port number is generated by
83   * the system when the socket is created, and the node hash is generated from the corresponding node
84   * identity.
85   * </p>
86   * <p>
87   * An address of this type can be used for connecting or for sending messages in the same way as
88   * service addresses can be used, but is only valid as long as the referenced socket exists.
89   * </p>
90   * <p>
91   * Typical invocation: {@link #ofSocket(int, int)}.
92   * </p>
93   * </li>
94   * </ul>
95   * <p>
96   * When binding a socket to a service address or address range, the visibility scope of the binding
97   * must be indicated. There are two options, {@link Scope#SCOPE_NODE} if the user only wants node
98   * local visibility, and {@link Scope#SCOPE_CLUSTER} if he wants cluster global visibility. There
99   * are almost no limitations to how sockets can be bound to service addresses: one socket can be
100  * bound to many addresses or ranges, and many sockets can be bound to the same address or range.
101  * </p>
102  * <p>
103  * The service types 0 through 63 are however reserved for system internal use, and are not
104  * available for user space applications.
105  * </p>
106  * <p>
107  * When sending a message by service address the sender may indicate a lookup domain, also called
108  * lookup scope. This is a node hash number, limiting the set of eligible destination sockets to the
109  * indicated node. If this value is zero, all matching sockets in the whole cluster, as visible from
110  * the source node, are eligible.
111  * </p>
112  *
113  * @author Christian Kohlschütter (documentation credits to Jon Maloy and the TIPC team).
114  */
115 @SuppressFBWarnings({"REDOS", "USO_UNSAFE_STATIC_METHOD_SYNCHRONIZATION"})
116 public final class AFTIPCSocketAddress extends AFSocketAddress {
117   private static final long serialVersionUID = 1L; // do not change!
118 
119   private static final Pattern PAT_TIPC_URI_HOST_AND_PORT = Pattern.compile(
120       "^((?:(?:(?<scope>cluster|node|default|[0-9a-fx]+)\\-)?(?<type>service|service-range|socket)\\.)|"
121           + "(?<scope2>cluster|node|default|[0-9a-fx]+)\\-(?<type2>[0-9a-fx]+)\\.)?"
122           + "(?<a>[0-9a-fx]+)\\.(?<b>[0-9a-fx]+)(?:\\.(?<c>[0-9a-fx]+))?(?:\\:(?<javaPort>[0-9]+))?$");
123 
124   /**
125    * The "topology" service name type.
126    */
127   public static final int TIPC_TOP_SRV = 1;
128 
129   /**
130    * The lowest user-publishable name type.
131    */
132   public static final int TIPC_RESERVED_TYPES = 64;
133 
134   private static AFAddressFamily<AFTIPCSocketAddress> afTipc;
135 
136   /**
137    * The TIPC address type.
138    *
139    * @author Christian Kohlschütter
140    */
141   @NonNullByDefault
142   public static final class AddressType extends NamedInteger {
143     private static final long serialVersionUID = 1L;
144 
145     /**
146      * Describes a TIPC "service range" address.
147      */
148     public static final AddressType SERVICE_RANGE;
149 
150     /**
151      * Describes a TIPC "service" address.
152      */
153     public static final AddressType SERVICE_ADDR;
154 
155     /**
156      * Describes a TIPC "socket" address.
157      */
158     public static final AddressType SOCKET_ADDR;
159 
160     private static final @NonNull AddressType[] VALUES = init(new @NonNull AddressType[] {
161         SERVICE_RANGE = new AddressType("SERVICE_RANGE", 1, //
162             (a, b, c) -> formatTIPCInt(a) + "@" + formatTIPCInt(b) + "-" + formatTIPCInt(c)), //
163         SERVICE_ADDR = new AddressType("SERVICE_ADDR", 2, //
164             (a, b, c) -> formatTIPCInt(a) + "@" + formatTIPCInt(b) + (c == 0 ? "" : ":"
165                 + formatTIPCInt(c))), //
166         SOCKET_ADDR = new AddressType("SOCKET_ADDR", 3, //
167             (a, b, c) -> formatTIPCInt(a) + "@" + formatTIPCInt(b) + (c == 0 ? "" : ":"
168                 + formatTIPCInt(c))), //
169     });
170 
171     /**
172      * The provider of a debug string.
173      */
174     private final DebugStringProvider ds;
175 
176     private AddressType(int id) {
177       super(id);
178       this.ds = (a, b, c) -> ":" + toUnsignedString(a) + ":" + toUnsignedString(b) + ":"
179           + toUnsignedString(c);
180     }
181 
182     private AddressType(String name, int id, DebugStringProvider ds) {
183       super(name, id);
184       this.ds = ds;
185     }
186 
187     static AddressType ofValue(int v) {
188       return ofValue(VALUES, AddressType::new, v);
189     }
190 
191     @FunctionalInterface
192     interface DebugStringProvider extends Serializable {
193       String toDebugString(int a, int b, int c);
194     }
195 
196     /**
197      * Formats an integer as an unsigned, zero-padded 32-bit hexadecimal number.
198      *
199      * @param i The number.
200      * @return The string.
201      */
202     @SuppressWarnings("null")
203     public static String formatTIPCInt(int i) {
204       return String.format(Locale.ENGLISH, "0x%08x", (i & 0xFFFFFFFFL));
205     }
206 
207     private String toDebugString(Scope scope, int a, int b, int c) {
208       if (this == SOCKET_ADDR && scope.equals(Scope.SCOPE_NOT_SPECIFIED)) {
209         return name() + "(" + value() + ");" + ds.toDebugString(a, b, c);
210       } else {
211         return name() + "(" + value() + ");" + scope + ":" + ds.toDebugString(a, b, c);
212       }
213     }
214   }
215 
216   /**
217    * The TIPC visibility scope.
218    *
219    * @author Christian Kohlschütter
220    */
221   @NonNullByDefault
222   public static final class Scope extends NamedInteger {
223     private static final long serialVersionUID = 1L;
224 
225     /**
226      * Cluster-wide scope.
227      */
228     public static final Scope SCOPE_CLUSTER;
229 
230     /**
231      * Node-only scope.
232      */
233     public static final Scope SCOPE_NODE;
234 
235     /**
236      * Scope not specified (for example, when using socket addresses).
237      */
238     public static final Scope SCOPE_NOT_SPECIFIED;
239 
240     private static final @NonNull Scope[] VALUES = init(new @NonNull Scope[] {
241         SCOPE_NOT_SPECIFIED = new Scope("SCOPE_NOT_SPECIFIED", 0), //
242         SCOPE_CLUSTER = new Scope("SCOPE_CLUSTER", 2), //
243         SCOPE_NODE = new Scope("SCOPE_NODE", 3), //
244     });
245 
246     private Scope(int id) {
247       super(id);
248     }
249 
250     private Scope(String name, int id) {
251       super(name, id);
252     }
253 
254     /**
255      * Returns a {@link Scope} instance given an integer value.
256      *
257      * @param v The scope value.
258      * @return The {@link Scope} instance.
259      */
260     public static Scope ofValue(int v) {
261       return ofValue(VALUES, Scope::new, v);
262     }
263   }
264 
265   private AFTIPCSocketAddress(int port, final byte[] socketAddress, Lease<ByteBuffer> nativeAddress)
266       throws SocketException {
267     super(port, socketAddress, nativeAddress, addressFamily());
268   }
269 
270   private static AFTIPCSocketAddress newAFSocketAddress(int port, final byte[] socketAddress,
271       Lease<ByteBuffer> nativeAddress) throws SocketException {
272     return newDeserializedAFSocketAddress(port, socketAddress, nativeAddress, addressFamily(),
273         AFTIPCSocketAddress::new);
274   }
275 
276   /**
277    * Returns an {@link AFTIPCSocketAddress} that refers to a given service type and instance, using
278    * the given scope.
279    *
280    * @param scope The address scope.
281    * @param type The service type (0-63 are reserved).
282    * @param instance The service instance ID.
283    * @return A corresponding {@link AFTIPCSocketAddress} instance.
284    * @throws SocketException if the operation fails.
285    */
286   public static AFTIPCSocketAddress ofService(Scope scope, int type, int instance)
287       throws SocketException {
288     return ofService(scope, type, instance, 0);
289   }
290 
291   /**
292    * Returns an {@link AFTIPCSocketAddress} that refers to a given service type and instance,
293    * implicitly using cluster scope ({@link Scope#SCOPE_CLUSTER}).
294    *
295    * @param type The service type (0-63 are reserved).
296    * @param instance The service instance ID.
297    * @return A corresponding {@link AFTIPCSocketAddress} instance.
298    * @throws SocketException if the operation fails.
299    */
300   public static AFTIPCSocketAddress ofService(int type, int instance) throws SocketException {
301     return ofService(Scope.SCOPE_CLUSTER, type, instance, 0);
302   }
303 
304   /**
305    * Returns an {@link AFTIPCSocketAddress} that refers to a given service type and instance, using
306    * the given scope and the given lookup domain.
307    *
308    * @param scope The address scope.
309    * @param type The service type (0-63 are reserved).
310    * @param instance The service instance ID.
311    * @param domain The lookup domain. 0 indicates cluster global lookup, otherwise a node hash,
312    *          indicating that lookup should be performed only on that node
313    * @return A corresponding {@link AFTIPCSocketAddress} instance.
314    * @throws SocketException if the operation fails.
315    */
316   public static AFTIPCSocketAddress ofService(Scope scope, int type, int instance, int domain)
317       throws SocketException {
318     return ofService(0, scope, type, instance, domain);
319   }
320 
321   /**
322    * Returns an {@link AFTIPCSocketAddress} that refers to a given service type and instance, using
323    * the given scope and the given lookup domain. A Java-only "IP port number" is stored along the
324    * instance for compatibility reasons.
325    *
326    * @param javaPort The emulated "port" number (not part of TIPC).
327    * @param scope The address scope.
328    * @param type The service type (0-63 are reserved).
329    * @param instance The service instance ID.
330    * @param domain The lookup domain. 0 indicates cluster global lookup, otherwise a node hash,
331    *          indicating that lookup should be performed only on that node
332    * @return A corresponding {@link AFTIPCSocketAddress} instance.
333    * @throws SocketException if the operation fails.
334    */
335   public static AFTIPCSocketAddress ofService(int javaPort, Scope scope, int type, int instance,
336       int domain) throws SocketException {
337     return resolveAddress(toBytes(AddressType.SERVICE_ADDR, scope, type, instance, domain),
338         javaPort, addressFamily());
339   }
340 
341   /**
342    * Returns an {@link AFTIPCSocketAddress} that refers to a given service range type and instance
343    * boundaries (lower/upper values), using the given scope.
344    *
345    * @param scope The address scope.
346    * @param type The service type (0-63 are reserved).
347    * @param lower Lower end of service instance ID range.
348    * @param upper Upper end of service instance ID range.
349    * @return A corresponding {@link AFTIPCSocketAddress} instance.
350    * @throws SocketException if the operation fails.
351    */
352   public static AFTIPCSocketAddress ofServiceRange(Scope scope, int type, int lower, int upper)
353       throws SocketException {
354     return ofServiceRange(0, scope, type, lower, upper);
355   }
356 
357   /**
358    * Returns an {@link AFTIPCSocketAddress} that refers to a given service range type and instance
359    * boundaries (lower/upper values), implicitly using cluster scope ({@link Scope#SCOPE_CLUSTER}).
360    *
361    * @param type The service type (0-63 are reserved).
362    * @param lower Lower end of service instance ID range.
363    * @param upper Upper end of service instance ID range.
364    * @return A corresponding {@link AFTIPCSocketAddress} instance.
365    * @throws SocketException if the operation fails.
366    */
367   public static AFTIPCSocketAddress ofServiceRange(int type, int lower, int upper)
368       throws SocketException {
369     return ofServiceRange(0, Scope.SCOPE_CLUSTER, type, lower, upper);
370   }
371 
372   /**
373    * Returns an {@link AFTIPCSocketAddress} that refers to a given service range type and instance
374    * boundaries (lower/upper values), implicitly using cluster scope ({@link Scope#SCOPE_CLUSTER}).
375    * A Java-only "IP port number" is stored along the instance for compatibility reasons.
376    *
377    * @param javaPort The emulated "port" number (not part of TIPC).
378    * @param scope The address scope.
379    * @param type The service type (0-63 are reserved).
380    * @param lower Lower end of service instance ID range.
381    * @param upper Upper end of service instance ID range.
382    * @return A corresponding {@link AFTIPCSocketAddress} instance.
383    * @throws SocketException if the operation fails.
384    */
385   public static AFTIPCSocketAddress ofServiceRange(int javaPort, Scope scope, int type, int lower,
386       int upper) throws SocketException {
387     return resolveAddress(toBytes(AddressType.SERVICE_RANGE, scope, type, lower, upper), javaPort,
388         addressFamily());
389   }
390 
391   /**
392    * Returns an {@link AFTIPCSocketAddress} that refers to a given TIPC socket address (i.e.,
393    * referring to a particular socket instance instead of a service address).
394    *
395    * @param ref 32-bit port reference ID (not to be confused with the
396    *          {@link InetSocketAddress#getPort()} port).
397    * @param node Node hash number (can be used as lookup domain with
398    *          {@link #ofService(Scope, int, int, int)}).
399    * @return A corresponding {@link AFTIPCSocketAddress} instance.
400    * @throws SocketException if the operation fails.
401    */
402   public static AFTIPCSocketAddress ofSocket(int ref, int node) throws SocketException {
403     return ofSocket(0, ref, node);
404   }
405 
406   /**
407    * Returns an {@link AFTIPCSocketAddress} that refers to a given TIPC socket address (i.e.,
408    * referring to a particular socket instance instead of a service address). A Java-only "IP port
409    * number" is stored along the instance for compatibility reasons.
410    *
411    * @param javaPort The emulated "port" number (not part of TIPC).
412    * @param ref 32-bit port reference ID (not to be confused with the
413    *          {@link InetSocketAddress#getPort()} port).
414    * @param node Node hash number (can be used as lookup domain with
415    *          {@link #ofService(Scope, int, int, int)}).
416    * @return A corresponding {@link AFTIPCSocketAddress} instance.
417    * @throws SocketException if the operation fails.
418    */
419   public static AFTIPCSocketAddress ofSocket(int javaPort, int ref, int node)
420       throws SocketException {
421     return resolveAddress(toBytes(AddressType.SOCKET_ADDR, Scope.SCOPE_NOT_SPECIFIED, ref, node, 0),
422         javaPort, addressFamily());
423   }
424 
425   /**
426    * Returns an {@link AFTIPCSocketAddress} that refers to the topology service.
427    *
428    * @return A corresponding {@link AFTIPCSocketAddress} instance.
429    * @throws SocketException if the operation fails.
430    */
431   public static AFTIPCSocketAddress ofTopologyService() throws SocketException {
432     return resolveAddress(toBytes(AddressType.SERVICE_ADDR, Scope.SCOPE_NOT_SPECIFIED, TIPC_TOP_SRV,
433         TIPC_TOP_SRV, 0), 0, addressFamily());
434   }
435 
436   private static int parseUnsignedInt(String v) {
437     if (v.startsWith("0x")) {
438       return parseUnsignedInt(v.substring(2), 16);
439     } else {
440       return parseUnsignedInt(v, 10);
441     }
442   }
443 
444   /**
445    * Returns an {@link AFTIPCSocketAddress} given a special {@link InetAddress} that encodes the
446    * byte sequence of an AF_TIPC socket address, like those returned by {@link #wrapAddress()}.
447    *
448    * @param address The "special" {@link InetAddress}.
449    * @param port The port (use 0 for "none").
450    * @return The {@link AFTIPCSocketAddress} instance.
451    * @throws SocketException if the operation fails, for example when an unsupported address is
452    *           specified.
453    */
454   public static AFTIPCSocketAddress unwrap(InetAddress address, int port) throws SocketException {
455     return AFSocketAddress.unwrap(address, port, addressFamily());
456   }
457 
458   /**
459    * Returns an {@link AFTIPCSocketAddress} given a special {@link InetAddress} hostname that
460    * encodes the byte sequence of an AF_TIPC socket address, like those returned by
461    * {@link #wrapAddress()}.
462    *
463    * @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}.
464    * @param port The port (use 0 for "none").
465    * @return The {@link AFTIPCSocketAddress} instance.
466    * @throws SocketException if the operation fails, for example when an unsupported address is
467    *           specified.
468    */
469   public static AFTIPCSocketAddress unwrap(String hostname, int port) throws SocketException {
470     return AFSocketAddress.unwrap(hostname, port, addressFamily());
471   }
472 
473   /**
474    * Returns an {@link AFTIPCSocketAddress} given a generic {@link SocketAddress}.
475    *
476    * @param address The address to unwrap.
477    * @return The {@link AFTIPCSocketAddress} instance.
478    * @throws SocketException if the operation fails, for example when an unsupported address is
479    *           specified.
480    */
481   public static AFTIPCSocketAddress unwrap(SocketAddress address) throws SocketException {
482     Objects.requireNonNull(address);
483     if (!isSupportedAddress(address)) {
484       throw new SocketException("Unsupported address");
485     }
486     return (AFTIPCSocketAddress) address;
487   }
488 
489   /**
490    * Returns the scope of this address.
491    *
492    * @return The scope.
493    */
494   public Scope getScope() {
495     byte[] bytes = getBytes();
496     if (bytes.length != (5 * 4)) {
497       return Scope.SCOPE_NOT_SPECIFIED;
498     }
499     return Scope.ofValue(ByteBuffer.wrap(bytes, 4, 4).getInt());
500   }
501 
502   /**
503    * Returns the TIPC type part of this address.
504    *
505    * @return The type identifier
506    */
507   public int getTIPCType() {
508     ByteBuffer bb = ByteBuffer.wrap(getBytes());
509     return bb.getInt(2 * 4);
510   }
511 
512   /**
513    * Returns the TIPC instance part of this address.
514    *
515    * @return The instance identifier.
516    */
517   public int getTIPCInstance() {
518     ByteBuffer bb = ByteBuffer.wrap(getBytes());
519     return bb.getInt(3 * 4);
520   }
521 
522   /**
523    * Returns the TIPC domain part of this address.
524    *
525    * @return The domain identifier.
526    */
527   public int getTIPCDomain() {
528     ByteBuffer bb = ByteBuffer.wrap(getBytes());
529     return bb.getInt(4 * 4);
530   }
531 
532   /**
533    * Returns the TIPC lower instance of this address.
534    *
535    * @return The lower instance identifier.
536    */
537   public int getTIPCLower() {
538     ByteBuffer bb = ByteBuffer.wrap(getBytes());
539     return bb.getInt(2 * 4);
540   }
541 
542   /**
543    * Returns the TIPC upper instance of this address.
544    *
545    * @return The lower instance identifier.
546    */
547   public int getTIPCUpper() {
548     ByteBuffer bb = ByteBuffer.wrap(getBytes());
549     return bb.getInt(3 * 4);
550   }
551 
552   /**
553    * Returns the TIPC ref of this address.
554    *
555    * @return The ref identifier.
556    */
557   public int getTIPCRef() {
558     ByteBuffer bb = ByteBuffer.wrap(getBytes());
559     return bb.getInt(2 * 4);
560   }
561 
562   /**
563    * Returns the TIPC node hash of this address.
564    *
565    * @return The node hash.
566    */
567   public int getTIPCNodeHash() {
568     ByteBuffer bb = ByteBuffer.wrap(getBytes());
569     return bb.getInt(3 * 4);
570   }
571 
572   @Override
573   public String toString() {
574     int port = getPort();
575 
576     byte[] bytes = getBytes();
577     if (bytes.length != (5 * 4)) {
578       return getClass().getName() + "[" + (port == 0 ? "" : "port=" + port) + ";UNKNOWN" + "]";
579     }
580 
581     ByteBuffer bb = ByteBuffer.wrap(bytes);
582     int typeId = bb.getInt();
583     int scopeId = bb.getInt();
584     int a = bb.getInt();
585     int b = bb.getInt();
586     int c = bb.getInt();
587 
588     Scope scope = Scope.ofValue((byte) scopeId);
589 
590     AddressType type = AddressType.ofValue(typeId);
591     String typeString = type.toDebugString(scope, a, b, c);
592 
593     return getClass().getName() + "[" + (port == 0 ? "" : "port=" + port + ";") + typeString + "]";
594   }
595 
596   @Override
597   public boolean hasFilename() {
598     return false;
599   }
600 
601   @Override
602   public File getFile() throws FileNotFoundException {
603     throw new FileNotFoundException("no file");
604   }
605 
606   /**
607    * Checks if an {@link InetAddress} can be unwrapped to an {@link AFTIPCSocketAddress}.
608    *
609    * @param addr The instance to check.
610    * @return {@code true} if so.
611    * @see #wrapAddress()
612    * @see #unwrap(InetAddress, int)
613    */
614   public static boolean isSupportedAddress(InetAddress addr) {
615     return AFSocketAddress.isSupportedAddress(addr, addressFamily());
616   }
617 
618   /**
619    * Checks if a {@link SocketAddress} can be unwrapped to an {@link AFTIPCSocketAddress}.
620    *
621    * @param addr The instance to check.
622    * @return {@code true} if so.
623    * @see #unwrap(InetAddress, int)
624    */
625   public static boolean isSupportedAddress(SocketAddress addr) {
626     return (addr instanceof AFTIPCSocketAddress);
627   }
628 
629   @SuppressWarnings("cast")
630   private static byte[] toBytes(AddressType addrType, Scope scope, int a, int b, int c) {
631     ByteBuffer bb = ByteBuffer.allocate(5 * 4);
632     bb.putInt(addrType.value());
633     bb.putInt(scope.value());
634     bb.putInt(a);
635     bb.putInt(b);
636     bb.putInt(c);
637     return (byte[]) bb.flip().array();
638   }
639 
640   /**
641    * Returns the corresponding {@link AFAddressFamily}.
642    *
643    * @return The address family instance.
644    */
645   @SuppressWarnings("null")
646   public static synchronized AFAddressFamily<AFTIPCSocketAddress> addressFamily() {
647     if (afTipc == null) {
648       afTipc = AFAddressFamily.registerAddressFamily("tipc", //
649           AFTIPCSocketAddress.class, new AFSocketAddressConfig<AFTIPCSocketAddress>() {
650 
651             private final AFSocketAddressConstructor<AFTIPCSocketAddress> addrConstr =
652                 isUseDeserializationForInit() ? AFTIPCSocketAddress::newAFSocketAddress
653                     : AFTIPCSocketAddress::new;
654 
655             @Override
656             protected AFTIPCSocketAddress parseURI(URI u, int port) throws SocketException {
657               return AFTIPCSocketAddress.of(u, port);
658             }
659 
660             @Override
661             protected AFSocketAddressConstructor<AFTIPCSocketAddress> addressConstructor() {
662               return addrConstr;
663             }
664 
665             @Override
666             protected String selectorProviderClassname() {
667               return "org.newsclub.net.unix.tipc.AFTIPCSelectorProvider";
668             }
669 
670             @Override
671             protected Set<String> uriSchemes() {
672               return new HashSet<>(Arrays.asList("tipc", "http+tipc", "https+tipc"));
673             }
674           });
675       try {
676         Class.forName("org.newsclub.net.unix.tipc.AFTIPCSelectorProvider");
677       } catch (ClassNotFoundException e) {
678         // ignore
679       }
680     }
681     return afTipc;
682   }
683 
684   private String toTipcInt(int v) {
685     if (v < 0) {
686       return "0x" + toUnsignedString(v, 16);
687     } else {
688       return toUnsignedString(v);
689     }
690   }
691 
692   /**
693    * Returns an {@link AFTIPCSocketAddress} for the given URI, if possible.
694    *
695    * @param uri The URI.
696    * @return The address.
697    * @throws SocketException if the operation fails.
698    */
699   @SuppressWarnings("PMD.ShortMethodName")
700   public static AFTIPCSocketAddress of(URI uri) throws SocketException {
701     return of(uri, -1);
702   }
703 
704   /**
705    * Returns an {@link AFTIPCSocketAddress} for the given URI, if possible.
706    *
707    * @param uri The URI.
708    * @param overridePort The port to forcibly use, or {@code -1} for "don't override".
709    * @return The address.
710    * @throws SocketException if the operation fails.
711    */
712   @SuppressWarnings({
713       "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.NcssCount", "PMD.NPathComplexity",
714       "PMD.ShortMethodName"})
715   public static AFTIPCSocketAddress of(URI uri, int overridePort) throws SocketException {
716     switch (uri.getScheme()) {
717       case "tipc":
718       case "http+tipc":
719       case "https+tipc":
720         break;
721       default:
722         throw new SocketException("Unsupported URI scheme: " + uri.getScheme());
723     }
724 
725     String host = uri.getHost();
726     if (host == null) {
727       host = uri.getAuthority();
728       if (host != null) {
729         int at = host.indexOf('@');
730         if (at >= 0) {
731           host = host.substring(at + 1);
732         }
733       }
734     }
735     if (host == null) {
736       throw new SocketException("Cannot get hostname from URI: " + uri);
737     }
738     int port = overridePort != -1 ? overridePort : uri.getPort();
739     if (port != -1) {
740       host += ":" + port;
741     }
742     try {
743       Matcher m = PAT_TIPC_URI_HOST_AND_PORT.matcher(host);
744       if (!m.matches()) {
745         throw new SocketException("Invalid TIPC URI: " + uri);
746       }
747 
748       String typeStr = m.group("type");
749       String scopeStr = m.group("scope");
750       if (typeStr == null) {
751         typeStr = m.group("type2");
752         scopeStr = m.group("scope2");
753       }
754       String strA = m.group("a");
755       String strB = m.group("b");
756       String strC = m.group("c");
757       String javaPortStr = m.group("javaPort");
758 
759       final AddressType addrType;
760       switch (typeStr == null ? "" : typeStr) {
761         case "service":
762           addrType = AddressType.SERVICE_ADDR;
763           break;
764         case "service-range":
765           addrType = AddressType.SERVICE_RANGE;
766           break;
767         case "socket":
768           addrType = AddressType.SOCKET_ADDR;
769           break;
770         case "":
771           addrType = AddressType.SERVICE_ADDR;
772           break;
773         default:
774           addrType = AddressType.ofValue(parseUnsignedInt(typeStr));
775           break;
776       }
777 
778       final Scope scope;
779       switch (scopeStr == null ? "" : scopeStr) {
780         case "cluster":
781           scope = Scope.SCOPE_CLUSTER;
782           break;
783         case "node":
784           scope = Scope.SCOPE_NODE;
785           break;
786         case "default":
787           scope = Scope.SCOPE_NOT_SPECIFIED;
788           break;
789         case "":
790           if (addrType == AddressType.SERVICE_ADDR || addrType == AddressType.SERVICE_RANGE) {
791             scope = Scope.SCOPE_CLUSTER;
792           } else {
793             scope = Scope.SCOPE_NOT_SPECIFIED;
794           }
795           break;
796         default:
797           scope = Scope.ofValue(parseUnsignedInt(scopeStr));
798           break;
799       }
800 
801       int a = parseUnsignedInt(strA);
802       int b = parseUnsignedInt(strB);
803 
804       int c;
805       if (strC == null || strC.isEmpty()) {
806         if (addrType == AddressType.SERVICE_RANGE) {
807           c = b;
808         } else {
809           c = 0;
810         }
811       } else {
812         c = parseUnsignedInt(strC);
813       }
814 
815       int javaPort = javaPortStr == null || javaPortStr.isEmpty() ? port : Integer.parseInt(
816           javaPortStr);
817       if (overridePort != -1) {
818         javaPort = overridePort;
819       }
820 
821       return resolveAddress(toBytes(addrType, scope, a, b, c), javaPort, addressFamily());
822     } catch (IllegalArgumentException e) {
823       throw (SocketException) new SocketException("Invalid TIPC URI: " + uri).initCause(e);
824     }
825   }
826 
827   @Override
828   public URI toURI(String scheme, URI template) throws IOException {
829     switch (scheme) {
830       case "tipc":
831       case "http+tipc":
832       case "https+tipc":
833         break;
834       default:
835         return super.toURI(scheme, template);
836     }
837 
838     byte[] bytes = getBytes();
839     if (bytes.length != (5 * 4)) {
840       return super.toURI(scheme, template);
841     }
842 
843     ByteBuffer bb = ByteBuffer.wrap(bytes);
844     AddressType addrType = AddressType.ofValue(bb.getInt());
845     Scope scope = Scope.ofValue(bb.getInt());
846 
847     StringBuilder sb = new StringBuilder();
848 
849     boolean haveScope = true;
850     if (scope == Scope.SCOPE_NOT_SPECIFIED) {
851       sb.append("default-");
852     } else if (scope == Scope.SCOPE_CLUSTER) {
853       if (addrType == AddressType.SERVICE_ADDR || addrType == AddressType.SERVICE_RANGE) {
854         // implied
855         haveScope = false;
856       } else {
857         sb.append("cluster-");
858       }
859     } else if (scope == Scope.SCOPE_NODE) {
860       sb.append("node-");
861     } else {
862       sb.append(toTipcInt(scope.value()));
863       sb.append('-');
864     }
865 
866     boolean addrTypeImplied = false;
867     if (addrType == AddressType.SERVICE_ADDR) {
868       if (!haveScope) {
869         addrTypeImplied = true;
870       } else {
871         sb.append("service");
872       }
873     } else if (addrType == AddressType.SERVICE_RANGE) {
874       sb.append("service-range");
875     } else if (addrType == AddressType.SOCKET_ADDR) {
876       sb.append("socket");
877     } else {
878       sb.append(toTipcInt(addrType.value()));
879     }
880     if (!addrTypeImplied) {
881       sb.append('.');
882     }
883 
884     int a = bb.getInt();
885     int b = bb.getInt();
886     int c = bb.getInt();
887 
888     sb.append(toTipcInt(a));
889     sb.append('.');
890     sb.append(toTipcInt(b));
891     if (c != 0) {
892       sb.append('.');
893       sb.append(toTipcInt(c));
894     }
895 
896     return new HostAndPort(sb.toString(), getPort()).toURI(scheme, template);
897   }
898 }