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