1use std::io;
4
5use bytes::{Buf, BufMut, Bytes, BytesMut};
6
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub enum BackendReplication {
10 XLogData {
12 wal_start: u64,
14 wal_end: u64,
16 server_time: i64,
18 data: Bytes,
20 },
21 PrimaryKeepalive {
23 wal_end: u64,
25 server_time: i64,
27 reply_requested: bool,
29 },
30 Unknown {
32 tag: u8,
34 body: Bytes,
36 },
37}
38
39#[derive(Clone, Debug, Eq, PartialEq)]
40pub enum FrontendReplication {
42 StandbyStatus {
44 written: u64,
46 flushed: u64,
48 applied: u64,
50 client_time: i64,
52 reply_requested: bool,
54 },
55 HotStandbyFeedback {
57 client_time: i64,
59 xmin: u32,
61 xmin_epoch: u32,
63 catalog_xmin: u32,
65 catalog_xmin_epoch: u32,
67 },
68 Unknown {
70 tag: u8,
72 body: Bytes,
74 },
75}
76
77impl BackendReplication {
78 pub fn decode(mut payload: Bytes) -> io::Result<Self> {
84 let tag = take_tag(&mut payload)?;
85 match tag {
86 b'w' => {
87 require(&payload, 24, "truncated XLogData")?;
88 Ok(Self::XLogData {
89 wal_start: payload.get_u64(),
90 wal_end: payload.get_u64(),
91 server_time: payload.get_i64(),
92 data: payload,
93 })
94 }
95 b'k' => {
96 require_exact(&payload, 17, "invalid primary keepalive length")?;
97 let wal_end = payload.get_u64();
98 let server_time = payload.get_i64();
99 let reply_requested = take_bool(payload.get_u8())?;
100 Ok(Self::PrimaryKeepalive {
101 wal_end,
102 server_time,
103 reply_requested,
104 })
105 }
106 tag => Ok(Self::Unknown { tag, body: payload }),
107 }
108 }
109
110 #[must_use]
111 pub fn encode(&self) -> Bytes {
113 let mut output = BytesMut::new();
114 match self {
115 Self::XLogData {
116 wal_start,
117 wal_end,
118 server_time,
119 data,
120 } => {
121 output.put_u8(b'w');
122 output.put_u64(*wal_start);
123 output.put_u64(*wal_end);
124 output.put_i64(*server_time);
125 output.extend_from_slice(data);
126 }
127 Self::PrimaryKeepalive {
128 wal_end,
129 server_time,
130 reply_requested,
131 } => {
132 output.put_u8(b'k');
133 output.put_u64(*wal_end);
134 output.put_i64(*server_time);
135 output.put_u8(u8::from(*reply_requested));
136 }
137 Self::Unknown { tag, body } => {
138 output.put_u8(*tag);
139 output.extend_from_slice(body);
140 }
141 }
142 output.freeze()
143 }
144}
145
146impl FrontendReplication {
147 pub fn decode(mut payload: Bytes) -> io::Result<Self> {
153 let tag = take_tag(&mut payload)?;
154 match tag {
155 b'r' => {
156 require_exact(&payload, 33, "invalid standby status length")?;
157 let written = payload.get_u64();
158 let flushed = payload.get_u64();
159 let applied = payload.get_u64();
160 let client_time = payload.get_i64();
161 let reply_requested = take_bool(payload.get_u8())?;
162 Ok(Self::StandbyStatus {
163 written,
164 flushed,
165 applied,
166 client_time,
167 reply_requested,
168 })
169 }
170 b'h' => {
171 require_exact(&payload, 24, "invalid hot standby feedback length")?;
172 Ok(Self::HotStandbyFeedback {
173 client_time: payload.get_i64(),
174 xmin: payload.get_u32(),
175 xmin_epoch: payload.get_u32(),
176 catalog_xmin: payload.get_u32(),
177 catalog_xmin_epoch: payload.get_u32(),
178 })
179 }
180 tag => Ok(Self::Unknown { tag, body: payload }),
181 }
182 }
183
184 #[must_use]
185 pub fn encode(&self) -> Bytes {
187 let mut output = BytesMut::new();
188 match self {
189 Self::StandbyStatus {
190 written,
191 flushed,
192 applied,
193 client_time,
194 reply_requested,
195 } => {
196 output.put_u8(b'r');
197 output.put_u64(*written);
198 output.put_u64(*flushed);
199 output.put_u64(*applied);
200 output.put_i64(*client_time);
201 output.put_u8(u8::from(*reply_requested));
202 }
203 Self::HotStandbyFeedback {
204 client_time,
205 xmin,
206 xmin_epoch,
207 catalog_xmin,
208 catalog_xmin_epoch,
209 } => {
210 output.put_u8(b'h');
211 output.put_i64(*client_time);
212 output.put_u32(*xmin);
213 output.put_u32(*xmin_epoch);
214 output.put_u32(*catalog_xmin);
215 output.put_u32(*catalog_xmin_epoch);
216 }
217 Self::Unknown { tag, body } => {
218 output.put_u8(*tag);
219 output.extend_from_slice(body);
220 }
221 }
222 output.freeze()
223 }
224}
225
226fn take_tag(payload: &mut Bytes) -> io::Result<u8> {
227 if payload.is_empty() {
228 Err(invalid("empty replication payload"))
229 } else {
230 Ok(payload.get_u8())
231 }
232}
233
234fn take_bool(value: u8) -> io::Result<bool> {
235 match value {
236 0 => Ok(false),
237 1 => Ok(true),
238 _ => Err(invalid("invalid replication Boolean")),
239 }
240}
241
242fn require(payload: &Bytes, minimum: usize, message: &'static str) -> io::Result<()> {
243 if payload.len() < minimum {
244 Err(invalid(message))
245 } else {
246 Ok(())
247 }
248}
249
250fn require_exact(payload: &Bytes, length: usize, message: &'static str) -> io::Result<()> {
251 if payload.len() == length {
252 Ok(())
253 } else {
254 Err(invalid(message))
255 }
256}
257
258fn invalid(message: &'static str) -> io::Error {
259 io::Error::new(io::ErrorKind::InvalidData, message)
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn physical_replication_messages_round_trip() {
268 let backend = [
269 BackendReplication::XLogData {
270 wal_start: 10,
271 wal_end: 20,
272 server_time: -30,
273 data: Bytes::from_static(b"wal"),
274 },
275 BackendReplication::PrimaryKeepalive {
276 wal_end: 40,
277 server_time: 50,
278 reply_requested: true,
279 },
280 BackendReplication::Unknown {
281 tag: b'z',
282 body: Bytes::from_static(b"extension"),
283 },
284 ];
285 for message in backend {
286 assert_eq!(
287 BackendReplication::decode(message.encode()).unwrap(),
288 message
289 );
290 }
291
292 let frontend = [
293 FrontendReplication::StandbyStatus {
294 written: 10,
295 flushed: 20,
296 applied: 30,
297 client_time: 40,
298 reply_requested: false,
299 },
300 FrontendReplication::HotStandbyFeedback {
301 client_time: 50,
302 xmin: 60,
303 xmin_epoch: 70,
304 catalog_xmin: 80,
305 catalog_xmin_epoch: 90,
306 },
307 FrontendReplication::Unknown {
308 tag: b'z',
309 body: Bytes::from_static(b"extension"),
310 },
311 ];
312 for message in frontend {
313 assert_eq!(
314 FrontendReplication::decode(message.encode()).unwrap(),
315 message
316 );
317 }
318 }
319
320 #[test]
321 fn known_messages_reject_invalid_shapes() {
322 assert!(BackendReplication::decode(Bytes::from_static(b"kshort")).is_err());
323 let mut invalid_bool = BytesMut::new();
324 invalid_bool.put_u8(b'k');
325 invalid_bool.extend_from_slice(&[0; 16]);
326 invalid_bool.put_u8(2);
327 assert!(BackendReplication::decode(invalid_bool.freeze()).is_err());
328 }
329}