1 package org.olsr.v1.info.proxy;
2 
3 import java.io.IOException;
4 import java.net.HttpURLConnection;
5 import java.net.InetSocketAddress;
6 import java.net.Socket;
7 import java.net.SocketAddress;
8 import java.util.List;
9 import java.util.Random;
10 import java.util.concurrent.CopyOnWriteArrayList;
11 
12 import org.olsr.v1.info.proxy.api.InfoResult;
13 import org.olsr.v1.info.proxy.api.OlsrdInfoProxy;
14 import org.osgi.service.component.annotations.Activate;
15 import org.osgi.service.component.annotations.Component;
16 import org.osgi.service.component.annotations.Deactivate;
17 import org.osgi.service.component.annotations.Modified;
18 import org.osgi.service.metatype.annotations.Designate;
19 
20 @Component
21 @Designate(ocd = Config.class)
22 public class OlsrdInfoProxyImpl implements OlsrdInfoProxy {
23   /*
24    * Config
25    */
26 
27   private final Object          configLock      = new Object();
28   private Config                config          = null;
29   InetSocketAddress             addressTxtInfo  =
30       new InetSocketAddress(Config.ADDRESS_DEFAULT, Config.PORT_TXTINFO_DEFAULT);
31   InetSocketAddress             addressJsonInfo =
32       new InetSocketAddress(Config.ADDRESS_DEFAULT, Config.PORT_JSONINFO_DEFAULT);
33 
34   CopyOnWriteArrayList<Integer> usedPorts       = new CopyOnWriteArrayList<>();
35 
36   private final Random          random          = new Random();
37 
38   /* MUST be called in a synchronized (configLock) block */
setupConfig(final Config config)39   private Config setupConfig(final Config config) {
40     final Config preConfig = this.config;
41     this.config = config;
42 
43     final InetSocketAddress sa = new InetSocketAddress(this.config.address(), 0);
44     if (!sa.isUnresolved()) {
45       this.addressTxtInfo = new InetSocketAddress(this.config.address(), this.config.portTxtInfo());
46       this.addressJsonInfo = new InetSocketAddress(this.config.address(), this.config.portJsonInfo());
47     }
48 
49     return preConfig;
50   }
51 
52   /*
53    * Lifecycle
54    */
55 
56   @Activate
activate(final Config config)57   void activate(final Config config) {
58     synchronized (this.configLock) {
59       this.setupConfig(config);
60       this.usedPorts.clear();
61       this.usedPorts.add(Integer.valueOf(this.config.localPort()));
62     }
63   }
64 
65   @Deactivate
deactivate()66   void deactivate() {
67     /* nothing to do */
68   }
69 
70   @Modified
modified(final Config config)71   void modified(final Config config) {
72     Integer preLocalPort;
73     Integer localPort;
74     synchronized (this.configLock) {
75       preLocalPort = Integer.valueOf(this.config.localPort());
76       this.setupConfig(config);
77       localPort = Integer.valueOf(this.config.localPort());
78     }
79 
80     if (!localPort.equals(preLocalPort)) {
81       this.usedPorts.remove(preLocalPort);
82       this.usedPorts.add(0, localPort);
83     }
84   }
85 
86   /*
87    * Helpers
88    */
89 
getSocket(final int socketTimeout)90   Socket getSocket(final int socketTimeout) {
91     Socket socket = null;
92 
93     for (final Integer port : this.usedPorts) {
94       final SocketAddress bindpoint = new InetSocketAddress(port.intValue());
95 
96       socket = new Socket();
97       try {
98         socket.setSoTimeout(socketTimeout);
99         socket.setReuseAddress(true);
100         socket.bind(bindpoint);
101       }
102       catch (final IOException e) {
103         /* skip to next port */
104         socket = null;
105       }
106     }
107 
108     if (socket == null) {
109       socket = new Socket();
110       try {
111         socket.setSoTimeout(socketTimeout);
112         socket.setReuseAddress(true);
113         socket.bind(null);
114         final Integer localPort = Integer.valueOf(socket.getLocalPort());
115         this.usedPorts.addIfAbsent(localPort);
116       }
117       catch (final IOException e) {
118         /* swallow & can't be covered in a test */
119       }
120     }
121 
122     assert (socket != null);
123     return socket;
124   }
125 
sendInternal(final String command, final List<String> txtInfo, final StringBuilder jsonInfo)126   public int sendInternal(final String command, final List<String> txtInfo, final StringBuilder jsonInfo)
127       throws IOException {
128     assert (!((txtInfo == null) && (jsonInfo == null)));
129     assert (!((txtInfo != null) && (jsonInfo != null)));
130 
131     if ((command == null) //
132         || command.isEmpty()) {
133       throw new IOException("No command specified");
134     }
135 
136     int socketTimeout;
137     int connectionTimeout;
138     InetSocketAddress address;
139     int randomSleep;
140     int retries;
141     synchronized (this.configLock) {
142       socketTimeout = this.config.socketTimeout();
143       connectionTimeout = this.config.connectionTimeout();
144       address = (txtInfo != null) ? this.addressTxtInfo : this.addressJsonInfo;
145       randomSleep = this.config.randomSleep();
146       retries = this.config.connectionRetries();
147     }
148 
149     int retry = 0;
150     while (retry <= retries) {
151       try (Socket socket = this.getSocket(socketTimeout)) {
152         final int r =
153             OlsrdInfoReader.send(socket, command, connectionTimeout, socketTimeout, address, txtInfo, jsonInfo);
154         if (r != HttpURLConnection.HTTP_UNAVAILABLE) {
155           return r;
156         }
157       }
158 
159       try {
160         Thread.sleep(Math.max(Config.RANDOM_SLEEP_MIN, this.random.nextInt(randomSleep)));
161       }
162       catch (final InterruptedException e) {
163         /* swallow & can't be covered in a test */
164         retry = retries;
165       }
166 
167       retry++;
168     }
169 
170     return HttpURLConnection.HTTP_UNAVAILABLE;
171   }
172 
173   /*
174    * OlsrdInfoProxy
175    */
176 
177   @Override
getTxtInfo(final String command)178   public InfoResult getTxtInfo(final String command) throws IOException {
179     final InfoResult result = new InfoResult();
180     result.status = this.sendInternal(command, result.output, null);
181     return result;
182   }
183 
184   @Override
getJsonInfo(final String command)185   public InfoResult getJsonInfo(final String command) throws IOException {
186     final InfoResult result = new InfoResult();
187     final StringBuilder sb = new StringBuilder();
188     result.status = this.sendInternal(command, null, sb);
189     result.output.add(sb.toString());
190     return result;
191   }
192 }