/* * Copyright 2011-17 Fraunhofer ISE, energy & meteo Systems GmbH and other contributors * * 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.openmuc.josistack; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeoutException; import org.openmuc.jasn1.ber.ReverseByteArrayOutputStream; import org.openmuc.jasn1.ber.types.BerAny; import org.openmuc.jasn1.ber.types.BerInteger; import org.openmuc.jasn1.ber.types.BerObjectIdentifier; import org.openmuc.jasn1.ber.types.string.BerGraphicString; import org.openmuc.josistack.internal.acse.asn1.AAREApdu; import org.openmuc.josistack.internal.acse.asn1.AARQApdu; import org.openmuc.josistack.internal.acse.asn1.ACSEApdu; import org.openmuc.josistack.internal.acse.asn1.ACSERequirements; import org.openmuc.josistack.internal.acse.asn1.AEQualifier; import org.openmuc.josistack.internal.acse.asn1.AEQualifierForm2; import org.openmuc.josistack.internal.acse.asn1.APTitle; import org.openmuc.josistack.internal.acse.asn1.APTitleForm2; import org.openmuc.josistack.internal.acse.asn1.AssociateResult; import org.openmuc.josistack.internal.acse.asn1.AssociateSourceDiagnostic; import org.openmuc.josistack.internal.acse.asn1.AssociationInformation; import org.openmuc.josistack.internal.acse.asn1.AuthenticationValue; import org.openmuc.josistack.internal.acse.asn1.MechanismName; import org.openmuc.josistack.internal.acse.asn1.Myexternal; import org.openmuc.josistack.internal.presentation.asn1.CPAPPDU; import org.openmuc.josistack.internal.presentation.asn1.CPType; import org.openmuc.josistack.internal.presentation.asn1.CalledPresentationSelector; import org.openmuc.josistack.internal.presentation.asn1.CallingPresentationSelector; import org.openmuc.josistack.internal.presentation.asn1.FullyEncodedData; import org.openmuc.josistack.internal.presentation.asn1.ModeSelector; import org.openmuc.josistack.internal.presentation.asn1.PDVList; import org.openmuc.josistack.internal.presentation.asn1.PresentationContextDefinitionList; import org.openmuc.josistack.internal.presentation.asn1.PresentationContextDefinitionResultList; import org.openmuc.josistack.internal.presentation.asn1.PresentationContextIdentifier; import org.openmuc.josistack.internal.presentation.asn1.RespondingPresentationSelector; import org.openmuc.josistack.internal.presentation.asn1.UserData; import org.openmuc.jositransport.ClientTSap; import org.openmuc.jositransport.TConnection; public final class AcseAssociation { // private static final Logger logger = LoggerFactory.getLogger(AcseAssociation.class); private boolean connected = false; private TConnection tConnection; private ByteBuffer associateResponseAPDU = null; private final RespondingPresentationSelector pSelLocalBerOctetString; private static final PresentationContextDefinitionList context_list = new PresentationContextDefinitionList( new byte[] { (byte) 0x23, (byte) 0x30, (byte) 0x0f, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x04, (byte) 0x52, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x30, (byte) 0x04, (byte) 0x06, (byte) 0x02, (byte) 0x51, (byte) 0x01, (byte) 0x30, (byte) 0x10, (byte) 0x02, (byte) 0x01, (byte) 0x03, (byte) 0x06, (byte) 0x05, (byte) 0x28, (byte) 0xca, (byte) 0x22, (byte) 0x02, (byte) 0x01, (byte) 0x30, (byte) 0x04, (byte) 0x06, (byte) 0x02, (byte) 0x51, (byte) 0x01 }); private static final PresentationContextIdentifier acsePresentationContextId = new PresentationContextIdentifier( new byte[] { (byte) 0x01, (byte) 0x01 }); private static final ModeSelector normalModeSelector = new ModeSelector(); private static final PresentationContextDefinitionResultList presentationResultList = new PresentationContextDefinitionResultList( new byte[] { (byte) 0x12, (byte) 0x30, (byte) 0x07, (byte) 0x80, (byte) 0x01, (byte) 0x00, (byte) 0x81, (byte) 0x02, (byte) 0x51, (byte) 0x01, (byte) 0x30, (byte) 0x07, (byte) 0x80, (byte) 0x01, (byte) 0x00, (byte) 0x81, (byte) 0x02, (byte) 0x51, (byte) 0x01 }); private static final AssociateResult aareAccepted = new AssociateResult(new byte[] { (byte) 0x01, (byte) 0x00 }); private static final AssociateSourceDiagnostic associateSourceDiagnostic = new AssociateSourceDiagnostic( new byte[] { (byte) 0xa1, (byte) 0x03, (byte) 0x02, (byte) 0x01, (byte) 0x00 }); // is always equal to 1.0.9506.2.3 (MMS) private static final BerObjectIdentifier application_context_name = new BerObjectIdentifier( new byte[] { (byte) 0x05, (byte) 0x28, (byte) 0xca, (byte) 0x22, (byte) 0x02, (byte) 0x03 }); private static final BerObjectIdentifier directReference = new BerObjectIdentifier( new byte[] { (byte) 0x02, (byte) 0x51, (byte) 0x01 }); private static final BerInteger indirectReference = new BerInteger(new byte[] { (byte) 0x01, (byte) 0x03 }); private static final MechanismName default_mechanism_name = new MechanismName( new byte[] { 0x03, 0x52, 0x03, 0x01 }); static { normalModeSelector.setModeValue(new BerInteger(BigInteger.ONE)); } AcseAssociation(TConnection tConnection, byte[] pSelLocal) { this.tConnection = tConnection; pSelLocalBerOctetString = new RespondingPresentationSelector(pSelLocal); } /** * A server that got an Association Request Indication may use this function to accept the association. * * @param payload * the payload to send with the accept message * @throws IOException * if an error occures accepting the association */ public void accept(ByteBuffer payload) throws IOException { BerAny anyPayload = new BerAny(Arrays.copyOfRange(payload.array(), payload.position(), payload.limit())); Myexternal.Encoding encoding = new Myexternal.Encoding(); encoding.setSingleASN1Type(anyPayload); Myexternal myExternal = new Myexternal(); myExternal.setDirectReference(directReference); myExternal.setIndirectReference(indirectReference); myExternal.setEncoding(encoding); AssociationInformation userInformation = new AssociationInformation(); List externalList = userInformation.getMyexternal(); externalList.add(myExternal); AAREApdu aare = new AAREApdu(); aare.setApplicationContextName(application_context_name); aare.setResult(aareAccepted); aare.setResultSourceDiagnostic(associateSourceDiagnostic); aare.setUserInformation(userInformation); ACSEApdu acse = new ACSEApdu(); acse.setAare(aare); ReverseByteArrayOutputStream reverseOStream = new ReverseByteArrayOutputStream(100, true); acse.encode(reverseOStream); UserData userData = getPresentationUserDataField(reverseOStream.getArray()); CPAPPDU.NormalModeParameters normalModeParameters = new CPAPPDU.NormalModeParameters(); normalModeParameters.setRespondingPresentationSelector(pSelLocalBerOctetString); normalModeParameters.setPresentationContextDefinitionResultList(presentationResultList); normalModeParameters.setUserData(userData); CPAPPDU cpaPPdu = new CPAPPDU(); cpaPPdu.setModeSelector(normalModeSelector); cpaPPdu.setNormalModeParameters(normalModeParameters); reverseOStream.reset(); cpaPPdu.encode(reverseOStream, true); List ssduList = new LinkedList<>(); List ssduOffsets = new LinkedList<>(); List ssduLengths = new LinkedList<>(); ssduList.add(reverseOStream.buffer); ssduOffsets.add(reverseOStream.index + 1); ssduLengths.add(reverseOStream.buffer.length - (reverseOStream.index + 1)); writeSessionAccept(ssduList, ssduOffsets, ssduLengths); connected = true; } private void writeSessionAccept(List ssdu, List ssduOffsets, List ssduLengths) throws IOException { byte[] sduAcceptHeader = new byte[20]; int idx = 0; int ssduLength = 0; for (int ssduElementLength : ssduLengths) { ssduLength += ssduElementLength; } // write ISO 8327-1 Header // SPDU Type: ACCEPT (14) sduAcceptHeader[idx++] = 0x0e; // Length: length of session user data + 22 ( header data after length // field ) sduAcceptHeader[idx++] = (byte) ((ssduLength + 18) & 0xff); // -- start Connect Accept Item // Parameter type: Connect Accept Item (5) sduAcceptHeader[idx++] = 0x05; // Parameter length sduAcceptHeader[idx++] = 0x06; // Protocol options: // Parameter Type: Protocol Options (19) sduAcceptHeader[idx++] = 0x13; // Parameter length sduAcceptHeader[idx++] = 0x01; // flags: (.... ...0 = Able to receive extended concatenated SPDU: // False) sduAcceptHeader[idx++] = 0x00; // Version number: // Parameter type: Version Number (22) sduAcceptHeader[idx++] = 0x16; // Parameter length sduAcceptHeader[idx++] = 0x01; // flags: (.... ..1. = Protocol Version 2: True) sduAcceptHeader[idx++] = 0x02; // -- end Connect Accept Item // Session Requirement // Parameter type: Session Requirement (20) sduAcceptHeader[idx++] = 0x14; // Parameter length sduAcceptHeader[idx++] = 0x02; // flags: (.... .... .... ..1. = Duplex functional unit: True) sduAcceptHeader[idx++] = 0x00; sduAcceptHeader[idx++] = 0x02; // Called Session Selector // Parameter type: Called Session Selector (52) sduAcceptHeader[idx++] = 0x34; // Parameter length sduAcceptHeader[idx++] = 0x02; // Called Session Selector sduAcceptHeader[idx++] = 0x00; sduAcceptHeader[idx++] = 0x01; // Session user data // Parameter type: Session user data (193) sduAcceptHeader[idx++] = (byte) 0xc1; // Parameter length sduAcceptHeader[idx++] = (byte) ssduLength; ssdu.add(0, sduAcceptHeader); ssduOffsets.add(0, 0); ssduLengths.add(0, sduAcceptHeader.length); tConnection.send(ssdu, ssduOffsets, ssduLengths); } public ByteBuffer getAssociateResponseAPdu() { ByteBuffer returnBuffer = associateResponseAPDU; associateResponseAPDU = null; return returnBuffer; } /** * Starts an Application Association by sending an association request and waiting for an association accept message * * @param payload * payload that can be sent with the association request * @param port * @param address * @param tSAP * @param aeQualifierCalling * @param aeQualifierCalled * @param apTitleCalling * @param apTitleCalled * @throws IOException */ void startAssociation(ByteBuffer payload, InetAddress address, int port, InetAddress localAddr, int localPort, String authenticationParameter, byte[] sSelRemote, byte[] sSelLocal, byte[] pSelRemote, ClientTSap tSAP, int[] apTitleCalled, int[] apTitleCalling, int aeQualifierCalled, int aeQualifierCalling) throws IOException { if (connected == true) { throw new IOException(); } APTitle called_ap_title = new APTitle(); called_ap_title.setApTitleForm2(new APTitleForm2(apTitleCalled)); APTitle calling_ap_title = new APTitle(); calling_ap_title.setApTitleForm2(new APTitleForm2(apTitleCalling)); AEQualifier called_ae_qualifier = new AEQualifier(); called_ae_qualifier.setAeQualifierForm2(new AEQualifierForm2(aeQualifierCalled)); AEQualifier calling_ae_qualifier = new AEQualifier(); calling_ae_qualifier.setAeQualifierForm2(new AEQualifierForm2(aeQualifierCalling)); Myexternal.Encoding encoding = new Myexternal.Encoding(); encoding.setSingleASN1Type( new BerAny(Arrays.copyOfRange(payload.array(), payload.position(), payload.limit()))); Myexternal myExternal = new Myexternal(); myExternal.setDirectReference(directReference); myExternal.setIndirectReference(indirectReference); myExternal.setEncoding(encoding); AssociationInformation userInformation = new AssociationInformation(); List externalList = userInformation.getMyexternal(); externalList.add(myExternal); ACSERequirements sender_acse_requirements = null; MechanismName mechanism_name = null; AuthenticationValue authentication_value = null; if (authenticationParameter != null) { sender_acse_requirements = new ACSERequirements(new byte[] { (byte) 0x02, (byte) 0x07, (byte) 0x80 }); mechanism_name = default_mechanism_name; authentication_value = new AuthenticationValue(); authentication_value.setCharstring(new BerGraphicString(authenticationParameter.getBytes())); } AARQApdu aarq = new AARQApdu(); aarq.setApplicationContextName(application_context_name); aarq.setCalledAPTitle(called_ap_title); aarq.setCalledAEQualifier(called_ae_qualifier); aarq.setCallingAPTitle(calling_ap_title); aarq.setCallingAEQualifier(calling_ae_qualifier); aarq.setSenderAcseRequirements(sender_acse_requirements); aarq.setMechanismName(mechanism_name); aarq.setCallingAuthenticationValue(authentication_value); aarq.setUserInformation(userInformation); ACSEApdu acse = new ACSEApdu(); acse.setAarq(aarq); ReverseByteArrayOutputStream reverseOStream = new ReverseByteArrayOutputStream(200, true); acse.encode(reverseOStream); UserData userData = getPresentationUserDataField(reverseOStream.getArray()); CPType.NormalModeParameters normalModeParameter = new CPType.NormalModeParameters(); normalModeParameter .setCallingPresentationSelector(new CallingPresentationSelector(pSelLocalBerOctetString.value)); normalModeParameter.setCalledPresentationSelector(new CalledPresentationSelector(pSelRemote)); normalModeParameter.setPresentationContextDefinitionList(context_list); normalModeParameter.setUserData(userData); CPType cpType = new CPType(); cpType.setModeSelector(normalModeSelector); cpType.setNormalModeParameters(normalModeParameter); reverseOStream.reset(); cpType.encode(reverseOStream, true); List ssduList = new LinkedList<>(); List ssduOffsets = new LinkedList<>(); List ssduLengths = new LinkedList<>(); ssduList.add(reverseOStream.buffer); ssduOffsets.add(reverseOStream.index + 1); ssduLengths.add(reverseOStream.buffer.length - (reverseOStream.index + 1)); ByteBuffer res = null; res = startSConnection(ssduList, ssduOffsets, ssduLengths, address, port, localAddr, localPort, tSAP, sSelRemote, sSelLocal); associateResponseAPDU = decodePConResponse(res); } private static ByteBuffer decodePConResponse(ByteBuffer ppdu) throws IOException { CPAPPDU cpa_ppdu = new CPAPPDU(); InputStream iStream = new ByteBufferInputStream(ppdu); cpa_ppdu.decode(iStream); iStream = new ByteArrayInputStream(cpa_ppdu.getNormalModeParameters() .getUserData() .getFullyEncodedData() .getPDVList() .get(0) .getPresentationDataValues() .getSingleASN1Type().value); ACSEApdu acseApdu = new ACSEApdu(); acseApdu.decode(iStream, null); return ByteBuffer.wrap( acseApdu.getAare().getUserInformation().getMyexternal().get(0).getEncoding().getSingleASN1Type().value); } private static UserData getPresentationUserDataField(byte[] userDataBytes) { PDVList.PresentationDataValues presDataValues = new PDVList.PresentationDataValues(); presDataValues.setSingleASN1Type(new BerAny(userDataBytes)); PDVList pdvList = new PDVList(); pdvList.setPresentationContextIdentifier(acsePresentationContextId); pdvList.setPresentationDataValues(presDataValues); FullyEncodedData fullyEncodedData = new FullyEncodedData(); List pdvListList = fullyEncodedData.getPDVList(); pdvListList.add(pdvList); UserData userData = new UserData(); userData.setFullyEncodedData(fullyEncodedData); return userData; } /** * Starts a session layer connection, sends a CONNECT (CN), waits for a ACCEPT (AC) and throws an IOException if not * successful * * @throws IOException */ private ByteBuffer startSConnection(List ssduList, List ssduOffsets, List ssduLengths, InetAddress address, int port, InetAddress localAddr, int localPort, ClientTSap tSAP, byte[] sSelRemote, byte[] sSelLocal) throws IOException { if (connected == true) { throw new IOException(); } byte[] spduHeader = new byte[24]; int idx = 0; // byte[] res = null; int ssduLength = 0; for (int ssduElementLength : ssduLengths) { ssduLength += ssduElementLength; } // write ISO 8327-1 Header // SPDU Type: CONNECT (13) spduHeader[idx++] = 0x0d; // Length: length of session user data + 22 ( header data after // length field ) spduHeader[idx++] = (byte) ((ssduLength + 22) & 0xff); // -- start Connect Accept Item // Parameter type: Connect Accept Item (5) spduHeader[idx++] = 0x05; // Parameter length spduHeader[idx++] = 0x06; // Protocol options: // Parameter Type: Protocol Options (19) spduHeader[idx++] = 0x13; // Parameter length spduHeader[idx++] = 0x01; // flags: (.... ...0 = Able to receive extended concatenated SPDU: // False) spduHeader[idx++] = 0x00; // Version number: // Parameter type: Version Number (22) spduHeader[idx++] = 0x16; // Parameter length spduHeader[idx++] = 0x01; // flags: (.... ..1. = Protocol Version 2: True) spduHeader[idx++] = 0x02; // -- end Connect Accept Item // Session Requirement // Parameter type: Session Requirement (20) spduHeader[idx++] = 0x14; // Parameter length spduHeader[idx++] = 0x02; // flags: (.... .... .... ..1. = Duplex functional unit: True) spduHeader[idx++] = 0x00; spduHeader[idx++] = 0x02; // Calling Session Selector // Parameter type: Calling Session Selector (51) spduHeader[idx++] = 0x33; // Parameter length spduHeader[idx++] = 0x02; // Calling Session Selector spduHeader[idx++] = sSelRemote[0]; spduHeader[idx++] = sSelRemote[1]; // Called Session Selector // Parameter type: Called Session Selector (52) spduHeader[idx++] = 0x34; // Parameter length spduHeader[idx++] = 0x02; // Called Session Selector spduHeader[idx++] = sSelLocal[0]; spduHeader[idx++] = sSelLocal[1]; // Session user data // Parameter type: Session user data (193) spduHeader[idx++] = (byte) 0xc1; // Parameter length spduHeader[idx++] = (byte) (ssduLength & 0xff); // write session user data ssduList.add(0, spduHeader); ssduOffsets.add(0, 0); ssduLengths.add(0, spduHeader.length); tConnection = tSAP.connectTo(address, port, localAddr, localPort); tConnection.send(ssduList, ssduOffsets, ssduLengths); // TODO how much should be allocated here? ByteBuffer pduBuffer = ByteBuffer.allocate(500); try { tConnection.receive(pduBuffer); } catch (TimeoutException e) { throw new IOException("ResponseTimeout waiting for connection response.", e); } idx = 0; // read ISO 8327-1 Header // SPDU Type: ACCEPT (14) byte spduType = pduBuffer.get(); if (spduType != 0x0e) { throw new IOException("ISO 8327-1 header wrong SPDU type, expected ACCEPT (14), got " + getSPDUTypeString(spduType) + " (" + spduType + ")"); } pduBuffer.get(); // skip length byte parameter_loop: while (true) { // read parameter type int parameterType = pduBuffer.get() & 0xff; // read parameter length int parameterLength = pduBuffer.get() & 0xff; switch (parameterType) { // Connect Accept Item (5) case 0x05: int bytesToRead = parameterLength; while (bytesToRead > 0) { // read parameter type int ca_parameterType = pduBuffer.get(); // read parameter length // int ca_parameterLength = res[idx++]; pduBuffer.get(); bytesToRead -= 2; switch (ca_parameterType & 0xff) { // Protocol Options (19) case 0x13: // flags: .... ...0 = Able to receive extended // concatenated SPDU: False byte protocolOptions = pduBuffer.get(); if (protocolOptions != 0x00) { throw new IOException( "SPDU Connect Accept Item/Protocol Options is " + protocolOptions + ", expected 0"); } bytesToRead--; break; // Version Number case 0x16: // flags .... ..1. = Protocol Version 2: True byte versionNumber = pduBuffer.get(); if (versionNumber != 0x02) { throw new IOException( "SPDU Connect Accept Item/Version Number is " + versionNumber + ", expected 2"); } bytesToRead--; break; default: throw new IOException( "SPDU Connect Accept Item: parameter not implemented: " + ca_parameterType); } } break; // Session Requirement (20) case 0x14: // flags: (.... .... .... ..1. = Duplex functional unit: True) long sessionRequirement = extractInteger(pduBuffer, parameterLength); if (sessionRequirement != 0x02) { throw new IOException("SPDU header parameter 'Session Requirement (20)' is " + sessionRequirement + ", expected 2"); } break; // Calling Session Selector (51) case 0x33: long css = extractInteger(pduBuffer, parameterLength); if (css != 0x01) { throw new IOException( "SPDU header parameter 'Calling Session Selector (51)' is " + css + ", expected 1"); } break; // Called Session Selector (52) case 0x34: long calledSessionSelector = extractInteger(pduBuffer, parameterLength); if (calledSessionSelector != 0x01) { throw new IOException("SPDU header parameter 'Called Session Selector (52)' is " + calledSessionSelector + ", expected 1"); } break; // Session user data (193) case 0xc1: break parameter_loop; default: throw new IOException("SPDU header parameter type " + parameterType + " not implemented"); } } // got correct ACCEPT (AC) from the server connected = true; return pduBuffer; } public void send(ByteBuffer payload) throws IOException { List ssduList = new ArrayList<>(); List ssduOffsets = new LinkedList<>(); List ssduLengths = new LinkedList<>(); encodePresentationLayer(payload, ssduList, ssduOffsets, ssduLengths); encodeSessionLayer(ssduList, ssduOffsets, ssduLengths); tConnection.send(ssduList, ssduOffsets, ssduLengths); } private void encodePresentationLayer(ByteBuffer payload, List ssduList, List ssduOffsets, List ssduLengths) throws IOException { PDVList pdv_list = new PDVList(); pdv_list.setPresentationContextIdentifier(new PresentationContextIdentifier(3l)); PDVList.PresentationDataValues presentationDataValues = new PDVList.PresentationDataValues(); presentationDataValues.setSingleASN1Type( new BerAny(Arrays.copyOfRange(payload.array(), payload.position(), payload.limit()))); pdv_list.setPresentationDataValues(presentationDataValues); FullyEncodedData fully_encoded_data = new FullyEncodedData(); List pdv_list_list = fully_encoded_data.getPDVList(); pdv_list_list.add(pdv_list); UserData user_data = new UserData(); user_data.setFullyEncodedData(fully_encoded_data); ReverseByteArrayOutputStream reverseOStream = new ReverseByteArrayOutputStream(200, true); user_data.encode(reverseOStream); ssduList.add(reverseOStream.buffer); ssduOffsets.add(reverseOStream.index + 1); ssduLengths.add(reverseOStream.buffer.length - (reverseOStream.index + 1)); } private void encodeSessionLayer(List ssduList, List ssduOffsets, List ssduLengths) throws IOException { byte[] spduHeader = new byte[4]; // --write iso 8327-1 Header-- // write SPDU Type: give tokens PDU spduHeader[0] = 0x01; // length 0 spduHeader[1] = 0; // write SPDU Type: DATA TRANSFER (DT) spduHeader[2] = 0x01; // length 0 spduHeader[3] = 0; ssduList.add(0, spduHeader); ssduOffsets.add(0, 0); ssduLengths.add(0, spduHeader.length); } /** * Listens for a new PDU and writes it into the given buffer. Decodes all ACSE and lower layer headers. The * resulting buffer's position points to the beginning of the ACSE SDU. The limit will point to the byte after the * last byte of the ACSE SDU. * * @param pduBuffer * buffer to write the received pdu into * @throws DecodingException * if a decoding error occurs * @throws IOException * if a non recoverable error occurs. Afterwards the association should be closed by the user * @throws TimeoutException * if a timeout occurs */ public byte[] receive(ByteBuffer pduBuffer) throws DecodingException, IOException, TimeoutException { if (connected == false) { throw new IllegalStateException("ACSE Association not connected"); } tConnection.receive(pduBuffer); decodeSessionLayer(pduBuffer); return decodePresentationLayer(pduBuffer); } private byte[] decodePresentationLayer(ByteBuffer pduBuffer) throws DecodingException { // decode PPDU header UserData user_data = new UserData(); try { user_data.decode(new ByteBufferInputStream(pduBuffer), null); } catch (IOException e) { throw new DecodingException("error decoding PPDU header", e); } return user_data.getFullyEncodedData() .getPDVList() .get(0) .getPresentationDataValues() .getSingleASN1Type().value; } private void decodeSessionLayer(ByteBuffer pduBuffer) throws EOFException, DecodingException { int firstByte = pduBuffer.get(); if (firstByte == 25) { // got an ABORT SPDU throw new EOFException("Received an ABORT SPDU"); } // -- read ISO 8327-1 header // SPDU type: Give tokens PDU (1) if (firstByte != 0x01) { throw new DecodingException("SPDU header syntax errror: first SPDU type not 1"); } // length if (pduBuffer.get() != 0) { throw new DecodingException("SPDU header syntax errror: first SPDU type length not 0"); } // SPDU Type: DATA TRANSFER (DT) SPDU (1) if (pduBuffer.get() != 0x01) { throw new DecodingException("SPDU header syntax errror: second SPDU type not 1"); } // length if (pduBuffer.get() != 0) { throw new DecodingException("SPDU header syntax errror: second SPDU type length not 0"); } } /** * Disconnects by sending a disconnect request at the Transport Layer and then closing the socket. */ public void disconnect() { connected = false; if (tConnection != null) { tConnection.disconnect(); } } /** * Closes the connection simply by closing the socket. */ public void close() { connected = false; if (tConnection != null) { tConnection.close(); } } private long extractInteger(ByteBuffer buffer, int size) throws IOException { switch (size) { case 1: return buffer.get(); case 2: return buffer.getShort(); case 4: return buffer.getInt(); case 8: return buffer.getLong(); default: throw new IOException("invalid length for reading numeric value"); } } ByteBuffer listenForCn(ByteBuffer pduBuffer) throws IOException, TimeoutException { if (connected == true) { throw new IllegalStateException("ACSE Association is already connected"); } int parameter; int parameterLength; tConnection.receive(pduBuffer); // start reading ISO 8327-1 header // SPDU Type: CONNECT (CN) SPDU (13) byte spduType = pduBuffer.get(); if (spduType != 0x0d) { throw new IOException("ISO 8327-1 header wrong SPDU type, expected CONNECT (13), got " + getSPDUTypeString(spduType) + " (" + spduType + ")"); } pduBuffer.get(); // skip lenght byte parameter_loop: while (true) { // read parameter code parameter = pduBuffer.get() & 0xff; // read parameter length parameterLength = pduBuffer.get() & 0xff; switch (parameter) { // Connect Accept Item (5) case 0x05: int bytesToRead = parameterLength; while (bytesToRead > 0) { // read parameter type int ca_parameterType = pduBuffer.get(); // read parameter length pduBuffer.get(); bytesToRead -= 2; switch (ca_parameterType & 0xff) { // Protocol Options (19) case 0x13: // flags: .... ...0 = Able to receive extended // concatenated SPDU: False byte protocolOptions = pduBuffer.get(); if (protocolOptions != 0x00) { throw new IOException( "SPDU Connect Accept Item/Protocol Options is " + protocolOptions + ", expected 0"); } bytesToRead--; break; // Version Number case 0x16: // flags .... ..1. = Protocol Version 2: True byte versionNumber = pduBuffer.get(); if (versionNumber != 0x02) { throw new IOException( "SPDU Connect Accept Item/Version Number is " + versionNumber + ", expected 2"); } bytesToRead--; break; default: throw new IOException( "SPDU Connect Accept Item: parameter not implemented: " + ca_parameterType); } } break; // Session Requirement (20) case 0x14: // flags: (.... .... .... ..1. = Duplex functional unit: True) long sessionRequirement = extractInteger(pduBuffer, parameterLength); if (sessionRequirement != 0x02) { throw new IOException("SPDU header parameter 'Session Requirement (20)' is " + sessionRequirement + ", expected 2"); } break; // Calling Session Selector (51) case 0x33: extractInteger(pduBuffer, parameterLength); break; // Called Session Selector (52) case 0x34: long calledSessionSelector = extractInteger(pduBuffer, parameterLength); if (calledSessionSelector != 0x01) { throw new IOException("SPDU header parameter 'Called Session Selector (52)' is " + calledSessionSelector + ", expected 1"); } break; // Session user data (193) case 0xc1: break parameter_loop; default: throw new IOException("SPDU header parameter type " + parameter + " not implemented"); } } CPType cpType = new CPType(); InputStream iStream = new ByteBufferInputStream(pduBuffer); cpType.decode(iStream, true); iStream = new ByteArrayInputStream(cpType.getNormalModeParameters() .getUserData() .getFullyEncodedData() .getPDVList() .get(0) .getPresentationDataValues() .getSingleASN1Type().value); ACSEApdu acseApdu = new ACSEApdu(); acseApdu.decode(iStream, null); return ByteBuffer.wrap( acseApdu.getAarq().getUserInformation().getMyexternal().get(0).getEncoding().getSingleASN1Type().value); } public int getMessageTimeout() { return tConnection.getMessageTimeout(); } public void setMessageTimeout(int i) { tConnection.setMessageTimeout(i); } public static String getSPDUTypeString(byte spduType) { switch (spduType) { case 0: return "EXCEPTION REPORT (ER)"; case 1: return "DATA TRANSFER (DT)"; case 2: return "PLEASE TOKENS (PT)"; case 5: return "EXPEDITED (EX)"; case 7: return "PREPARE (PR)"; case 8: return "NOT FINISHED (NF)"; case 9: return "FINISH (FN)"; case 10: return "DISCONNECT (DN)"; case 12: return "REFUSE (RF)"; case 13: return "CONNECT (CN)"; case 14: return "ACCEPT (AC)"; case 15: return "CONNECT DATA OVERFLOW (CDO)"; case 16: return "OVERFLOW ACCEPT (OA)"; case 21: return "GIVE TOKENS CONFIRM (GTC)"; case 22: return "GIVE TOKENS ACK (GTA)"; case 25: return "ABORT (AB)"; case 26: return "ABORT ACCEPT (AA)"; case 29: return "ACTIVITY RESUME (AR)"; case 33: return "TYPED DATA (TD)"; case 34: return "RESYNCHRONIZE ACK (RA)"; case 41: return "MAJOR SYNC POINT (MAP)"; case 42: return "MAJOR SYNC ACK (MAA)"; case 45: return "ACTIVITY START (AS)"; case 48: return "EXCEPTION DATA (ED)"; case 49: return "MINOR SYNC POINT (MIP)"; case 50: return "MINOR SYNC ACK (MIA)"; case 53: return "RESYNCHRONIZE (RS)"; case 57: return "ACTIVITY DISCARD (AD)"; case 58: return "ACTIVITY DISCARD ACK (ADA)"; case 61: return "CAPABILITY DATA (CD)"; case 62: return "CAPABILITY DATA ACK (CDA)"; case 64: return "UNIT DATA (UD)"; default: return ""; } } }