/// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="max_recv_fragment">The initial maximum receive fragment length.</param>
        /// <param name="max_send_fragment">The initial maximum send fragment length.</param>
        /// <param name="transport_security">The transport security for the connection.</param>
        /// <param name="data_rep">The data representation.</param>
        protected RpcConnectedClientTransport(ushort max_recv_fragment, ushort max_send_fragment,
                                              NdrDataRepresentation data_rep, RpcTransportSecurity transport_security)
        {
            _max_recv_fragment        = max_recv_fragment;
            _max_send_fragment        = max_send_fragment;
            _data_rep                 = data_rep;
            _security_context         = new Dictionary <int, RpcTransportSecurityContext>();
            _current_security_context = new RpcTransportSecurityContext(this, transport_security, _current_context_id++);
            _security_context[_current_security_context.ContextId] = _current_security_context;
            switch (transport_security.AuthenticationLevel)
            {
            case RpcAuthenticationLevel.PacketIntegrity:
            case RpcAuthenticationLevel.PacketPrivacy:
                _auth_data_required = true;
                break;
            }

            if (DisableBindTimeFeatureNegotiation)
            {
                _bind_time_features = BindTimeFeatureNegotiation.None;
            }
        }
        private byte[] SendReceiveRequestPDU(int proc_num, Guid objuuid, byte[] stub_data, RpcTransportSecurityContext security_context)
        {
            try
            {
                CallId++;
                PDURequest request_pdu = new PDURequest()
                {
                    OpNum      = (short)proc_num,
                    ObjectUUID = objuuid
                };

                int max_fragment     = _max_send_fragment - request_pdu.HeaderLength;
                int auth_data_length = 0;

                if (_auth_data_required)
                {
                    auth_data_length = security_context.AuthDataLength;
                    max_fragment    -= (auth_data_length + AuthData.PDU_AUTH_DATA_HEADER_SIZE);
                    max_fragment    &= ~0xF;
                }

                List <byte[]> fragments = PDURequest.DoFragment(stub_data, max_fragment);
                for (int i = 0; i < fragments.Count; ++i)
                {
                    PDUHeader pdu_header = new PDUHeader()
                    {
                        MajorVersion = PDUHeader.RPC_VERSION_MAJOR,
                        MinorVersion = PDUHeader.RPC_VERSION_MINOR,
                        DataRep      = _data_rep,
                        CallId       = CallId,
                        Type         = PDUType.Request
                    };

                    if (i == 0)
                    {
                        pdu_header.Flags |= PDUFlags.FirstFrag;
                    }
                    if (i == fragments.Count - 1)
                    {
                        pdu_header.Flags |= PDUFlags.LastFrag;
                    }

                    byte[] stub_fragment = fragments[i];
                    byte[] auth_data     = new byte[0];
                    byte[] header        = request_pdu.ToArray(pdu_header, stub_fragment.Length, 0);
                    if (_auth_data_required)
                    {
                        int auth_data_padding  = 0;
                        int auth_trailing_size = (header.Length + stub_fragment.Length + AuthData.PDU_AUTH_DATA_HEADER_SIZE) & 0xF;
                        if (auth_trailing_size != 0)
                        {
                            auth_data_padding = 16 - auth_trailing_size;
                            Array.Resize(ref stub_fragment, stub_fragment.Length + auth_data_padding);
                        }

                        header    = request_pdu.ToArray(pdu_header, stub_fragment.Length + AuthData.PDU_AUTH_DATA_HEADER_SIZE, auth_data_length);
                        auth_data = security_context.ProtectPDU(header, ref stub_fragment, auth_data_padding, _send_sequence_no);
                    }

                    MemoryStream send_stm = new MemoryStream();
                    BinaryWriter writer   = new BinaryWriter(send_stm);
                    writer.Write(header);
                    writer.Write(stub_fragment);
                    writer.Write(auth_data);
                    byte[] fragment = send_stm.ToArray();
                    string name     = fragments.Count == 1 ? $"{GetType().Name} Send Buffer" : $"{GetType().Name} Send Buffer - Fragment {i}";
                    RpcUtils.DumpBuffer(true, name, fragment);
                    if (!WriteFragment(fragment))
                    {
                        throw new RpcTransportException("Failed to write out PDU buffer.");
                    }
                    _send_sequence_no++;
                }

                MemoryStream recv_stm    = new MemoryStream();
                PDUHeader    curr_header = new PDUHeader();
                int          frag_count  = 0;
                while ((curr_header.Flags & PDUFlags.LastFrag) == 0)
                {
                    var pdu = ReadPDU(frag_count++);
                    curr_header = pdu.Item1;
                    AuthData auth_data = pdu.Item3;
                    if (curr_header.CallId != CallId)
                    {
                        throw new RpcTransportException($"Mismatching call ID - {curr_header.CallId} should be {CallId}.");
                    }

                    if (auth_data.ContextId != security_context.ContextId)
                    {
                        security_context = GetContext(auth_data.ContextId);
                    }

                    var recv_pdu = CheckFault(curr_header.ToPDU(pdu.Item2));
                    if (recv_pdu is PDUResponse resp_pdu)
                    {
                        byte[] resp_stub_data = _auth_data_required ? security_context.UnprotectPDU(resp_pdu.ToArray(curr_header),
                                                                                                    resp_pdu.StubData, auth_data, _recv_sequence_no) : resp_pdu.StubData;
                        _recv_sequence_no++;
                        recv_stm.Write(resp_stub_data, 0, resp_stub_data.Length);
                    }
                    else
                    {
                        throw new RpcTransportException($"Unexpected {recv_pdu.PDUType} PDU from server.");
                    }
                }

                return(recv_stm.ToArray());
            }
            catch (EndOfStreamException)
            {
                throw new RpcTransportException("End of stream.");
            }
        }
        private void BindAuth(bool alter_context, RpcTransportSecurityContext security_context)
        {
            // 8 should be more than enough legs to complete authentication.
            int max_legs = security_context.MaxAuthLegs;
            int call_id  = ++CallId;
            int count    = 0;

            while (count++ < max_legs)
            {
                PDUBind bind_pdu = new PDUBind(_max_send_fragment, _max_recv_fragment, 0, alter_context);

                bind_pdu.Elements.Add(new ContextElement(_interface_id, _interface_version, _transfer_syntax_id, _transfer_syntax_version));
                if (!_bind_time_features.HasValue)
                {
                    _bind_time_features = BindTimeFeatureNegotiation.None;
                    bind_pdu.Elements.Add(new ContextElement(_interface_id, _interface_version,
                                                             BindTimeFeatureNegotiation.SecurityContextMultiplexingSupported));
                }

                var recv = SendReceivePDU(call_id, bind_pdu, security_context.AuthContext.Token.ToArray(), true, security_context);
                if (recv.Item1 is PDUBindAck bind_ack)
                {
                    if (bind_ack.ResultList.Count < 1 || bind_ack.ResultList[0].Result != PresentationResultType.Acceptance)
                    {
                        throw new RpcTransportException($"Bind to {_interface_id}:{_interface_version} was rejected.");
                    }

                    if (bind_ack.ResultList.Count == 2)
                    {
                        _bind_time_features = bind_ack.ResultList[1].BindTimeFeature;
                    }

                    if (!alter_context)
                    {
                        // Only capture values from the BindAck.
                        _max_recv_fragment = bind_ack.MaxRecvFrag;
                        _max_send_fragment = bind_ack.MaxXmitFrag;
                        _assoc_group_id    = bind_ack.AssocGroupId;
                        alter_context      = true;
                    }

                    if (recv.Item2.Data == null || recv.Item2.Data.Length == 0)
                    {
                        // No auth, assume success.
                        break;
                    }

                    security_context.AuthContext.Continue(new AuthenticationToken(recv.Item2.Data));
                    if (security_context.AuthContext.Done)
                    {
                        byte[] token = security_context.AuthContext.Token.ToArray();
                        if (token.Length == 0)
                        {
                            break;
                        }
                        // If we still have an NTLM token to complete then send as an Auth3 PDU.
                        if (security_context.TransportSecurity.AuthenticationType == RpcAuthenticationType.WinNT)
                        {
                            SendReceivePDU(call_id, new PDUAuth3(), token, false, security_context);
                            break;
                        }
                    }
                }
                else if (recv.Item1 is PDUBindNack bind_nack)
                {
                    throw new RpcTransportException($"Bind NACK returned with rejection reason {bind_nack.RejectionReason}");
                }
                else
                {
                    throw new RpcTransportException($"Unexpected {recv.Item1.PDUType} PDU from server.");
                }
            }

            if (!security_context.AuthContext.Done)
            {
                throw new RpcTransportException("Failed to complete the client authentication.");
            }
            security_context.SetNegotiatedAuthType();
        }
        private Tuple <PDUBase, AuthData> SendReceivePDU(int call_id, PDUBase send_pdu, byte[] auth_data,
                                                         bool receive_pdu, RpcTransportSecurityContext security_context)
        {
            try
            {
                int trailing_auth_length = auth_data.Length > 0 ? auth_data.Length + AuthData.PDU_AUTH_DATA_HEADER_SIZE : 0;

                PDUHeader pdu_header = new PDUHeader()
                {
                    MajorVersion = PDUHeader.RPC_VERSION_MAJOR,
                    MinorVersion = PDUHeader.RPC_VERSION_MINOR,
                    DataRep      = _data_rep,
                    CallId       = CallId,
                    Type         = send_pdu.PDUType,
                    Flags        = PDUFlags.LastFrag | PDUFlags.FirstFrag,
                    AuthLength   = checked ((ushort)auth_data.Length)
                };

                byte[] pdu_data        = send_pdu.ToArray();
                int    pdu_data_length = pdu_data.Length + PDUHeader.PDU_HEADER_SIZE;
                int    auth_padding    = 0;
                if (auth_data.Length > 0 && (pdu_data_length & 15) != 0 && send_pdu.PDUType != PDUType.Auth3)
                {
                    auth_padding = 16 - (pdu_data_length & 15);
                }

                pdu_header.FragmentLength = checked ((ushort)(pdu_data.Length + PDUHeader.PDU_HEADER_SIZE + trailing_auth_length + auth_padding));
                MemoryStream send_stm = new MemoryStream();
                BinaryWriter writer   = new BinaryWriter(send_stm);
                pdu_header.Write(writer);
                writer.Write(pdu_data);
                if (auth_data.Length > 0)
                {
                    writer.Write(new byte[auth_padding]);
                    new AuthData(security_context.TransportSecurity.AuthenticationType, security_context.TransportSecurity.AuthenticationLevel,
                                 auth_padding, security_context.ContextId, auth_data).Write(writer, auth_padding);
                }
                byte[] fragment = send_stm.ToArray();
                RpcUtils.DumpBuffer(true, $"{GetType().Name} Send Buffer", fragment);
                if (!WriteFragment(fragment))
                {
                    throw new RpcTransportException("Failed to write out PDU buffer.");
                }
                _send_sequence_no++;
                if (!receive_pdu)
                {
                    return(null);
                }

                var pdu         = ReadPDU(0);
                var curr_header = pdu.Item1;
                if (!curr_header.Flags.HasFlagAllSet(PDUFlags.LastFrag | PDUFlags.FirstFrag))
                {
                    throw new RpcTransportException($"Invalid PDU flags {curr_header.Flags}.");
                }

                if (curr_header.CallId != call_id)
                {
                    throw new RpcTransportException($"Mismatching call ID - {curr_header.CallId} should be {call_id}.");
                }

                if (pdu.Item3.ContextId != security_context.ContextId)
                {
                    throw new RpcTransportException($"Mismatching context ID - {pdu.Item3.ContextId} should be {security_context.ContextId}.");
                }

                _recv_sequence_no++;

                return(Tuple.Create(CheckFault(curr_header.ToPDU(pdu.Item2)), pdu.Item3));
            }
            catch (EndOfStreamException)
            {
                throw new RpcTransportException("End of stream.");
            }
        }