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