All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.digitalpetri.enip.cip.CipConnectionPool Maven / Gradle / Ivy

There is a newer version: 1.5.0-RC1
Show newest version
/*
 * Copyright 2014
 *
 * 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 com.digitalpetri.enip.cip;

import java.time.Duration;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;

import com.digitalpetri.enip.cip.epath.EPath.PaddedEPath;
import com.digitalpetri.enip.cip.epath.LogicalSegment.ClassId;
import com.digitalpetri.enip.cip.epath.LogicalSegment.ConnectionPoint;
import com.digitalpetri.enip.cip.epath.LogicalSegment.InstanceId;
import com.digitalpetri.enip.cip.services.CipService.PartialResponseException;
import com.digitalpetri.enip.cip.services.ForwardCloseService;
import com.digitalpetri.enip.cip.services.ForwardOpenService;
import com.digitalpetri.enip.cip.services.LargeForwardOpenService;
import com.digitalpetri.enip.cip.structs.ForwardCloseRequest;
import com.digitalpetri.enip.cip.structs.ForwardCloseResponse;
import com.digitalpetri.enip.cip.structs.ForwardOpenRequest;
import com.digitalpetri.enip.cip.structs.ForwardOpenResponse;
import com.digitalpetri.enip.cip.structs.LargeForwardOpenRequest;
import com.digitalpetri.enip.cip.structs.LargeForwardOpenResponse;
import com.digitalpetri.enip.cip.structs.NetworkConnectionParameters;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CipConnectionPool {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final Queue queue = new LinkedList<>();

    private final Queue> waitQueue = new LinkedList<>();
    private final AtomicInteger count = new AtomicInteger(0);

    private final int connectionLimit;
    private final CipConnectionFactory connectionFactory;

    public CipConnectionPool(int connectionLimit, CipClient client, PaddedEPath connectionPath, int connectionSize) {
        this(connectionLimit, new DefaultConnectionFactory(client, connectionPath, connectionSize));
    }

    public CipConnectionPool(int connectionLimit, CipConnectionFactory connectionFactory) {
        this.connectionLimit = connectionLimit;
        this.connectionFactory = connectionFactory;
    }

    public synchronized CompletableFuture acquire() {
        CompletableFuture future = new CompletableFuture<>();

        acquire0().whenComplete((c, ex) -> {
            if (c != null) {
                if (c.isExpired()) {
                    remove(c);

                    acquire().whenComplete((c2, ex2) -> {
                        if (c2 != null) future.complete(c2);
                        else future.completeExceptionally(ex2);
                    });
                } else {
                    future.complete(c);
                }
            } else {
                future.completeExceptionally(ex);
            }
        });

        return future;
    }

    private synchronized CompletableFuture acquire0() {
        CompletableFuture future = new CompletableFuture<>();

        if (!queue.isEmpty()) {
            future.complete(queue.poll());
        } else {
            waitQueue.add(future);

            if (count.incrementAndGet() <= connectionLimit) {
                CompletableFuture f = connectionFactory.open();

                f.whenComplete((c, ex) -> {
                    CompletableFuture waiter;
                    synchronized (CipConnectionPool.this) {
                        waiter = waitQueue.poll();
                    }

                    if (c != null) {
                        if (waiter != null) {
                            waiter.complete(c);
                        } else {
                            queue.add(c);
                        }
                        logger.debug("Forward open succeeded: {}", c);
                    } else {
                        count.decrementAndGet();
                        if (waiter != null) waiter.completeExceptionally(ex);
                        logger.debug("Forward open failed: {}", ex.getMessage(), ex);
                    }
                });
            } else {
                count.decrementAndGet();
            }
        }

        return future;
    }

    public synchronized void release(CipConnection connection) {
        connection.updateLastUse();

        if (!waitQueue.isEmpty()) {
            waitQueue.poll().complete(connection);
        } else {
            queue.add(connection);
        }
    }

    public synchronized void remove(CipConnection connection) {
        connectionFactory.close(connection).thenRun(
                () -> logger.debug("Connection closed: {}", connection));

        queue.remove(connection);
        count.decrementAndGet();

        if (!waitQueue.isEmpty()) {
            CompletableFuture next = waitQueue.poll();

            acquire().whenComplete((c, ex) -> {
                if (c != null) next.complete(c);
                else next.completeExceptionally(ex);
            });
        }
    }

    public interface CipConnectionFactory {

        CompletableFuture open();

        CompletableFuture close(CipConnection connection);

    }

    public static class DefaultConnectionFactory implements CipConnectionFactory {

        private static final Duration DEFAULT_RPI = Duration.ofSeconds(2);
        private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(15);

        private static final PaddedEPath MESSAGE_ROUTER_CP_PATH = new PaddedEPath(
                new ClassId(0x02),
                new InstanceId(0x01),
                new ConnectionPoint(0x01));

        private static final AtomicInteger T2O_CONNECTION_ID = new AtomicInteger(0);

        private final CipClient client;
        private final PaddedEPath connectionPath;
        private final int connectionSize;

        public DefaultConnectionFactory(CipClient client, PaddedEPath connectionPath, int connectionSize) {
            this.client = client;
            this.connectionPath = connectionPath;
            this.connectionSize = connectionSize;
        }

        @Override
        public CompletableFuture open() {
            return connectionSize <= 500 ?
                    forwardOpen() : largeForwardOpen();
        }

        private CompletableFuture forwardOpen() {
            CompletableFuture future = new CompletableFuture<>();

            NetworkConnectionParameters parameters = getNetworkConnectionParameters();

            ForwardOpenRequest request = new ForwardOpenRequest(
                    DEFAULT_TIMEOUT,
                    0,
                    T2O_CONNECTION_ID.incrementAndGet(),
                    new Random().nextInt(),
                    client.getConfig().getVendorId(),
                    client.getConfig().getSerialNumber(),
                    1, // 0 = x4, 1 = x8, 2 = x16, 3 = x32, 4 = x128, 5 = x256, 6 = x512
                    connectionPath.append(MESSAGE_ROUTER_CP_PATH),
                    DEFAULT_RPI,
                    parameters,
                    DEFAULT_RPI,
                    parameters,
                    0xA3);

            ForwardOpenService service = new ForwardOpenService(request);

            client.sendUnconnectedData(service::encodeRequest).whenComplete((b, ex) -> {
                if (b != null) {
                    try {
                        ForwardOpenResponse response = service.decodeResponse(b);

                        CipConnection connection = new CipConnection(
                                DEFAULT_TIMEOUT.toNanos(),
                                response.getO2tConnectionId(),
                                response.getT2oConnectionId(),
                                response.getConnectionSerialNumber(),
                                response.getOriginatorVendorId(),
                                response.getOriginatorSerialNumber());

                        ReferenceCountUtil.release(response.getApplicationReply());

                        future.complete(connection);
                    } catch (CipResponseException | PartialResponseException e) {
                        future.completeExceptionally(e);
                    } finally {
                        ReferenceCountUtil.release(b);
                    }
                } else {
                    future.completeExceptionally(ex);
                }
            });

            return future;
        }

        protected NetworkConnectionParameters getNetworkConnectionParameters() {
            return new NetworkConnectionParameters(
                    connectionSize,
                    NetworkConnectionParameters.SizeType.Variable,
                    NetworkConnectionParameters.Priority.Low,
                    NetworkConnectionParameters.ConnectionType.PointToPoint,
                    false);
        }

        private CompletableFuture largeForwardOpen() {
            CompletableFuture future = new CompletableFuture<>();

            NetworkConnectionParameters parameters = getNetworkConnectionParameters();

            LargeForwardOpenRequest request = new LargeForwardOpenRequest(
                    DEFAULT_TIMEOUT,                                // timeout
                    0,                                              // o2tConnectionId
                    T2O_CONNECTION_ID.incrementAndGet(),            // t2oConnectionId
                    new Random().nextInt(),                         // connectionSerialNumber
                    client.getConfig().getVendorId(),               // vendorId
                    client.getConfig().getSerialNumber(),           // vendorSerialNumber
                    1,                                              // connectionTimeoutMultiplier
                    connectionPath.append(MESSAGE_ROUTER_CP_PATH),  // connectionPath
                    DEFAULT_RPI,                                    // o2tRpi
                    parameters,                                     // o2tParameters
                    DEFAULT_RPI,                                    // t2oRpi
                    parameters,                                     // t2oParameters
                    0xA3);                                          // transportClassAndTrigger

            LargeForwardOpenService service = new LargeForwardOpenService(request);

            client.sendUnconnectedData(service::encodeRequest).whenComplete((b, ex) -> {
                if (b != null) {
                    try {
                        LargeForwardOpenResponse response = service.decodeResponse(b);

                        CipConnection connection = new CipConnection(
                                DEFAULT_TIMEOUT.toNanos(),
                                response.getO2tConnectionId(),
                                response.getT2oConnectionId(),
                                response.getConnectionSerialNumber(),
                                response.getOriginatorVendorId(),
                                response.getOriginatorSerialNumber());

                        ReferenceCountUtil.release(response.getApplicationReply());

                        future.complete(connection);
                    } catch (CipResponseException | PartialResponseException e) {
                        future.completeExceptionally(e);
                    } finally {
                        ReferenceCountUtil.release(b);
                    }
                } else {
                    future.completeExceptionally(ex);
                }
            });

            return future;
        }

        @Override
        public CompletableFuture close(CipConnection connection) {
            CompletableFuture future = new CompletableFuture<>();

            ForwardCloseRequest request = new ForwardCloseRequest(
                    Duration.ofNanos(connection.getTimeoutNanos()),
                    connection.getSerialNumber(),
                    connection.getOriginatorVendorId(),
                    connection.getOriginatorSerialNumber(),
                    connectionPath.append(MESSAGE_ROUTER_CP_PATH)
            );

            ForwardCloseService service = new ForwardCloseService(request);

            client.sendUnconnectedData(service::encodeRequest).whenComplete((b, ex) -> {
                if (b != null) {
                    try {
                        ForwardCloseResponse response = service.decodeResponse(b);

                        future.complete(response);
                    } catch (CipResponseException | PartialResponseException e) {
                        future.completeExceptionally(e);
                    } finally {
                        ReferenceCountUtil.release(b);
                    }
                } else {
                    future.completeExceptionally(ex);
                }
            });

            return future;
        }

    }

    public static class CipConnection {

        private volatile long lastUse = System.nanoTime();

        private final long timeoutNanos;
        private final int o2tConnectionId;
        private final int t2oConnectionId;
        private final int serialNumber;
        private final int originatorVendorId;
        private final long originatorSerialNumber;

        public CipConnection(long timeoutNanos,
                             int o2tConnectionId,
                             int t2oConnectionId,
                             int serialNumber,
                             int originatorVendorId,
                             long originatorSerialNumber) {

            this.timeoutNanos = timeoutNanos;
            this.o2tConnectionId = o2tConnectionId;
            this.t2oConnectionId = t2oConnectionId;
            this.serialNumber = serialNumber;
            this.originatorVendorId = originatorVendorId;
            this.originatorSerialNumber = originatorSerialNumber;
        }

        public long getTimeoutNanos() {
            return timeoutNanos;
        }

        public int getO2tConnectionId() {
            return o2tConnectionId;
        }

        public int getT2oConnectionId() {
            return t2oConnectionId;
        }

        public int getSerialNumber() {
            return serialNumber;
        }

        public int getOriginatorVendorId() {
            return originatorVendorId;
        }

        public long getOriginatorSerialNumber() {
            return originatorSerialNumber;
        }

        void updateLastUse() {
            lastUse = System.nanoTime();
        }

        boolean isExpired() {
            return (System.nanoTime() - lastUse) > timeoutNanos;
        }

        @Override
        public String toString() {
            return "CipConnection{" +
                    "o2tConnectionId=" + o2tConnectionId +
                    ", t2oConnectionId=" + t2oConnectionId +
                    ", serialNumber=" + serialNumber +
                    '}';
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy