package com.digitalpetri.enip.logix.services;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import com.digitalpetri.enip.cip.CipResponseException;
import com.digitalpetri.enip.cip.epath.DataSegment.AnsiDataSegment;
import com.digitalpetri.enip.cip.epath.EPath.PaddedEPath;
import com.digitalpetri.enip.cip.epath.LogicalSegment.ClassId;
import com.digitalpetri.enip.cip.epath.LogicalSegment.InstanceId;
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.SymbolInstance;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import io.netty.util.ReferenceCountUtil;
public class GetInstanceAttributeListService implements CipService<List<SymbolInstance>> {
public static final int SERVICE_CODE = 0x55;
private volatile int instanceId = 0;
private final List<SymbolInstance> symbols = Lists.newCopyOnWriteArrayList();
private final String program;
public GetInstanceAttributeListService() {
this(null);
}
public GetInstanceAttributeListService(@Nullable String program) {
this.program = program;
}
@Override
public void encodeRequest(ByteBuf buffer) {
PaddedEPath requestPath = Optional.ofNullable(program)
.map(p ->
new PaddedEPath(
new AnsiDataSegment(p),
new ClassId(0x6B),
new InstanceId(instanceId)))
.orElse(
new PaddedEPath(
new ClassId(0x6B),
new InstanceId(instanceId)));
MessageRouterRequest request = new MessageRouterRequest(
SERVICE_CODE,
requestPath,
this::encode
);
MessageRouterRequest.encode(request, buffer);
}
@Override
public List<SymbolInstance> decodeResponse(ByteBuf buffer) throws CipResponseException, PartialResponseException {
MessageRouterResponse response = MessageRouterResponse.decode(buffer);
int status = response.getGeneralStatus();
ByteBuf data = response.getData();
try {
if (status == 0x00 || status == 0x06) {
symbols.addAll(decode(data));
if (status == 0x00) {
return Lists.newArrayList(symbols);
} else {
instanceId = symbols.get(symbols.size() - 1).getInstanceId() + 1;
throw PartialResponseException.INSTANCE;
}
} else {
throw new CipResponseException(status, response.getAdditionalStatus());
}
} finally {
ReferenceCountUtil.release(data);
}
}
private void encode(ByteBuf buffer) {
buffer.writeShort(3); // 3 attributes:
buffer.writeShort(1); // symbol name
buffer.writeShort(2); // symbol type
buffer.writeShort(8); // dimensions
}
private static final Charset ASCII = Charset.forName("US-ASCII");
private List<SymbolInstance> decode(ByteBuf buffer) {
List<SymbolInstance> l = Lists.newArrayList();
while (buffer.isReadable()) {
// reply data includes instanceId + requested attributes
int instanceId = buffer.readInt();
// attribute 1 - symbol name
int nameLength = buffer.readUnsignedShort();
String name = buffer.toString(buffer.readerIndex(), nameLength, ASCII);
buffer.skipBytes(nameLength);
// attribute 2 - symbol type
int type = buffer.readUnsignedShort();
// attribute 8 - dimensions
int d1Size = buffer.readInt();
int d2Size = buffer.readInt();
int d3Size = buffer.readInt();
l.add(new SymbolInstance(program, name, instanceId, type, d1Size, d2Size, d3Size));
}
return l;
}
}