From c1121777969fc5f183e325ad672b581cbe881895 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Tue, 28 Dec 2021 23:09:40 -0800 Subject: Extract connector interface --- src/main/java/org/traccar/BasePipelineFactory.java | 10 ++++---- src/main/java/org/traccar/BaseProtocol.java | 10 +++++--- src/main/java/org/traccar/Protocol.java | 2 +- src/main/java/org/traccar/ServerManager.java | 16 ++++++------ src/main/java/org/traccar/TrackerConnector.java | 30 ++++++++++++++++++++++ src/main/java/org/traccar/TrackerServer.java | 29 ++++++++++----------- .../org/traccar/handler/OpenChannelHandler.java | 14 +++++----- 7 files changed, 70 insertions(+), 41 deletions(-) create mode 100644 src/main/java/org/traccar/TrackerConnector.java (limited to 'src/main/java/org/traccar') diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java index c9f3a2346..88a1bc713 100644 --- a/src/main/java/org/traccar/BasePipelineFactory.java +++ b/src/main/java/org/traccar/BasePipelineFactory.java @@ -54,12 +54,12 @@ import java.util.Map; public abstract class BasePipelineFactory extends ChannelInitializer { - private final TrackerServer server; + private final TrackerConnector connector; private final String protocol; private int timeout; - public BasePipelineFactory(TrackerServer server, String protocol) { - this.server = server; + public BasePipelineFactory(TrackerConnector connector, String protocol) { + this.connector = connector; this.protocol = protocol; timeout = Context.getConfig().getInteger(Keys.PROTOCOL_TIMEOUT.withPrefix(protocol)); if (timeout == 0) { @@ -97,10 +97,10 @@ public abstract class BasePipelineFactory extends ChannelInitializer { protected void initChannel(Channel channel) { final ChannelPipeline pipeline = channel.pipeline(); - if (timeout > 0 && !server.isDatagram()) { + if (timeout > 0 && !connector.isDatagram()) { pipeline.addLast(new IdleStateHandler(timeout, 0, 0)); } - pipeline.addLast(new OpenChannelHandler(server)); + pipeline.addLast(new OpenChannelHandler(connector)); pipeline.addLast(new NetworkMessageHandler()); pipeline.addLast(new StandardLoggingHandler(protocol)); diff --git a/src/main/java/org/traccar/BaseProtocol.java b/src/main/java/org/traccar/BaseProtocol.java index bd3391822..2fca9432e 100644 --- a/src/main/java/org/traccar/BaseProtocol.java +++ b/src/main/java/org/traccar/BaseProtocol.java @@ -35,7 +35,7 @@ public abstract class BaseProtocol implements Protocol { private final String name; private final Set supportedDataCommands = new HashSet<>(); private final Set supportedTextCommands = new HashSet<>(); - private final List serverList = new LinkedList<>(); + private final List connectorList = new LinkedList<>(); private StringProtocolEncoder textCommandEncoder = null; @@ -54,12 +54,14 @@ public abstract class BaseProtocol implements Protocol { } protected void addServer(TrackerServer server) { - serverList.add(server); + connectorList.add(server); } + // TODO addClient + @Override - public Collection getServerList() { - return serverList; + public Collection getConnectorList() { + return connectorList; } public void setSupportedDataCommands(String... commands) { diff --git a/src/main/java/org/traccar/Protocol.java b/src/main/java/org/traccar/Protocol.java index aea69b353..bc9c99557 100644 --- a/src/main/java/org/traccar/Protocol.java +++ b/src/main/java/org/traccar/Protocol.java @@ -25,7 +25,7 @@ public interface Protocol { String getName(); - Collection getServerList(); + Collection getConnectorList(); Collection getSupportedDataCommands(); diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java index 935a821aa..45ac656f3 100644 --- a/src/main/java/org/traccar/ServerManager.java +++ b/src/main/java/org/traccar/ServerManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ public class ServerManager { private static final Logger LOGGER = LoggerFactory.getLogger(ServerManager.class); - private final List serverList = new LinkedList<>(); + private final List connectorList = new LinkedList<>(); private final Map protocolList = new ConcurrentHashMap<>(); private void loadPackage(String packageName) throws IOException, URISyntaxException, ReflectiveOperationException { @@ -75,7 +75,7 @@ public class ServerManager { if (BaseProtocol.class.isAssignableFrom(protocolClass) && Context.getConfig().hasKey( Keys.PROTOCOL_PORT.withPrefix(BaseProtocol.nameFromClass(protocolClass)))) { BaseProtocol protocol = (BaseProtocol) protocolClass.getDeclaredConstructor().newInstance(); - serverList.addAll(protocol.getServerList()); + connectorList.addAll(protocol.getConnectorList()); protocolList.put(protocol.getName(), protocol); } } @@ -90,18 +90,18 @@ public class ServerManager { } public void start() throws Exception { - for (TrackerServer server: serverList) { + for (TrackerConnector connector: connectorList) { try { - server.start(); + connector.start(); } catch (BindException e) { - LOGGER.warn("Port {} is disabled due to conflict", server.getPort()); + LOGGER.warn("Port disabled due to conflict", e); } } } public void stop() { - for (TrackerServer server: serverList) { - server.stop(); + for (TrackerConnector connector: connectorList) { + connector.stop(); } GlobalTimer.release(); } diff --git a/src/main/java/org/traccar/TrackerConnector.java b/src/main/java/org/traccar/TrackerConnector.java new file mode 100644 index 000000000..9fc5e0f62 --- /dev/null +++ b/src/main/java/org/traccar/TrackerConnector.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.group.ChannelGroup; + +public interface TrackerConnector { + + boolean isDatagram(); + + ChannelGroup getChannelGroup(); + + void start() throws Exception; + + void stop(); + +} diff --git a/src/main/java/org/traccar/TrackerServer.java b/src/main/java/org/traccar/TrackerServer.java index 59ba123e2..7b25e5cc5 100644 --- a/src/main/java/org/traccar/TrackerServer.java +++ b/src/main/java/org/traccar/TrackerServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,19 @@ import org.traccar.config.Keys; import java.net.InetSocketAddress; -public abstract class TrackerServer { +public abstract class TrackerServer implements TrackerConnector { private final boolean datagram; + + @SuppressWarnings("rawtypes") private final AbstractBootstrap bootstrap; + private final int port; + private final String address; + + private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + @Override public boolean isDatagram() { return datagram; } @@ -69,32 +77,20 @@ public abstract class TrackerServer { protected abstract void addProtocolHandlers(PipelineBuilder pipeline); - private int port; - public int getPort() { return port; } - public void setPort(int port) { - this.port = port; - } - - private String address; - public String getAddress() { return address; } - public void setAddress(String address) { - this.address = address; - } - - private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); - + @Override public ChannelGroup getChannelGroup() { return channelGroup; } + @Override public void start() throws Exception { InetSocketAddress endpoint; if (address == null) { @@ -109,6 +105,7 @@ public abstract class TrackerServer { } } + @Override public void stop() { channelGroup.close().awaitUninterruptibly(); } diff --git a/src/main/java/org/traccar/handler/OpenChannelHandler.java b/src/main/java/org/traccar/handler/OpenChannelHandler.java index d09d617ab..e416f35ae 100644 --- a/src/main/java/org/traccar/handler/OpenChannelHandler.java +++ b/src/main/java/org/traccar/handler/OpenChannelHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,26 +17,26 @@ package org.traccar.handler; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; -import org.traccar.TrackerServer; +import org.traccar.TrackerConnector; public class OpenChannelHandler extends ChannelDuplexHandler { - private final TrackerServer server; + private final TrackerConnector connector; - public OpenChannelHandler(TrackerServer server) { - this.server = server; + public OpenChannelHandler(TrackerConnector connector) { + this.connector = connector; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); - server.getChannelGroup().add(ctx.channel()); + connector.getChannelGroup().add(ctx.channel()); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); - server.getChannelGroup().remove(ctx.channel()); + connector.getChannelGroup().remove(ctx.channel()); } } -- cgit v1.2.3 From 93745ce5de3f5004cb98d951794c692db284a2e2 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 9 Jan 2022 23:27:54 -0800 Subject: Start client implementation --- src/main/java/org/traccar/BasePipelineFactory.java | 4 +- src/main/java/org/traccar/TrackerClient.java | 85 ++++++++++++++++++++++ src/main/java/org/traccar/TrackerServer.java | 6 +- src/main/java/org/traccar/config/Keys.java | 9 ++- 4 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/traccar/TrackerClient.java (limited to 'src/main/java/org/traccar') diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java index 88a1bc713..d50852649 100644 --- a/src/main/java/org/traccar/BasePipelineFactory.java +++ b/src/main/java/org/traccar/BasePipelineFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,7 +70,7 @@ public abstract class BasePipelineFactory extends ChannelInitializer { protected abstract void addProtocolHandlers(PipelineBuilder pipeline); @SafeVarargs - private final void addHandlers(ChannelPipeline pipeline, Class... handlerClasses) { + private void addHandlers(ChannelPipeline pipeline, Class... handlerClasses) { for (Class handlerClass : handlerClasses) { if (handlerClass != null) { pipeline.addLast(Main.getInjector().getInstance(handlerClass)); diff --git a/src/main/java/org/traccar/TrackerClient.java b/src/main/java/org/traccar/TrackerClient.java new file mode 100644 index 000000000..d86dc43e1 --- /dev/null +++ b/src/main/java/org/traccar/TrackerClient.java @@ -0,0 +1,85 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.GlobalEventExecutor; +import org.traccar.config.Keys; + +import java.util.List; + +public abstract class TrackerClient implements TrackerConnector { + + private final Bootstrap bootstrap; + + private final int port; + private final String address; + private final String[] devices; + + private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + @Override + public boolean isDatagram() { + return false; + } + + public TrackerClient(String protocol) { + + address = Context.getConfig().getString(Keys.PROTOCOL_ADDRESS.withPrefix(protocol)); + port = Context.getConfig().getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol)); + devices = Context.getConfig().getString(Keys.PROTOCOL_DEVICES.withPrefix(protocol)).split("[, ]"); + + BasePipelineFactory pipelineFactory = new BasePipelineFactory(this, protocol) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + TrackerClient.this.addProtocolHandlers(pipeline); + } + }; + + bootstrap = new Bootstrap() + .group(EventLoopGroupFactory.getWorkerGroup()) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel channel) { + pipelineFactory.initChannel(channel); + } + }); + } + + protected abstract void addProtocolHandlers(PipelineBuilder pipeline); + + @Override + public ChannelGroup getChannelGroup() { + return channelGroup; + } + + @Override + public void start() throws Exception { + bootstrap.connect(address, port).sync(); + } + + @Override + public void stop() { + channelGroup.close().awaitUninterruptibly(); + } + +} diff --git a/src/main/java/org/traccar/TrackerServer.java b/src/main/java/org/traccar/TrackerServer.java index 7b25e5cc5..caae6c585 100644 --- a/src/main/java/org/traccar/TrackerServer.java +++ b/src/main/java/org/traccar/TrackerServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,14 +60,14 @@ public abstract class TrackerServer implements TrackerConnector { if (datagram) { - this.bootstrap = new Bootstrap() + bootstrap = new Bootstrap() .group(EventLoopGroupFactory.getWorkerGroup()) .channel(NioDatagramChannel.class) .handler(pipelineFactory); } else { - this.bootstrap = new ServerBootstrap() + bootstrap = new ServerBootstrap() .group(EventLoopGroupFactory.getBossGroup(), EventLoopGroupFactory.getWorkerGroup()) .channel(NioServerSocketChannel.class) .childHandler(pipelineFactory); diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index e8e0ff207..8f93b21c1 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,13 @@ public final class Keys { ".port", Collections.singletonList(KeyType.GLOBAL)); + /** + * List of devices for polling protocols. List should contain unique ids separated by commas. + */ + public static final ConfigSuffix PROTOCOL_DEVICES = new ConfigSuffix<>( + ".devices", + Collections.singletonList(KeyType.GLOBAL)); + /** * Connection timeout value in seconds. Because sometimes there is no way to detect lost TCP connection old * connections stay in open state. On most systems there is a limit on number of open connection, so this leads to -- cgit v1.2.3 From 0ce163ba62cc991fee56d9c05fca41c9f7a28143 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 15 Jan 2022 23:19:23 -0800 Subject: Finish initial implementation --- src/main/java/org/traccar/BasePipelineFactory.java | 4 ++ src/main/java/org/traccar/BaseProtocol.java | 4 +- src/main/java/org/traccar/BaseProtocolPoller.java | 56 ++++++++++++++++++ src/main/java/org/traccar/ServerManager.java | 5 +- src/main/java/org/traccar/TrackerClient.java | 66 +++++++++++++++++---- src/main/java/org/traccar/TrackerConnector.java | 4 +- src/main/java/org/traccar/TrackerServer.java | 24 +++++++- src/main/java/org/traccar/config/Keys.java | 31 +++++++++- .../java/org/traccar/protocol/OrbcommProtocol.java | 40 +++++++++++++ .../traccar/protocol/OrbcommProtocolDecoder.java | 57 ++++++++++++++++++ .../traccar/protocol/OrbcommProtocolPoller.java | 69 ++++++++++++++++++++++ src/test/java/org/traccar/ProtocolTest.java | 6 ++ .../protocol/OrbcommProtocolDecoderTest.java | 18 ++++++ 13 files changed, 366 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/traccar/BaseProtocolPoller.java create mode 100644 src/main/java/org/traccar/protocol/OrbcommProtocol.java create mode 100644 src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java create mode 100644 src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java create mode 100644 src/test/java/org/traccar/protocol/OrbcommProtocolDecoderTest.java (limited to 'src/main/java/org/traccar') diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java index d50852649..89ef76a80 100644 --- a/src/main/java/org/traccar/BasePipelineFactory.java +++ b/src/main/java/org/traccar/BasePipelineFactory.java @@ -67,6 +67,8 @@ public abstract class BasePipelineFactory extends ChannelInitializer { } } + protected abstract void addTransportHandlers(PipelineBuilder pipeline); + protected abstract void addProtocolHandlers(PipelineBuilder pipeline); @SafeVarargs @@ -97,6 +99,8 @@ public abstract class BasePipelineFactory extends ChannelInitializer { protected void initChannel(Channel channel) { final ChannelPipeline pipeline = channel.pipeline(); + addTransportHandlers(pipeline::addLast); + if (timeout > 0 && !connector.isDatagram()) { pipeline.addLast(new IdleStateHandler(timeout, 0, 0)); } diff --git a/src/main/java/org/traccar/BaseProtocol.java b/src/main/java/org/traccar/BaseProtocol.java index 2fca9432e..52d34dc44 100644 --- a/src/main/java/org/traccar/BaseProtocol.java +++ b/src/main/java/org/traccar/BaseProtocol.java @@ -57,7 +57,9 @@ public abstract class BaseProtocol implements Protocol { connectorList.add(server); } - // TODO addClient + protected void addClient(TrackerClient client) { + connectorList.add(client); + } @Override public Collection getConnectorList() { diff --git a/src/main/java/org/traccar/BaseProtocolPoller.java b/src/main/java/org/traccar/BaseProtocolPoller.java new file mode 100644 index 000000000..88138577c --- /dev/null +++ b/src/main/java/org/traccar/BaseProtocolPoller.java @@ -0,0 +1,56 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.concurrent.Future; +import org.traccar.config.Keys; + +import java.net.SocketAddress; +import java.util.concurrent.TimeUnit; + +public abstract class BaseProtocolPoller extends ChannelDuplexHandler { + + private final long interval; + private Future timeout; + + public BaseProtocolPoller(Protocol protocol) { + interval = Context.getConfig().getLong(Keys.PROTOCOL_INTERVAL.withPrefix(protocol.getName())); + } + + protected abstract void sendRequest(Channel channel, SocketAddress remoteAddress); + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + if (interval > 0) { + timeout = ctx.executor().scheduleAtFixedRate( + () -> sendRequest(ctx.channel(), ctx.channel().remoteAddress()), 0, interval, TimeUnit.SECONDS); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + if (timeout != null) { + timeout.cancel(false); + timeout = null; + } + } + +} diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java index 45ac656f3..0db786bdb 100644 --- a/src/main/java/org/traccar/ServerManager.java +++ b/src/main/java/org/traccar/ServerManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.traccar.config.Keys; import java.io.File; import java.io.IOException; import java.net.BindException; +import java.net.ConnectException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -95,6 +96,8 @@ public class ServerManager { connector.start(); } catch (BindException e) { LOGGER.warn("Port disabled due to conflict", e); + } catch (ConnectException e) { + LOGGER.warn("Connection failed", e); } } } diff --git a/src/main/java/org/traccar/TrackerClient.java b/src/main/java/org/traccar/TrackerClient.java index d86dc43e1..dda02f909 100644 --- a/src/main/java/org/traccar/TrackerClient.java +++ b/src/main/java/org/traccar/TrackerClient.java @@ -16,18 +16,24 @@ package org.traccar; import io.netty.bootstrap.Bootstrap; -import io.netty.channel.ChannelInitializer; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; -import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.GlobalEventExecutor; import org.traccar.config.Keys; -import java.util.List; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import java.util.concurrent.TimeUnit; public abstract class TrackerClient implements TrackerConnector { + private final boolean secure; + private final long interval; + private final Bootstrap bootstrap; private final int port; @@ -41,31 +47,54 @@ public abstract class TrackerClient implements TrackerConnector { return false; } + @Override + public boolean isSecure() { + return secure; + } + public TrackerClient(String protocol) { + secure = Context.getConfig().getBoolean(Keys.PROTOCOL_SSL.withPrefix(protocol)); + interval = Context.getConfig().getLong(Keys.PROTOCOL_INTERVAL.withPrefix(protocol)); address = Context.getConfig().getString(Keys.PROTOCOL_ADDRESS.withPrefix(protocol)); - port = Context.getConfig().getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol)); + port = Context.getConfig().getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol), secure ? 443 : 80); devices = Context.getConfig().getString(Keys.PROTOCOL_DEVICES.withPrefix(protocol)).split("[, ]"); BasePipelineFactory pipelineFactory = new BasePipelineFactory(this, protocol) { + @Override + protected void addTransportHandlers(PipelineBuilder pipeline) { + try { + if (isSecure()) { + SSLEngine engine = SSLContext.getDefault().createSSLEngine(); + engine.setUseClientMode(true); + pipeline.addLast(new SslHandler(engine)); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { - TrackerClient.this.addProtocolHandlers(pipeline); + try { + TrackerClient.this.addProtocolHandlers(pipeline); + } catch (Exception e) { + throw new RuntimeException(e); + } } }; bootstrap = new Bootstrap() .group(EventLoopGroupFactory.getWorkerGroup()) .channel(NioSocketChannel.class) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel channel) { - pipelineFactory.initChannel(channel); - } - }); + .handler(pipelineFactory); } - protected abstract void addProtocolHandlers(PipelineBuilder pipeline); + protected abstract void addProtocolHandlers(PipelineBuilder pipeline) throws Exception; + + public String[] getDevices() { + return devices; + } @Override public ChannelGroup getChannelGroup() { @@ -74,7 +103,18 @@ public abstract class TrackerClient implements TrackerConnector { @Override public void start() throws Exception { - bootstrap.connect(address, port).sync(); + bootstrap.connect(address, port) + .syncUninterruptibly().channel().closeFuture().addListener(new GenericFutureListener<>() { + @Override + public void operationComplete(Future future) { + if (interval > 0) { + GlobalEventExecutor.INSTANCE.schedule(() -> { + bootstrap.connect(address, port) + .syncUninterruptibly().channel().closeFuture().addListener(this); + }, interval, TimeUnit.SECONDS); + } + } + }); } @Override diff --git a/src/main/java/org/traccar/TrackerConnector.java b/src/main/java/org/traccar/TrackerConnector.java index 9fc5e0f62..9e2d27ae5 100644 --- a/src/main/java/org/traccar/TrackerConnector.java +++ b/src/main/java/org/traccar/TrackerConnector.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ public interface TrackerConnector { boolean isDatagram(); + boolean isSecure(); + ChannelGroup getChannelGroup(); void start() throws Exception; diff --git a/src/main/java/org/traccar/TrackerServer.java b/src/main/java/org/traccar/TrackerServer.java index caae6c585..8e2fce616 100644 --- a/src/main/java/org/traccar/TrackerServer.java +++ b/src/main/java/org/traccar/TrackerServer.java @@ -23,14 +23,18 @@ import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.ssl.SslHandler; import io.netty.util.concurrent.GlobalEventExecutor; import org.traccar.config.Keys; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import java.net.InetSocketAddress; public abstract class TrackerServer implements TrackerConnector { private final boolean datagram; + private final boolean secure; @SuppressWarnings("rawtypes") private final AbstractBootstrap bootstrap; @@ -45,13 +49,31 @@ public abstract class TrackerServer implements TrackerConnector { return datagram; } + @Override + public boolean isSecure() { + return secure; + } + public TrackerServer(boolean datagram, String protocol) { this.datagram = datagram; + secure = Context.getConfig().getBoolean(Keys.PROTOCOL_SSL.withPrefix(protocol)); address = Context.getConfig().getString(Keys.PROTOCOL_ADDRESS.withPrefix(protocol)); port = Context.getConfig().getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol)); BasePipelineFactory pipelineFactory = new BasePipelineFactory(this, protocol) { + @Override + protected void addTransportHandlers(PipelineBuilder pipeline) { + try { + if (isSecure()) { + SSLEngine engine = SSLContext.getDefault().createSSLEngine(); + pipeline.addLast(new SslHandler(engine)); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { TrackerServer.this.addProtocolHandlers(pipeline); @@ -99,7 +121,7 @@ public abstract class TrackerServer implements TrackerConnector { endpoint = new InetSocketAddress(address, port); } - Channel channel = bootstrap.bind(endpoint).sync().channel(); + Channel channel = bootstrap.bind(endpoint).syncUninterruptibly().channel(); if (channel != null) { getChannelGroup().add(channel); } diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 8f93b21c1..cb3bd4de8 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -38,12 +38,27 @@ public final class Keys { Collections.singletonList(KeyType.GLOBAL)); /** - * List of devices for polling protocols. List should contain unique ids separated by commas. + * List of devices for polling protocols. List should contain unique ids separated by commas. Used only for polling + * protocols. */ public static final ConfigSuffix PROTOCOL_DEVICES = new ConfigSuffix<>( ".devices", Collections.singletonList(KeyType.GLOBAL)); + /** + * Polling interval in seconds. Used only for polling protocols. + */ + public static final ConfigSuffix PROTOCOL_INTERVAL = new ConfigSuffix<>( + ".interval", + Collections.singletonList(KeyType.GLOBAL)); + + /** + * Enable SSL support for the protocol. Not all protocols support this. + */ + public static final ConfigSuffix PROTOCOL_SSL = new ConfigSuffix<>( + ".ssl", + Collections.singletonList(KeyType.GLOBAL)); + /** * Connection timeout value in seconds. Because sometimes there is no way to detect lost TCP connection old * connections stay in open state. On most systems there is a limit on number of open connection, so this leads to @@ -182,6 +197,20 @@ public final class Keys { ".ignoreSessionCache", Collections.singletonList(KeyType.GLOBAL)); + /** + * ORBCOMM API access id. + */ + public static final ConfigKey ORBCOMM_ACCESS_ID = new ConfigKey<>( + "orbcomm.accessId", + Collections.singletonList(KeyType.GLOBAL)); + + /** + * ORBCOMM API password. + */ + public static final ConfigKey ORBCOMM_PASSWORD = new ConfigKey<>( + "orbcomm.password", + Collections.singletonList(KeyType.GLOBAL)); + /** * Skip device connection session cache. Global configuration. */ diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocol.java b/src/main/java/org/traccar/protocol/OrbcommProtocol.java new file mode 100644 index 000000000..bdfce3b1e --- /dev/null +++ b/src/main/java/org/traccar/protocol/OrbcommProtocol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestEncoder; +import io.netty.handler.codec.http.HttpResponseDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerClient; + +public class OrbcommProtocol extends BaseProtocol { + + public OrbcommProtocol() { + addClient(new TrackerClient(getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpRequestEncoder()); + pipeline.addLast(new HttpResponseDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new OrbcommProtocolDecoder(OrbcommProtocol.this)); + pipeline.addLast(new OrbcommProtocolPoller(OrbcommProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java b/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java new file mode 100644 index 000000000..8f828beff --- /dev/null +++ b/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java @@ -0,0 +1,57 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpResponse; +import org.traccar.BasePipelineFactory; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Protocol; + +import javax.json.Json; +import javax.json.JsonObject; +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +public class OrbcommProtocolDecoder extends BaseProtocolDecoder { + + public OrbcommProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpResponse response = (FullHttpResponse) msg; + String content = response.content().toString(StandardCharsets.UTF_8); + JsonObject json = Json.createReader(new StringReader(content)).readObject(); + + OrbcommProtocolPoller poller = BasePipelineFactory.getHandler(channel.pipeline(), OrbcommProtocolPoller.class); + if (poller != null) { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + poller.setStartTime(dateFormat.parse(json.getString("NextStartUTC"))); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java b/src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java new file mode 100644 index 000000000..87fa039e1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java @@ -0,0 +1,69 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.QueryStringEncoder; +import org.traccar.BaseProtocolPoller; +import org.traccar.Context; +import org.traccar.Protocol; +import org.traccar.config.Keys; + +import java.net.SocketAddress; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class OrbcommProtocolPoller extends BaseProtocolPoller { + + private final String accessId; + private final String password; + + private Date startTime = new Date(); + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public OrbcommProtocolPoller(Protocol protocol) { + super(protocol); + accessId = Context.getConfig().getString(Keys.ORBCOMM_ACCESS_ID); + password = Context.getConfig().getString(Keys.ORBCOMM_PASSWORD); + } + + @Override + protected void sendRequest(Channel channel, SocketAddress remoteAddress) { + + QueryStringEncoder encoder = new QueryStringEncoder("/GLGW/2/RestMessages.svc/JSON/get_return_messages/"); + encoder.addParam("access_id", accessId); + encoder.addParam("password", password); + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + encoder.addParam("start_utc", dateFormat.format(startTime)); + + HttpRequest request = new DefaultFullHttpRequest( + HttpVersion.HTTP_1_1, HttpMethod.POST, encoder.toString(), Unpooled.buffer()); + channel.writeAndFlush(request); + } + +} diff --git a/src/test/java/org/traccar/ProtocolTest.java b/src/test/java/org/traccar/ProtocolTest.java index c40a15dcc..353593c29 100644 --- a/src/test/java/org/traccar/ProtocolTest.java +++ b/src/test/java/org/traccar/ProtocolTest.java @@ -4,9 +4,11 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import org.traccar.helper.DataConverter; import org.traccar.model.CellTower; @@ -89,6 +91,10 @@ public class ProtocolTest extends BaseTest { return new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method, url, Unpooled.buffer(), headers, new DefaultHttpHeaders()); } + protected DefaultFullHttpResponse response(ByteBuf data) { + return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, data); + } + protected void verifyNotNull(BaseProtocolDecoder decoder, Object object) throws Exception { assertNotNull(decoder.decode(null, null, object)); } diff --git a/src/test/java/org/traccar/protocol/OrbcommProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/OrbcommProtocolDecoderTest.java new file mode 100644 index 000000000..fe84c1af4 --- /dev/null +++ b/src/test/java/org/traccar/protocol/OrbcommProtocolDecoderTest.java @@ -0,0 +1,18 @@ +package org.traccar.protocol; + +import org.junit.Test; +import org.traccar.ProtocolTest; + +public class OrbcommProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + var decoder = new OrbcommProtocolDecoder(null); + + verifyNull(decoder, response( + buffer("{\"ErrorID\":0,\"NextStartUTC\":\"2016-10-13 15:19:59\",\"Messages\":[{\"ID\":120213064,\"MessageUTC\":\"2016-10-12 12:42:01\",\"ReceiveUTC\":\"2016-10-12 12:42:01\",\"SIN\":0,\"MobileID\":\"01173096SKY0E45\",\"Payload\":{\"Name\":\"modemRegistration\",\"SIN\":0,\"MIN\":0,\"Fields\":[{\"Name\":\"hardwareMajorVersion\",\"Value\":\"4\"},{\"Name\":\"hardwareMinorVersion\",\"Value\":\"2\"},{\"Name\":\"softwareMajorVersion\",\"Value\":\"13\"},{\"Name\":\"softwareMinorVersion\",\"Value\":\"1\"},{\"Name\":\"product\",\"Value\":\"4\"},{\"Name\":\"wakeupPeriod\",\"Value\":\"None\"},{\"Name\":\"lastResetReason\",\"Value\":\"Software\"},{\"Name\":\"virtualCarrier\",\"Value\":\"6\"},{\"Name\":\"beam\",\"Value\":\"1\"},{\"Name\":\"vain\",\"Value\":\"0\"},{\"Name\":\"reserved\",\"Value\":\"0\"},{\"Name\":\"operatorTxState\",\"Value\":\"0\"},{\"Name\":\"userTxState\",\"Value\":\"0\"},{\"Name\":\"broadcastIDCount\",\"Value\":\"0\"}}],\"RegionName\":\"AMERRB11\",\"OTAMessageSize\":15,\"CustomerID\":0}]}"))); + + } + +} -- cgit v1.2.3