This is an automated email from the ASF dual-hosted git repository.

chenBright pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new 881077d1 Support RDMA handshake protocol (#3350)
881077d1 is described below

commit 881077d15bce70df3cec46ed59dfaaf95aa2993b
Author: Bright Chen <[email protected]>
AuthorDate: Sat Jul 18 17:42:52 2026 +0800

    Support RDMA handshake protocol (#3350)
    
    * Support RDMA handshake protocol
    
    * Fix comment
---
 src/brpc/global.cpp                         |  10 +
 src/brpc/input_messenger.cpp                |  11 -
 src/brpc/input_messenger.h                  |   2 +
 src/brpc/options.proto                      |  57 ++---
 src/brpc/parse_result.h                     |   1 +
 src/brpc/policy/rdma_handshake_protocol.cpp |  41 ++++
 src/brpc/policy/rdma_handshake_protocol.h   |  56 +++++
 src/brpc/rdma/rdma_endpoint.cpp             | 355 ++++++++++++----------------
 src/brpc/rdma/rdma_endpoint.h               |  22 +-
 src/brpc/rdma/rdma_handshake.cpp            | 285 +++++++++++++---------
 src/brpc/rdma/rdma_handshake.h              | 108 ++++-----
 src/brpc/rdma/rdma_handshake_constants.h    |  56 +++++
 src/brpc/rdma/rdma_handshake_server.cpp     | 214 +++++++++++++++++
 src/brpc/rdma/rdma_handshake_server.h       |  48 ++++
 src/brpc/rdma_transport.cpp                 |  21 +-
 src/brpc/rdma_transport.h                   |   1 +
 src/brpc/server.cpp                         |   7 +-
 src/brpc/socket.h                           |   3 +
 src/butil/raw_pack.h                        |  34 ++-
 test/brpc_rdma_unittest.cpp                 | 223 +++++++++--------
 20 files changed, 1008 insertions(+), 547 deletions(-)

diff --git a/src/brpc/global.cpp b/src/brpc/global.cpp
index e9402512..04a56282 100644
--- a/src/brpc/global.cpp
+++ b/src/brpc/global.cpp
@@ -69,6 +69,7 @@
 
 // Protocols
 #include "brpc/protocol.h"
+#include "brpc/policy/rdma_handshake_protocol.h"
 #include "brpc/policy/baidu_rpc_protocol.h"
 #include "brpc/policy/http_rpc_protocol.h"
 #include "brpc/policy/http2_rpc_protocol.h"
@@ -437,6 +438,15 @@ static void GlobalInitializeOrDieImpl() {
     }
 
     // Protocols
+    Protocol rdma_handshake_protocol = {
+        ParseRdmaHandshake, NULL, NULL,
+        ProcessRdmaHandshake, NULL,
+        NULL, NULL, NULL,
+        CONNECTION_TYPE_ALL, "rdma_handshake" };
+    if (RegisterProtocol(PROTOCOL_RDMA_HANDSHAKE, rdma_handshake_protocol) != 
0) {
+        exit(1);
+    }
+
     Protocol baidu_protocol = { ParseRpcMessage,
                                 SerializeRpcRequest, PackRpcRequest,
                                 ProcessRpcRequest, ProcessRpcResponse,
diff --git a/src/brpc/input_messenger.cpp b/src/brpc/input_messenger.cpp
index c249cca2..ad47f32e 100644
--- a/src/brpc/input_messenger.cpp
+++ b/src/brpc/input_messenger.cpp
@@ -79,7 +79,6 @@ DECLARE_uint64(max_body_size);
 const size_t MSG_SIZE_WINDOW = 10;  // Take last so many message into stat.
 const size_t MIN_ONCE_READ = 4096;
 const size_t MAX_ONCE_READ = 524288;
-const size_t PROTO_DUMMY_LEN = 4;
 
 ParseResult InputMessenger::CutInputMessage(
         Socket* m, size_t* index, bool read_eof) {
@@ -107,16 +106,6 @@ ParseResult InputMessenger::CutInputMessage(
                     << " bytes, the connection will be closed."
                     " Set max_body_size to allow bigger messages";
                 return result;
-            } else {
-                if (m->_read_buf.size() >= 4) {
-                    // The length of `data' must be PROTO_DUMMY_LEN + 1 to 
store extra ending char '\0'
-                    char data[PROTO_DUMMY_LEN + 1];
-                    m->_read_buf.copy_to_cstr(data, PROTO_DUMMY_LEN);
-                    if (strncmp(data, "RDMA", PROTO_DUMMY_LEN) == 0) {
-                        // To avoid timeout when client uses RDMA but server 
uses TCP
-                        return MakeParseError(PARSE_ERROR_TRY_OTHERS);
-                    }
-                }
             }
 
             if (m->CreatedByConnect()) {
diff --git a/src/brpc/input_messenger.h b/src/brpc/input_messenger.h
index 8482c3f3..74e40a92 100644
--- a/src/brpc/input_messenger.h
+++ b/src/brpc/input_messenger.h
@@ -30,6 +30,7 @@ namespace rdma {
 class RdmaEndpoint;
 }
 class TcpTransport;
+class RdmaTransport;
 struct InputMessageHandler {
     // The callback to cut a message from `source'.
     // Returned message will be passed to process_request or process_response
@@ -92,6 +93,7 @@ private:
 class InputMessenger : public SocketUser {
 friend class Socket;
 friend class TcpTransport;
+friend class RdmaTransport;
 friend class rdma::RdmaEndpoint;
 public:
     explicit InputMessenger(size_t capacity = 128);
diff --git a/src/brpc/options.proto b/src/brpc/options.proto
index 935caaaa..13b8b682 100644
--- a/src/brpc/options.proto
+++ b/src/brpc/options.proto
@@ -37,35 +37,36 @@ enum ConnectionType {
 
 enum ProtocolType {
     PROTOCOL_UNKNOWN = 0;
-    PROTOCOL_BAIDU_STD = 1;
-    PROTOCOL_STREAMING_RPC = 2;
-    PROTOCOL_HULU_PBRPC = 3;
-    PROTOCOL_SOFA_PBRPC = 4;
-    PROTOCOL_RTMP = 5;
-    PROTOCOL_THRIFT = 6;
-    PROTOCOL_HTTP = 7;
-    PROTOCOL_PUBLIC_PBRPC = 8;
-    PROTOCOL_NOVA_PBRPC = 9;
-    PROTOCOL_REDIS = 10;
-    PROTOCOL_NSHEAD_CLIENT = 11;       // implemented in baidu-rpc-ub
-    PROTOCOL_NSHEAD = 12;
-    PROTOCOL_HADOOP_RPC = 13;
-    PROTOCOL_HADOOP_SERVER_RPC = 14;
-    PROTOCOL_MONGO = 15;               // server side only
-    PROTOCOL_UBRPC_COMPACK = 16;
-    PROTOCOL_DIDX_CLIENT = 17;         // Client side only
-    PROTOCOL_MEMCACHE = 18;            // Client side only
-    PROTOCOL_ITP = 19;
-    PROTOCOL_NSHEAD_MCPACK = 20;
-    PROTOCOL_DISP_IDL = 21;            // Client side only
-    PROTOCOL_ERSDA_CLIENT = 22;        // Client side only
-    PROTOCOL_UBRPC_MCPACK2 = 23;       // Client side only
+    PROTOCOL_RDMA_HANDSHAKE = 1;
+    PROTOCOL_BAIDU_STD = 2;
+    PROTOCOL_STREAMING_RPC = 3;
+    PROTOCOL_HULU_PBRPC = 4;
+    PROTOCOL_SOFA_PBRPC = 5;
+    PROTOCOL_RTMP = 6;
+    PROTOCOL_THRIFT = 7;
+    PROTOCOL_HTTP = 8;
+    PROTOCOL_PUBLIC_PBRPC = 9;
+    PROTOCOL_NOVA_PBRPC = 10;
+    PROTOCOL_REDIS = 11;
+    PROTOCOL_NSHEAD_CLIENT = 12;       // implemented in baidu-rpc-ub
+    PROTOCOL_NSHEAD = 13;
+    PROTOCOL_HADOOP_RPC = 14;
+    PROTOCOL_HADOOP_SERVER_RPC = 15;
+    PROTOCOL_MONGO = 16;               // server side only
+    PROTOCOL_UBRPC_COMPACK = 17;
+    PROTOCOL_DIDX_CLIENT = 18;         // Client side only
+    PROTOCOL_MEMCACHE = 19;            // Client side only
+    PROTOCOL_ITP = 20;
+    PROTOCOL_NSHEAD_MCPACK = 21;
+    PROTOCOL_DISP_IDL = 22;            // Client side only
+    PROTOCOL_ERSDA_CLIENT = 23;        // Client side only
+    PROTOCOL_UBRPC_MCPACK2 = 24;       // Client side only
     // Reserve special protocol for cds-agent, which depends on FIFO right now
-    PROTOCOL_CDS_AGENT = 24;           // Client side only
-    PROTOCOL_ESP = 25;                 // Client side only
-    PROTOCOL_H2 = 26;
-    PROTOCOL_COUCHBASE = 27;
-    PROTOCOL_MYSQL = 28;               // Client side only
+    PROTOCOL_CDS_AGENT = 25;           // Client side only
+    PROTOCOL_ESP = 26;                 // Client side only
+    PROTOCOL_H2 = 27;
+    PROTOCOL_COUCHBASE = 28;
+    PROTOCOL_MYSQL = 29;               // Client side only
 }
 
 enum CompressType {
diff --git a/src/brpc/parse_result.h b/src/brpc/parse_result.h
index e84f80e0..d916acc5 100644
--- a/src/brpc/parse_result.h
+++ b/src/brpc/parse_result.h
@@ -19,6 +19,7 @@
 #ifndef BRPC_PARSE_RESULT_H
 #define BRPC_PARSE_RESULT_H
 
+#include <stddef.h>
 
 namespace brpc {
 
diff --git a/src/brpc/policy/rdma_handshake_protocol.cpp 
b/src/brpc/policy/rdma_handshake_protocol.cpp
new file mode 100644
index 00000000..580abdda
--- /dev/null
+++ b/src/brpc/policy/rdma_handshake_protocol.cpp
@@ -0,0 +1,41 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+#include "brpc/policy/rdma_handshake_protocol.h"
+
+#include "butil/logging.h"
+#include "brpc/destroyable.h"
+#include "brpc/rdma/rdma_handshake_server.h"
+
+namespace brpc {
+namespace policy {
+
+ParseResult ParseRdmaHandshake(butil::IOBuf* source, Socket* socket,
+                               bool /*read_eof*/, const void* /*arg*/) {
+    return rdma::ExecuteServerHandshake(source, socket);
+}
+
+void ProcessRdmaHandshake(InputMessageBase* msg) {
+    // ParseRdmaHandshake replies inline and only ever returns
+    // NOT_ENOUGH_DATA / TRY_OTHERS / hard errors, never a real message, so 
this
+    // must never run. Keep a placeholder (required for server registration).
+    DestroyingPtr<InputMessageBase> destroying_msg(msg);
+    CHECK(false) << "ProcessRdmaHandshake should never be called";
+}
+
+}  // namespace policy
+}  // namespace brpc
diff --git a/src/brpc/policy/rdma_handshake_protocol.h 
b/src/brpc/policy/rdma_handshake_protocol.h
new file mode 100644
index 00000000..e569a4c3
--- /dev/null
+++ b/src/brpc/policy/rdma_handshake_protocol.h
@@ -0,0 +1,56 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+#ifndef BRPC_POLICY_RDMA_HANDSHAKE_PROTOCOL_H
+#define BRPC_POLICY_RDMA_HANDSHAKE_PROTOCOL_H
+
+// NOTE: This file is intentionally INDEPENDENT of BRPC_WITH_RDMA. A server may
+// run in TCP mode either because it was built without RDMA, or because RDMA 
was
+// not enabled at runtime. In both cases an RDMA client that connects to it 
will
+// send an RDMA handshake magic ("RDMA" for v2, "RDM3" for v3) first. Without
+// special handling the server treats those bytes as an unknown protocol and
+// closes the connection, so the client (blocked reading the server hello) only
+// sees EOF and cannot fall back to TCP.
+//
+// To let the client fall back on the SAME connection, the server recognizes 
the
+// RDMA handshake as a "first-class" protocol (magic in the first 4 bytes,
+// PROTOCOL_RDMA_HANDSHAKE is ordered before PROTOCOL_HTTP), replies a hello 
with
+// an incompatible version so the client rejects it and downgrades to TCP, then
+// drains the client's subsequent ACK and lets normal RPC parsing continue.
+
+#include "butil/iobuf.h"
+#include "brpc/input_message_base.h"
+#include "brpc/parse_result.h"
+#include "brpc/socket.h"
+
+namespace brpc {
+namespace policy {
+
+// Parse binary format of rdma handshake.
+ParseResult ParseRdmaHandshake(butil::IOBuf* source, Socket* socket,
+                               bool read_eof, const void* arg);
+
+// Actions to a rdma handshake request, which is left unimplemented.
+// All requests are processed in the parsing process. This function
+// must be declared since server only enables rdma handshake as a
+// server-side protocol when this function is declared.
+void ProcessRdmaHandshake(InputMessageBase* msg);
+
+}  // namespace policy
+}  // namespace brpc
+
+#endif  // BRPC_POLICY_RDMA_HANDSHAKE_PROTOCOL_H
diff --git a/src/brpc/rdma/rdma_endpoint.cpp b/src/brpc/rdma/rdma_endpoint.cpp
index a5016dc7..daefbcb9 100644
--- a/src/brpc/rdma/rdma_endpoint.cpp
+++ b/src/brpc/rdma/rdma_endpoint.cpp
@@ -32,6 +32,7 @@
 #include "brpc/rdma/rdma_endpoint.h"
 #include "brpc/rdma_transport.h"
 #include "brpc/rdma/rdma_handshake.h"
+#include "brpc/rdma/rdma_handshake_constants.h"
 
 DECLARE_int32(task_group_ntags);
 
@@ -84,14 +85,6 @@ extern const uint16_t MIN_QP_SIZE = 16;
 static const uint16_t MAX_QP_SIZE = 4096;
 extern const uint16_t MIN_BLOCK_SIZE = 1024;
 
-// ACK message wire format (shared by all protocol versions): a single
-// 4B big-endian flags word; bit 0 (HELLO_ACK_RDMA_OK) indicates the
-// sender wants to use RDMA. The state machines in
-// ProcessHandshakeAt{Client,Server} inline the corresponding 4B
-// send/recv directly using ReadFromFd / WriteToFd.
-static const size_t HELLO_ACK_LEN = 4;
-static const uint32_t HELLO_ACK_RDMA_OK = 0x1;
-
 static butil::Mutex* g_rdma_resource_mutex = NULL;
 static RdmaResource* g_rdma_resource_list = NULL;
 
@@ -161,7 +154,7 @@ RdmaEndpoint::~RdmaEndpoint() {
 void RdmaEndpoint::Reset() {
     DeallocateResources();
 
-    _state.store(UNINIT, butil::memory_order_relaxed);
+    _state = UNINIT;
     _resource = NULL;
     _send_cq_events = 0;
     _recv_cq_events = 0;
@@ -195,8 +188,7 @@ void RdmaConnect::StartConnect(const Socket* socket,
         return;
     }
     if (!IsRdmaAvailable()) {
-        rdma_transport->_rdma_ep->_state.store(RdmaEndpoint::FALLBACK_TCP,
-                                               butil::memory_order_relaxed);
+        rdma_transport->_rdma_ep->_state = RdmaEndpoint::FALLBACK_TCP;
         rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
         done(0, data);
         return;
@@ -222,34 +214,6 @@ void RdmaConnect::Run() {
     _done(errno, _data);
 }
 
-static void TryReadOnTcpDuringRdmaEst(Socket* s) {
-    int progress = Socket::PROGRESS_INIT;
-    while (true) {
-        uint8_t tmp;
-        ssize_t nr = read(s->fd(), &tmp, 1);
-        if (nr < 0) {
-            if (errno != EAGAIN) {
-                const int saved_errno = errno;
-                PLOG(WARNING) << "Fail to read from " << s;
-                s->SetFailed(saved_errno, "Fail to read from %s: %s",
-                             s->description().c_str(), berror(saved_errno));
-                return;
-            }
-            if (!s->MoreReadEvents(&progress)) {
-                break;
-            }
-        } else if (nr == 0) {
-            s->SetEOF();
-            return;
-        } else {
-            LOG(WARNING) << "Read unexpected data from " << s;
-            s->SetFailed(EPROTO, "Read unexpected data from %s",
-                    s->description().c_str());
-            return;
-        }
-    }
-}
-
 void RdmaEndpoint::OnNewDataFromTcp(Socket* m) {
     auto* rdma_transport = static_cast<RdmaTransport*>(m->_transport.get());
     RdmaEndpoint* ep = rdma_transport->GetRdmaEp();
@@ -257,40 +221,36 @@ void RdmaEndpoint::OnNewDataFromTcp(Socket* m) {
 
     int progress = Socket::PROGRESS_INIT;
     while (true) {
-        State state = ep->_state.load(butil::memory_order_acquire);
-        if (state == UNINIT) {
-            if (!m->CreatedByConnect()) {
-                if (!IsRdmaAvailable()) {
-                    rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
-                    ep->_state.store(FALLBACK_TCP, 
butil::memory_order_relaxed);
-                    continue;
-                }
-                bthread_t tid;
-                ep->_state.store(S_HELLO_WAIT, butil::memory_order_relaxed);
-                SocketUniquePtr s;
-                m->ReAddress(&s);
-                bthread_attr_t attr = BTHREAD_ATTR_NORMAL;
-                bthread_attr_set_name(&attr, "RdmaProcessHandshakeAtServer");
-                if (bthread_start_background(&tid, &attr, 
ProcessHandshakeAtServer, ep) < 0) {
-                    ep->_state.store(UNINIT, butil::memory_order_relaxed);
-                    LOG(FATAL) << "Fail to start handshake bthread";
-                } else {
-                    s.release();
-                }
-            } else {
-                // The connection may be closed or reset before the client
-                // starts handshake. This will be handled by client handshake.
-                // Ignore the exception here.
-            }
-        } else if (state < ESTABLISHED) {  // during handshake
+        if (ep->_state == UNINIT) {
+            // The connection may be closed or reset before the client starts
+            // handshake. This will be handled by client handshake. Ignore 
here.
+        } else if (ep->_state < ESTABLISHED) {  // during handshake
             ep->_read_butex->fetch_add(1, butil::memory_order_release);
             bthread::butex_wake(ep->_read_butex);
-        } else if (state == FALLBACK_TCP){  // handshake finishes
+        } else if (ep->_state == FALLBACK_TCP){  // handshake finishes
             InputMessenger::OnNewMessages(m);
             return;
-        } else if (state == ESTABLISHED) {
-            TryReadOnTcpDuringRdmaEst(ep->_socket);
-            return;
+        } else if (ep->_state == ESTABLISHED) {
+            uint8_t tmp;
+            ssize_t nr = read(ep->_socket->fd(), &tmp, 1);
+            if (nr == 0) {
+                ep->_socket->SetEOF();
+                return;
+            }
+            if (nr > 0) {
+                LOG(WARNING) << "Read unexpected data from " << ep->_socket;
+                ep->_socket->SetFailed(EPROTO, "Read unexpected data from %s",
+                                       ep->_socket->description().c_str());
+                return;
+            }
+
+            if (errno != EAGAIN) {
+                const int saved_errno = errno;
+                PLOG(WARNING) << "Fail to read from " << ep->_socket;
+                ep->_socket->SetFailed(saved_errno, "Fail to read from %s: %s",
+                                       ep->_socket->description().c_str(),
+                                       berror(saved_errno));
+            }
         }
         if (!m->MoreReadEvents(&progress)) {
             break;
@@ -422,28 +382,13 @@ int RdmaEndpoint::WriteToFd(butil::IOBuf* data) {
         });
 }
 
-inline void RdmaEndpoint::TryReadOnTcp() {
-    if (_socket->_nevent.fetch_add(1, butil::memory_order_acq_rel) == 0) {
-        State state = _state.load(butil::memory_order_acquire);
-        if (state == FALLBACK_TCP) {
-            InputMessenger::OnNewMessages(_socket);
-        } else if (state == ESTABLISHED) {
-            TryReadOnTcpDuringRdmaEst(_socket);
-        }
-    }
-}
-
 void RdmaEndpoint::ApplyRemoteHello(const ParsedHello& remote) {
     _remote_recv_block_size = remote.block_size;
-    _local_window_capacity =
-        std::min(_sq_size, remote.rq_size) - RESERVED_WR_NUM;
-    _remote_window_capacity =
-        std::min(_rq_size, remote.sq_size) - RESERVED_WR_NUM;
+    _local_window_capacity = std::min(_sq_size, remote.rq_size) - 
RESERVED_WR_NUM;
+    _remote_window_capacity = std::min(_rq_size, remote.sq_size) - 
RESERVED_WR_NUM;
     _sq_imm_window_size = RESERVED_WR_NUM;
-    _remote_rq_window_size.store(
-        _local_window_capacity, butil::memory_order_relaxed);
-    _sq_window_size.store(
-        _local_window_capacity, butil::memory_order_relaxed);
+    _remote_rq_window_size.store(_local_window_capacity, 
butil::memory_order_relaxed);
+    _sq_window_size.store(_local_window_capacity, butil::memory_order_relaxed);
 }
 
 // Client-side handshake entry: the state machine.
@@ -478,47 +423,47 @@ void* RdmaEndpoint::ProcessHandshakeAtClient(void* arg) {
     ep->_handshake_version = handshake->ProtocolVersion();
 
     // First initialize CQ and QP resources.
-    ep->_state.store(C_ALLOC_QPCQ, butil::memory_order_relaxed);
+    ep->_state = C_ALLOC_QPCQ;
     if (ep->AllocateResources() < 0) {
         LOG(WARNING) << "Fallback to tcp:" << s->description();
         rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
-        ep->_state.store(FALLBACK_TCP, butil::memory_order_release);
+        ep->_state = FALLBACK_TCP;
         return NULL;
     }
 
     // Send hello message to server
-    ep->_state.store(C_HELLO_SEND, butil::memory_order_relaxed);
+    ep->_state = C_HELLO_SEND;
     if (handshake->SendLocalHello() < 0) {
         int saved_errno = errno;
         PLOG(WARNING) << "Fail to send hello message to server:"
                       << s->description();
         s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: 
%s",
                      s->description().c_str(), berror(saved_errno));
-        ep->_state.store(FAILED, butil::memory_order_relaxed);
+        ep->_state = FAILED;
         return NULL;
     }
 
     // Receive and parse remote hello.
-    ep->_state.store(C_HELLO_WAIT, butil::memory_order_relaxed);
+    ep->_state = C_HELLO_WAIT;
     ParsedHello remote{};
-    bool negotiated = false;
-    if (handshake->ReceiveAndParseRemoteHello(&remote, &negotiated) < 0) {
+    const RemoteHelloResult r = handshake->ReceiveAndParseRemoteHello(&remote);
+    if (r == RemoteHelloResult::ERROR) {
         int saved_errno = errno;
         PLOG(WARNING) << "Fail to receive hello from server:"
                       << s->description();
         s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: 
%s",
                      s->description().c_str(), berror(saved_errno));
-        ep->_state.store(FAILED, butil::memory_order_relaxed);
+        ep->_state = FAILED;
         return NULL;
     }
 
-    if (!negotiated) {
+    if (r != RemoteHelloResult::NEGOTIATED) {
         LOG(WARNING) << "Fail to negotiate with server, fallback to tcp:"
                      << s->description();
         rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
     } else {
         ep->ApplyRemoteHello(remote);
-        ep->_state.store(C_BRINGUP_QP, butil::memory_order_relaxed);
+        ep->_state = C_BRINGUP_QP;
         if (ep->BringUpQp(remote.lid, remote.gid, remote.qp_num) < 0) {
             LOG(WARNING) << "Fail to bringup QP, fallback to tcp:"
                          << s->description();
@@ -529,7 +474,7 @@ void* RdmaEndpoint::ProcessHandshakeAtClient(void* arg) {
     }
 
     // Send ACK message to server
-    ep->_state.store(C_ACK_SEND, butil::memory_order_relaxed);
+    ep->_state = C_ACK_SEND;
     bool rdma_on = rdma_transport->_rdma_state == RdmaTransport::RDMA_ON;
     uint32_t flags = rdma_on ? HELLO_ACK_RDMA_OK : 0;
     uint32_t flags_be = butil::HostToNet32(flags);
@@ -539,17 +484,17 @@ void* RdmaEndpoint::ProcessHandshakeAtClient(void* arg) {
                       << s->description();
         s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: 
%s",
                      s->description().c_str(), berror(saved_errno));
-        ep->_state.store(FAILED, butil::memory_order_relaxed);
+        ep->_state = FAILED;
         return NULL;
     }
 
     if (rdma_transport->_rdma_state == RdmaTransport::RDMA_ON) {
-        ep->_state.store(ESTABLISHED, butil::memory_order_release);
+        ep->_state = ESTABLISHED;
         LOG_IF(INFO, FLAGS_rdma_trace_verbose)
             << "Client handshake ends (use rdma v" << ep->_handshake_version
             << ") on " << s->description();
     } else {
-        ep->_state.store(FALLBACK_TCP, butil::memory_order_release);
+        ep->_state = FALLBACK_TCP;
         LOG_IF(INFO, FLAGS_rdma_trace_verbose)
             << "Client handshake ends (use tcp) on " << s->description();
     }
@@ -574,129 +519,119 @@ void* RdmaEndpoint::ProcessHandshakeAtClient(void* arg) 
{
 //     |
 //     v
 //   ESTABLISHED / FALLBACK_TCP
-void* RdmaEndpoint::ProcessHandshakeAtServer(void* arg) {
-    auto ep = static_cast<RdmaEndpoint*>(arg);
-    SocketUniquePtr s(ep->_socket);
-    auto rdma_transport = static_cast<RdmaTransport*>(s->_transport.get());
+ParseResult RdmaEndpoint::ExecuteServerHandshake(butil::IOBuf* source, Socket* 
s) {
+    RdmaTransport* rdma_transport = 
static_cast<RdmaTransport*>(s->_transport.get());
+    RdmaEndpoint* ep = rdma_transport->_rdma_ep;
+    CHECK(ep != NULL);
 
-    LOG_IF(INFO, FLAGS_rdma_trace_verbose)
-        << "Start handshake on " << s->description();
+    if (s->parsing_context() == NULL) {
+        // Phase 1: read the client hello, negotiate, reply server hello.
+        if (source->size() < HELLO_MAGIC_LEN) {
+            return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+        }
+        uint8_t magic[HELLO_MAGIC_LEN];
+        CHECK_EQ(source->copy_to(magic, HELLO_MAGIC_LEN), HELLO_MAGIC_LEN);
+
+        // Pick the version-specific server handshake from the peeked magic 
(the
+        // magic is NOT consumed; ReceiveAndParseRemoteHello() reads it again
+        // from `source`).
+        std::unique_ptr<RdmaHandshake> hs = CreateServerHandshakeByMagic(ep, 
source, magic);
+        if (hs == NULL) {
+            return MakeParseError(PARSE_ERROR_TRY_OTHERS);
+        }
+        ep->_handshake_version = hs->ProtocolVersion();
+        ep->_state = S_HELLO_WAIT;
+
+        ParsedHello remote{};
+        const RemoteHelloResult r = hs->ReceiveAndParseRemoteHello(&remote);
+        if (r == RemoteHelloResult::NEED_MORE) {
+            return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+        }
+        if (r == RemoteHelloResult::ERROR) {
+            ep->_state = FAILED;
+            return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
+        }
+
+        // Negotiate + allocate resources.
+        bool negotiated = r == RemoteHelloResult::NEGOTIATED;
+        if (negotiated) {
+            ep->ApplyRemoteHello(remote);
+            ep->_state = S_ALLOC_QPCQ;
+            if (ep->AllocateResources() < 0) {
+                LOG(WARNING) << "Fail to allocate rdma resources, fallback to 
tcp:"
+                             << s->description();
+                negotiated = false;
+            } else {
+                ep->_state = S_BRINGUP_QP;
+                if (ep->BringUpQp(remote.lid, remote.gid, remote.qp_num) < 0) {
+                    LOG(WARNING) << "Fail to bringup QP, fallback to tcp:"
+                                 << s->description();
+                    negotiated = false;
+                }
+            }
+        }
+        if (!negotiated) {
+            rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
+        }
 
-    ep->_state.store(S_HELLO_WAIT, butil::memory_order_relaxed);
-    uint8_t magic[MAGIC_STR_LEN];
-    if (ep->ReadFromFd(magic, MAGIC_STR_LEN) < 0) {
-        int saved_errno = errno;
-        PLOG(WARNING) << "Fail to read Hello Message from client:"
-                      << s->description() << " " << s->_remote_side;
-        s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: 
%s",
-                     s->description().c_str(), berror(saved_errno));
-        ep->_state.store(FAILED, butil::memory_order_relaxed);
-        return NULL;
-    }
+        // Reply the server hello.
+        // Emits a real hello when _rdma_state != RDMA_OFF;
+        // an un-negotiable one otherwise.
+        ep->_state = S_HELLO_SEND;
+        if (hs->SendLocalHello() < 0) {
+            PLOG(WARNING) << "Fail to send server hello to " << 
s->description();
+            ep->_state = FAILED;
+            return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
+        }
 
-    // Dispatch on magic, or fall back to TCP
-    std::unique_ptr<RdmaHandshake> handshake = 
CreateServerHandshakeByMagic(ep, magic);
-    if (!handshake) {
-        LOG_IF(INFO, FLAGS_rdma_trace_verbose)
-            << "It seems that the client does not use RDMA, fallback to TCP:"
-            << s->description();
-        // We need to copy data read back to _socket->_read_buf.
-        s->_read_buf.append(magic, MAGIC_STR_LEN);
-        rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
-        // Use release memory order to publish the magic bytes appended
-        // above to whoever reads `_state == FALLBACK_TCP` (the event
-        // thread in OnNewDataFromTcp).
-        ep->_state.store(FALLBACK_TCP, butil::memory_order_release);
-        ep->TryReadOnTcp();
-        return NULL;
+        // Enter the wait-ACK phase. Whether negotiation succeeded is already
+        // recorded in rdma_transport->_rdma_state (RDMA_OFF iff negotiation
+        // failed), so the context itself needs no extra flag.
+        s->reset_parsing_context(ServerHandshakeContext::Create());
+        ep->_state = S_ACK_WAIT;
+        return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
     }
-    ep->_handshake_version = handshake->ProtocolVersion();
 
-    // Magic was already consumed above; the subclass MUST NOT re-read it.
-    ParsedHello remote{};
-    bool negotiated = false;
-    if (handshake->ReceiveAndParseRemoteHello(&remote, &negotiated) < 0) {
-        int saved_errno = errno;
-        PLOG(WARNING) << "Fail to receive hello from client:"
-                      << s->description();
-        s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: 
%s",
-                     s->description().c_str(), berror(saved_errno));
-        ep->_state.store(FAILED, butil::memory_order_relaxed);
-        return NULL;
+    // Phase 2: drain the 4B ACK and finalize.
+    if (source->size() < HELLO_ACK_LEN) {
+        return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
     }
-
-    if (!negotiated) {
-        LOG(WARNING) << "Fail to negotiate with client, fallback to tcp:"
+    if (source->size() > HELLO_ACK_LEN) {
+        LOG(WARNING) << "Too many bytes in handshake ACK, drop connection: "
                      << s->description();
-        rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
-    } else {
-        ep->ApplyRemoteHello(remote);
-        ep->_state.store(S_ALLOC_QPCQ, butil::memory_order_relaxed);
-        if (ep->AllocateResources() < 0) {
-            LOG(WARNING) << "Fail to allocate rdma resources, fallback to tcp:"
-                         << s->description();
-            rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
-        } else {
-            ep->_state.store(S_BRINGUP_QP, butil::memory_order_relaxed);
-            if (ep->BringUpQp(remote.lid, remote.gid, remote.qp_num) < 0) {
-                LOG(WARNING) << "Fail to bringup QP, fallback to tcp:"
-                             << s->description();
-                rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
-            }
-        }
+        ep->_state = FAILED;
+        s->reset_parsing_context(NULL);
+        return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
     }
 
-    ep->_state.store(S_HELLO_SEND, butil::memory_order_relaxed);
-    if (handshake->SendLocalHello() < 0) {
-        int saved_errno = errno;
-        PLOG(WARNING) << "Fail to send Hello Message to client:"
-                      << s->description();
-        s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: 
%s",
-                     s->description().c_str(), berror(saved_errno));
-        ep->_state.store(FAILED, butil::memory_order_relaxed);
-        return NULL;
-    }
-
-    ep->_state.store(S_ACK_WAIT, butil::memory_order_relaxed);
     uint32_t flags_be = 0;
-    if (ep->ReadFromFd(&flags_be, HELLO_ACK_LEN) < 0) {
-        int saved_errno = errno;
-        PLOG(WARNING) << "Fail to read ack message from client:"
-                      << s->description();
-        s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: 
%s",
-                     s->description().c_str(), berror(saved_errno));
-        ep->_state.store(FAILED, butil::memory_order_relaxed);
-        return NULL;
-    }
+    CHECK_EQ(source->cutn(&flags_be, HELLO_ACK_LEN), HELLO_ACK_LEN);
     uint32_t flags = butil::NetToHost32(flags_be);
     bool client_ack_ok = (flags & HELLO_ACK_RDMA_OK) != 0;
-    if (client_ack_ok) {
-        if (rdma_transport->_rdma_state == RdmaTransport::RDMA_OFF) {
-            // Client asked for RDMA but we are falling back: protocol
-            // breakdown, abort the connection so the client sees a
-            // clean error rather than a half-up RDMA channel.
-            LOG(WARNING) << "Client wants RDMA in ACK but server is in "
-                         << "RDMA_OFF state: " << s->description();
-            s->SetFailed(EPROTO, "Fail to complete rdma handshake from %s: %s",
-                         s->description().c_str(), berror(EPROTO));
-            ep->_state.store(FAILED, butil::memory_order_relaxed);
-            return NULL;
-        }
-        rdma_transport->_rdma_state = RdmaTransport::RDMA_ON;
-        ep->_state.store(ESTABLISHED, butil::memory_order_release);
-        LOG_IF(INFO, FLAGS_rdma_trace_verbose)
-            << "Server handshake ends (use rdma v" << ep->_handshake_version
-            << ") on " << s->description();
-    } else {
-        rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
-        ep->_state.store(FALLBACK_TCP, butil::memory_order_release);
+    if (!client_ack_ok) {
         LOG_IF(INFO, FLAGS_rdma_trace_verbose)
             << "Server handshake ends (use tcp) on " << s->description();
+        rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF;
+        ep->_state = FALLBACK_TCP;
+        s->reset_parsing_context(NULL);
+        return MakeParseError(PARSE_ERROR_TRY_OTHERS);
     }
 
-    ep->TryReadOnTcp();
+    if (rdma_transport->_rdma_state == RdmaTransport::RDMA_OFF) {
+        LOG(WARNING) << "Client wants RDMA in ACK but server fell back: "
+                     << s->description();
+        ep->_state = FAILED;
+        s->reset_parsing_context(NULL);
+        return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
+    }
 
-    return NULL;
+    LOG_IF(INFO, FLAGS_rdma_trace_verbose)
+        << "Server handshake ends (use rdma v" << ep->_handshake_version
+        << ") on " << s->description();
+    rdma_transport->_rdma_state = RdmaTransport::RDMA_ON;
+    ep->_state = ESTABLISHED;
+    s->reset_parsing_context(NULL);
+    return MakeParseError(PARSE_ERROR_TRY_OTHERS);
 }
 
 bool RdmaEndpoint::IsWritable() const {
@@ -974,7 +909,7 @@ ssize_t RdmaEndpoint::HandleCompletion(ibv_wc& wc) {
             if (wc.byte_len < (uint32_t)FLAGS_rdma_zerocopy_min_size) {
                 zerocopy = false;
             }
-            CHECK_NE(_state.load(butil::memory_order_acquire), FALLBACK_TCP);
+            CHECK_NE(_state, FALLBACK_TCP);
             if (zerocopy) {
                 _rbuf[_rq_received].cutn(&_socket->_read_buf, wc.byte_len);
             } else {
@@ -1593,7 +1528,7 @@ void RdmaEndpoint::PollCq(Socket* m) {
 }
 
 std::string RdmaEndpoint::GetStateStr() const {
-    switch (_state.load(butil::memory_order_relaxed)) {
+    switch (_state) {
     case UNINIT: return "UNINIT";
     case C_ALLOC_QPCQ: return "C_ALLOC_QPCQ";
     case C_HELLO_SEND: return "C_HELLO_SEND";
diff --git a/src/brpc/rdma/rdma_endpoint.h b/src/brpc/rdma/rdma_endpoint.h
index 41c33824..49ee2c9e 100644
--- a/src/brpc/rdma/rdma_endpoint.h
+++ b/src/brpc/rdma/rdma_endpoint.h
@@ -30,6 +30,7 @@
 #include "butil/macros.h"
 #include "butil/containers/mpsc_queue.h"
 #include "brpc/socket.h"
+#include "brpc/rdma/rdma_handshake_server.h"
 
 
 namespace brpc {
@@ -45,10 +46,11 @@ class RdmaHandshakeServerV2;
 class RdmaHandshakeClientV3;
 class RdmaHandshakeServerV3;
 struct ParsedHello;
+enum class RemoteHelloResult;
 class RdmaHello;
 class RdmaEndpoint;
 namespace v2_wire {
-    int ReadBodyAndNegotiate(RdmaEndpoint* ep, ParsedHello* remote, bool* 
negotiated);
+    RemoteHelloResult ReadBodyAndNegotiate(RdmaEndpoint* ep, ParsedHello* 
remote);
     int DrainBytes(RdmaEndpoint* ep, size_t n);
 }  // namespace v2_wire
 
@@ -96,7 +98,7 @@ friend class RdmaHandshakeClientV2;
 friend class RdmaHandshakeServerV2;
 friend class RdmaHandshakeClientV3;
 friend class RdmaHandshakeServerV3;
-friend int v2_wire::ReadBodyAndNegotiate(RdmaEndpoint*, ParsedHello*, bool*);
+friend RemoteHelloResult v2_wire::ReadBodyAndNegotiate(RdmaEndpoint*, 
ParsedHello*);
 friend int v2_wire::DrainBytes(RdmaEndpoint*, size_t);
 friend void v3_wire::FillLocalRdmaHello(const RdmaEndpoint*, RdmaHello*);
 friend int  v3_wire::ReadAndParseV3Hello(RdmaEndpoint*, RdmaHello*);
@@ -125,9 +127,13 @@ public:
     void DebugInfo(std::ostream& os,
                    butil::StringPiece connector = "\n") const;
 
-    // Callback when there is new epollin event on TCP fd
+    // Callback when there is new epollin event on TCP fd.
+    // Only used by client-side RDMA sockets.
     static void OnNewDataFromTcp(Socket* m);
 
+    // Real handshake for RDMA-mode sockets.
+    static ParseResult ExecuteServerHandshake(butil::IOBuf* source, Socket* 
socket);
+
     // Initialize polling mode
     static int PollingModeInitialize(bthread_tag_t tag,
                                      std::function<void(void)> callback,
@@ -157,9 +163,6 @@ private:
     // Process handshake at the client
     static void* ProcessHandshakeAtClient(void* arg);
 
-    // Process handshake at the server
-    static void* ProcessHandshakeAtServer(void* arg);
-
     // Allocate resources
     // Return 0 if success, -1 if failed and errno set
     int AllocateResources();
@@ -249,9 +252,6 @@ private:
     // Get the description of current handshake state
     std::string GetStateStr() const;
 
-    // Try to read data on TCP fd in _socket
-    void TryReadOnTcp();
-
     // Add cq socket id to poller
     void PollerAddCqSid();
 
@@ -261,8 +261,8 @@ private:
     // Not owner
     Socket* _socket;
 
-    // State of Handshake
-    butil::atomic<State> _state;
+    // State of Handshake.
+    State _state;
 
     // Wire-level handshake protocol version (set by dispatch in
     // ProcessHandshakeAtClient/Server). Aligned with the protocol code:
diff --git a/src/brpc/rdma/rdma_handshake.cpp b/src/brpc/rdma/rdma_handshake.cpp
index 9bd2312e..1e6004ff 100644
--- a/src/brpc/rdma/rdma_handshake.cpp
+++ b/src/brpc/rdma/rdma_handshake.cpp
@@ -18,6 +18,7 @@
 #if BRPC_WITH_RDMA
 
 #include "brpc/rdma/rdma_handshake.h"
+#include "brpc/rdma/rdma_handshake_constants.h"
 
 #include <string.h>
 #include <algorithm>            // std::min
@@ -26,11 +27,13 @@
 #include <gflags/gflags.h>
 #include "butil/iobuf.h"        // IOBuf, IOPortal, IOBufAsZeroCopy*Stream
 #include "butil/sys_byteorder.h"
+#include "butil/raw_pack.h"      // RawPacker, RawUnpacker
 #include "brpc/socket.h"
 #include "brpc/rdma/rdma_endpoint.h"
 #include "brpc/rdma/rdma_helper.h"
 #include "brpc/rdma_transport.h"
 #include "brpc/rdma/rdma_handshake.pb.h"
+#include "butil/object_pool.h"
 
 namespace brpc {
 namespace rdma {
@@ -40,58 +43,46 @@ DEFINE_int32(rdma_client_handshake_version, 2,
              "2 = legacy 'RDMA' magic (default, compatible with all servers); "
              "3 = new 'RDM3' protobuf-based handshake "
              "(MUST only be enabled after target servers support v3).");
+DECLARE_bool(rdma_trace_verbose);
 
 extern const uint16_t MIN_QP_SIZE;
 extern const uint16_t MIN_BLOCK_SIZE;
 extern uint32_t g_rdma_recv_block_size;
 extern bool g_skip_rdma_init;
 
-// Wire-level constants for the v2 handshake.
-static const char* MAGIC_STR = "RDMA";
-static constexpr uint16_t RDMA_HELLO_V2_MSG_LEN = 40;  // In Byte
-extern const uint16_t RDMA_HELLO_V2_VERSION = 2;
-extern const uint16_t RDMA_IMPL_V2_VERSION = 1;
-
-// Wire-level constants for the v3 handshake.
-static const char* MAGIC_STR_V3 = "RDM3";
-static const size_t RDMA_HELLO_V3_PB_SIZE_LEN = 4;
-static const size_t RDMA_HELLO_V3_MAX_PB_SIZE = 4096;
-
 namespace v2_wire {
 
 void HelloMessage::Serialize(void* data) const {
-    uint16_t* current_pos = (uint16_t*)data;
-    *(current_pos++) = butil::HostToNet16(msg_len);
-    *(current_pos++) = butil::HostToNet16(hello_ver);
-    *(current_pos++) = butil::HostToNet16(impl_ver);
-    uint32_t* block_size_pos = (uint32_t*)current_pos;
-    *block_size_pos = butil::HostToNet32(block_size);
-    current_pos += 2; // move forward 4 Bytes
-    *(current_pos++) = butil::HostToNet16(sq_size);
-    *(current_pos++) = butil::HostToNet16(rq_size);
-    *(current_pos++) = butil::HostToNet16(lid);
-    fast_memcpy(current_pos, gid.raw, 16);
-    uint32_t* qp_num_pos = (uint32_t*)((char*)current_pos + 16);
-    *qp_num_pos = butil::HostToNet32(qp_num);
+    butil::RawPacker(data)
+        .pack16(msg_len)
+        .pack16(hello_ver)
+        .pack16(impl_ver)
+        .pack32(block_size)
+        .pack16(sq_size)
+        .pack16(rq_size)
+        .pack16(lid)
+        // gid is a raw 16-byte identifier and must NOT be byte-swapped.
+        .pack_bytes(gid.raw, sizeof(gid.raw))
+        .pack32(qp_num);
 }
 
 void HelloMessage::Deserialize(void* data) {
-    uint16_t* current_pos = (uint16_t*)data;
-    msg_len = butil::NetToHost16(*current_pos++);
-    hello_ver = butil::NetToHost16(*current_pos++);
-    impl_ver = butil::NetToHost16(*current_pos++);
-    block_size = butil::NetToHost32(*(uint32_t*)current_pos);
-    current_pos += 2; // move forward 4 Bytes
-    sq_size = butil::NetToHost16(*current_pos++);
-    rq_size = butil::NetToHost16(*current_pos++);
-    lid = butil::NetToHost16(*current_pos++);
-    fast_memcpy(gid.raw, current_pos, 16);
-    qp_num = butil::NetToHost32(*(uint32_t*)((char*)current_pos + 16));
+    butil::RawUnpacker(data)
+        .unpack16(msg_len)
+        .unpack16(hello_ver)
+        .unpack16(impl_ver)
+        .unpack32(block_size)
+        .unpack16(sq_size)
+        .unpack16(rq_size)
+        .unpack16(lid)
+        // gid is a raw 16-byte identifier and must NOT be byte-swapped.
+        .unpack_bytes(gid.raw, sizeof(gid.raw))
+        .unpack32(qp_num);
 }
 
 static bool ValidHelloMessage(const HelloMessage& msg) {
-    return msg.hello_ver == RDMA_HELLO_V2_VERSION &&
-           msg.impl_ver == RDMA_IMPL_V2_VERSION &&
+    return msg.hello_ver == HELLO_V2_VERSION &&
+           msg.impl_ver == IMPL_V2_VERSION &&
            msg.block_size >= MIN_BLOCK_SIZE &&
            msg.sq_size >= MIN_QP_SIZE &&
            msg.rq_size >= MIN_QP_SIZE;
@@ -106,35 +97,33 @@ static void TranslateV2Hello(const HelloMessage& msg, 
ParsedHello* out) {
     out->qp_num = msg.qp_num;
 }
 
-int ReadBodyAndNegotiate(RdmaEndpoint* ep, ParsedHello* remote, bool* 
negotiated) {
-    uint8_t data[HELLO_MSG_LEN_MIN];
-    if (ep->ReadFromFd(data, HELLO_MSG_LEN_MIN - MAGIC_STR_LEN) < 0) {
-        return -1;
+RemoteHelloResult ReadBodyAndNegotiate(RdmaEndpoint* ep, ParsedHello* remote) {
+    uint8_t data[HELLO_V2_MSG_LEN_MIN];
+    if (ep->ReadFromFd(data, HELLO_V2_MSG_LEN_MIN - HELLO_MAGIC_LEN) < 0) {
+        return RemoteHelloResult::ERROR;
     }
     HelloMessage remote_msg{};
     remote_msg.Deserialize(data);
-    if (remote_msg.msg_len < HELLO_MSG_LEN_MIN ||
-        remote_msg.msg_len > HELLO_MSG_LEN_MAX) {
+    if (remote_msg.msg_len < HELLO_V2_MSG_LEN_MIN ||
+        remote_msg.msg_len > HELLO_V2_MSG_LEN_MAX) {
         errno = EPROTO;
-        return -1;
+        return RemoteHelloResult::ERROR;
     }
-    if (remote_msg.msg_len > HELLO_MSG_LEN_MIN) {
+    if (remote_msg.msg_len > HELLO_V2_MSG_LEN_MIN) {
         // Drain unknown trailing bytes so they don't pollute subsequent
         // reads (e.g. the upcoming ACK message). v2 base fields already
         // carry enough information for negotiation; unknown trailing
         // bytes are treated as optional hints that v2 safely ignores.
-        size_t ext_len = remote_msg.msg_len - HELLO_MSG_LEN_MIN;
+        size_t ext_len = remote_msg.msg_len - HELLO_V2_MSG_LEN_MIN;
         if (DrainBytes(ep, ext_len) < 0) {
-            return -1;
+            return RemoteHelloResult::ERROR;
         }
     }
     if (!ValidHelloMessage(remote_msg)) {
-        *negotiated = false;
-        return 0;
+        return RemoteHelloResult::FALLBACK;
     }
-    *negotiated = true;
     TranslateV2Hello(remote_msg, remote);
-    return 0;
+    return RemoteHelloResult::NEGOTIATED;
 }
 
 int DrainBytes(RdmaEndpoint* ep, size_t n) {
@@ -153,12 +142,12 @@ int DrainBytes(RdmaEndpoint* ep, size_t n) {
 
 int RdmaHandshakeClientV2::SendLocalHello() {
     RdmaEndpoint* ep = _ep;
-    uint8_t data[RDMA_HELLO_V2_MSG_LEN];
+    uint8_t data[HELLO_V2_MSG_LEN_MIN];
 
     v2_wire::HelloMessage local_msg{};
-    local_msg.msg_len = RDMA_HELLO_V2_MSG_LEN;
-    local_msg.hello_ver = RDMA_HELLO_V2_VERSION;
-    local_msg.impl_ver = RDMA_IMPL_V2_VERSION;
+    local_msg.msg_len = HELLO_V2_MSG_LEN_MIN;
+    local_msg.hello_ver = HELLO_V2_VERSION;
+    local_msg.impl_ver = IMPL_V2_VERSION;
     local_msg.block_size = g_rdma_recv_block_size;
     local_msg.sq_size = ep->_sq_size;
     local_msg.rq_size = ep->_rq_size;
@@ -170,36 +159,70 @@ int RdmaHandshakeClientV2::SendLocalHello() {
         // Only happens in UT
         local_msg.qp_num = 0;
     }
-    fast_memcpy(data, MAGIC_STR, 4);
+    fast_memcpy(data, HELLO_MAGIC, 4);
     local_msg.Serialize((char*)data + 4);
-    return ep->WriteToFd(data, RDMA_HELLO_V2_MSG_LEN);
+    return ep->WriteToFd(data, HELLO_V2_MSG_LEN_MIN);
 }
 
-int RdmaHandshakeClientV2::ReceiveAndParseRemoteHello(ParsedHello* remote,
-                                                     bool* negotiated) {
-    RdmaEndpoint* ep = _ep;
-
-    // Read and verify magic (the endpoint did NOT pre-read magic on the 
client side).
-    uint8_t magic[MAGIC_STR_LEN];
-    if (ep->ReadFromFd(magic, MAGIC_STR_LEN) < 0) {
-        return -1;
+RemoteHelloResult 
RdmaHandshakeClientV2::ReceiveAndParseRemoteHello(ParsedHello* remote) {
+    uint8_t magic[HELLO_MAGIC_LEN];
+    if (_ep->ReadFromFd(magic, HELLO_MAGIC_LEN) < 0) {
+        return RemoteHelloResult::ERROR;
     }
-    if (memcmp(magic, MAGIC_STR, MAGIC_STR_LEN) != 0) {
+    if (memcmp(magic, HELLO_MAGIC, HELLO_MAGIC_LEN) != 0) {
         errno = EPROTO;
-        return -1;
+        return RemoteHelloResult::ERROR;
     }
-    return v2_wire::ReadBodyAndNegotiate(ep, remote, negotiated);
+
+    return v2_wire::ReadBodyAndNegotiate(_ep, remote);
 }
 
-int RdmaHandshakeServerV2::ReceiveAndParseRemoteHello(ParsedHello* remote, 
bool* negotiated) {
-    // Magic already consumed by ProcessHandshakeAtServer.
-    return v2_wire::ReadBodyAndNegotiate(_ep, remote, negotiated);
+// Parse one complete v2 client hello out of `_source` (non-blocking).
+// v2 hello: [ "RDMA" 4B ][ msg_len 2B ][ 34B ... ], base total = 40B.
+RemoteHelloResult 
RdmaHandshakeServerV2::ReceiveAndParseRemoteHello(ParsedHello* remote) {
+    butil::IOBuf* source = _source;
+    constexpr size_t HDR_LEN = HELLO_MAGIC_LEN + 2;
+    if (source->size() < HDR_LEN) {
+        // msg_len has not fully arrived yet.
+        return RemoteHelloResult::NEED_MORE;
+    }
+
+    uint8_t hdr[HDR_LEN];
+    CHECK_EQ(source->copy_to(hdr, sizeof(hdr)), sizeof(hdr));
+
+    uint16_t msg_len = 0;
+    butil::RawUnpacker(hdr + HELLO_MAGIC_LEN).unpack16(msg_len);
+    if (msg_len < HELLO_V2_MSG_LEN_MIN || msg_len > HELLO_V2_MSG_LEN_MAX) {
+        errno = EPROTO;
+        return RemoteHelloResult::ERROR;
+    }
+    if (source->size() < msg_len) {
+        // Full message has not fully arrived yet.
+        return RemoteHelloResult::NEED_MORE;
+    }
+
+    // Consume the whole hello: magic + 36B base body + optional extension.
+    CHECK_EQ(source->pop_front(HELLO_MAGIC_LEN), HELLO_MAGIC_LEN);
+    uint8_t body[HELLO_V2_MSG_LEN_MIN - HELLO_MAGIC_LEN];  // 36B
+    CHECK_EQ(source->cutn(body, sizeof(body)), sizeof(body));
+    if (!source->empty()) {
+        // Drain unknown trailing bytes.
+        source->clear();
+    }
+
+    v2_wire::HelloMessage remote_msg{};
+    remote_msg.Deserialize(body);
+    if (!v2_wire::ValidHelloMessage(remote_msg)) {
+        return RemoteHelloResult::FALLBACK;
+    }
+    v2_wire::TranslateV2Hello(remote_msg, remote);
+    return RemoteHelloResult::NEGOTIATED;
 }
 
 int RdmaHandshakeServerV2::SendLocalHello() {
-    uint8_t data[RDMA_HELLO_V2_MSG_LEN];
+    uint8_t data[HELLO_V2_MSG_LEN_MIN];
     v2_wire::HelloMessage local_msg{};
-    local_msg.msg_len = RDMA_HELLO_V2_MSG_LEN;
+    local_msg.msg_len = HELLO_V2_MSG_LEN_MIN;
     auto rdma_transport = 
static_cast<RdmaTransport*>(_ep->_socket->_transport.get());
     if (rdma_transport->_rdma_state == RdmaTransport::RDMA_OFF) {
         local_msg.hello_ver = 0;
@@ -211,8 +234,8 @@ int RdmaHandshakeServerV2::SendLocalHello() {
         memset(local_msg.gid.raw, 0, sizeof(local_msg.gid.raw));
         local_msg.qp_num     = 0;
     } else {
-        local_msg.hello_ver = RDMA_HELLO_V2_VERSION;
-        local_msg.impl_ver = RDMA_IMPL_V2_VERSION;
+        local_msg.hello_ver = HELLO_V2_VERSION;
+        local_msg.impl_ver = IMPL_V2_VERSION;
         local_msg.block_size = g_rdma_recv_block_size;
         local_msg.sq_size = _ep->_sq_size;
         local_msg.rq_size = _ep->_rq_size;
@@ -225,9 +248,9 @@ int RdmaHandshakeServerV2::SendLocalHello() {
             local_msg.qp_num = 0;
         }
     }
-    fast_memcpy(data, MAGIC_STR, 4);
+    fast_memcpy(data, HELLO_MAGIC, 4);
     local_msg.Serialize((char*)data + 4);
-    return _ep->WriteToFd(data, RDMA_HELLO_V2_MSG_LEN);
+    return _ep->WriteToFd(data, HELLO_V2_MSG_LEN_MIN);
 }
 
 namespace v3_wire {
@@ -263,8 +286,7 @@ void FillLocalRdmaHello(const RdmaEndpoint* ep, RdmaHello* 
msg) {
     msg->set_rq_size(ep->_rq_size);
     msg->set_lid(GetRdmaLid());
     ibv_gid gid = GetRdmaGid();
-    msg->set_gid(std::string(reinterpret_cast<const char*>(gid.raw),
-                             sizeof(gid.raw)));
+    msg->set_gid(reinterpret_cast<const char*>(gid.raw), sizeof(gid.raw));
     if (BAIDU_LIKELY(ep->_resource)) {
         msg->set_qp_num(ep->_resource->qp->qp_num);
     } else {
@@ -274,13 +296,13 @@ void FillLocalRdmaHello(const RdmaEndpoint* ep, 
RdmaHello* msg) {
 }
 
 int ReadAndParseV3Hello(RdmaEndpoint* ep, RdmaHello* out) {
-    uint8_t size_buf[RDMA_HELLO_V3_PB_SIZE_LEN];
-    if (ep->ReadFromFd(size_buf, RDMA_HELLO_V3_PB_SIZE_LEN) < 0) {
+    uint8_t size_buf[HELLO_V3_PB_SIZE_LEN];
+    if (ep->ReadFromFd(size_buf, HELLO_V3_PB_SIZE_LEN) < 0) {
         return -1;
     }
     uint32_t pb_size = butil::NetToHost32(
         *reinterpret_cast<const uint32_t*>(size_buf));
-    if (pb_size == 0 || pb_size > RDMA_HELLO_V3_MAX_PB_SIZE) {
+    if (pb_size == 0 || pb_size > HELLO_V3_MAX_PB_SIZE) {
         errno = EPROTO;
         return -1;
     }
@@ -300,16 +322,16 @@ int ReadAndParseV3Hello(RdmaEndpoint* ep, RdmaHello* out) 
{
 
 int WriteV3Hello(RdmaEndpoint* ep, const RdmaHello& msg) {
     uint32_t pb_size = static_cast<uint32_t>(msg.ByteSizeLong());
-    if (pb_size > RDMA_HELLO_V3_MAX_PB_SIZE) {
+    if (pb_size > HELLO_V3_MAX_PB_SIZE) {
         errno = EPROTO;
         return -1;
     }
 
     // [ "RDM3" 4B ][ pb_size 4B (big-endian) ][ RdmaHello protobuf bytes ]
     butil::IOBuf packet;
-    packet.append(MAGIC_STR_V3, MAGIC_STR_LEN);
+    packet.append(HELLO_MAGIC_V3, HELLO_MAGIC_LEN);
     uint32_t pb_size_be = butil::HostToNet32(pb_size);
-    packet.append(&pb_size_be, RDMA_HELLO_V3_PB_SIZE_LEN);
+    packet.append(&pb_size_be, HELLO_V3_PB_SIZE_LEN);
     butil::IOBufAsZeroCopyOutputStream output(&packet);
     if (!msg.SerializeToZeroCopyStream(&output)) {
         LOG(ERROR) << "Failed to serialize RdmaHello";
@@ -336,48 +358,83 @@ int RdmaHandshakeClientV3::SendLocalHello() {
     return v3_wire::WriteV3Hello(_ep, local_msg);
 }
 
-int RdmaHandshakeClientV3::ReceiveAndParseRemoteHello(ParsedHello* remote,
-                                                     bool* negotiated) {
-    uint8_t magic[MAGIC_STR_LEN];
-    if (_ep->ReadFromFd(magic, MAGIC_STR_LEN) < 0) {
-        return -1;
+RemoteHelloResult 
RdmaHandshakeClientV3::ReceiveAndParseRemoteHello(ParsedHello* remote) {
+    uint8_t magic[HELLO_MAGIC_LEN];
+    if (_ep->ReadFromFd(magic, HELLO_MAGIC_LEN) < 0) {
+        return RemoteHelloResult::ERROR;
     }
-    if (memcmp(magic, MAGIC_STR_V3, MAGIC_STR_LEN) != 0) {
+    if (memcmp(magic, HELLO_MAGIC_V3, HELLO_MAGIC_LEN) != 0) {
         errno = EPROTO;
-        return -1;
+        return RemoteHelloResult::ERROR;
     }
 
     RdmaHello remote_msg{};
     if (v3_wire::ReadAndParseV3Hello(_ep, &remote_msg) < 0) {
-        return -1;
+        return RemoteHelloResult::ERROR;
     }
     if (!v3_wire::ValidRdmaHello(remote_msg)) {
-        *negotiated = false;
-        return 0;
+        return RemoteHelloResult::FALLBACK;
     }
-    *negotiated = true;
     v3_wire::TranslateHello(remote_msg, remote);
-    return 0;
+    return RemoteHelloResult::NEGOTIATED;
 }
 
-int RdmaHandshakeServerV3::ReceiveAndParseRemoteHello(ParsedHello* remote, 
bool* negotiated) {
-    // Magic already consumed by ProcessHandshakeAtServer.
-    RdmaHello remote_msg{};
-    if (v3_wire::ReadAndParseV3Hello(_ep, &remote_msg) < 0) {
-        return -1;
+// Parse one complete v3 client hello out of `_source` (non-blocking).
+// v3 hello: [ "RDM3" 4B ][ pb_size 4B (big-endian) ][ RdmaHello ]
+RemoteHelloResult 
RdmaHandshakeServerV3::ReceiveAndParseRemoteHello(ParsedHello* remote) {
+    constexpr size_t HDR_LEN = HELLO_MAGIC_LEN + HELLO_V3_PB_SIZE_LEN;
+    if (_source->size() < HDR_LEN) {
+        // pb_size has not fully arrived yet.
+        return RemoteHelloResult::NEED_MORE;
+    }
+
+    uint8_t hdr[HDR_LEN];
+    CHECK_EQ(_source->copy_to(hdr, sizeof(hdr)), sizeof(hdr));
+
+    uint32_t pb_size = butil::NetToHost32(
+        *reinterpret_cast<const uint32_t*>(hdr + HELLO_MAGIC_LEN));
+    if (pb_size == 0 || pb_size > HELLO_V3_MAX_PB_SIZE) {
+        errno = EPROTO;
+        return RemoteHelloResult::ERROR;
+    }
+    size_t total = HDR_LEN + pb_size;
+    if (_source->size() < total) {
+        // Full message has not fully arrived yet.
+        return RemoteHelloResult::NEED_MORE;
+    }
+
+    CHECK_EQ(_source->cutn(hdr, HDR_LEN), HDR_LEN);
+    butil::IOBuf pb;
+    CHECK_EQ(_source->cutn(&pb, pb_size), pb_size);
+    RdmaHello remote_msg;
+    butil::IOBufAsZeroCopyInputStream input(pb);
+    if (!remote_msg.ParseFromZeroCopyStream(&input)) {
+        LOG(ERROR) << "Failed to parse RdmaHello";
+        errno = EPROTO;
+        return RemoteHelloResult::ERROR;
     }
     if (!v3_wire::ValidRdmaHello(remote_msg)) {
-        *negotiated = false;
-        return 0;
+        return RemoteHelloResult::FALLBACK;
     }
-    *negotiated = true;
     v3_wire::TranslateHello(remote_msg, remote);
-    return 0;
+    return RemoteHelloResult::NEGOTIATED;
 }
 
 int RdmaHandshakeServerV3::SendLocalHello() {
     RdmaHello local_msg{};
-    v3_wire::FillLocalRdmaHello(_ep, &local_msg);
+    auto rdma_transport = 
static_cast<RdmaTransport*>(_ep->_socket->_transport.get());
+    if (rdma_transport->_rdma_state == RdmaTransport::RDMA_OFF) {
+        // Un-negotiable hello: all body fields are zero so the client's
+        // rejects it and downgrades to TCP on the same connection.
+        local_msg.set_block_size(0);
+        local_msg.set_sq_size(0);
+        local_msg.set_rq_size(0);
+        local_msg.set_lid(0);
+        local_msg.set_gid(std::string(sizeof(ibv_gid), '\0'));
+        local_msg.set_qp_num(0);
+    } else {
+        v3_wire::FillLocalRdmaHello(_ep, &local_msg);
+    }
     return v3_wire::WriteV3Hello(_ep, local_msg);
 }
 
@@ -392,14 +449,16 @@ std::unique_ptr<RdmaHandshake> 
CreateClientHandshake(RdmaEndpoint* ep) {
 }
 
 std::unique_ptr<RdmaHandshake> CreateServerHandshakeByMagic(
-    RdmaEndpoint* ep, const uint8_t magic[MAGIC_STR_LEN]) {
-    if (memcmp(magic, MAGIC_STR, MAGIC_STR_LEN) == 0) {
-        return std::unique_ptr<RdmaHandshake>(new RdmaHandshakeServerV2(ep));
+    RdmaEndpoint* ep, butil::IOBuf* source, const uint8_t 
magic[HELLO_MAGIC_LEN]) {
+    if (memcmp(magic, HELLO_MAGIC, HELLO_MAGIC_LEN) == 0) {
+        return std::unique_ptr<RdmaHandshake>(
+                new RdmaHandshakeServerV2(ep, source));
     }
-    if (memcmp(magic, MAGIC_STR_V3, MAGIC_STR_LEN) == 0) {
-        return std::unique_ptr<RdmaHandshake>(new RdmaHandshakeServerV3(ep));
+    if (memcmp(magic, HELLO_MAGIC_V3, HELLO_MAGIC_LEN) == 0) {
+        return std::unique_ptr<RdmaHandshake>(
+                new RdmaHandshakeServerV3(ep, source));
     }
-    return nullptr;
+    return NULL;
 }
 
 }  // namespace rdma
diff --git a/src/brpc/rdma/rdma_handshake.h b/src/brpc/rdma/rdma_handshake.h
index 5f36a9e6..b35a2378 100644
--- a/src/brpc/rdma/rdma_handshake.h
+++ b/src/brpc/rdma/rdma_handshake.h
@@ -20,20 +20,20 @@
 
 #if BRPC_WITH_RDMA
 
-#include <cstddef>
-#include <cstdint>
 #include <memory>
 #include <infiniband/verbs.h>
 #include "butil/macros.h"
+#include "brpc/rdma/rdma_handshake_constants.h"
+
+namespace butil {
+class IOBuf;
+}
 
 namespace brpc {
 namespace rdma {
 
 class RdmaEndpoint;
 
-// Length of the RDMA handshake magic string (e.g. "RDMA", "RDM3").
-static const size_t MAGIC_STR_LEN = 4;
-
 // Wire-format-agnostic representation of a peer's hello message.
 // Each protocol version (v2 binary, v3 protobuf) translates its own
 // wire format into this struct so the state-machine driver in
@@ -48,18 +48,19 @@ struct ParsedHello {
     uint32_t qp_num;
 };
 
-namespace v2_wire {
+// Result of reading/parsing a peer's hello (see ReceiveAndParseRemoteHello).
+enum class RemoteHelloResult {
+    // A full hello was read and negotiation succeeded.
+    NEGOTIATED,
+    // A full hello was read but negotiation failed.
+    FALLBACK,
+    // (server only) Not enough data yet.
+    NEED_MORE,
+    // IO/protocol error (errno set).
+    ERROR,
+};
 
-// Wire constants for the v2 hello.
-//
-// HELLO_MSG_LEN_MIN: total length of the base v2 hello (4B magic +
-// 36B HelloMessage). Anything shorter than this is malformed.
-// HELLO_MSG_LEN_MAX: upper bound for the entire v2 hello message
-// length declared by HelloMessage::msg_len. Anything beyond this is
-// treated as a protocol error and the connection is closed without
-// attempting to drain.
-static constexpr size_t HELLO_MSG_LEN_MIN = 40;
-static constexpr size_t HELLO_MSG_LEN_MAX = 4096;
+namespace v2_wire {
 
 // v2 binary HelloMessage.
 struct HelloMessage {
@@ -79,19 +80,16 @@ struct HelloMessage {
 
 }  // namespace v2_wire
 
-// Abstract base class of an RDMA handshake.
-//
-// Acts as the protocol-version dispatch point for the state machine
-// driven by RdmaEndpoint::ProcessHandshakeAt{Client,Server}.
+// Base class of an RDMA handshake, shared by both roles.
 class RdmaHandshake {
 public:
-    explicit RdmaHandshake(RdmaEndpoint* ep) : _ep(ep) {}
+    RdmaHandshake(RdmaEndpoint* ep, int version) : _ep(ep), _version(version) 
{}
     virtual ~RdmaHandshake() = default;
 
     DISALLOW_COPY_AND_ASSIGN(RdmaHandshake);
 
     // Wire-level protocol version (2 for "RDMA", 3 for "RDM3").
-    virtual int ProtocolVersion() const = 0;
+    int ProtocolVersion() const { return _version; }
 
     // Build and send the local hello (including the protocol magic).
     // Returns 0 on success, -1 on IO error (errno set).
@@ -104,68 +102,56 @@ public:
     //   - v3: qp_num==0 so the peer's ValidRdmaHello rejects it.
     virtual int SendLocalHello() = 0;
 
-    // Read the peer's hello, validate it, and translate into ParsedHello.
-    //
-    // Role-specific semantics:
-    //   - Client subclasses: read & verify the 4B magic first, then the
-    //     body. (The endpoint did NOT pre-read the magic on the client
-    //     side.)
-    //   - Server subclasses: read ONLY the body. The 4B magic was
-    //     already consumed by ProcessHandshakeAtServer and was used to
-    //     pick `this` from CreateServerHandshakeByMagic; re-reading
-    //     would deadlock.
-    //
-    // Outputs:
-    //   *negotiated -- true if the remote hello is structurally valid
-    //                  AND passes per-protocol negotiation checks;
-    //                  false means the peer asked for fallback or sent
-    //                  something we can't honor.
-    // Returns:
-    //    0 -- IO/parsing layer OK; check *negotiated and *remote.
-    //   -1 -- IO error or unrecoverable protocol error (errno set).
-    virtual int ReceiveAndParseRemoteHello(ParsedHello* remote, bool* 
negotiated) = 0;
+    // Read and parse the peer's hello into *remote.
+    virtual RemoteHelloResult ReceiveAndParseRemoteHello(ParsedHello* remote) 
= 0;
 
 protected:
     RdmaEndpoint* _ep;
+    int _version;
+};
+
+// Server-side handshake base: parses the remote hello non-blockingly out of
+// `_source` (an IOBuf filled by InputMessenger), never touching the fd.
+class ServerRdmaHandshake : public RdmaHandshake {
+public:
+    ServerRdmaHandshake(RdmaEndpoint* ep, butil::IOBuf* source, int version)
+        : RdmaHandshake(ep, version), _source(source) {}
+
+protected:
+    butil::IOBuf* _source;
 };
 
 // v2 handshake (legacy "RDMA" magic, 36B binary HelloMessage).
 class RdmaHandshakeClientV2 : public RdmaHandshake {
 public:
-    using RdmaHandshake::RdmaHandshake;
-    int ProtocolVersion() const override { return 2; }
-
+    explicit RdmaHandshakeClientV2(RdmaEndpoint* ep) : RdmaHandshake(ep, 2) {}
     int SendLocalHello() override;
-    int ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) 
override;
+    RemoteHelloResult ReceiveAndParseRemoteHello(ParsedHello* remote) override;
 };
 
-class RdmaHandshakeServerV2 : public RdmaHandshake {
+class RdmaHandshakeServerV2 : public ServerRdmaHandshake {
 public:
-    using RdmaHandshake::RdmaHandshake;
-    int ProtocolVersion() const override { return 2; }
-
+    RdmaHandshakeServerV2(RdmaEndpoint* ep, butil::IOBuf* source)
+        : ServerRdmaHandshake(ep, source, 2) {}
     int SendLocalHello() override;
-    int ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) 
override;
+    RemoteHelloResult ReceiveAndParseRemoteHello(ParsedHello* remote) override;
 };
 
 // v3 handshake (new "RDM3" magic, protobuf RdmaHello).
 // [ "RDM3" 4B ][ pb_size 4B (big-endian) ][ RdmaHello protobuf bytes ]
 class RdmaHandshakeClientV3 : public RdmaHandshake {
 public:
-    using RdmaHandshake::RdmaHandshake;
-    int ProtocolVersion() const override { return 3; }
-
+    explicit RdmaHandshakeClientV3(RdmaEndpoint* ep) : RdmaHandshake(ep, 3) {}
     int SendLocalHello() override;
-    int ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) 
override;
+    RemoteHelloResult ReceiveAndParseRemoteHello(ParsedHello* remote) override;
 };
 
-class RdmaHandshakeServerV3 : public RdmaHandshake {
+class RdmaHandshakeServerV3 : public ServerRdmaHandshake {
 public:
-    using RdmaHandshake::RdmaHandshake;
-    int ProtocolVersion() const override { return 3; }
-
+    RdmaHandshakeServerV3(RdmaEndpoint* ep, butil::IOBuf* source)
+        : ServerRdmaHandshake(ep, source, 3) {}
     int SendLocalHello() override;
-    int ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) 
override;
+    RemoteHelloResult ReceiveAndParseRemoteHello(ParsedHello* remote) override;
 };
 
 // Factory methods
@@ -183,7 +169,7 @@ std::unique_ptr<RdmaHandshake> 
CreateClientHandshake(RdmaEndpoint* ep);
 //   "RDMA" -> RdmaHandshakeServerV2
 //   "RDM3" -> RdmaHandshakeServerV3
 std::unique_ptr<RdmaHandshake> CreateServerHandshakeByMagic(
-    RdmaEndpoint* ep, const uint8_t magic[MAGIC_STR_LEN]);
+    RdmaEndpoint* ep, butil::IOBuf* source, const uint8_t 
magic[HELLO_MAGIC_LEN]);
 
 }  // namespace rdma
 }  // namespace brpc
diff --git a/src/brpc/rdma/rdma_handshake_constants.h 
b/src/brpc/rdma/rdma_handshake_constants.h
new file mode 100644
index 00000000..aa9811b9
--- /dev/null
+++ b/src/brpc/rdma/rdma_handshake_constants.h
@@ -0,0 +1,56 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+#ifndef BRPC_RDMA_RDMA_HANDSHAKE_CONSTANTS_H
+#define BRPC_RDMA_RDMA_HANDSHAKE_CONSTANTS_H
+
+namespace brpc {
+namespace rdma {
+
+// Handshake magic strings and their (shared) length.
+//   v2 ("RDMA"): [ "RDMA" 4B ][ msg_len 2B ][ 34B body ], total 40B.
+//   v3 ("RDM3"): [ "RDM3" 4B ][ pb_size 4B (big-endian) ][ RdmaHello bytes ].
+constexpr const char* HELLO_MAGIC = "RDMA";
+constexpr const char* HELLO_MAGIC_V3 = "RDM3";
+constexpr size_t HELLO_MAGIC_LEN = 4;
+
+// v2 hello version fields. A valid v2 hello must carry exactly these.
+constexpr uint16_t HELLO_V2_VERSION = 2;
+constexpr uint16_t IMPL_V2_VERSION = 1;
+
+// v2 hello length bounds. HELLO_V2_MSG_LEN_MIN is the total length of the
+// base v2 hello (4B magic + 36B HelloMessage); anything shorter is malformed.
+// HELLO_V2_MSG_LEN_MAX caps the msg_len declared by the peer; anything
+// beyond it is treated as a protocol error and the connection is closed 
without
+// attempting to drain.
+constexpr size_t HELLO_V2_MSG_LEN_MIN = 40;
+constexpr size_t HELLO_V2_MSG_LEN_MAX = 4096;
+
+// v3 hello framing: a 4B big-endian protobuf size prefix, capped to avoid
+// waiting forever for bytes that never come.
+constexpr size_t HELLO_V3_PB_SIZE_LEN = 4;
+constexpr size_t HELLO_V3_MAX_PB_SIZE = 8192;
+
+// Handshake ACK (shared by all versions): a single 4B big-endian flags word;
+// bit 0 (HELLO_ACK_RDMA_OK) means the sender wants to use RDMA.
+constexpr size_t HELLO_ACK_LEN = 4;
+constexpr uint32_t HELLO_ACK_RDMA_OK = 0x1;
+
+}  // namespace rdma
+}  // namespace brpc
+
+#endif  // BRPC_RDMA_RDMA_HANDSHAKE_CONSTANTS_H
diff --git a/src/brpc/rdma/rdma_handshake_server.cpp 
b/src/brpc/rdma/rdma_handshake_server.cpp
new file mode 100644
index 00000000..4072a11c
--- /dev/null
+++ b/src/brpc/rdma/rdma_handshake_server.cpp
@@ -0,0 +1,214 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+#include "brpc/rdma/rdma_handshake_server.h"
+
+#include <limits>
+#include <string>
+#include <string.h>
+#include "butil/iobuf.h"
+#include "butil/logging.h"
+#include "butil/object_pool.h"
+#include "butil/raw_pack.h"
+#include "butil/sys_byteorder.h"
+#include "brpc/socket.h"
+#include "brpc/rdma/rdma_handshake.pb.h"
+#include "brpc/rdma/rdma_handshake_constants.h"
+#if BRPC_WITH_RDMA
+#include "brpc/rdma/rdma_endpoint.h"
+#endif
+
+namespace brpc {
+namespace rdma {
+
+ServerHandshakeContext* ServerHandshakeContext::Create() {
+    return butil::get_object<ServerHandshakeContext>();
+}
+
+void ServerHandshakeContext::Destroy() {
+    butil::return_object(this);
+}
+
+// Fallback-only server handshake. Used for any connection that is NOT in RDMA
+// mode: builds without RDMA (no RdmaEndpoint exists at all), and RDMA-enabled
+// builds where this particular connection is plain TCP. Every RDMA client that
+// reaches here is answered with an un-negotiable hello and asked to downgrade
+// to TCP on the same connection, then its ACK is drained.
+
+// An intentionally-invalid v2 hello_ver: the client's ValidHelloMessage()
+// requires hello_ver==2, so it rejects this and falls back.
+static constexpr uint16_t V2_HELLO_VERSION_INVALID = 
std::numeric_limits<uint16_t>::max();
+// Length of the gid field (== sizeof(ibv_gid)), spelled as a literal so this
+// compile-switch-independent file stays free of <infiniband/verbs.h>.
+static constexpr size_t V3_GID_LEN = 16;
+
+// Consume one complete v2 client hello ("RDMA" + 2B msg_len + body) from
+// `source` without interpreting its content (fallback does not negotiate).
+// Returns 1 (consumed), 0 (not enough data yet, nothing consumed) or -1 
(error).
+static int DrainClientHelloV2(butil::IOBuf* source) {
+    constexpr size_t HDR_LEN = HELLO_MAGIC_LEN + 2;
+    if (source->size() < HDR_LEN) {
+        return 0;
+    }
+
+    uint8_t hdr[HDR_LEN];
+    CHECK_EQ(source->copy_to(hdr, sizeof(hdr)), sizeof(hdr));
+
+    uint16_t msg_len = 0;
+    butil::RawUnpacker(hdr + HELLO_MAGIC_LEN).unpack16(msg_len);
+    if (msg_len < HELLO_V2_MSG_LEN_MIN || msg_len > HELLO_V2_MSG_LEN_MAX) {
+        return -1;
+    }
+
+    if (source->size() < msg_len) {
+        return 0;
+    }
+
+    CHECK_EQ(source->pop_front(msg_len), msg_len);
+    return 1;
+}
+
+// Consume one complete v3 client hello ("RDM3" + 4B pb_size + protobuf body)
+// from `source` without interpreting its content.
+// Returns 1 (consumed), 0 (not enough data yet, nothing consumed) or -1 
(error).
+static int DrainClientHelloV3(butil::IOBuf* source) {
+    constexpr size_t HDR_LEN = HELLO_MAGIC_LEN + HELLO_V3_PB_SIZE_LEN;
+    if (source->size()< HDR_LEN) {
+        return 0;
+    }
+
+    uint8_t hdr[HDR_LEN];
+    CHECK_EQ(source->copy_to(hdr, sizeof(hdr)), sizeof(hdr));
+
+    uint32_t pb_size = butil::NetToHost32(
+        *reinterpret_cast<const uint32_t*>(hdr + HELLO_MAGIC_LEN));
+    if (pb_size == 0 || pb_size > HELLO_V3_MAX_PB_SIZE) {
+        return -1;
+    }
+
+    const size_t total = HDR_LEN + pb_size;
+    if (source->size() < total) {
+        return 0;
+    }
+
+    CHECK_EQ(source->pop_front(total), total);
+    return 1;
+}
+
+// Reply an un-negotiable hello so the client downgrades to TCP. All body 
fields
+// are zero/invalid so the client's validity check rejects it.
+// Returns 0 on success, -1 otherwise.
+static int SendUnnegotiableHello(Socket* socket, int version) {
+    butil::IOBuf packet;
+    if (version == 2) {
+        // magic "RDMA" + msg_len(=40) + invalid hello_ver; rest stays zero.
+        // NOTE: msg_len is the length of the WHOLE hello INCLUDING the 4B 
magic
+        // (== HELLO_V2_MSG_LEN_MIN), not just the body; the client rejects any
+        // msg_len < HELLO_V2_MSG_LEN_MIN as a protocol error.
+        packet.append(HELLO_MAGIC, HELLO_MAGIC_LEN);
+        char reply[HELLO_V2_MSG_LEN_MIN - HELLO_MAGIC_LEN]{};
+        butil::RawPacker(reply).pack16(HELLO_V2_MSG_LEN_MIN)
+                               .pack16(V2_HELLO_VERSION_INVALID);
+        packet.append(reply, sizeof(reply));
+    } else {
+        // "RDM3" + pb_size + RdmaHello with block_size==0 & qp_num==0 so the
+        // client's ValidRdmaHello() returns false.
+        RdmaHello reply_msg;
+        reply_msg.set_block_size(0);
+        reply_msg.set_sq_size(0);
+        reply_msg.set_rq_size(0);
+        reply_msg.set_lid(0);
+        reply_msg.set_gid(std::string(V3_GID_LEN, '\0'));
+        reply_msg.set_qp_num(0);
+        packet.append(HELLO_MAGIC_V3, HELLO_MAGIC_LEN);
+        uint32_t pb_size_be =
+            
butil::HostToNet32(static_cast<uint32_t>(reply_msg.ByteSizeLong()));
+        packet.append(&pb_size_be, sizeof(pb_size_be));
+        butil::IOBufAsZeroCopyOutputStream output(&packet);
+        if (!reply_msg.SerializeToZeroCopyStream(&output)) {
+            LOG(WARNING) << "Fail to serialize RDMA v3 fallback hello";
+            return -1;
+        }
+    }
+
+    if (socket->Write(&packet) != 0) {
+        PLOG(WARNING) << "Fail to send RDMA fallback hello to " << 
socket->description();
+        return -1;
+    }
+    return 0;
+}
+
+// Fallback handshake for connections that are NOT in RDMA mode.
+static ParseResult FallbackServerHandshake(butil::IOBuf* source, Socket* 
socket) {
+    if (socket->parsing_context() == NULL) {
+        if (source->size() < HELLO_MAGIC_LEN) {
+            return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+        }
+        // Phase 1: consume the client hello and reply an un-negotiable hello.
+        uint8_t magic[HELLO_MAGIC_LEN];
+        CHECK_EQ(source->copy_to(magic, HELLO_MAGIC_LEN), HELLO_MAGIC_LEN);
+
+        int version;
+        if (memcmp(magic, HELLO_MAGIC, HELLO_MAGIC_LEN) == 0) {
+            version = 2;
+        } else if (memcmp(magic, HELLO_MAGIC_V3, HELLO_MAGIC_LEN) == 0) {
+            version = 3;
+        } else {
+            return MakeParseError(PARSE_ERROR_TRY_OTHERS);
+        }
+
+        const int r = version == 2 ? DrainClientHelloV2(source) : 
DrainClientHelloV3(source);
+        if (r == 0) {
+            // Hello not complete yet; keep the buffer intact and retry later.
+            return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+        }
+        if (r < 0) {
+            return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
+        }
+        if (SendUnnegotiableHello(socket, version) < 0) {
+            return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
+        }
+        // Wait for the client ACK across subsequent reads.
+        socket->reset_parsing_context(ServerHandshakeContext::Create());
+        return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+    }
+
+    // Phase 2: drain the 4B ACK.
+    if (source->size() < HELLO_ACK_LEN) {
+        return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+    }
+    CHECK_EQ(source->pop_front(HELLO_ACK_LEN), HELLO_ACK_LEN);
+    // Handshake done (downgraded to TCP); drop the context and let
+    // InputMessenger parse the following real RPC.
+    socket->reset_parsing_context(NULL);
+    return MakeParseError(PARSE_ERROR_TRY_OTHERS);
+}
+
+ParseResult ExecuteServerHandshake(butil::IOBuf* source, Socket* socket) {
+    // Only RDMA-mode connections carry a live RdmaEndpoint and run the real
+    // handshake. A connection that is not in RDMA mode (RDMA compiled in but
+    // this connection is plain TCP, or RDMA not compiled at all) falls back.
+#if BRPC_WITH_RDMA
+    if (socket->socket_mode() == SOCKET_MODE_RDMA) {
+        return RdmaEndpoint::ExecuteServerHandshake(source, socket);
+    }
+#endif
+    return FallbackServerHandshake(source, socket);
+}
+
+}  // namespace rdma
+}  // namespace brpc
diff --git a/src/brpc/rdma/rdma_handshake_server.h 
b/src/brpc/rdma/rdma_handshake_server.h
new file mode 100644
index 00000000..705aa893
--- /dev/null
+++ b/src/brpc/rdma/rdma_handshake_server.h
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+#ifndef BRPC_RDMA_RDMA_HANDSHAKE_SERVER_H
+#define BRPC_RDMA_RDMA_HANDSHAKE_SERVER_H
+
+#include "brpc/destroyable.h"
+#include "brpc/parse_result.h"
+
+namespace butil {
+class IOBuf;
+}
+
+namespace brpc {
+class Socket;
+namespace rdma {
+
+// State kept across multiple parse calls of the server handshake.
+struct ServerHandshakeContext : public Destroyable {
+    static ServerHandshakeContext* Create();
+    void Destroy() override;
+};
+
+// The single server-side RDMA handshake entry for policy::ParseRdmaHandshake.
+// Returns a ParseResult ready to be handed back from the protocol parser:
+//   - not an RDMA magic / handshake finished -> PARSE_ERROR_TRY_OTHERS;
+//   - an RDMA magic but not enough bytes yet  -> PARSE_ERROR_NOT_ENOUGH_DATA;
+//   - IO/protocol error                       -> PARSE_ERROR_ABSOLUTELY_WRONG.
+ParseResult ExecuteServerHandshake(butil::IOBuf* source, Socket* socket);
+
+}  // namespace rdma
+}  // namespace brpc
+
+#endif  // BRPC_RDMA_RDMA_HANDSHAKE_SERVER_H
diff --git a/src/brpc/rdma_transport.cpp b/src/brpc/rdma_transport.cpp
index 88d89a7b..b172e47f 100644
--- a/src/brpc/rdma_transport.cpp
+++ b/src/brpc/rdma_transport.cpp
@@ -20,6 +20,7 @@
 #include "brpc/rdma_transport.h"
 #include "brpc/event_dispatcher.h"
 #include "brpc/tcp_transport.h"
+#include "brpc/input_messenger.h"
 #include "brpc/rdma/rdma_endpoint.h"
 #include "brpc/rdma/rdma_helper.h"
 
@@ -48,7 +49,16 @@ void RdmaTransport::Init(Socket *socket, const SocketOptions 
&options) {
     _default_connect = options.app_connect;
     _on_edge_trigger = options.on_edge_triggered_events;
     if (options.need_on_edge_trigger && _on_edge_trigger == NULL) {
-        _on_edge_trigger = rdma::RdmaEndpoint::OnNewDataFromTcp;
+        // Server-side RDMA sockets drive the handshake through the standard
+        // InputMessenger path (ParseRdmaHandshake), so they use OnNewMessages
+        // just like TCP sockets. Only client-side sockets, whose handshake
+        // (ProcessHandshakeAtClient) is an active blocking bthread relying on
+        // _read_butex woken by OnNewDataFromTcp, still need OnNewDataFromTcp.
+        if (options.user == 
static_cast<SocketUser*>(get_client_side_messenger())) {
+            _on_edge_trigger = rdma::RdmaEndpoint::OnNewDataFromTcp;
+        } else {
+            _on_edge_trigger = InputMessenger::OnNewMessages;
+        }
     }
     _tcp_transport = std::make_shared<TcpTransport>();
     _tcp_transport->Init(socket, options);
@@ -78,7 +88,12 @@ std::shared_ptr<AppConnect> RdmaTransport::Connect() {
 }
 
 int RdmaTransport::CutFromIOBuf(butil::IOBuf *buf) {
-    if (_rdma_ep && _rdma_state != RDMA_OFF) {
+    // Only send over the RDMA channel once the handshake has NEGOTIATED it
+    // (RDMA_ON). While the state is still RDMA_UNKNOWN (handshake in progress,
+    // or a server connection that turned out to be plain TCP and never
+    // handshook) or RDMA_OFF (fell back), the QP is not usable and everything
+    // must go over the TCP fd. Mirrors the RDMA_ON check in WaitEpollOut().
+    if (_rdma_ep && _rdma_state == RDMA_ON) {
         butil::IOBuf *data_arr[1] = {buf};
         return _rdma_ep->CutFromIOBufList(data_arr, 1);
     } else {
@@ -87,7 +102,7 @@ int RdmaTransport::CutFromIOBuf(butil::IOBuf *buf) {
 }
 
 ssize_t RdmaTransport::CutFromIOBufList(butil::IOBuf **buf, size_t ndata) {
-    if (_rdma_ep && _rdma_state != RDMA_OFF) {
+    if (_rdma_ep && _rdma_state == RDMA_ON) {
         return _rdma_ep->CutFromIOBufList(buf, ndata);
     }
     return _tcp_transport->CutFromIOBufList(buf, ndata);
diff --git a/src/brpc/rdma_transport.h b/src/brpc/rdma_transport.h
index d8520b1a..0ac8a253 100644
--- a/src/brpc/rdma_transport.h
+++ b/src/brpc/rdma_transport.h
@@ -29,6 +29,7 @@ friend class TransportFactory;
 friend class rdma::RdmaEndpoint;
 friend class rdma::RdmaConnect;
 friend class rdma::RdmaHandshakeServerV2;
+friend class rdma::RdmaHandshakeServerV3;
 public:
     void Init(Socket* socket, const SocketOptions& options) override;
     void Release() override;
diff --git a/src/brpc/server.cpp b/src/brpc/server.cpp
index 99ecab8a..230c2284 100644
--- a/src/brpc/server.cpp
+++ b/src/brpc/server.cpp
@@ -607,13 +607,17 @@ int Server::AddBuiltinServices() {
     return 0;
 }
 
-bool is_http_protocol(const char* name) {
+BUTIL_FORCE_INLINE bool is_http_protocol(const char* name) {
     if (name[0] != 'h') {
         return false;
     }
     return strcmp(name, "http") == 0 || strcmp(name, "h2") == 0;
 }
 
+BUTIL_FORCE_INLINE bool is_rdma_handshake_protocol(const char* name) {
+    return strcmp(name, "rdma_handshake") == 0;
+}
+
 Acceptor* Server::BuildAcceptor() {
     std::set<std::string> whitelist;
     for (butil::StringSplitter sp(_options.enabled_protocols.c_str(), ' ');
@@ -637,6 +641,7 @@ Acceptor* Server::BuildAcceptor() {
         }
         if (has_whitelist &&
             !is_http_protocol(protocols[i].name) &&
+            !is_rdma_handshake_protocol(protocols[i].name) &&
             !whitelist.erase(protocols[i].name)) {
             // the protocol is not allowed to serve.
             RPC_VLOG << "Skip protocol=" << protocols[i].name;
diff --git a/src/brpc/socket.h b/src/brpc/socket.h
index ca83b921..b48c9bca 100644
--- a/src/brpc/socket.h
+++ b/src/brpc/socket.h
@@ -597,6 +597,9 @@ public:
     // True if this socket was created by Connect.
     bool CreatedByConnect() const;
 
+    // The socket mode decided at creation.
+    SocketMode socket_mode() const { return _socket_mode; }
+
     // Get an UNUSED socket connecting to the same place as this socket
     // from the SocketPool of this socket.
     int GetPooledSocket(SocketUniquePtr* pooled_socket);
diff --git a/src/butil/raw_pack.h b/src/butil/raw_pack.h
index d4f8167f..f5f69f83 100644
--- a/src/butil/raw_pack.h
+++ b/src/butil/raw_pack.h
@@ -18,6 +18,7 @@
 #ifndef BUTIL_RAW_PACK_H
 #define BUTIL_RAW_PACK_H
 
+#include <string.h>
 #include "butil/sys_byteorder.h"
 
 namespace butil {
@@ -44,10 +45,16 @@ class RawPacker {
 public:
     // Notice: User must guarantee `stream' is as long as the packed data.
     explicit RawPacker(void* stream) : _stream((char*)stream) {}
-    ~RawPacker() {}
 
     // Not using operator<< because some values may be packed differently from
     // its type.
+    RawPacker& pack16(uint16_t host_value) {
+        *(uint16_t*)_stream = HostToNet16(host_value);
+        _stream += 2;
+        return *this;
+    }
+
+
     RawPacker& pack32(uint32_t host_value) {
         *(uint32_t*)_stream = HostToNet32(host_value);
         _stream += 4;
@@ -62,6 +69,13 @@ public:
         return *this;
     }
 
+    // Pack `n' raw bytes from `data' as-is (no byte order conversion).
+    RawPacker& pack_bytes(const void* data, size_t n) {
+        memcpy(_stream, data, n);
+        _stream += n;
+        return *this;
+    }
+
 private:
     char* _stream;
 };
@@ -71,21 +85,33 @@ private:
 class RawUnpacker {
 public:
     explicit RawUnpacker(const void* stream) : _stream((const char*)stream) {}
-    ~RawUnpacker() {}
 
-    RawUnpacker& unpack32(uint32_t & host_value) {
+    RawUnpacker& unpack16(uint16_t& host_value) {
+        host_value = NetToHost16(*(const uint16_t*)_stream);
+        _stream += 2;
+        return *this;
+    }
+
+    RawUnpacker& unpack32(uint32_t& host_value) {
         host_value = NetToHost32(*(const uint32_t*)_stream);
         _stream += 4;
         return *this;
     }
 
-    RawUnpacker& unpack64(uint64_t & host_value) {
+    RawUnpacker& unpack64(uint64_t& host_value) {
         const uint32_t *p = (const uint32_t*)_stream;
         host_value = (((uint64_t)NetToHost32(p[0])) << 32) | NetToHost32(p[1]);
         _stream += 8;
         return *this;
     }
 
+    // Unpack `n' raw bytes into `data' as-is (no byte order conversion).
+    RawUnpacker& unpack_bytes(void* data, size_t n) {
+        memcpy(data, _stream, n);
+        _stream += n;
+        return *this;
+    }
+
 private:
     const char* _stream;
 };
diff --git a/test/brpc_rdma_unittest.cpp b/test/brpc_rdma_unittest.cpp
index 43c6edfd..9aff49d3 100644
--- a/test/brpc_rdma_unittest.cpp
+++ b/test/brpc_rdma_unittest.cpp
@@ -39,6 +39,7 @@
 #include "brpc/rdma/block_pool.h"
 #include "brpc/rdma/rdma_endpoint.h"
 #include "brpc/rdma/rdma_handshake.h"
+#include "brpc/rdma/rdma_handshake_constants.h"
 #include "brpc/rdma/rdma_handshake.pb.h"
 #include "brpc/rdma/rdma_helper.h"
 #include "echo.pb.h"
@@ -55,8 +56,8 @@ DEFINE_bool(rdma_test_enable, false, "Enable tests requring 
rdma runtime.");
 
 namespace rdma {
 
-extern const uint16_t RDMA_HELLO_V2_VERSION;
-extern const uint16_t RDMA_IMPL_V2_VERSION;
+// HELLO_V2_VERSION / IMPL_V2_VERSION come from
+// brpc/rdma/rdma_handshake_constants.h (shared wire constants).
 
 DECLARE_bool(rdma_trace_verbose);
 DECLARE_int32(rdma_memory_pool_max_regions);
@@ -220,11 +221,14 @@ TEST_F(RdmaTest, client_hello_msg_invalid_magic_str) {
     Socket* s = GetSocketFromServer(0);
     ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
 
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
     memcpy(data, "PRPC", 4);  // send as normal baidu_std protocol
     ASSERT_EQ(4, write(sockfd, data, 4));
     usleep(100000);  // wait for server to handle the msg
-    ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
+    // A non-RDMA magic makes ParseRdmaHandshake return TRY_OTHERS and hand the
+    // bytes to other protocols; it does not touch the endpoint state, so it
+    // stays UNINIT (the old blocking handshake used to set FALLBACK_TCP here).
+    ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
 
     StopServer();
 }
@@ -248,7 +252,10 @@ TEST_F(RdmaTest, client_close_during_hello_send) {
     memcpy(data, "RD", 2);
     ASSERT_EQ(2, write(sockfd1, data, 2));  // break in magic str
     usleep(100000);  // wait for server to handle the msg
-    ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
+    // Fewer than 4 magic bytes: ParseRdmaHandshake can't tell yet, returns
+    // NOT_ENOUGH_DATA and leaves the endpoint UNINIT (the old blocking
+    // handshake used to set S_HELLO_WAIT before reading the magic).
+    ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
     close(sockfd1);
     usleep(100000);  // wait for server to handle the msg
     ASSERT_EQ(NULL, GetSocketFromServer(0));
@@ -273,9 +280,13 @@ TEST_F(RdmaTest, client_close_during_hello_send) {
     usleep(100000);  // wait for server to handle the msg
     s = GetSocketFromServer(0);
     ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
+    // Send the 4B magic plus a valid msg_len (=40) but no body, so the server
+    // recognizes an RDMA v2 hello and waits for the remaining bytes. (A zero
+    // msg_len would now be rejected up-front as a protocol error.)
     memcpy(data, "RDMA", 4);
-    memset(data + 4, 0, 4);
-    ASSERT_EQ(8, write(sockfd3, data, 8));  // break after magic str
+    uint16_t v2_len = butil::HostToNet16(rdma::HELLO_V2_MSG_LEN_MIN);
+    memcpy(data + 4, &v2_len, sizeof(v2_len));
+    ASSERT_EQ(6, write(sockfd3, data, 6));  // magic + msg_len, body missing
     usleep(100000);  // wait for server to handle the msg
     ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
     close(sockfd3);
@@ -293,7 +304,7 @@ TEST_F(RdmaTest, client_hello_msg_invalid_len) {
     addr.sin_family = AF_INET;
     addr.sin_port = htons(PORT);
     Socket* s = NULL;
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
 
     butil::fd_guard sockfd1(socket(AF_INET, SOCK_STREAM, 0));
     ASSERT_TRUE(sockfd1 >= 0);
@@ -338,8 +349,8 @@ TEST_F(RdmaTest, client_hello_msg_invalid_version) {
     addr.sin_family = AF_INET;
     addr.sin_port = htons(PORT);
     Socket* s = NULL;
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    uint16_t len = butil::HostToNet16(rdma::v2_wire::HELLO_MSG_LEN_MIN);
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    uint16_t len = butil::HostToNet16(rdma::HELLO_V2_MSG_LEN_MIN);
     uint16_t ver = butil::HostToNet16(1);
 
     butil::fd_guard sockfd1(socket(AF_INET, SOCK_STREAM, 0));
@@ -359,7 +370,7 @@ TEST_F(RdmaTest, client_hello_msg_invalid_version) {
     // UT mistakenly wrote `data, 36` which included the leftover "RDMA"
     // magic at data[0..4); the server parsed it as msg_len = 0x5244 and
     // happened to fall through to NegotiationValid (which then failed on
-    // hello_ver). Now that Step 1 enforces a HELLO_MSG_LEN_MAX upper bound,
+    // hello_ver). Now that Step 1 enforces a HELLO_V2_MSG_LEN_MAX upper bound,
     // such an oversized msg_len would be rejected before reaching the
     // version check, breaking the intent of this UT.
     ASSERT_EQ(36, write(sockfd1, data + 4, 36));
@@ -413,10 +424,10 @@ TEST_F(RdmaTest, 
client_hello_msg_invalid_sq_rq_block_size) {
     Socket* s = NULL;
     uint32_t flags = butil::HostToNet32(0);
     rdma::v2_wire::HelloMessage msg{};
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    msg.msg_len = rdma::HELLO_V2_MSG_LEN_MIN;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
 
     msg.sq_size = 10;
     msg.rq_size = 16;
@@ -505,10 +516,10 @@ TEST_F(RdmaTest, client_close_after_qp_build) {
     addr.sin_port = htons(PORT);
     Socket* s = NULL;
     rdma::v2_wire::HelloMessage msg{};
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    msg.msg_len = rdma::HELLO_V2_MSG_LEN_MIN;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
     msg.sq_size = 16;
     msg.rq_size = 16;
     msg.block_size = 8192;
@@ -542,10 +553,10 @@ TEST_F(RdmaTest, client_close_during_ack_send) {
     addr.sin_port = htons(PORT);
     Socket* s = NULL;
     rdma::v2_wire::HelloMessage msg{};
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    msg.msg_len = rdma::HELLO_V2_MSG_LEN_MIN;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
     msg.sq_size = 16;
     msg.rq_size = 16;
     msg.block_size = 8192;
@@ -586,10 +597,10 @@ TEST_F(RdmaTest, client_close_after_ack_send) {
     addr.sin_port = htons(PORT);
     Socket* s = NULL;
     rdma::v2_wire::HelloMessage msg{};
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    msg.msg_len = rdma::HELLO_V2_MSG_LEN_MIN;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
     msg.sq_size = 16;
     msg.rq_size = 16;
     msg.block_size = 8192;
@@ -651,10 +662,10 @@ TEST_F(RdmaTest, client_send_data_on_tcp_after_ack_send) {
     addr.sin_port = htons(PORT);
     Socket* s = NULL;
     rdma::v2_wire::HelloMessage msg{};
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    msg.msg_len = rdma::HELLO_V2_MSG_LEN_MIN;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
     msg.sq_size = 16;
     msg.rq_size = 16;
     msg.block_size = 8192;
@@ -763,8 +774,8 @@ TEST_F(RdmaTest, server_close_before_hello_send) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     close(acc_fd);
     usleep(100000);
     ASSERT_EQ(rdma::RdmaEndpoint::FAILED, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
@@ -799,8 +810,8 @@ TEST_F(RdmaTest, server_miss_during_magic_str) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     ASSERT_EQ(2, write(acc_fd, "RD", 2));
     usleep(100000);
     bthread_id_join(cntl.call_id());
@@ -834,8 +845,8 @@ TEST_F(RdmaTest, server_close_during_magic_str) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     ASSERT_EQ(2, write(acc_fd, "RD", 2));
     usleep(100000);
     close(acc_fd);
@@ -872,8 +883,8 @@ TEST_F(RdmaTest, server_hello_invalid_magic_str) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     ASSERT_EQ(4, write(acc_fd, "ABCD", 4));
     usleep(100000);
     ASSERT_EQ(rdma::RdmaEndpoint::FAILED, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
@@ -908,8 +919,8 @@ TEST_F(RdmaTest, server_miss_during_hello_msg) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     ASSERT_EQ(4, write(acc_fd, "RDMA", 4));
     ASSERT_EQ(2, write(acc_fd, "00", 2));
     bthread_id_join(cntl.call_id());
@@ -943,8 +954,8 @@ TEST_F(RdmaTest, server_close_during_hello_msg) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     ASSERT_EQ(4, write(acc_fd, "RDMA", 4));
     ASSERT_EQ(2, write(acc_fd, "00", 2));
     close(acc_fd);
@@ -981,13 +992,13 @@ TEST_F(RdmaTest, server_hello_invalid_msg_len) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     memcpy(data, "RDMA", 4);
     uint16_t len = butil::HostToNet16(35);
     memcpy(data + 4, &len, 2);
     memset(data + 6, 0, 32);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, write(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     usleep(100000);
     ASSERT_EQ(rdma::RdmaEndpoint::FAILED, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
     bthread_id_join(cntl.call_id());
@@ -1021,13 +1032,13 @@ TEST_F(RdmaTest, server_hello_invalid_version) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     memcpy(data, "RDMA", 4);
-    uint16_t len = butil::HostToNet16(rdma::v2_wire::HELLO_MSG_LEN_MIN);
+    uint16_t len = butil::HostToNet16(rdma::HELLO_V2_MSG_LEN_MIN);
     memcpy(data + 4, &len, 2);
     memset(data + 6, 0, 32);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, write(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     usleep(100000);
     ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
     ASSERT_EQ(4, read(acc_fd, data, 4));
@@ -1064,11 +1075,11 @@ TEST_F(RdmaTest, server_hello_invalid_sq_rq_size) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
 
     rdma::v2_wire::HelloMessage msg{};
-    msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN;
+    msg.msg_len = rdma::HELLO_V2_MSG_LEN_MIN;
     msg.hello_ver = 1;
     msg.impl_ver = 1;
     msg.sq_size = 0;
@@ -1078,7 +1089,7 @@ TEST_F(RdmaTest, server_hello_invalid_sq_rq_size) {
     msg.gid = rdma::GetRdmaGid();
     memcpy(data, "RDMA", 4);
     msg.Serialize(data + 4);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, write(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
 
     usleep(100000);
     ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
@@ -1116,13 +1127,13 @@ TEST_F(RdmaTest, server_miss_after_ack) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
 
     rdma::v2_wire::HelloMessage msg{};
-    msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    msg.msg_len = rdma::HELLO_V2_MSG_LEN_MIN;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
     msg.sq_size = 16;
     msg.rq_size = 16;
     msg.block_size = 8192;
@@ -1130,7 +1141,7 @@ TEST_F(RdmaTest, server_miss_after_ack) {
     msg.gid = rdma::GetRdmaGid();
     memcpy(data, "RDMA", 4);
     msg.Serialize(data + 4);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, write(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
 
     usleep(100000);
     ASSERT_EQ(rdma::RdmaEndpoint::ESTABLISHED, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
@@ -1168,13 +1179,13 @@ TEST_F(RdmaTest, server_close_after_ack) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
 
     rdma::v2_wire::HelloMessage msg{};
-    msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    msg.msg_len = rdma::HELLO_V2_MSG_LEN_MIN;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
     msg.sq_size = 16;
     msg.rq_size = 16;
     msg.block_size = 8192;
@@ -1182,7 +1193,7 @@ TEST_F(RdmaTest, server_close_after_ack) {
     msg.gid = rdma::GetRdmaGid();
     memcpy(data, "RDMA", 4);
     msg.Serialize(data + 4);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, write(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
 
     usleep(100000);
     ASSERT_EQ(rdma::RdmaEndpoint::ESTABLISHED, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
@@ -1221,13 +1232,13 @@ TEST_F(RdmaTest, server_send_data_on_tcp_after_ack) {
 
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
 
     rdma::v2_wire::HelloMessage msg{};
-    msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    msg.msg_len = rdma::HELLO_V2_MSG_LEN_MIN;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
     msg.sq_size = 16;
     msg.rq_size = 16;
     msg.block_size = 8192;
@@ -1235,11 +1246,11 @@ TEST_F(RdmaTest, server_send_data_on_tcp_after_ack) {
     msg.gid = rdma::GetRdmaGid();
     memcpy(data, "RDMA", 4);
     msg.Serialize(data + 4);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, write(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
 
     usleep(100000);
     ASSERT_EQ(rdma::RdmaEndpoint::ESTABLISHED, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, write(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     bthread_id_join(cntl.call_id());
 
     ASSERT_EQ(EPROTO, cntl.ErrorCode());
@@ -1272,26 +1283,26 @@ TEST_F(RdmaTest, v2_client_hello_bytes_baseline) {
     butil::fd_guard acc_fd(accept(sockfd, NULL, NULL));
     ASSERT_TRUE(acc_fd >= 0);
 
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(acc_fd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
 
     // [0..4) magic
     ASSERT_EQ(0, memcmp(data, "RDMA", 4));
     // [4..6) msg_len, big-endian uint16 == 40
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN,
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN,
               (size_t)(((uint16_t)data[4] << 8) | (uint16_t)data[5]));
-    // [6..8) hello_ver, big-endian uint16 == rdma::RDMA_HELLO_V2_VERSION
-    ASSERT_EQ(rdma::RDMA_HELLO_V2_VERSION,
+    // [6..8) hello_ver, big-endian uint16 == rdma::HELLO_V2_VERSION
+    ASSERT_EQ(rdma::HELLO_V2_VERSION,
               (uint16_t)(((uint16_t)data[6] << 8) | (uint16_t)data[7]));
-    // [8..10) impl_ver, big-endian uint16 == rdma::RDMA_IMPL_V2_VERSION
-    ASSERT_EQ(rdma::RDMA_IMPL_V2_VERSION,
+    // [8..10) impl_ver, big-endian uint16 == rdma::IMPL_V2_VERSION
+    ASSERT_EQ(rdma::IMPL_V2_VERSION,
               (uint16_t)(((uint16_t)data[8] << 8) | (uint16_t)data[9]));
 
     rdma::v2_wire::HelloMessage msg{};
     msg.Deserialize(data + 4);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, msg.msg_len);
-    ASSERT_EQ(rdma::RDMA_HELLO_V2_VERSION, msg.hello_ver);
-    ASSERT_EQ(rdma::RDMA_IMPL_V2_VERSION,  msg.impl_ver);
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, msg.msg_len);
+    ASSERT_EQ(rdma::HELLO_V2_VERSION, msg.hello_ver);
+    ASSERT_EQ(rdma::IMPL_V2_VERSION,  msg.impl_ver);
 
     bthread_id_join(cntl.call_id());
 }
@@ -1313,39 +1324,39 @@ TEST_F(RdmaTest, v2_server_hello_bytes_baseline) {
 
     // Send a well-formed v2 hello so the server enters S_ACK_WAIT.
     rdma::v2_wire::HelloMessage msg{};
-    msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    msg.msg_len = rdma::HELLO_V2_MSG_LEN_MIN;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
     msg.sq_size = 16;
     msg.rq_size = 16;
     msg.block_size = 8192;
     msg.qp_num = 0;
     msg.gid = rdma::GetRdmaGid();
 
-    uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN];
+    uint8_t data[rdma::HELLO_V2_MSG_LEN_MIN];
     memcpy(data, "RDMA", 4);
     msg.Serialize(data + 4);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(sockfd, data, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, write(sockfd, data, 
rdma::HELLO_V2_MSG_LEN_MIN));
     usleep(100000);
     ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
 
     // Read server's reply hello and assert its byte-level layout.
-    uint8_t reply[rdma::v2_wire::HELLO_MSG_LEN_MIN];
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(sockfd, reply, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    uint8_t reply[rdma::HELLO_V2_MSG_LEN_MIN];
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, read(sockfd, reply, 
rdma::HELLO_V2_MSG_LEN_MIN));
 
     ASSERT_EQ(0, memcmp(reply, "RDMA", 4));
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN,
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN,
               (size_t)(((uint16_t)reply[4] << 8) | (uint16_t)reply[5]));
-    ASSERT_EQ(rdma::RDMA_HELLO_V2_VERSION,
+    ASSERT_EQ(rdma::HELLO_V2_VERSION,
               (uint16_t)(((uint16_t)reply[6] << 8) | (uint16_t)reply[7]));
-    ASSERT_EQ(rdma::RDMA_IMPL_V2_VERSION,
+    ASSERT_EQ(rdma::IMPL_V2_VERSION,
               (uint16_t)(((uint16_t)reply[8] << 8) | (uint16_t)reply[9]));
 
     rdma::v2_wire::HelloMessage reply_msg{};
     reply_msg.Deserialize(reply + 4);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, reply_msg.msg_len);
-    ASSERT_EQ(rdma::RDMA_HELLO_V2_VERSION, reply_msg.hello_ver);
-    ASSERT_EQ(rdma::RDMA_IMPL_V2_VERSION,  reply_msg.impl_ver);
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, reply_msg.msg_len);
+    ASSERT_EQ(rdma::HELLO_V2_VERSION, reply_msg.hello_ver);
+    ASSERT_EQ(rdma::IMPL_V2_VERSION,  reply_msg.impl_ver);
 
     // Drive the server into FALLBACK_TCP via ACK flags=0 so the test ends
     // cleanly without requiring real RDMA hardware.
@@ -1379,8 +1390,8 @@ TEST_F(RdmaTest, v2_server_drains_tail_then_reads_ack) {
     // Build a v2 hello with msg_len = 48 (40 base + 8B zero tail).
     rdma::v2_wire::HelloMessage msg{};
     msg.msg_len = 48;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
     msg.sq_size = 16;
     msg.rq_size = 16;
     msg.block_size = 8192;
@@ -1423,23 +1434,23 @@ TEST_F(RdmaTest, v2_server_rejects_oversized_msg_len) {
     ASSERT_TRUE(s != NULL);
     ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, 
static_cast<RdmaTransport*>(s->_transport.get())->_rdma_ep->_state);
 
-    // Build a v2 hello with msg_len = 4097 (HELLO_MSG_LEN_MAX + 1).
+    // Build a v2 hello with msg_len = 4097 (HELLO_V2_MSG_LEN_MAX + 1).
     // We only send the 40B base; the server must reject before reading
     // (and definitely before attempting to drain) any "tail".
     rdma::v2_wire::HelloMessage msg{};
     msg.msg_len = 4097;
-    msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION;
-    msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION;
+    msg.hello_ver = rdma::HELLO_V2_VERSION;
+    msg.impl_ver = rdma::IMPL_V2_VERSION;
     msg.sq_size = 16;
     msg.rq_size = 16;
     msg.block_size = 8192;
     msg.qp_num = 0;
     msg.gid = rdma::GetRdmaGid();
 
-    uint8_t buf[rdma::v2_wire::HELLO_MSG_LEN_MIN];
+    uint8_t buf[rdma::HELLO_V2_MSG_LEN_MIN];
     memcpy(buf, "RDMA", 4);
     msg.Serialize(buf + 4);
-    ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(sockfd, buf, 
rdma::v2_wire::HELLO_MSG_LEN_MIN));
+    ASSERT_EQ(rdma::HELLO_V2_MSG_LEN_MIN, write(sockfd, buf, 
rdma::HELLO_V2_MSG_LEN_MIN));
     usleep(100000);
 
 
@@ -1659,7 +1670,9 @@ TEST_F(RdmaTest, v3_server_rejects_oversized_pb_size) {
 
     uint8_t buf[8];
     memcpy(buf, "RDM3", 4);
-    uint32_t pb_size_be = butil::HostToNet32(4097);
+    // pb_size just above the allowed maximum -> rejected.
+    uint32_t pb_size_be =
+        butil::HostToNet32(static_cast<uint32_t>(rdma::HELLO_V3_MAX_PB_SIZE + 
1));
     memcpy(buf + 4, &pb_size_be, 4);
     ASSERT_EQ(8, write(sockfd, buf, 8));
     usleep(100000);
@@ -1958,7 +1971,7 @@ TEST_P(RdmaRpcTest, rdma_client_to_tcp_server) {
     ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done);
     usleep(100000);
     bthread_id_join(cntl.call_id());
-    ASSERT_EQ(EEOF, cntl.ErrorCode());
+    ASSERT_FALSE(cntl.Failed());
 
     StopServer();
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to