1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.gnss.metric.ntrip;
18  
19  import org.hipparchus.util.FastMath;
20  import org.junit.jupiter.api.Assertions;
21  import org.junit.jupiter.api.Test;
22  import org.orekit.data.DataContext;
23  import org.orekit.errors.OrekitException;
24  import org.orekit.errors.OrekitMessages;
25  
26  import java.io.IOException;
27  import java.net.InetSocketAddress;
28  import java.net.Proxy;
29  import java.net.URISyntaxException;
30  import java.nio.file.Paths;
31  import java.util.concurrent.TimeUnit;
32  
33  public class NtripClientTest {
34  
35      @Test
36      public void testProxy() {
37          NtripClient client = new NtripClient("ntrip.example.org", NtripClient.DEFAULT_PORT,
38                                               DataContext.getDefault().getTimeScales());
39          client.setProxy(Proxy.Type.SOCKS, "localhost", 1080);
40          Assertions.assertEquals(Proxy.Type.SOCKS, client.getProxy().type());
41          Assertions.assertEquals("localhost", ((InetSocketAddress) client.getProxy().address()).getHostName());
42          Assertions.assertEquals(1080, ((InetSocketAddress) client.getProxy().address()).getPort());
43      }
44  
45      @Test
46      public void testUnknownProxy() {
47          final String nonExistant = "socks.invalid";
48          try {
49              NtripClient client = new NtripClient("ntrip.example.org", NtripClient.DEFAULT_PORT,
50                                                   DataContext.getDefault().getTimeScales());
51              client.setProxy(Proxy.Type.SOCKS, nonExistant, 1080);
52              Assertions.fail("an exception should have been thrown");
53          } catch (OrekitException me) {
54              Assertions.assertEquals(OrekitMessages.UNKNOWN_HOST, me.getSpecifier());
55              Assertions.assertEquals(nonExistant, me.getParts()[0]);
56          }
57      }
58  
59      @Test
60      public void testUnknownCaster() {
61          final String nonExistant = "caster.invalid";
62          try {
63              NtripClient client = new NtripClient(nonExistant, NtripClient.DEFAULT_PORT,
64                                                   DataContext.getDefault().getTimeScales());
65              client.setTimeout(100);
66              client.getSourceTable();
67              Assertions.fail("an exception should have been thrown");
68          } catch (OrekitException me) {
69              Assertions.assertEquals(OrekitMessages.CANNOT_PARSE_SOURCETABLE, me.getSpecifier());
70              Assertions.assertEquals(nonExistant, me.getParts()[0]);
71          }
72      }
73  
74      @Test
75      public void testWrongContentType1() {
76          try {
77              DummyServer server = prepareServer("/gnss/ntrip/wrong-content-type.txt");
78              server.run();
79              NtripClient client = new NtripClient("localhost", server.getServerPort(),
80                                                   DataContext.getDefault().getTimeScales());
81              client.setTimeout(500);
82              client.getSourceTable();
83              Assertions.fail("an exception should have been thrown");
84          } catch (OrekitException me) {
85              Assertions.assertEquals(OrekitMessages.UNEXPECTED_CONTENT_TYPE, me.getSpecifier());
86              Assertions.assertEquals("text/html", me.getParts()[0]);
87          }
88      }
89  
90      @Test
91      public void testWrongContentType2() {
92          DummyServer server = prepareServer("/gnss/ntrip/sourcetable-products.igs-ip.net.txt",
93                                             "/gnss/ntrip/wrong-content-type.txt");
94          server.run();
95          NtripClient client = new NtripClient("localhost", server.getServerPort(),
96                                               DataContext.getDefault().getTimeScales());
97          client.setTimeout(500);
98          client.setReconnectParameters(0.001, 2.0, 2);
99          try {
100             client.startStreaming("RTCM3EPH01", Type.RTCM, false, false);
101             try {
102                 Thread.sleep(400);
103             } catch (InterruptedException ie) {
104                 // ignored
105             }
106             client.stopStreaming(100);
107             Assertions.fail("an exception should have been thrown");
108         } catch (OrekitException me) {
109             Assertions.assertEquals(OrekitMessages.UNEXPECTED_CONTENT_TYPE, me.getSpecifier());
110             Assertions.assertEquals("text/html", me.getParts()[0]);
111         }
112     }
113 
114     @Test
115     public void testOtherResponseCode() {
116         DummyServer server = prepareServer("/gnss/ntrip/sourcetable-products.igs-ip.net.txt",
117                                            "/gnss/ntrip/gone.txt");
118         server.run();
119         NtripClient client = new NtripClient("localhost", server.getServerPort(),
120                                              DataContext.getDefault().getTimeScales());
121         client.setTimeout(500);
122         client.setReconnectParameters(0.001, 2.0, 2);
123         try {
124             client.startStreaming("RTCM3EPH01", Type.RTCM, false, false);
125             try {
126                 Thread.sleep(400);
127             } catch (InterruptedException ie) {
128                 // ignored
129             }
130             client.stopStreaming(100);
131             Assertions.fail("an exception should have been thrown");
132         } catch (OrekitException me) {
133             Assertions.assertEquals(OrekitMessages.CONNECTION_ERROR, me.getSpecifier());
134             Assertions.assertEquals("localhost", me.getParts()[0]);
135             Assertions.assertEquals("Gone", me.getParts()[1]);
136         }
137     }
138 
139     @Test
140     public void testWrongRecordType() {
141         try {
142             DummyServer server = prepareServer("/gnss/ntrip/wrong-record-type.txt");
143             server.run();
144             NtripClient client = new NtripClient("localhost", server.getServerPort(),
145                                                  DataContext.getDefault().getTimeScales());
146             client.setTimeout(500);
147             client.getSourceTable();
148             Assertions.fail("an exception should have been thrown");
149         } catch (OrekitException me) {
150             Assertions.assertEquals(OrekitMessages.SOURCETABLE_PARSE_ERROR, me.getSpecifier());
151             Assertions.assertEquals("localhost", me.getParts()[0]);
152             Assertions.assertEquals(7,           me.getParts()[1]);
153             Assertions.assertEquals("BCE;CLK01;BRDC_CoM_ITRF;RTCM 3.1;1057(60),1058(5),1059(5),1063(60),1064(5);0;GPS+GLO;MISC;DEU;50.09;8.66;0;1;RTNet;none;B;N;1400;BKG",       me.getParts()[2]);
154         }
155     }
156 
157     @Test
158     public void testLocalSourceTable() {
159         DummyServer server = prepareServer("/gnss/ntrip/sourcetable-products.igs-ip.net.txt");
160         server.run();
161         NtripClient client = new NtripClient("localhost", server.getServerPort(),
162                                              DataContext.getDefault().getTimeScales());
163         client.setTimeout(500);
164         Assertions.assertEquals("localhost", client.getHost());
165         Assertions.assertEquals(server.getServerPort(), client.getPort());
166         SourceTable table = client.getSourceTable();
167         Assertions.assertEquals("st_filter,st_auth,st_match,st_strict,rtsp,plain_rtp", table.getNtripFlags());
168         Assertions.assertEquals( 2, table.getCasters().size());
169         Assertions.assertEquals( 2, table.getNetworks().size());
170         Assertions.assertEquals(42, table.getDataStreams().size());
171         Assertions.assertSame(table, client.getSourceTable());
172     }
173 
174     @Test
175     public void testUnknownMessage() throws Exception {
176         DummyServer server = prepareServer("/gnss/ntrip/sourcetable-products.igs-ip.net.txt",
177                                            "/gnss/ntrip/RTCM3EPH01.dat");
178         server.run();
179         NtripClient client = new NtripClient("localhost", server.getServerPort(),
180                                              DataContext.getDefault().getTimeScales());
181         client.setTimeout(100);
182         client.setReconnectParameters(0.001, 2.0, 2);
183         final CountingObserver counter = new CountingObserver(m -> true);
184         client.addObserver(1042, "RTCM3EPH01", counter);
185         client.addObserver(1042, "RTCM3EPH01", new LoggingObserver());
186         client.startStreaming("RTCM3EPH01", Type.RTCM, false, false);
187         server.await(10, TimeUnit.SECONDS);
188         // the 31st message causes the exception
189         counter.awaitCount(30, 30 * 1000);
190         // wait a bit for next message, the 31st
191         // better condition would be to wait for StreamMonitor.exception to not be null
192         Thread.sleep(1000);
193         try {
194             client.stopStreaming(100);
195             Assertions.fail("an exception should have been thrown");
196         } catch (OrekitException oe) {
197             Assertions.assertEquals(OrekitMessages.UNKNOWN_ENCODED_MESSAGE_NUMBER, oe.getSpecifier());
198             Assertions.assertEquals("1046", oe.getParts()[0]);
199         }
200     }
201 
202     @Test
203     public void testMountPointAlreadyConnected() {
204         DummyServer server = prepareServer("/gnss/ntrip/sourcetable-products.igs-ip.net.txt",
205                                            "/gnss/ntrip/RTCM3EPH01.dat");
206         server.run();
207         NtripClient client = new NtripClient("localhost", server.getServerPort(),
208                                              DataContext.getDefault().getTimeScales());
209         client.setTimeout(100);
210         client.setReconnectParameters(0.001, 2.0, 2);
211         client.startStreaming("RTCM3EPH01", Type.RTCM, false, false);
212         try {
213             client.startStreaming("RTCM3EPH01", Type.RTCM, false, false);
214             Assertions.fail("an exception should have been thrown");
215         } catch (OrekitException oe) {
216             Assertions.assertEquals(OrekitMessages.MOUNPOINT_ALREADY_CONNECTED, oe.getSpecifier());
217             Assertions.assertEquals("RTCM3EPH01", oe.getParts()[0]);
218         }
219     }
220 
221     @Test
222     public void testGGA() {
223         DummyServer server = prepareServer("/gnss/ntrip/sourcetable-products.igs-ip.net.txt",
224                                            "/gnss/ntrip/zero-length-response.txt");
225         server.run();
226         NtripClient client = new NtripClient("localhost", server.getServerPort(),
227                                              DataContext.getDefault().getTimeScales());
228         client.setTimeout(100);
229         client.setFix(2, 42, 13.456, FastMath.toRadians(43.5), FastMath.toRadians(-1.25), 317.5, 12.2);
230         client.startStreaming("", Type.IGS_SSR, true, true);
231         try {
232             Thread.sleep(400);
233         } catch (InterruptedException ie) {
234             // ignored
235         }
236         client.stopStreaming(100);
237         Assertions.assertEquals("$GPGGA,024213.456,4330.0000,N,0115.0000,W,1,04,1.0,317.5,M,12.2,M,,*7A",
238                             server.getRequestProperty("Ntrip-GGA"));
239     }
240 
241     @Test
242     public void testGGA2() {
243         DummyServer server = prepareServer("/gnss/ntrip/sourcetable-products.igs-ip.net.txt",
244                                            "/gnss/ntrip/zero-length-response.txt");
245         server.run();
246         NtripClient client = new NtripClient("localhost", server.getServerPort(),
247                                              DataContext.getDefault().getTimeScales());
248         client.setTimeout(100);
249         client.setFix(2, 42, 13.456, FastMath.toRadians(-43.5), FastMath.toRadians(1.25), 317.5, 12.2);
250         client.startStreaming("", Type.IGS_SSR, true, true);
251         try {
252             Thread.sleep(400);
253         } catch (InterruptedException ie) {
254             // ignored
255         }
256         client.stopStreaming(100);
257         Assertions.assertEquals("$GPGGA,024213.456,4330.0000,S,0115.0000,E,1,04,1.0,317.5,M,12.2,M,,*75",
258                             server.getRequestProperty("Ntrip-GGA"));
259     }
260 
261     @Test
262     public void testNullGGA() {
263         DummyServer server = prepareServer("/gnss/ntrip/sourcetable-products.igs-ip.net.txt",
264                                            "/gnss/ntrip/zero-length-response.txt");
265         server.run();
266         NtripClient client = new NtripClient("localhost", server.getServerPort(),
267                                              DataContext.getDefault().getTimeScales());
268         client.setTimeout(100);
269         try {
270             client.startStreaming("", Type.IGS_SSR, true, true);
271             try {
272                 Thread.sleep(400);
273             } catch (InterruptedException ie) {
274                 // ignored
275             }
276             client.stopStreaming(100);
277         } catch (OrekitException oe) {
278             Assertions.assertEquals(OrekitMessages.STREAM_REQUIRES_NMEA_FIX, oe.getSpecifier());
279         }
280     }
281 
282     @Test
283     public void testAuthenticationStream() {
284         DummyServer server = prepareServer("/gnss/ntrip/sourcetable-products.igs-ip.net.txt",
285                                            "/gnss/ntrip/requires-basic-authentication.txt");
286         server.run();
287         NtripClient client = new NtripClient("localhost", server.getServerPort(),
288                                              DataContext.getDefault().getTimeScales());
289         client.setTimeout(100);
290         try {
291             client.startStreaming("RTCM3EPH01", Type.RTCM, false, false);
292             try {
293                 Thread.sleep(400);
294             } catch (InterruptedException ie) {
295                 // ignored
296             }
297             client.stopStreaming(100);
298             Assertions.fail("an exception should have been thrown");
299         } catch (OrekitException oe) {
300             Assertions.assertEquals(OrekitMessages.FAILED_AUTHENTICATION, oe.getSpecifier());
301             Assertions.assertEquals("RTCM3EPH01", oe.getParts()[0]);
302         }
303     }
304 
305     @Test
306     public void testAuthenticationCaster() {
307         DummyServer server = prepareServer("/gnss/ntrip/requires-basic-authentication.txt");
308         server.run();
309         NtripClient client = new NtripClient("localhost", server.getServerPort(),
310                                              DataContext.getDefault().getTimeScales());
311         client.setTimeout(100);
312         try {
313             client.getSourceTable();
314             try {
315                 Thread.sleep(400);
316             } catch (InterruptedException ie) {
317                 // ignored
318             }
319             client.stopStreaming(100);
320             Assertions.fail("an exception should have been thrown");
321         } catch (OrekitException oe) {
322             Assertions.assertEquals(OrekitMessages.FAILED_AUTHENTICATION, oe.getSpecifier());
323             Assertions.assertEquals("caster", oe.getParts()[0]);
324         }
325     }
326 
327     @Test
328     public void testForbiddenRequest() {
329         DummyServer server = prepareServer("/gnss/ntrip/forbidden-request.txt");
330         server.run();
331         NtripClient client = new NtripClient("localhost", server.getServerPort(),
332                                              DataContext.getDefault().getTimeScales());
333         client.setTimeout(100);
334         try {
335             client.getSourceTable();
336             try {
337                 Thread.sleep(400);
338             } catch (InterruptedException ie) {
339                 // ignored
340             }
341             client.stopStreaming(100);
342             Assertions.fail("an exception should have been thrown");
343         } catch (OrekitException oe) {
344             Assertions.assertEquals(OrekitMessages.CONNECTION_ERROR, oe.getSpecifier());
345             Assertions.assertEquals("localhost", oe.getParts()[0]);
346             Assertions.assertEquals("Forbidden", oe.getParts()[1]);
347         }
348     }
349 
350     @Test
351     public void testMissingFlags() {
352         DummyServer server = prepareServer("/gnss/ntrip/missing-flags.txt");
353         server.run();
354         NtripClient client = new NtripClient("localhost", server.getServerPort(),
355                                              DataContext.getDefault().getTimeScales());
356         client.setTimeout(100);
357         try {
358             client.getSourceTable();
359             try {
360                 Thread.sleep(400);
361             } catch (InterruptedException ie) {
362                 // ignored
363             }
364             client.stopStreaming(100);
365             Assertions.fail("an exception should have been thrown");
366         } catch (OrekitException oe) {
367             Assertions.assertEquals(OrekitMessages.MISSING_HEADER, oe.getSpecifier());
368             Assertions.assertEquals("localhost", oe.getParts()[0]);
369             Assertions.assertEquals("Ntrip-Flags", oe.getParts()[1]);
370         }
371     }
372 
373     private DummyServer prepareServer(String... names) {
374         DummyServer server = null;
375         try {
376             final String[] fileNames = new String[names.length];
377             for (int i = 0; i < names.length; ++i) {
378                 fileNames[i] = Paths.get(getClass().getResource(names[i]).toURI()).toString();
379             }
380             server = new DummyServer(fileNames);
381         } catch (URISyntaxException | IOException e) {
382             Assertions.fail(e.getLocalizedMessage());
383         }
384         return server;
385     }
386 
387 }