├── .gitignore ├── LICENSE ├── Makefile ├── README.rst ├── authentication-request-md5.dot ├── authentication-request.dot ├── basic-message-flow.msc ├── bind-1.dot ├── bind-2.dot ├── bind-3.dot ├── bind-4.dot ├── cancel-request.dot ├── command-complete.dot ├── copy-data.dot ├── copy-flow.msc ├── copy-in.dot ├── data-row.dot ├── describe.dot ├── error-response.dot ├── execute.dot ├── extended-query-protocol-1.msc ├── extended-query-protocol-2.msc ├── extended-query-protocol-adv-1.msc ├── extended-query-protocol-adv-2.msc ├── function-call-1.dot ├── function-call-2.dot ├── function-call-3.dot ├── function-call-4.dot ├── notification-response.dot ├── parameter-description.dot ├── parameter-status.dot ├── parse.dot ├── postgres-on-the-wire.tex ├── query.dot ├── ready-for-query.dot ├── regular-packet.dot ├── row-description-bottom.dot ├── row-description-top.dot ├── row-description.dot ├── simple-query-protocol.msc ├── ssl-message-flow.msc ├── ssl-negotiation.dot ├── startup-packet-detail.dot ├── startup-packet.dot ├── sync.dot └── the-wire.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | authentication-request-md5.pdf 2 | authentication-request.pdf 3 | basic-message-flow.pdf 4 | bind-1.pdf 5 | bind-2.pdf 6 | bind-3.pdf 7 | bind-4.pdf 8 | cancel-request.pdf 9 | command-complete.pdf 10 | copy-data.pdf 11 | copy-flow.pdf 12 | data-row.pdf 13 | describe.pdf 14 | error-response.pdf 15 | execute.pdf 16 | extended-query-protocol-1.pdf 17 | extended-query-protocol-2.pdf 18 | extended-query-protocol-adv-1.pdf 19 | extended-query-protocol-adv-2.pdf 20 | function-call-1.pdf 21 | function-call-2.pdf 22 | function-call-3.pdf 23 | function-call-4.pdf 24 | notification-response.pdf 25 | parameter-description.pdf 26 | parameter-status.pdf 27 | parse.pdf 28 | postgres-on-the-wire.aux 29 | postgres-on-the-wire.log 30 | postgres-on-the-wire.nav 31 | postgres-on-the-wire.out 32 | postgres-on-the-wire.pdf 33 | postgres-on-the-wire.snm 34 | postgres-on-the-wire.toc 35 | postgres-on-the-wire.vrb 36 | query.pdf 37 | ready-for-query.pdf 38 | regular-packet.pdf 39 | row-description-bottom.pdf 40 | row-description-top.pdf 41 | row-description.pdf 42 | simple-query-protocol.pdf 43 | ssl-message-flow.pdf 44 | ssl-negotiation.pdf 45 | startup-packet-detail.pdf 46 | startup-packet.pdf 47 | sync.pdf 48 | copy-in.pdf 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Jan Urbanski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCUMENT = postgres-on-the-wire 2 | 3 | EXTRA_CLEAN = $(wildcard *.toc *.aux *.pdf *.log \ 4 | *.out *.nav *.snm *.vrb *.bbl *.blg *.eps) 5 | 6 | DIAGRAM_DOTS = $(wildcard *.dot) 7 | DIAGRAM_PDFS = $(patsubst %.dot,%.pdf,$(DIAGRAM_DOTS)) 8 | 9 | FLOWCHART_MSC = $(wildcard *.msc) 10 | FLOWCHART_PDFS = $(patsubst %.msc,%.pdf,$(FLOWCHART_MSC)) 11 | 12 | .SECONDARY: $(DIAGRAM_PDFS) $(FLOWCHART_PDFS) 13 | 14 | all: $(DOCUMENT).pdf 15 | 16 | clean: 17 | rm -f $(EXTRA_CLEAN) 18 | 19 | full_%.pdf: %.dot 20 | dot -Tpdf -o $@ $< 21 | 22 | full_%.eps: %.msc 23 | mscgen -Teps -o $@ $< 24 | 25 | %.pdf: %.eps 26 | epstopdf -o $@ $< 27 | 28 | %.pdf: full_%.pdf 29 | pdfcrop $< $@ 30 | 31 | %.pdf: %.tex $(DIAGRAM_PDFS) $(FLOWCHART_PDFS) 32 | pdflatex $< 33 | pdflatex $< 34 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Source files for a PGCon 2014 talk about the Postgres wire protocol. 2 | 3 | To build, run `make`. You need LaTeX with the beamer class, graphviz and mscgen 4 | installed. 5 | -------------------------------------------------------------------------------- /authentication-request-md5.dot: -------------------------------------------------------------------------------- 1 | graph AuthenticationRequestMD5 { 2 | label="AuthenticationRequestMD5"; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'R'|int32 len|int32 method|char[4] salt"]; 7 | } 8 | -------------------------------------------------------------------------------- /authentication-request.dot: -------------------------------------------------------------------------------- 1 | graph AuthenticationRequest { 2 | label="AuthenticationRequest"; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'R'|int32 len|int32 method|optional other"]; 7 | } 8 | -------------------------------------------------------------------------------- /basic-message-flow.msc: -------------------------------------------------------------------------------- 1 | msc { 2 | arcgradient=10; 3 | 4 | a [label="Client"], b [label="Server"]; 5 | 6 | a=>>b [label="startup packet\n\n"]; 7 | 8 | a<<=b [label="auth request | auth OK\n\n"]; 9 | 10 | a=>>b [label="(optional) PasswordMessage\n\n"]; 11 | 12 | |||; 13 | 14 | b=>>a [label="ParameterStatus\n\n"]; 15 | b=>>a [label="ParameterStatus\n\n"]; 16 | b=>>a [label="ParameterStatus\n\n"]; 17 | 18 | b=>>a [label="BackendKeyData\n\n"]; 19 | 20 | b=>>a [label="ReadyForQuery\n\n"]; 21 | } 22 | -------------------------------------------------------------------------------- /bind-1.dot: -------------------------------------------------------------------------------- 1 | graph Bind { 2 | label=Bind; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'B'|int32 len|str portal|str stmt"]; 7 | } 8 | -------------------------------------------------------------------------------- /bind-2.dot: -------------------------------------------------------------------------------- 1 | graph Bind { 2 | node [shape=record]; 3 | struct1 [label="int16 numformats|int16 format|..."]; 4 | } 5 | -------------------------------------------------------------------------------- /bind-3.dot: -------------------------------------------------------------------------------- 1 | graph Bind { 2 | node [shape=record]; 3 | struct1 [label="int16 numparams|int32 paramlen|char[paramlen] param|..."]; 4 | } 5 | -------------------------------------------------------------------------------- /bind-4.dot: -------------------------------------------------------------------------------- 1 | graph Bind { 2 | node [shape=record]; 3 | struct1 [label="int16 numresults|int16 format|..."]; 4 | } 5 | -------------------------------------------------------------------------------- /cancel-request.dot: -------------------------------------------------------------------------------- 1 | graph CancelRequest { 2 | label="Cancel request"; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="int32 len|int32 cancelcode|int32 pid|int32 secret"]; 7 | } 8 | -------------------------------------------------------------------------------- /command-complete.dot: -------------------------------------------------------------------------------- 1 | graph CommandComplete { 2 | label=CommandComplete; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'C'|int32 len|str tag"]; 7 | } 8 | -------------------------------------------------------------------------------- /copy-data.dot: -------------------------------------------------------------------------------- 1 | graph CopyData { 2 | label=CopyData; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'d'|int32 len|data"]; 7 | } 8 | -------------------------------------------------------------------------------- /copy-flow.msc: -------------------------------------------------------------------------------- 1 | msc { 2 | arcgradient=10; 3 | 4 | a [label="Client"], b [label="Server"]; 5 | 6 | a=>>b [label="Query - 'COPY tab FROM STDIN'\n\n"]; 7 | 8 | a<<=b [label="CopyInResponse\n\n"]; 9 | 10 | |||; 11 | 12 | a=>>b [label="CopyData\n\n"]; 13 | a=>>b [label="CopyData\n\n"]; 14 | a=>>b [label="CopyData\n\n"]; 15 | a=>>b [label="CopyComplete\n\n"]; 16 | 17 | |||; 18 | 19 | b=>>a [label="CommandComplete\n\n"]; 20 | b=>>a [label="ReadyForQuery\n\n"]; 21 | } 22 | -------------------------------------------------------------------------------- /copy-in.dot: -------------------------------------------------------------------------------- 1 | graph CopyInResponse { 2 | label="CopyInResponse"; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'G'|int32 len|int8 format|int16 numfields|int16 format|..."]; 7 | } 8 | -------------------------------------------------------------------------------- /data-row.dot: -------------------------------------------------------------------------------- 1 | graph DataRow { 2 | label=DataRow; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'D'|int32 len|int16 numfields|int32 fieldlen|char[fieldlen] data|..."]; 7 | } 8 | -------------------------------------------------------------------------------- /describe.dot: -------------------------------------------------------------------------------- 1 | graph Describe { 2 | label=Describe; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'D'|int32 len|'S' or 'P'|str name"]; 7 | } 8 | -------------------------------------------------------------------------------- /error-response.dot: -------------------------------------------------------------------------------- 1 | graph ErrorResponse { 2 | label="ErrorResponse"; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'E'|int32 len|char code|str value|\\0|char code|str value|\\0|...|\\0"]; 7 | } 8 | -------------------------------------------------------------------------------- /execute.dot: -------------------------------------------------------------------------------- 1 | graph Execute { 2 | label=Execute; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'E'|int32 len|str portal|int32 rowlimit"]; 7 | } 8 | -------------------------------------------------------------------------------- /extended-query-protocol-1.msc: -------------------------------------------------------------------------------- 1 | msc { 2 | arcgradient=10; 3 | 4 | a [label="Client"], b [label="Server"]; 5 | 6 | a=>>b [label="Parse\n\n"]; 7 | a=>>b [label="Bind\n\n"]; 8 | a=>>b [label="Describe\n\n"]; 9 | a=>>b [label="Execute\n\n"]; 10 | a=>>b [label="Sync\n\n"]; 11 | } 12 | -------------------------------------------------------------------------------- /extended-query-protocol-2.msc: -------------------------------------------------------------------------------- 1 | msc { 2 | arcgradient=10; 3 | 4 | a [label="Client"], b [label="Server"]; 5 | 6 | a<<=b [label="ParseOK\n\n"]; 7 | a<<=b [label="BindOK\n\n"]; 8 | a<<=b [label="RowDescription\n\n"]; 9 | a<<=b [label="DataRow\n\n"]; 10 | a<<=b [label="DataRow\n\n"]; 11 | a<<=b [label="DataRow\n\n"]; 12 | a<<=b [label="CommandComplete\n\n"]; 13 | a<<=b [label="ReadyForQuery\n\n"]; 14 | } 15 | -------------------------------------------------------------------------------- /extended-query-protocol-adv-1.msc: -------------------------------------------------------------------------------- 1 | msc { 2 | arcgradient=10; 3 | 4 | a [label="Client"], b [label="Server"]; 5 | 6 | a=>>b [label="Parse\n\n"]; 7 | a=>>b [label="Sync\n\n"]; 8 | 9 | |||; 10 | 11 | a<<=b [label="ParseOK\n\n"]; 12 | a<<=b [label="ReadyForQuery\n\n"]; 13 | } 14 | -------------------------------------------------------------------------------- /extended-query-protocol-adv-2.msc: -------------------------------------------------------------------------------- 1 | msc { 2 | arcgradient=10; 3 | 4 | a [label="Client"], b [label="Server"]; 5 | 6 | a=>>b [label="Bind\n\n"]; 7 | a=>>b [label="Execute\n\n"]; 8 | a=>>b [label="Bind\n\n"]; 9 | a=>>b [label="Execute\n\n"]; 10 | a=>>b [label="Sync\n\n"]; 11 | 12 | |||; 13 | 14 | a<<=b [label="BindOK\n\n"]; 15 | a<<=b [label="CommandComplete\n\n"]; 16 | a<<=b [label="BindOK\n\n"]; 17 | a<<=b [label="CommandComplete\n\n"]; 18 | a<<=b [label="ReadyForQuery\n\n"]; 19 | } 20 | -------------------------------------------------------------------------------- /function-call-1.dot: -------------------------------------------------------------------------------- 1 | graph FunctionCall1 { 2 | label=FunctionCall; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'F'|int32 len|int32 funoid"]; 7 | } 8 | -------------------------------------------------------------------------------- /function-call-2.dot: -------------------------------------------------------------------------------- 1 | graph FunctionCall2 { 2 | node [shape=record]; 3 | struct1 [label="int16 numformats|int16 format|..."]; 4 | } 5 | -------------------------------------------------------------------------------- /function-call-3.dot: -------------------------------------------------------------------------------- 1 | graph FunctionCall3 { 2 | node [shape=record]; 3 | struct1 [label="int16 numparams|int32 paramlen|char[paramlen] param|...|int16 resultformat"]; 4 | } 5 | -------------------------------------------------------------------------------- /function-call-4.dot: -------------------------------------------------------------------------------- 1 | graph FunctionCall4 { 2 | node [shape=record]; 3 | struct1 [label="int16 numparams|int32 paramlen|char[paramlen] param|..."]; 4 | } 5 | -------------------------------------------------------------------------------- /notification-response.dot: -------------------------------------------------------------------------------- 1 | graph NotificationResponse { 2 | label=NotificationResponse; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'A'|int32 len|int32 pid|str channel|str payload"]; 7 | } 8 | -------------------------------------------------------------------------------- /parameter-description.dot: -------------------------------------------------------------------------------- 1 | graph ParameterDescription { 2 | label=ParameterDescription; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'t'|int32 len|int16 numparams|int32 paramoid|..."] ; 7 | } 8 | -------------------------------------------------------------------------------- /parameter-status.dot: -------------------------------------------------------------------------------- 1 | graph ParameterStatus { 2 | label="ParameterStatus"; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'S'|int32 len|str name|str value"]; 7 | } 8 | -------------------------------------------------------------------------------- /parse.dot: -------------------------------------------------------------------------------- 1 | graph Parse { 2 | label=Parse; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'P'|int32 len|str stmt|str query|int16 numparams|int32 paramoid|..."]; 7 | } 8 | -------------------------------------------------------------------------------- /postgres-on-the-wire.tex: -------------------------------------------------------------------------------- 1 | \documentclass{beamer} 2 | 3 | \usepackage[utf8]{inputenc} 4 | 5 | \usetheme{CambridgeUS} 6 | \usecolortheme{rose} 7 | 8 | \useoutertheme{infolines} 9 | \setbeamertemplate{blocks}[default] 10 | \setbeamertemplate{items}[triangle] 11 | \setbeamertemplate{enumerate items}[square] 12 | \setbeamertemplate{sections/subsections in toc}[square] 13 | 14 | \newcommand{\mscdiagram}[2][1]{ 15 | \begin{center} 16 | \includegraphics[height=0.7\paperheight,width=#1\paperwidth,keepaspectratio]{#2} 17 | \end{center} 18 | } 19 | 20 | \newcommand{\fullframepicture}[1]{ 21 | \mode 22 | { 23 | } 24 | \mode{\usebackgroundtemplate{}} 25 | } 26 | 27 | \AtBeginSubsection[] 28 | { 29 | \begin{frame}{Outline} 30 | \tableofcontents[sectionstyle=show/shaded,subsectionstyle=show/shaded/hide] 31 | \end{frame} 32 | } 33 | 34 | \title{Postgres on the wire} 35 | \subtitle{A look at the PostgreSQL wire protocol} 36 | \author[Jan Urbański]{Jan Urbański \\ \texttt{j.urbanski@wulczer.org}} 37 | \institute{Ducksboard} 38 | \date[PGCon 2014]{PGCon 2014, Ottawa, May 23} 39 | 40 | \begin{document} 41 | 42 | \frame{\titlepage} 43 | 44 | \begin{frame}[fragile] 45 | \frametitle{For those following at home} 46 | 47 | \begin{block}{Getting the slides} 48 | \begin{semiverbatim} 49 | \$ wget http://wulczer.org/postgres-on-the-wire.pdf 50 | \end{semiverbatim} 51 | \end{block} 52 | 53 | \begin{block}{Getting the source} 54 | \begin{semiverbatim} 55 | \$ https://github.com/wulczer/postgres-on-the-wire 56 | \end{semiverbatim} 57 | \end{block} 58 | \end{frame} 59 | 60 | \usebackgroundtemplate{ 61 | \includegraphics[width=\paperwidth,height=\paperheight]{the-wire.jpg} 62 | } 63 | \begin{frame}[plain] 64 | \end{frame} 65 | \usebackgroundtemplate{} 66 | 67 | \begin{frame} 68 | \tableofcontents 69 | \end{frame} 70 | 71 | \section{Protocol basics} 72 | \subsection{Frame format} 73 | 74 | \begin{frame} 75 | \frametitle{Protocol versions} 76 | 77 | \begin{itemize} 78 | \item the 2.0 protocol got introduced in 6.4, around 1999 79 | \begin{itemize} 80 | \item protocol versioning got added in the previous release 81 | \end{itemize} 82 | \item the 3.0 got introduced in 7.4, in 2003 83 | \item the server \alert{still supports} protocol 1.0! 84 | \item 3.0 has some new features 85 | \begin{itemize} 86 | \item extended query protocol 87 | \item COPY improvements 88 | \item overall better frame structure 89 | \end{itemize} 90 | \end{itemize} 91 | \end{frame} 92 | 93 | \begin{frame} 94 | \frametitle{Handling incoming connections} 95 | 96 | Connections are received by the postmaster process, which immediately forks a 97 | new process to deal with them. 98 | 99 | \begin{itemize} 100 | \item any \alert{parsing issues} won't affect the postmaster 101 | \item authentication is done \alert{after} a process is forked 102 | \item closing the connection results in \alert{terminating} the backend 103 | \begin{itemize} 104 | \item but the backend needs to notice that first 105 | \item killing the client might not terminate the running query 106 | \end{itemize} 107 | \end{itemize} 108 | \end{frame} 109 | 110 | \begin{frame} 111 | \frametitle{FEBE frame format} 112 | 113 | Virtually all messages start with an ASCII identifier, followed by length and 114 | payload. 115 | 116 | \begin{center} 117 | \includegraphics[height=1.2cm]{regular-packet} 118 | \end{center} 119 | 120 | The exception is the startup packet, which starts with the length followed by 121 | the protocol version. 122 | 123 | \begin{center} 124 | \includegraphics[height=1.2cm]{startup-packet} 125 | \end{center} 126 | 127 | \end{frame} 128 | 129 | \begin{frame} 130 | \frametitle{Startup packet} 131 | 132 | \begin{center} 133 | \includegraphics[height=1cm]{startup-packet-detail} 134 | \end{center} 135 | 136 | \begin{itemize} 137 | \item the very first bit of data received by the backend is parsed as the 138 | startup packet 139 | \item starts with a 32 bit \alert{protocol version} field 140 | \item in protocol 2.0 it had a fixed length, in 3.0 it's variable length 141 | \item what follows is a list of key/value pairs denoting options 142 | \begin{itemize} 143 | \item some keys, like \texttt{user}, \texttt{database} or \texttt{options} are special 144 | \item the rest are generic GUC options 145 | \end{itemize} 146 | \end{itemize} 147 | \end{frame} 148 | 149 | \begin{frame} 150 | \frametitle{Regular data packet} 151 | 152 | \begin{center} 153 | \includegraphics[height=1cm]{regular-packet} 154 | \end{center} 155 | 156 | \begin{itemize} 157 | \item starts with an ASCII \alert{identifier} 158 | \item a 32 bit message length follows 159 | \begin{itemize} 160 | \item this means you can't send a query that's larger than 1 GB 161 | \end{itemize} 162 | \item interpretation of the payload depends on the identifier 163 | \end{itemize} 164 | \end{frame} 165 | 166 | \subsection{Message flow} 167 | 168 | \begin{frame} 169 | \frametitle{Authentication} 170 | 171 | \begin{center} 172 | \includegraphics[height=1cm]{authentication-request} 173 | \end{center} 174 | 175 | \begin{itemize} 176 | \item if a connection requires authentication, the backend will send a 177 | AuthenticationRequest 178 | \item there are several authentication types that can be demanded 179 | \begin{itemize} 180 | \item plain-text or MD5 password 181 | \item it's \alert{up to the server} to require plain text or encrypted 182 | \item GSSAPI, SSPI 183 | \end{itemize} 184 | \item if no auth is necessary, the server sends AuthenticationOK 185 | \end{itemize} 186 | \end{frame} 187 | 188 | \begin{frame} 189 | \frametitle{Encrypted password exchange} 190 | 191 | \begin{center} 192 | \includegraphics[height=1cm]{authentication-request-md5} 193 | \end{center} 194 | 195 | The MD5 AuthenticationRequest message includes a 4 byte salt. 196 | 197 | \begin{align*} 198 | pwdhash\;&=\;md5(password\;+\;username).hexdigest() \\ 199 | hash\;&=\;'md5'\;+\;md5(pwdhash\;+\;salt).hexdigest() 200 | \end{align*} 201 | 202 | \begin{itemize} 203 | \item using a salt prevents \alert{replay attacks} 204 | \item double-hashing allows the server to only store \alert{hashes} 205 | \end{itemize} 206 | \end{frame} 207 | 208 | \begin{frame} 209 | \frametitle{Parameter status} 210 | 211 | \begin{center} 212 | \includegraphics[height=1cm]{parameter-status} 213 | \end{center} 214 | 215 | \begin{itemize} 216 | \item the server notifies clients about important parameters 217 | \item first batch of ParameterStatus messages is sent on startup 218 | \begin{itemize} 219 | \item some of them are \alert{informative}, like \texttt{server\_version} 220 | \item others are critical for \alert{security}, like \texttt{client\_encoding} 221 | \item others yet are important for the \alert{client}, like \texttt{DateStyle} 222 | \end{itemize} 223 | \item when any of those parameters gets set, the server notifies the client 224 | on the next occasion 225 | \end{itemize} 226 | \end{frame} 227 | 228 | \begin{frame} 229 | \frametitle{Basic message flow} 230 | 231 | \mscdiagram{basic-message-flow} 232 | \end{frame} 233 | 234 | \begin{frame} 235 | \frametitle{Encryption} 236 | 237 | \begin{center} 238 | \includegraphics[height=1cm]{ssl-negotiation} 239 | \end{center} 240 | 241 | \begin{itemize} 242 | \item the startup packet can use a \alert{dummy protocol version} to ask for 243 | SSL support 244 | \item the server responds with with \alert{status byte} or an error message 245 | \item the client can reconnect or abort if the response is negative 246 | \end{itemize} 247 | \end{frame} 248 | 249 | \begin{frame} 250 | \frametitle{SSL message flow} 251 | 252 | \mscdiagram{ssl-message-flow} 253 | \end{frame} 254 | 255 | \begin{frame} 256 | \frametitle{Cancellation} 257 | 258 | \begin{center} 259 | \includegraphics[height=1cm]{cancel-request} 260 | \end{center} 261 | 262 | \begin{itemize} 263 | \item the \alert{cancel key} is transmitted by the server upon connection 264 | \item cancelling queries requires opening \alert{separate connection} 265 | \item another dummy protocol version is sent to ask for cancellation 266 | \item the cancellation message includes the process ID and a 32 bit key 267 | \begin{itemize} 268 | \item theoretically open to \alert{replay attacks}, but can be sent over 269 | SSL 270 | \item libpq \alert{does not}, so most applications will transmit it in the 271 | open 272 | \end{itemize} 273 | \end{itemize} 274 | \end{frame} 275 | 276 | \begin{frame} 277 | \frametitle{Handling errors} 278 | 279 | \begin{center} 280 | \includegraphics[height=1cm]{error-response} 281 | \end{center} 282 | 283 | \begin{itemize} 284 | \item the ErrorResponse message is sent for all kinds of errors 285 | \begin{itemize} 286 | \item both for authentication errors and client errors 287 | \end{itemize} 288 | \item it is a list of key-value fields 289 | \begin{itemize} 290 | \item in 2.0 it was just a string, in 3.0 it has structure 291 | \item example error fields are: message, detail, hint and error position 292 | \item detailed down to the source file and line, great for fingerprinting 293 | \end{itemize} 294 | \end{itemize} 295 | \end{frame} 296 | 297 | \begin{frame} 298 | \frametitle{Tools} 299 | 300 | \begin{itemize} 301 | \item standard tools like tcpdump or tshark work 302 | \item Wireshark has built-in support for deparsing the protocol 303 | \begin{itemize} 304 | \item but only for protocol 3.0 305 | \end{itemize} 306 | \item pgShark is a very nice tool that works with the Postgres protocol 307 | \end{itemize} 308 | \end{frame} 309 | 310 | \begin{frame}[fragile] 311 | \frametitle{pgShark examples} 312 | 313 | \begin{block}{generate a report from a pcap file} 314 | \begin{semiverbatim} 315 | \$ pgs-badger < dump.pcap 316 | \end{semiverbatim} 317 | \end{block} 318 | 319 | \begin{block}{display live protocol info} 320 | \begin{semiverbatim} 321 | \$ pgs-debug --interface eth0 322 | \end{semiverbatim} 323 | \end{block} 324 | 325 | \begin{block}{dump SQL from a 2.0 protocol connection on a nonstandard port} 326 | \begin{semiverbatim} 327 | \$ pgs-sql -2 --port 5433 328 | \end{semiverbatim} 329 | \end{block} 330 | \end{frame} 331 | 332 | \section{Sending queries} 333 | \subsection{Simple protocol} 334 | 335 | \begin{frame} 336 | \frametitle{Binary vs text data} 337 | 338 | \begin{itemize} 339 | \item every type has a text and binary representation 340 | \item depending on \alert{compile-time options}, timestamps are either 64 bit 341 | integers or floating point values 342 | \begin{itemize} 343 | \item this is why \texttt{integer\_datetimes} is sent in ParameterStatus 344 | \end{itemize} 345 | \item the client can choose if they want text or binary data 346 | \item the exact format for each type doesn't seem to be documented anywhere 347 | \begin{itemize} 348 | \item but that's what C code is for :) 349 | \end{itemize} 350 | \end{itemize} 351 | \end{frame} 352 | 353 | \begin{frame} 354 | \frametitle{Simple query protocol} 355 | 356 | \begin{itemize} 357 | \item client sends an SQL command 358 | \item server replies with RowDescription detailing the structure 359 | \begin{itemize} 360 | \item each column has a name 361 | \item the type OID, length and modifier (like \texttt{char(16)}) 362 | \item each column is marked as containing binary or text output 363 | \end{itemize} 364 | \item after that a DataRow message is sent for every row 365 | \item finally, the server sends CommandComplete and ReadyForQuery 366 | \end{itemize} 367 | \end{frame} 368 | 369 | \begin{frame} 370 | \frametitle{Simple query frames} 371 | 372 | \begin{center} 373 | \includegraphics[height=1cm]{query} 374 | \end{center} 375 | 376 | \bigskip 377 | 378 | \hspace{0.4cm}\includegraphics[height=0.9cm]{row-description-top}\;\raisebox{0.2cm}{$+$} \\ 379 | \hspace{0.4cm}\includegraphics[height=0.6cm]{row-description-bottom} \\ 380 | \hspace{0.4cm}\dots 381 | 382 | \bigskip 383 | 384 | \begin{center} 385 | \includegraphics[height=1cm]{data-row} 386 | \end{center} 387 | 388 | \end{frame} 389 | 390 | \begin{frame} 391 | \frametitle{Simple query frames cont.} 392 | 393 | \begin{center} 394 | \includegraphics[height=1cm]{command-complete} 395 | \end{center} 396 | 397 | \bigskip 398 | 399 | \begin{center} 400 | \includegraphics[height=1cm]{ready-for-query} 401 | \end{center} 402 | 403 | \end{frame} 404 | 405 | \begin{frame}[fragile] 406 | \frametitle{Detecting transaction status} 407 | 408 | \begin{itemize} 409 | \item the ReadyForQuery message includes \alert{transaction status} 410 | \item this is useful for things like psql's prompt or, more importantly, 411 | pgbouncer 412 | \item the transaction status only got included in protocol 3.0 413 | \begin{itemize} 414 | \item for 2.0 libpq does string comparison to try and track the status 415 | \end{itemize} 416 | \end{itemize} 417 | 418 | \begin{block}<2->{fe-protocol2.c} 419 | \begin{semiverbatim} 420 | By watching for messages (...), we can do a passable 421 | job of tracking the xact status. BUT: this does not 422 | work at all on 7.3 servers with AUTOCOMMIT OFF. 423 | (Man, was that feature ever a mistake.) Caveat user. 424 | \end{semiverbatim} 425 | \end{block} 426 | \end{frame} 427 | 428 | \begin{frame} 429 | \frametitle{Simple query protocol cont.} 430 | 431 | \begin{itemize} 432 | \item \alert{several commands} can be sent in one query string 433 | \begin{itemize} 434 | \item the server sends one CommandComplete per query 435 | \item in case of errors it's up to the client to figure out which one failed 436 | \end{itemize} 437 | \item sending an empty string yields a special EmptyQueryResponse instead of 438 | CommandComplete 439 | \item the simple protocol \alert{always} returns text data, except for binary cursors 440 | \end{itemize} 441 | \end{frame} 442 | 443 | \begin{frame} 444 | \frametitle{Simple query protocol flow} 445 | 446 | \mscdiagram{simple-query-protocol} 447 | \end{frame} 448 | 449 | \subsection{Extended protocol} 450 | \begin{frame} 451 | \frametitle{Extended query protocol} 452 | \setbeamercolor{sqlinjection}{bg=block title.bg} 453 | 454 | \begin{itemize} 455 | \item query execution is split into \alert{separate steps} 456 | \item each step is confirmed by a separately server message, but they can be 457 | sent \alert{consecutively} without waiting 458 | \item allows separating parameters from the query body 459 | \end{itemize} 460 | \begin{beamercolorbox}[center,sep=1em]{sqlinjection} 461 | \only<1>{ 462 | \texttt{SELECT admin FROM users WHERE login = '\alert{\$var}'} 463 | } 464 | \only<2-3>{ 465 | \texttt{SELECT admin FROM users WHERE login = '\alert{x' or 1=1; --}'} 466 | } 467 | \only<4->{ 468 | \texttt{SELECT admin FROM users WHERE login = \alert{\$1}} 469 | } 470 | \end{beamercolorbox} 471 | 472 | \begin{itemize} 473 | \item disallows sending several commands in one query 474 | \end{itemize} 475 | 476 | \begin{beamercolorbox}[center,sep=1em]{sqlinjection} 477 | \only<1-2>{ 478 | \texttt{SELECT * FROM posts WHERE id = \alert{\$var}} 479 | } 480 | \only<3>{ 481 | \texttt{SELECT * FROM posts WHERE id = \alert{1; delete from posts;}} 482 | } 483 | \only<4->{ 484 | \texttt{SELECT * FROM posts WHERE id = \alert{\$1}} 485 | } 486 | \end{beamercolorbox} 487 | \end{frame} 488 | 489 | \begin{frame} 490 | \frametitle{Parse messages} 491 | 492 | \begin{center} 493 | \includegraphics[height=1cm]{parse} 494 | \end{center} 495 | 496 | \begin{itemize} 497 | \item first, the client sends a Parse message with the query string 498 | \item it can contain \alert{placeholders} (\texttt{\$1, \$2, ...}) for 499 | parameters 500 | \item for each parameter you can specify its \alert{type} 501 | \begin{itemize} 502 | \item disambiguate between \texttt{select foo(1)} and \texttt{select foo('x')} 503 | \end{itemize} 504 | \item the statement can be optionally given a \alert{name} 505 | \begin{itemize} 506 | \item unnamed statements live until the next unnamed statement is parsed 507 | \item named statements need to be explicitly deallocated 508 | \end{itemize} 509 | \end{itemize} 510 | \end{frame} 511 | 512 | \begin{frame} 513 | \frametitle{Bind messages} 514 | 515 | \hspace{2cm}\includegraphics[height=0.9cm]{bind-1}\;\raisebox{0.2cm}{$+$} \\ 516 | \hspace{2cm}\includegraphics[height=0.6cm]{bind-2}\;\raisebox{0.2cm}{$+$} \\ 517 | \hspace{2cm}\includegraphics[height=0.6cm]{bind-3}\;\raisebox{0.2cm}{$+$} \\ 518 | \hspace{2cm}\includegraphics[height=0.6cm]{bind-4} 519 | 520 | \begin{itemize} 521 | \item after the query is parsed, the clients \alert{binds} its parameters 522 | \item an \alert{output portal} is created for a previously parsed statement 523 | \begin{itemize} 524 | \item an empty string can be used for the portal name 525 | \end{itemize} 526 | \item for each parameter, its \alert{format} (binary or text) and \alert{value} are specified 527 | \item finally, for each output column, the requested \alert{output format} is 528 | sent 529 | \end{itemize} 530 | \end{frame} 531 | 532 | \begin{frame} 533 | \frametitle{Interlude - Describe messages} 534 | 535 | 536 | \begin{center} 537 | \includegraphics[height=1cm]{describe} \\ 538 | 539 | \bigskip 540 | 541 | \includegraphics[height=1cm]{parameter-description} 542 | \end{center} 543 | 544 | \begin{itemize} 545 | \item clients can ask for a description of a statement or a portal 546 | \item \alert{statement} descriptions are returned as two separate messages: 547 | ParameterDescription and RowDescription 548 | \item \alert{portal} descriptions are just RowDescriptions 549 | \item clients can use Describe to make sure they know how to handle data 550 | being returned 551 | \end{itemize} 552 | \end{frame} 553 | 554 | \begin{frame} 555 | \frametitle{Execute messages} 556 | 557 | \begin{center} 558 | \includegraphics[height=1cm]{execute} 559 | \end{center} 560 | 561 | \begin{itemize} 562 | \item once the output portal is created, it can be \alert{executed} 563 | \item the output portal is referred to by name 564 | \item can specify the \alert{number of rows} to return, or 0 for all rows 565 | \item a series of DataRow messages follow 566 | \item no RowDescription is sent 567 | \end{itemize} 568 | \end{frame} 569 | 570 | \begin{frame} 571 | \frametitle{Execute messages cont.} 572 | 573 | \begin{itemize} 574 | \item after the portal has been \alert{run to completion}, CommandComplete is 575 | sent 576 | \item if the requested number of rows is \alert{less} than what the portal would 577 | return a PortalSuspended message is sent 578 | \item AFAIK, only JDBC actually exposes limits for Execute 579 | \item libpq doesn't even have code to handle PortalSuspended... 580 | \end{itemize} 581 | \end{frame} 582 | 583 | \begin{frame} 584 | \frametitle{Sync messages} 585 | 586 | \begin{center} 587 | \includegraphics[height=1cm]{sync} 588 | \end{center} 589 | 590 | \begin{itemize} 591 | \item an extended protocol query should end with a Sync 592 | \item upon receiving Sync the server \alert{closes the transaction} if it was 593 | implicit and responds with a ReadyForQuery message 594 | \item in case of earlier errors, the server sends an ErrorResponse and then 595 | skips until it sees a Sync 596 | \end{itemize} 597 | \end{frame} 598 | 599 | \begin{frame} 600 | \frametitle{Extended query protocol summary} 601 | 602 | \begin{itemize} 603 | \item queries are \alert{parsed} at Parse stage 604 | \item queries are \alert{planned} at Bind stage 605 | \item queries are \alert{executed} at Execute stage 606 | \item with statement logging, these three steps will be \alert{timed} and 607 | \alert{logged separately} 608 | \end{itemize} 609 | \end{frame} 610 | 611 | \begin{frame} 612 | \frametitle{Extended query protocol flow} 613 | 614 | \begin{columns}[onlytextwidth] 615 | \begin{column}{0.5\textwidth} 616 | \mscdiagram[0.4]{extended-query-protocol-1} 617 | \end{column} 618 | \begin{column}{0.5\textwidth} 619 | \mscdiagram[0.4]{extended-query-protocol-2} 620 | \end{column} 621 | \end{columns} 622 | \end{frame} 623 | 624 | \begin{frame} 625 | \frametitle{Advanced extended protocol usage} 626 | 627 | \begin{columns}[onlytextwidth] 628 | \begin{column}{0.5\textwidth} 629 | \mscdiagram[0.4]{extended-query-protocol-adv-1} 630 | \end{column} 631 | \begin{column}{0.5\textwidth} 632 | \mscdiagram[0.4]{extended-query-protocol-adv-2} 633 | \end{column} 634 | \end{columns} 635 | \end{frame} 636 | 637 | \section{Other features} 638 | \subsection{The COPY subprotocol} 639 | 640 | \begin{frame} 641 | \frametitle{Entering COPY mode} 642 | 643 | \begin{center} 644 | \includegraphics[height=1cm]{copy-in} 645 | \end{center} 646 | 647 | \begin{itemize} 648 | \item sending \texttt{COPY FROM STDIN} or \texttt{COPY TO STDIN} puts the 649 | connection in COPY mode 650 | \item this can happen both during simple and extended query processing 651 | \item CopyInResponse and CopyOutResponse indicate that the backend has 652 | switched to COPY mode 653 | \item they specify the overall format (text or binary) and the format for 654 | each column 655 | \begin{itemize} 656 | \item currently if the overall format is binary, all columns are binary 657 | \end{itemize} 658 | \end{itemize} 659 | \end{frame} 660 | 661 | \begin{frame} 662 | \frametitle{Sending COPY data} 663 | 664 | \begin{center} 665 | \includegraphics[height=1cm]{copy-data} 666 | \end{center} 667 | 668 | \begin{itemize} 669 | \item CopyData messages are simply \alert{binary blobs} 670 | \item to stop \texttt{COPY FROM}, the client can send a CopyFail message 671 | \item when transfer is complete, the client sends CopyDone 672 | \item in case of backend errors, an ErrorResponse is sent 673 | \item there is no way for the frontend to stop a \texttt{COPY TO} operation, 674 | short of cancelling or disconnecting 675 | \end{itemize} 676 | \end{frame} 677 | 678 | \begin{frame} 679 | \frametitle{COPY subprotocol flow} 680 | 681 | \mscdiagram{copy-flow} 682 | \end{frame} 683 | 684 | \subsection{Less known FEBE features} 685 | \begin{frame} 686 | \frametitle{Asynchronous operation} 687 | 688 | \begin{center} 689 | \includegraphics[height=1cm]{notification-response} 690 | \end{center} 691 | 692 | \begin{itemize} 693 | \item some messages can appear at \alert{any moment} during the connection 694 | \begin{itemize} 695 | \item ParameterStatus 696 | \item NoticeResponse 697 | \item NotificationResponse 698 | \end{itemize} 699 | \item NOTIFY messages are only sent when a transaction is committed, but you 700 | should expect them at any time 701 | \item notices can be sent at any moment 702 | \end{itemize} 703 | \end{frame} 704 | 705 | \begin{frame} 706 | \frametitle{Fast-path interface} 707 | 708 | \hspace{1cm}\includegraphics[height=0.9cm]{function-call-1}\;\raisebox{0.2cm}{$+$} \\ 709 | \hspace{1cm}\includegraphics[height=0.6cm]{function-call-2}\;\raisebox{0.2cm}{$+$} \\ 710 | \hspace{1cm}\includegraphics[height=0.6cm]{function-call-3} 711 | 712 | \begin{itemize} 713 | \item a specialised interface for \alert{calling functions} 714 | \item separate protocol message, FunctionCall, similar to Query 715 | \begin{itemize} 716 | \item the function is identified by its OID 717 | \item arguments format and values are specified similar to Bind 718 | \end{itemize} 719 | \item libpq documentation calls it ``\alert{somewhat obsolete}'' :) 720 | \item can be substituted by a named Parse followed by Bind/Execute 721 | \item still used by libpq for large object functions 722 | \end{itemize} 723 | \end{frame} 724 | 725 | \begin{frame} 726 | \frametitle{Replication subprotocol} 727 | 728 | \begin{itemize} 729 | \item entered using a special \texttt{replication} parameter in the startup 730 | packet 731 | \item switches the server to a mode where only the simple query protocol can 732 | be used 733 | \item instead of SQL, the server accepts replication commands 734 | \begin{itemize} 735 | \item for example, \texttt{START\_REPLICATION} or \texttt{BASE\_BACKUP} 736 | \end{itemize} 737 | \item responses are a mix of RowDescription/DataRow and COPY subprotocol data 738 | \end{itemize} 739 | \end{frame} 740 | 741 | \subsection{Future development} 742 | \begin{frame} 743 | \frametitle{Protocol version 4.0} 744 | 745 | There are \alert{surprisingly few} gripes about protocol 3.0, but some 746 | proposals have been floated on the development list. 747 | 748 | \begin{itemize} 749 | \item protocol compression 750 | \item adding nullable indicator to RowDescription 751 | \item multi-stage authentication, allowing falling back to a different 752 | authentication method 753 | \item negotiating the protocol version 754 | \item in-band query cancellation 755 | \item sending per-statement GUC 756 | \end{itemize} 757 | \end{frame} 758 | 759 | \subsection*{Questions} 760 | 761 | \begin{frame} 762 | \begin{beamercolorbox}[center]{note} 763 | \Huge Questions? 764 | \end{beamercolorbox} 765 | \end{frame} 766 | 767 | \end{document} 768 | -------------------------------------------------------------------------------- /query.dot: -------------------------------------------------------------------------------- 1 | graph Query { 2 | label=Query; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'Q'|int32 len|str query"]; 7 | } 8 | -------------------------------------------------------------------------------- /ready-for-query.dot: -------------------------------------------------------------------------------- 1 | graph ReqdyForQuery { 2 | label=ReadyForQuery; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'Z'|int32 len|'I' or 'T' or 'E'"]; 7 | } 8 | -------------------------------------------------------------------------------- /regular-packet.dot: -------------------------------------------------------------------------------- 1 | graph regular { 2 | label="Regular packet"; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="char tag|int32 len|payload"]; 7 | } 8 | -------------------------------------------------------------------------------- /row-description-bottom.dot: -------------------------------------------------------------------------------- 1 | graph RowDescriptionBottom { 2 | node [shape=record]; 3 | struct1 [label="str col|int32 tableoid|int16 colno|int32 typeoid|int16 typelen|int32 typmod|int16 format"]; 4 | } 5 | -------------------------------------------------------------------------------- /row-description-top.dot: -------------------------------------------------------------------------------- 1 | graph RowDescriptionTop { 2 | label=RowDescription; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'T'|int32 len|int16 numfields"]; 7 | } 8 | -------------------------------------------------------------------------------- /row-description.dot: -------------------------------------------------------------------------------- 1 | graph RowDescription { 2 | node [shape=record]; 3 | struct1 [label="'T'|int32 len|int16 numfields"]; 4 | struct2 [label="str fieldname|int32 tableoid|int16 colno|int32 typeoid|int16 typelen|int32 typmod|int16 format|..."]; 5 | } 6 | -------------------------------------------------------------------------------- /simple-query-protocol.msc: -------------------------------------------------------------------------------- 1 | msc { 2 | arcgradient=10; 3 | 4 | a [label="Client"], b [label="Server"]; 5 | 6 | a=>>b [label="Query\n\n"]; 7 | 8 | |||; 9 | 10 | a<<=b [label="RowDescription\n\n"]; 11 | a<<=b [label="DataRow\n\n"]; 12 | a<<=b [label="DataRow\n\n"]; 13 | a<<=b [label="DataRow\n\n"]; 14 | a<<=b [label="CommandComplete\n\n"]; 15 | a<<=b [label="ReadyForQuery\n\n"]; 16 | } 17 | -------------------------------------------------------------------------------- /ssl-message-flow.msc: -------------------------------------------------------------------------------- 1 | msc { 2 | arcgradient=10; 3 | 4 | a [label="Client"], b [label="Server"]; 5 | 6 | a=>>b [label="ssl negotiation packet\n\n"]; 7 | a<<=b [label="'S' | 'N' | error\n\n"]; 8 | 9 | --- [label="SSL handshake\n\n\n"]; 10 | 11 | a=>>b [label="startup packet\n\n"]; 12 | 13 | a<<=b [label="auth request | auth OK\n\n"]; 14 | 15 | a=>>b [label="(optional) PasswordMessage\n\n"]; 16 | } 17 | -------------------------------------------------------------------------------- /ssl-negotiation.dot: -------------------------------------------------------------------------------- 1 | graph SSLNegotiation { 2 | label="SSL negotiation"; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="int32 len|int32 sslcode"]; 7 | } 8 | -------------------------------------------------------------------------------- /startup-packet-detail.dot: -------------------------------------------------------------------------------- 1 | graph StartupPacket { 2 | label="Startup packet"; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="int32 len|int32 protocol|str name|\\0|str value|...|\\0"]; 7 | } 8 | -------------------------------------------------------------------------------- /startup-packet.dot: -------------------------------------------------------------------------------- 1 | graph StartupPacket { 2 | label="Startup packet"; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="int32 len|int32 protocol|payload"]; 7 | } 8 | -------------------------------------------------------------------------------- /sync.dot: -------------------------------------------------------------------------------- 1 | graph Sync { 2 | label=Sync; 3 | labelloc=t; 4 | labeljust=l; 5 | node [shape=record]; 6 | struct1 [label="'S'|int32 len"]; 7 | } 8 | -------------------------------------------------------------------------------- /the-wire.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulczer/postgres-on-the-wire/df3b857750ff49db0e14b3a9afe2dbc981442d48/the-wire.jpg --------------------------------------------------------------------------------