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 ++++++++++++++++++++++ 11 files changed, 342 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 (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); + } + +} -- cgit v1.2.3