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

com.digitalpetri.enip.logix.services.ReadTemplateService Maven / Gradle / Ivy

package com.digitalpetri.enip.logix.services;

import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;

import com.digitalpetri.enip.cip.CipResponseException;
import com.digitalpetri.enip.cip.epath.EPath.PaddedEPath;
import com.digitalpetri.enip.cip.services.CipService;
import com.digitalpetri.enip.cip.structs.MessageRouterRequest;
import com.digitalpetri.enip.cip.structs.MessageRouterResponse;
import com.digitalpetri.enip.logix.structs.TemplateAttributes;
import com.digitalpetri.enip.logix.structs.TemplateInstance;
import com.digitalpetri.enip.logix.structs.TemplateMember;
import com.digitalpetri.enip.util.IntUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.util.ReferenceCountUtil;

public class ReadTemplateService implements CipService {

    public static final int SERVICE_CODE = 0x4C;

    private final List buffers = new CopyOnWriteArrayList<>();
    private volatile int totalBytesRead = 0;

    private final PaddedEPath requestPath;
    private final TemplateAttributes attributes;
    private final int symbolType;

    public ReadTemplateService(PaddedEPath requestPath, TemplateAttributes attributes, int symbolType) {
        this.requestPath = requestPath;
        this.attributes = attributes;
        this.symbolType = symbolType;
    }

    @Override
    public void encodeRequest(ByteBuf buffer) {
        MessageRouterRequest request = new MessageRouterRequest(
            SERVICE_CODE,
            requestPath,
            this::encode
        );

        MessageRouterRequest.encode(request, buffer);
    }

    @Override
    public TemplateInstance decodeResponse(ByteBuf buffer) throws CipResponseException, PartialResponseException {
        MessageRouterResponse response = MessageRouterResponse.decode(buffer);

        int status = response.getGeneralStatus();

        try {
            if (status == 0x00 || status == 0x06) {
                buffers.add(response.getData().retain());

                totalBytesRead += response.getData().readableBytes();

                if (status == 0x00) {
                    ByteBuf composite = PooledByteBufAllocator.DEFAULT
                        .compositeBuffer(buffers.size())
                        .addComponents(buffers)
                        .writerIndex(totalBytesRead)
                        .order(ByteOrder.LITTLE_ENDIAN);

                    TemplateInstance instance = decode(composite, symbolType);

                    ReferenceCountUtil.release(composite);

                    return instance;
                } else {
                    throw PartialResponseException.INSTANCE;
                }
            } else {
                throw new CipResponseException(status, response.getAdditionalStatus());
            }
        } finally {
            ReferenceCountUtil.release(response.getData());
        }
    }

    private void encode(ByteBuf buffer) {
        int bytesToRead = (attributes.getObjectDefinitionSize() * 4) - 23;
        bytesToRead = roundUp(bytesToRead, 4) + 4;
        bytesToRead -= totalBytesRead;

        buffer.writeInt(totalBytesRead);
        buffer.writeShort(bytesToRead);
    }

    private TemplateInstance decode(ByteBuf buffer, int symbolType) {
        int memberCount = attributes.getMemberCount();

        List> functions = new ArrayList<>(memberCount);

        for (int i = 0; i < memberCount; i++) {
            int infoWord = buffer.readShort();
            int memberType = buffer.readUnsignedShort();
            int offset = IntUtil.saturatedCast(buffer.readUnsignedInt());

            functions.add((name) -> new TemplateMember(name, infoWord, memberType, offset));
        }

        String templateName = readNullTerminatedString(buffer);

        if (templateName.contains(";n")) {
            templateName = templateName.substring(0, templateName.indexOf(";n"));
        }

        List members = new ArrayList<>(memberCount);

        for (int i = 0; i < memberCount; i++) {
            String memberName = readNullTerminatedString(buffer);
            if (memberName.isEmpty()) memberName = "__UnnamedMember" + i;

            TemplateMember member = functions.get(i).apply(memberName);

            members.add(member);
        }

        return new TemplateInstance(templateName, symbolType, attributes, members);
    }

    private static final Charset ASCII = Charset.forName("US-ASCII");

    private String readNullTerminatedString(ByteBuf buffer) {
        int length = buffer.bytesBefore((byte) 0x00);

        if (length == -1) {
            return "";
        } else {
            String s = buffer.toString(buffer.readerIndex(), length, ASCII);
            buffer.skipBytes(length + 1);
            return s;
        }
    }

    /**
     * Round {@code n} up to the nearest multiple {@code m}.
     *
     * @param n the number to round.
     * @param m the multiple to up to.
     * @return {@code n} rounded up to the nearest multiple {@code m}.
     */
    private int roundUp(int n, int m) {
        return ((n + m - 1) / m) * m;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy