1 //
2 // Copyright (C) 2014 Sebastian 'tokkee' Harl <sh@tokkee.org>
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions
7 // are met:
8 // 1. Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 //
14 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16 // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
18 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 // Package proto provides helper functions for using the SysDB front-end
27 // protocol. That's the protocol used for communication between a client and a
28 // SysDB server instance.
29 package proto
31 import (
32 "encoding/binary"
33 "encoding/json"
34 "fmt"
35 "io"
36 "strings"
37 )
39 // Network byte order.
40 var nbo = binary.BigEndian
42 // A Status represents the type of a message. The message type describes the
43 // current status or state of a connection depending on the context.
44 type Status uint32
46 const (
47 // ConnectionOK indicates that a command was successful.
48 ConnectionOK = Status(0)
49 // ConnectionError indicates that a command has failed.
50 ConnectionError = Status(1)
51 // ConnectionLog indicates an (asynchronous) log message.
52 ConnectionLog = Status(2)
54 // ConnectionData indicates a successful query returning data.
55 ConnectionData = Status(100)
56 )
58 const (
59 // ConnectionIdle is the internal state for idle connections.
60 ConnectionIdle = Status(0)
61 // ConnectionPing is the state requesting a connection check.
62 ConnectionPing = Status(1)
63 // ConnectionStartup is the state requesting the setup of a client
64 // connection.
65 ConnectionStartup = Status(2)
67 // ConnectionQuery is the state requesting the execution of a query in the
68 // server.
69 ConnectionQuery = Status(3)
70 // ConnectionFetch is the state requesting the execution of the 'FETCH'
71 // command in the server.
72 ConnectionFetch = Status(4)
73 // ConnectionList is the state requesting the execution of the 'LIST'
74 // command in the server.
75 ConnectionList = Status(5)
76 // ConnectionLookup is the state requesting the execution of the 'LOOKUP'
77 // command in the server.
78 ConnectionLookup = Status(6)
79 // ConnectionTimeseries is the state requesting the execution of the
80 // 'TIMESERIES' command in the server.
81 ConnectionTimeseries = Status(7)
83 // ConnectionMatcher is the internal state for parsing matchers.
84 ConnectionMatcher = Status(100)
85 // ConnectionExpr is the internal state for parsing expressions.
86 ConnectionExpr = Status(101)
87 )
89 // The DataType describes the type of data in a ConnectionData message.
90 type DataType int
92 const (
93 // A HostList can be unmarshaled to []sysdb.Host.
94 HostList DataType = iota
95 // A Host can be unmarshaled to sysdb.Host.
96 Host
97 // A Timeseries can be unmarshaled to sysdb.Timeseries.
98 Timeseries
99 )
101 // A Message represents a raw message of the SysDB front-end protocol.
102 type Message struct {
103 Type Status
104 Raw []byte
105 }
107 // Read reads a raw message encoded in the SysDB wire format from r. The
108 // function parses the header but the raw body of the message will still be
109 // encoded in the wire format.
110 //
111 // The reader has to be in blocking mode. Otherwise, the client and server
112 // will be out of sync after reading a partial message and cannot recover from
113 // that.
114 func Read(r io.Reader) (*Message, error) {
115 var header [8]byte
116 if _, err := io.ReadFull(r, header[:]); err != nil {
117 return nil, err
118 }
120 typ := nbo.Uint32(header[:4])
121 l := nbo.Uint32(header[4:])
122 msg := make([]byte, l)
123 if _, err := io.ReadFull(r, msg); err != nil {
124 return nil, err
125 }
127 return &Message{Status(typ), msg}, nil
128 }
130 // Write writes a raw message to w. The raw body of m has to be encoded in the
131 // SysDB wire format. The function adds the right header to the message.
132 //
133 // The writer has to be in blocking mode. Otherwise, the client and server
134 // will be out of sync after writing a partial message and cannot recover from
135 // that.
136 func Write(w io.Writer, m *Message) error {
137 var header [8]byte
138 nbo.PutUint32(header[:4], uint32(m.Type))
139 nbo.PutUint32(header[4:], uint32(len(m.Raw)))
141 if _, err := io.WriteString(w, string(header[:])); err != nil {
142 return err
143 }
144 if _, err := io.WriteString(w, string(m.Raw)); err != nil {
145 return err
146 }
147 return nil
148 }
150 // DataType determines the type of data in a ConnectionData message.
151 func (m Message) DataType() (DataType, error) {
152 if m.Type != ConnectionData {
153 return 0, fmt.Errorf("message is not of type DATA")
154 }
156 typ := nbo.Uint32(m.Raw[:4])
157 switch Status(typ) {
158 case ConnectionList, ConnectionLookup:
159 return HostList, nil
160 case ConnectionFetch:
161 return Host, nil
162 case ConnectionTimeseries:
163 return Timeseries, nil
164 }
165 return 0, fmt.Errorf("unknown DATA type %d", typ)
166 }
168 // Unmarshal parses the raw body of m and stores the result in the value
169 // pointed to by v which has to match the type of the message and its data.
170 func Unmarshal(m *Message, v interface{}) error {
171 if m.Type != ConnectionData {
172 return fmt.Errorf("unmarshaling message of type %d not supported", m.Type)
173 }
174 if len(m.Raw) == 0 { // empty command
175 return nil
176 } else if len(m.Raw) < 4 {
177 return fmt.Errorf("DATA message body too short")
178 }
179 return json.Unmarshal(m.Raw[4:], v)
180 }
182 // EscapeString returns the quoted and escaped string s suitable for use
183 // in a query.
184 func EscapeString(s string) string {
185 // Currently, the server only handles double-quotes.
186 // Backslashes do not serve any special purpose.
187 return "'" + strings.Replace(s, "'", "''", -1) + "'"
188 }
190 // vim: set tw=78 sw=4 sw=4 noexpandtab :