inline void Assembler::jmpl( Register s1, int simm13a, Register d, RelocationHolder const& rspec ) { insert_nop_after_cbcond(); cti();  emit_data( op(arith_op) | rd(d) | op3(jmpl_op3) | rs1(s1) | immed(true) | simm(simm13a, 13), rspec);  has_delay_slot(); }
inline void Assembler::ldf(FloatRegisterImpl::Width w, Register s1, int simm13a, FloatRegister d, RelocationHolder const& rspec) { emit_data( op(ldst_op) | fd(d, w) | alt_op3(ldf_op3, w) | rs1(s1) | immed(true) | simm(simm13a, 13), rspec); }
inline void Assembler::call( address d,  relocInfo::relocType rt ) { insert_nop_after_cbcond(); cti();  emit_data( op(call_op) | wdisp(intptr_t(d), intptr_t(pc()), 30), rt);  has_delay_slot(); assert(rt != relocInfo::virtual_call_type, "must use virtual_call_Relocation::spec"); }
inline void Assembler::flush( Register s1, int simm13a) { emit_data( op(arith_op) | op3(flush_op3) | rs1(s1) | immed(true) | simm(simm13a, 13)); }
inline void Assembler::bp( Condition c, bool a, CC cc, Predict p, address d, relocInfo::relocType rt ) { v9_only();  insert_nop_after_cbcond(); cti();  emit_data( op(branch_op) | annul(a) | cond(c) | op2(bp_op2) | branchcc(cc) | predict(p) | wdisp(intptr_t(d), intptr_t(pc()), 19), rt);  has_delay_slot(); }
inline void Assembler::cbcond(Condition c, CC cc, Register s1, int simm5, Label& L)   { cti();  no_cbcond_before();  emit_data(op(branch_op) | cond_cbcond(c) | op2(bpr_op2) | branchcc(cc) | wdisp10(intptr_t(target(L)), intptr_t(pc())) | rs1(s1) | immed(true) | simm(simm5, 5)); }
inline void Assembler::bpr( RCondition c, bool a, Predict p, Register s1, address d, relocInfo::relocType rt ) { v9_only(); insert_nop_after_cbcond(); cti();  emit_data( op(branch_op) | annul(a) | cond(c) | op2(bpr_op2) | wdisp16(intptr_t(d), intptr_t(pc())) | predict(p) | rs1(s1), rt);  has_delay_slot(); }
inline void Assembler::rett( Register s1, int simm13a, relocInfo::relocType rt) { cti();  emit_data( op(arith_op) | op3(rett_op3) | rs1(s1) | immed(true) | simm(simm13a, 13), rt);  has_delay_slot(); }
inline void Assembler::std(  Register d, Register s1, int simm13a) { v9_dep(); assert(d->is_even(), "not even"); emit_data( op(ldst_op) | rd(d) | op3(std_op3) | rs1(s1) | immed(true) | simm(simm13a, 13)); }
inline void Assembler::swap(    Register s1, int simm13a, Register d) { v9_dep();  emit_data( op(ldst_op) | rd(d) | op3(swap_op3) | rs1(s1) | immed(true) | simm(simm13a, 13)); }
inline void Assembler::stx(  Register d, Register s1, int simm13a) { v9_only();  emit_data( op(ldst_op) | rd(d) | op3(stx_op3) | rs1(s1) | immed(true) | simm(simm13a, 13)); }
inline void Assembler::stf(    FloatRegisterImpl::Width w, FloatRegister d, Register s1, int simm13a) { emit_data( op(ldst_op) | fd(d, w) | alt_op3(stf_op3, w) | rs1(s1) | immed(true) | simm(simm13a, 13)); }
inline void Assembler::sethi( int imm22a, Register d, RelocationHolder const& rspec ) { emit_data( op(branch_op) | rd(d) | op2(sethi_op2) | hi22(imm22a), rspec); }
inline void Assembler::ldxfsr( Register s1, int simm13a) { v9_only();  emit_data( op(ldst_op) | rd(G1)    | op3(ldfsr_op3) | rs1(s1) | immed(true) | simm(simm13a, 13)); }
inline void Assembler::br( Condition c, bool a, address d, relocInfo::relocType rt ) { v9_dep(); insert_nop_after_cbcond(); cti();   emit_data( op(branch_op) | annul(a) | cond(c) | op2(br_op2) | wdisp(intptr_t(d), intptr_t(pc()), 22), rt);  has_delay_slot(); }
inline void Assembler::ldub(  Register s1, int simm13a, Register d) { emit_data( op(ldst_op) | rd(d) | op3(ldub_op3) | rs1(s1) | immed(true) | simm(simm13a, 13)); }
Socks5::Socks5(Settings s, Logger *lp, Poller *poller)
    : Emitter(lp), settings(s),
      conn(settings["family"].c_str(), settings["socks5_address"].c_str(),
           settings["socks5_port"].c_str(), lp, poller),
      proxy_address(settings["socks5_address"]),
      proxy_port(settings["socks5_port"]) {

    logger->debug("socks5: connecting to Tor at %s:%s",
                  settings["socks5_address"].c_str(),
                  settings["socks5_port"].c_str());

    // Step #0: Steal "error", "connect", and "flush" handlers

    conn.on_error([this](Error err) { emit_error(err); });
    conn.on_connect([this]() {
        conn.on_flush([]() {
            // Nothing
        });

        // Step #1: send out preferred authentication methods

        logger->debug("socks5: connected to Tor!");

        Buffer out;
        out.write_uint8(5); // Version
        out.write_uint8(1); // Number of methods
        out.write_uint8(0); // "NO_AUTH" meth.
        conn.send(out);

        logger->debug("socks5: >> version=5");
        logger->debug("socks5: >> number of methods=1");
        logger->debug("socks5: >> NO_AUTH (0)");

        // Step #2: receive the allowed authentication methods

        conn.on_data([this](Buffer d) {
            buffer << d;
            auto readbuf = buffer.readn(2);
            if (readbuf == "") {
                return; // Try again after next recv()
            }

            logger->debug("socks5: << version=%d", readbuf[0]);
            logger->debug("socks5: << auth=%d", readbuf[1]);

            if (readbuf[0] != 5 || // Reply version
                readbuf[1] != 0) { // Preferred auth method
                emit_error(BadSocksVersionError());
                return;
            }

            // Step #3: ask Tor to connect to remote host

            Buffer out;
            out.write_uint8(5); // Version
            out.write_uint8(1); // CMD_CONNECT
            out.write_uint8(0); // Reserved
            out.write_uint8(3); // ATYPE_DOMAINNAME

            logger->debug("socks5: >> version=5");
            logger->debug("socks5: >> CMD_CONNECT (0)");
            logger->debug("socks5: >> Reserved (0)");
            logger->debug("socks5: >> ATYPE_DOMAINNAME (3)");

            auto address = settings["address"];

            if (address.length() > 255) {
                emit_error(SocksAddressTooLongError());
                return;
            }
            out.write_uint8(address.length());            // Len
            out.write(address.c_str(), address.length()); // String

            logger->debug("socks5: >> domain len=%d",
                          (uint8_t)address.length());
            logger->debug("socks5: >> domain str=%s", address.c_str());

            auto portnum = std::stoi(settings["port"]);
            if (portnum < 0 || portnum > 65535) {
                emit_error(SocksInvalidPortError());
                return;
            }
            out.write_uint16(portnum); // Port

            logger->debug("socks5: >> port=%d", portnum);

            conn.send(out);

            // Step #4: receive Tor's response

            conn.on_data([this](Buffer d) {

                buffer << d;
                if (buffer.length() < 5) {
                    return; // Try again after next recv()
                }

                auto peekbuf = buffer.peek(5);

                logger->debug("socks5: << version=%d", peekbuf[0]);
                logger->debug("socks5: << reply=%d", peekbuf[1]);
                logger->debug("socks5: << reserved=%d", peekbuf[2]);
                logger->debug("socks5: << atype=%d", peekbuf[3]);

                // TODO: Here we should process peekbuf[1] more
                // carefully to map to the error that occurred
                // and report it correctly to the caller

                if (peekbuf[0] != 5 || // Version
                    peekbuf[1] != 0 || // Reply
                    peekbuf[2] != 0) { // Reserved
                    emit_error(SocksGenericError());
                    return;
                }
                auto atype = peekbuf[3]; // Atype

                size_t total = 4; // Version .. Atype size
                if (atype == 1) {
                    total += 4; // IPv4 addr size
                } else if (atype == 3) {
                    total += 1             // Len size
                             + peekbuf[4]; // String size
                } else if (atype == 4) {
                    total += 16; // IPv6 addr size
                } else {
                    emit_error(SocksGenericError());
                    return;
                }
                total += 2; // Port size
                if (buffer.length() < total) {
                    return; // Try again after next recv()
                }

                buffer.discard(total);

                //
                // Step #5: we are now connected
                // Restore the original hooks
                // Tell upstream we are connected
                // If more data, pass it up
                //

                conn.on_data([this](Buffer d) { emit_data(d); });
                conn.on_flush([this]() { emit_flush(); });

                emit_connect();

                // Note that emit_connect() may have called close()
                if (!isclosed && buffer.length() > 0) {
                    emit_data(buffer);
                }
            });
        });
    });
}