| | |
| | | /* |
| | | * 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<Myexternal> 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<byte[]> ssduList = new LinkedList<>(); |
| | | List<Integer> ssduOffsets = new LinkedList<>(); |
| | | List<Integer> 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<byte[]> ssdu, List<Integer> ssduOffsets, List<Integer> 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<Myexternal> 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<byte[]> ssduList = new LinkedList<>(); |
| | | List<Integer> ssduOffsets = new LinkedList<>(); |
| | | List<Integer> 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<PDVList> 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<byte[]> ssduList, List<Integer> ssduOffsets, List<Integer> 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<byte[]> ssduList = new ArrayList<>(); |
| | | List<Integer> ssduOffsets = new LinkedList<>(); |
| | | List<Integer> ssduLengths = new LinkedList<>(); |
| | | |
| | | encodePresentationLayer(payload, ssduList, ssduOffsets, ssduLengths); |
| | | |
| | | encodeSessionLayer(ssduList, ssduOffsets, ssduLengths); |
| | | |
| | | tConnection.send(ssduList, ssduOffsets, ssduLengths); |
| | | } |
| | | |
| | | private void encodePresentationLayer(ByteBuffer payload, List<byte[]> ssduList, List<Integer> ssduOffsets, |
| | | List<Integer> 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<PDVList> 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<byte[]> ssduList, List<Integer> ssduOffsets, List<Integer> 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 "<unknown SPDU type>"; |
| | | } |
| | | } |
| | | } |
| | | /*
|
| | | * 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<Myexternal> 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<byte[]> ssduList = new LinkedList<>();
|
| | | List<Integer> ssduOffsets = new LinkedList<>();
|
| | | List<Integer> 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<byte[]> ssdu, List<Integer> ssduOffsets, List<Integer> 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<Myexternal> 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<byte[]> ssduList = new LinkedList<>();
|
| | | List<Integer> ssduOffsets = new LinkedList<>();
|
| | | List<Integer> 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<PDVList> 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<byte[]> ssduList, List<Integer> ssduOffsets, List<Integer> 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<byte[]> ssduList = new ArrayList<>();
|
| | | List<Integer> ssduOffsets = new LinkedList<>();
|
| | | List<Integer> ssduLengths = new LinkedList<>();
|
| | |
|
| | | encodePresentationLayer(payload, ssduList, ssduOffsets, ssduLengths);
|
| | |
|
| | | encodeSessionLayer(ssduList, ssduOffsets, ssduLengths);
|
| | |
|
| | | tConnection.send(ssduList, ssduOffsets, ssduLengths);
|
| | | }
|
| | |
|
| | | private void encodePresentationLayer(ByteBuffer payload, List<byte[]> ssduList, List<Integer> ssduOffsets,
|
| | | List<Integer> 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<PDVList> 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<byte[]> ssduList, List<Integer> ssduOffsets, List<Integer> 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 "<unknown SPDU type>";
|
| | | }
|
| | | }
|
| | | }
|