digitalpetri / ethernet-ip

Asynchronous, non-blocking, EtherNet/IP client implementation for Java
Apache License 2.0
146 stars 51 forks source link

Feature: GenericService #64

Open tlf30 opened 1 year ago

tlf30 commented 1 year ago

Hello, First, thank you for this amazing library.

It would be very helpful to have a GenericService class, where the service code can be provided in the constructor. Something like this:

public class GenericService implements CipService<ByteBuf> {

    private final EPath.PaddedEPath requestPath;
    private final int serviceCode;

    public GenericService(int serviceCode, EPath.PaddedEPath requestPath) {
        this.requestPath = requestPath;
        this.serviceCode = serviceCode;
    }

    @Override
    public void encodeRequest(ByteBuf buffer) {
        MessageRouterRequest request = new MessageRouterRequest(
                serviceCode,
                requestPath,
                byteBuf -> {
                }
        );

        MessageRouterRequest.encode(request, buffer);
    }

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

        if (response.getGeneralStatus() == 0x00) {
            return response.getData();
        } else {
            ReferenceCountUtil.release(response.getData());

            throw new CipResponseException(response.getGeneralStatus(), response.getAdditionalStatus());
        }
    }
}

Thank you, Trevor

tlf30 commented 1 year ago

To add to this, a version that also accepts a ByteBuf for the source buffer as a generic solution would also be great.

kevinherron commented 1 year ago

Noted. In the meantime there's nothing stopping your project from having a CipService implementation like this, right?

tlf30 commented 1 year ago

Yep, this is my current implementation (which is somewhat specific to how my project moves data around):

package io.tlf.cip.tool.cip;

import com.digitalpetri.enip.cip.CipResponseException;
import com.digitalpetri.enip.cip.epath.EPath;
import com.digitalpetri.enip.cip.services.CipService;
import com.digitalpetri.enip.cip.structs.MessageRouterRequest;
import com.digitalpetri.enip.cip.structs.MessageRouterResponse;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil;

public class GenericService implements CipService<ByteBuf> {

    private final EPath.PaddedEPath requestPath;
    private final int serviceCode;
    private final byte[] requestData;
    private final MessageStruct requestStruct;

    public GenericService(int serviceCode, EPath.PaddedEPath requestPath) {
        this.requestPath = requestPath;
        this.serviceCode = serviceCode;
        this.requestData = null;
        this.requestStruct = null;
    }

    public GenericService(int serviceCode, EPath.PaddedEPath requestPath, byte[] requestData) {
        this.requestPath = requestPath;
        this.serviceCode = serviceCode;
        this.requestData = requestData;
        this.requestStruct = null;
    }

    public GenericService(int serviceCode, EPath.PaddedEPath requestPath, MessageStruct requestStruct) {
        this.requestPath = requestPath;
        this.serviceCode = serviceCode;
        this.requestData = null;
        this.requestStruct = requestStruct;
    }

    @Override
    public void encodeRequest(ByteBuf buffer) {
        MessageRouterRequest request = new MessageRouterRequest(
                serviceCode,
                requestPath,
                byteBuf -> {
                    if (requestData != null && requestData.length > 0) {
                        byteBuf.writeBytes(requestData);
                    } else if (requestStruct != null) {
                        requestStruct.encode(byteBuf);
                    }
                }
        );

        MessageRouterRequest.encode(request, buffer);
    }

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

        if (response.getGeneralStatus() == 0x00) {
            return response.getData();
        } else {
            ReferenceCountUtil.release(response.getData());

            throw new CipResponseException(response.getGeneralStatus(), response.getAdditionalStatus());
        }
    }
}

I just figured that something generic would be nice to have for others.