001    package com.skype.ipc;
002    
003    import java.io.ByteArrayInputStream;
004    import java.io.File;
005    import java.io.FileInputStream;
006    import java.io.IOException;
007    import java.io.InputStream;
008    import java.security.PrivateKey;
009    import java.security.cert.X509Certificate;
010    import java.security.spec.InvalidKeySpecException;
011    
012    import com.skype.util.PemReader;
013    //import com.sun.org.apache.xpath.internal.axes.HasPositionalPredChecker;
014    
015    /***
016     * Configuration of the ipc wrapper. All these parameters are checked once at the initialization phase, and any modifications
017     * done afterwards will be ignored.
018     */
019    public class ClientConfiguration {
020    
021            public ClientConfiguration() {
022                    dispatchAll                = false;
023                    numRetries                 = 3;
024                    setup                      = null;
025                    certificateContents        = null;
026                    privatekeyContents         = null;
027                    pemFileName                = null;
028                    initialLatency             = 600;
029                    transportInputBufferSize   = 512;
030                    transportOutputBufferSize  = 512;
031                    transportLogBaseName       = null;
032                    noTls                      = false;
033                    transportFactory           = new TransportFactory();
034                    setTcpTransport();
035            }
036    
037            /***
038             * use TCP socket for the connection to the runtime with the default settings, port 8963 on the local host
039             */
040            public void setTcpTransport() {
041                    useLocalTransport    = false;
042                    this.ip              = "127.0.0.1";
043                    this.port            = 8963;
044            }
045    
046            /***
047             * use TCP socket for the connection to the runtime with custom settings
048             * @param ip the ip address of the runtime host
049             * @param port the port used by the runtime
050             */
051            public void setTcpTransport(String ip, int port) {
052                    useLocalTransport    = false;
053                    this.ip              = ip;
054                    this.port            = port;
055            }
056    
057            /***
058             * tells if the configuration was set to use a TCP socket with runtime
059             */
060            public boolean useTcpTransport() {
061                    return !useLocalTransport;
062            }
063    
064            /***
065             * get the ip address to be used with a TCP connection
066             */
067            public String getIp() {
068                    return ip;
069            }
070    
071            /***
072             * get the port to be used with a TCP connection
073             */
074            public int getPort() {
075                    return port;
076            }
077    
078            /***
079             * when an event or a property change notification is recieved on an object that
080             * doesn't exist yet, create the object and forward the notification. The default
081             * behavior is to discard the notification.
082             */
083            public void setDispatchAll() {
084                    dispatchAll = true;
085            }
086    
087            /***
088             * tells if the event or a property change notification will always be dispatched (default if false)
089             */
090            public boolean isDispatchAll() {
091                    return dispatchAll;
092            }
093    
094            /***
095             * turn off Tls (use it only if the runtime support this)
096             */
097            public void dontUseTls() {
098                    noTls = true;
099            }
100    
101            /***
102             * check if Tls mode is turned off
103             */
104            public boolean isWithoutTls() {
105                    return noTls;
106            }
107    
108            /***
109             * check if Tls mode is turned on (default)
110             */
111            public boolean isWithTls() {
112                    return !noTls;
113            }
114            
115            /***
116             * specify the certificate file to be used for the Tls connection
117             * @param certificateFilename <x>.pem file (ensure that <x>.der is present as well)
118             * returns the input filename argument
119             */
120            public String setCertificate(String certificateFilename) {
121                    this.pemFileName = certificateFilename;
122                    return certificateFilename;
123            }
124            
125            /***
126             * Do not use, internal only
127             */
128            public void setCertificateContents(byte[] contents)
129            {
130                    certificateContents = contents;
131            }
132    
133            /***
134             * Do not use, internal only
135             */
136            public void setPrivateKeyContents(byte[] contents)
137            {
138                    privatekeyContents = contents;
139            }
140    
141            /***
142             * Do not use, internal only, loads the certicate
143             */
144            public String getCertificate() throws IOException
145            {
146                    assert(isWithoutTls());
147                    
148                    if (certificateContents != null && certificateContents.length > 0)
149                            return new String(certificateContents);
150                    
151                    if ( ! hasFilePath())
152                            throw new IOException("no PemFile");
153                    
154                    File tokenFile = new File(pemFileName);
155                    InputStream in = new FileInputStream(tokenFile);
156                    long fileSize = tokenFile.length();
157                    byte[] bytes = new byte[(int) fileSize];
158                    int offset = 0;
159                    int count = 0;
160                    while (offset < fileSize) {
161                            count = in.read(bytes, offset, (int) fileSize - offset);
162                            if (count >= 0)
163    
164                                    offset += count;
165                            else
166                                    throw new IOException("Unable to read App Token file: " + tokenFile.getName());
167                    }
168                    if (in != null)
169                    in.close();
170    
171                    String rawString = new String(bytes);
172                    return rawString.trim();
173            }
174    
175            /***
176             * Do not use, internal only, loads the certicate
177             */
178            public X509Certificate getX509Certificate() throws IOException
179            {
180                    assert (isWithTls());
181    
182                    try {
183                            if (hasFilePath()) {
184                                    PemReader donkey = new PemReader(pemFileName);
185                                    return donkey.getCertificate();
186                            }
187                            else if (certificateContents.length == 0 || privatekeyContents.length == 0) {
188                                    throw new IOException("no certificate data.");
189                            }
190                            else {
191                                    ByteArrayInputStream certStream = new ByteArrayInputStream(certificateContents);
192                                    ByteArrayInputStream keyStream = new ByteArrayInputStream(privatekeyContents);
193                                    PemReader donkey = new PemReader(certStream, keyStream);
194                                    return donkey.getCertificate();
195                            }
196                    }
197                    catch (InvalidKeySpecException e) {
198                            throw new IOException("Invalid certificate data.");
199                    }
200            }
201    
202            /***
203             * Do not use, internal only, loads the certicate
204             */
205            public PrivateKey getPrivateKey() throws IOException
206            {
207                    assert (isWithTls());
208                    
209                    if (hasFilePath()) {
210                            PemReader donkey = new PemReader(pemFileName);
211                            return donkey.getKey();
212                    }
213                    else if (certificateContents.length == 0 || privatekeyContents.length == 0) {
214                            throw new IOException("no PemFile");
215                    }
216                    else {
217                            ByteArrayInputStream certStream = new ByteArrayInputStream(certificateContents);
218                            ByteArrayInputStream keyStream = new ByteArrayInputStream(privatekeyContents);
219                            PemReader donkey = new PemReader(certStream, keyStream);
220                            return donkey.getKey();
221                    }
222            }
223    
224            /***
225             * Decide how many attempts at connecting to the runtime shall be done
226             * @param num with num > 0 and num < 10
227             * return num if valid or latest valid input or default, ie 3
228             */
229            public int setConnectionNumRetries(int num) {
230                    if (num > 0 && num < 10)
231                            numRetries = num;
232                    return numRetries;
233            }
234    
235            /***
236             * check how many attempts at connecting to the runtime will be done
237             */
238            public int getConnectionNumRetries() {
239                    return numRetries;
240            }
241    
242            /***
243             * Decide what initial latency in milliseconds will be used between each retry
244             * The policy is to increase this latency by 50% after each failed retry.
245             * The default is 600ms, so that the default pattern is 600 (+600ms) -> 900 (+1500ms) -> 1350 (+2850ms)
246             * @param ms with ms > 100 and ms < 1000
247             * return ms if valid or latest valid input or default, ie 600
248             */
249            public int setConnectionRetryInitialLatency(int ms) {
250                    if (ms > 100 && ms < 1000)
251                            initialLatency = ms;
252                    return initialLatency;
253            }
254    
255            /***
256             * check the initial latency used in case of retries
257             */
258            public int getConnectionRetryInitialLatency() {
259                    return initialLatency;
260            }
261    
262            /***
263             * Recommend the transport input buffer size (may or may not be used by the transport, indicative only)
264             * for the connection to the runtime
265             * @param bytes with bytes >= 256 and bytes < 8096 , bytes is rounded to the next power of 2
266             * return bytes if valid or latest valid input or default, ie 512
267             */
268            public int setTransportInputBufferSize(int bytes) {       // return 2^n so that 2^n >= bytes
269                    if (bytes >= 256 && bytes <= 8096) {
270                            int n = 256;
271                            while (n < bytes) n += n;
272                            transportInputBufferSize = n;
273                    }
274                    return transportInputBufferSize;
275            }
276    
277            /***
278             * check the recommended transport input buffer size
279             */
280            public int getTransportInputBufferSize() {
281                    return transportInputBufferSize;
282            }
283    
284            /***
285             * Recommend the transport output buffer size (may or may not be used by the transport, indicative only)
286             * for the connection to the runtime
287             * @param bytes with bytes >= 256 and bytes < 8096 , bytes is rounded to the next power of 2
288             * return bytes if valid or latest valid input or default, ie 512
289             */
290            public int setTransportOutputBufferSize(int bytes) {       // return 2^n so that 2^n >= bytes
291                    if (bytes >= 256 && bytes <= 8096) {
292                            int n = 256;
293                            while (n < bytes) n += n;
294                            transportOutputBufferSize = n;
295                    }
296                    return transportOutputBufferSize;
297            }
298    
299            /***
300             * check the recommended transport output buffer size
301             */
302            public int getTransportOutputBufferSize() {                // default 512
303                    return transportOutputBufferSize;
304            }
305    
306            /***
307             * turn on the recording of the data exchanged between the runtime and the java client
308             * or turn it off off the file name is empty (default). This is configured once 
309             * @param baseName is the base name for the transport log which consists in 2 files
310             * - <baseName>_log_in.1, data from the runtime to the client
311             * - <baseName>_log_out.1,  data from the client to the runtime
312             */
313            public void generateTransportLog(String baseName) {
314                    if (baseName == null || baseName.equals("")) return;
315                    transportLogBaseName = baseName;
316            }
317    
318            /***
319             * check if a transport log shall be produced
320             */
321            public boolean generateTransportLog() {
322                    return transportLogBaseName != null && !transportLogBaseName.equals("");
323            }
324    
325            /***
326             * get the input transport log file name
327             */
328            public String getInputTransportLogName() {
329                    return transportLogBaseName + "_log_in.1";
330            }
331    
332            /***
333             * get the output transport log file name
334             */
335            public String getOutputTransportLogName() {
336                    return transportLogBaseName + "_log_out.1";
337            }
338    
339            /***
340             * By default only a TCP transport is supported to communicate with the runtime,
341             * but with setting a TransportFactory 1 further non portable transport could be used, 
342             * provided that they are supported by the runtime as well. For example android or
343             * linux runtime shall accept unix socket too.
344             */
345            public void setTransportFactory(TransportFactory factory) {
346                    transportFactory = factory;
347            }
348    
349            public TransportFactory getTransportFactory() {
350                    return transportFactory;
351            }
352    
353            /***
354             * By default a runtime only forwards the int alike value in case of property changes
355             * this function allows forwarding the string values as well which can highly improve
356             * the performances if your application is always getting the strings on the change
357             * notification.
358             */
359            public void fowardStringChangedValue() {
360                    if (setup == null)
361                            setup = new String();
362                    setup += "SkypeKit/FowardStringChangedValue=1\n";
363            }
364    
365    
366            /***
367             * internal, don't use unless implementing a TransportFactory
368             */
369            public String getHandshakeSetup() {
370                    return setup;
371            }
372    
373            private boolean hasFilePath()
374            {
375                    return pemFileName != null && ! pemFileName.isEmpty();
376            }
377            
378            private int     numRetries;
379            private int     initialLatency;
380            private int     transportInputBufferSize;
381            private int     transportOutputBufferSize;
382            private String  transportLogBaseName;
383            private String  pemFileName;
384            private byte[]  certificateContents;
385            private byte[]  privatekeyContents;
386            private boolean noTls;
387            private boolean dispatchAll;
388            protected boolean useLocalTransport;
389            private String  ip;
390            private int     port;
391            private TransportFactory transportFactory;
392            private String  setup;
393    }
394