1// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package message
16
17import (
18	"bytes"
19	"encoding/binary"
20	"errors"
21	"unsafe"
22
23	"github.com/vmware/vmw-guestinfo/bdoor"
24)
25
26const (
27	messageTypeOpen = iota
28	messageTypeSendSize
29	messageTypeSendPayload
30	messageTypeReceiveSize
31	messageTypeReceivePayload
32	messageTypeReceiveStatus
33	messageTypeClose
34
35	messageStatusFail       = uint16(0x0000)
36	messageStatusSuccess    = uint16(0x0001)
37	messageStatusDoRecieve  = uint16(0x0002)
38	messageStatusCheckPoint = uint16(0x0010)
39	messageStatusHighBW     = uint16(0x0080)
40)
41
42var (
43	// ErrChannelOpen represents a failure to open a channel
44	ErrChannelOpen = errors.New("could not open channel")
45	// ErrChannelClose represents a failure to close a channel
46	ErrChannelClose = errors.New("could not close channel")
47	// ErrRpciSend represents a failure to send a message
48	ErrRpciSend = errors.New("unable to send RPCI command")
49	// ErrRpciReceive represents a failure to receive a message
50	ErrRpciReceive = errors.New("unable to receive RPCI command result")
51)
52
53type Channel struct {
54	id uint16
55
56	forceLowBW bool
57	buf        []byte
58
59	cookie bdoor.UInt64
60}
61
62// NewChannel opens a new Channel
63func NewChannel(proto uint32) (*Channel, error) {
64	flags := bdoor.CommandFlagCookie
65
66retry:
67	bp := &bdoor.BackdoorProto{}
68
69	bp.BX.AsUInt32().SetWord(proto | flags)
70	bp.CX.AsUInt32().High = messageTypeOpen
71	bp.CX.AsUInt32().Low = bdoor.CommandMessage
72
73	out := bp.InOut()
74	if (out.CX.AsUInt32().High & messageStatusSuccess) == 0 {
75		if flags != 0 {
76			flags = 0
77			goto retry
78		}
79
80		Errorf("Message: Unable to open communication channel")
81		return nil, ErrChannelOpen
82	}
83
84	ch := &Channel{}
85	ch.id = out.DX.AsUInt32().High
86	ch.cookie.High.SetWord(out.SI.AsUInt32().Word())
87	ch.cookie.Low.SetWord(out.DI.AsUInt32().Word())
88
89	Debugf("Opened channel %d", ch.id)
90	return ch, nil
91}
92
93func (c *Channel) Close() error {
94	bp := &bdoor.BackdoorProto{}
95
96	bp.CX.AsUInt32().High = messageTypeClose
97	bp.CX.AsUInt32().Low = bdoor.CommandMessage
98
99	bp.DX.AsUInt32().High = c.id
100	bp.SI.AsUInt32().SetWord(c.cookie.High.Word())
101	bp.DI.AsUInt32().SetWord(c.cookie.Low.Word())
102
103	out := bp.InOut()
104	if (out.CX.AsUInt32().High & messageStatusSuccess) == 0 {
105		Errorf("Message: Unable to close communication channel %d", c.id)
106		return ErrChannelClose
107	}
108
109	Debugf("Closed channel %d", c.id)
110	return nil
111}
112
113func (c *Channel) Send(buf []byte) error {
114retry:
115	bp := &bdoor.BackdoorProto{}
116	bp.CX.AsUInt32().High = messageTypeSendSize
117	bp.CX.AsUInt32().Low = bdoor.CommandMessage
118
119	bp.DX.AsUInt32().High = c.id
120	bp.SI.AsUInt32().SetWord(c.cookie.High.Word())
121	bp.DI.AsUInt32().SetWord(c.cookie.Low.Word())
122
123	bp.BX.AsUInt32().SetWord(uint32(len(buf)))
124
125	// send the size
126	out := bp.InOut()
127	if (out.CX.AsUInt32().High & messageStatusSuccess) == 0 {
128		Errorf("Message: Unable to send a message over the communication channel %d", c.id)
129		return ErrRpciSend
130	}
131
132	// size of buf 0 is fine, just return
133	if len(buf) == 0 {
134		return nil
135	}
136
137	if !c.forceLowBW && (out.CX.AsUInt32().High&messageStatusHighBW) == messageStatusHighBW {
138		hbbp := &bdoor.BackdoorProto{}
139
140		hbbp.BX.AsUInt32().Low = bdoor.CommandHighBWMessage
141		hbbp.BX.AsUInt32().High = messageStatusSuccess
142		hbbp.DX.AsUInt32().High = c.id
143		hbbp.BP.AsUInt32().SetWord(c.cookie.High.Word())
144		hbbp.DI.AsUInt32().SetWord(c.cookie.Low.Word())
145		hbbp.CX.AsUInt32().SetWord(uint32(len(buf)))
146		hbbp.SI.SetPointer(unsafe.Pointer(&buf[0]))
147
148		out := hbbp.HighBandwidthOut()
149		if (out.BX.AsUInt32().High & messageStatusSuccess) == 0 {
150			if (out.BX.AsUInt32().High & messageStatusCheckPoint) != 0 {
151				Debugf("A checkpoint occurred. Retrying the operation")
152				goto retry
153			}
154
155			Errorf("Message: Unable to send a message over the communication channel %d", c.id)
156			return ErrRpciSend
157		}
158	} else {
159		bp.CX.AsUInt32().High = messageTypeSendPayload
160
161		bbuf := bytes.NewBuffer(buf)
162		for {
163			// read 4 bytes at a time
164			words := bbuf.Next(4)
165			if len(words) == 0 {
166				break
167			}
168
169			Debugf("sending %q over %d", string(words), c.id)
170			switch len(words) {
171			case 3:
172				bp.BX.AsUInt32().SetWord(binary.LittleEndian.Uint32([]byte{0x0, words[2], words[1], words[0]}))
173			case 2:
174				bp.BX.AsUInt32().SetWord(uint32(binary.LittleEndian.Uint16(words)))
175			case 1:
176				bp.BX.AsUInt32().SetWord(uint32(words[0]))
177			default:
178				bp.BX.AsUInt32().SetWord(binary.LittleEndian.Uint32(words))
179			}
180
181			out = bp.InOut()
182			if (out.CX.AsUInt32().High & messageStatusSuccess) == 0 {
183				Errorf("Message: Unable to send a message over the communication channel %d", c.id)
184				return ErrRpciSend
185			}
186		}
187	}
188
189	return nil
190}
191
192func (c *Channel) Receive() ([]byte, error) {
193retry:
194	var err error
195	bp := &bdoor.BackdoorProto{}
196	bp.CX.AsUInt32().High = messageTypeReceiveSize
197	bp.CX.AsUInt32().Low = bdoor.CommandMessage
198
199	bp.DX.AsUInt32().High = c.id
200	bp.SI.AsUInt32().SetWord(c.cookie.High.Word())
201	bp.DI.AsUInt32().SetWord(c.cookie.Low.Word())
202
203	out := bp.InOut()
204	if (out.CX.AsUInt32().High & messageStatusSuccess) == 0 {
205		Errorf("Message: Unable to poll for messages over the communication channel %d", c.id)
206		return nil, ErrRpciReceive
207	}
208
209	if (out.CX.AsUInt32().High & messageStatusDoRecieve) == 0 {
210		Debugf("No message to retrieve")
211		return nil, nil
212	}
213
214	// Receive the size.
215	if out.DX.AsUInt32().High != messageTypeSendSize {
216		Errorf("Message: Protocol error. Expected a MESSAGE_TYPE_SENDSIZE request from vmware")
217		return nil, ErrRpciReceive
218	}
219
220	size := out.BX.Value()
221
222	var buf []byte
223
224	if size != 0 {
225		if !c.forceLowBW && (out.CX.AsUInt32().High&messageStatusHighBW == messageStatusHighBW) {
226			buf = make([]byte, size)
227
228			hbbp := &bdoor.BackdoorProto{}
229
230			hbbp.BX.AsUInt32().Low = bdoor.CommandHighBWMessage
231			hbbp.BX.AsUInt32().High = messageStatusSuccess
232			hbbp.DX.AsUInt32().High = c.id
233			hbbp.SI.AsUInt32().SetWord(c.cookie.High.Word())
234			hbbp.BP.AsUInt32().SetWord(c.cookie.Low.Word())
235			hbbp.CX.AsUInt32().SetWord(uint32(len(buf)))
236			hbbp.DI.SetPointer(unsafe.Pointer(&buf[0]))
237
238			out := hbbp.HighBandwidthIn()
239			if (out.BX.AsUInt32().High & messageStatusSuccess) == 0 {
240				Errorf("Message: Unable to send a message over the communication channel %d", c.id)
241				c.reply(messageTypeReceivePayload, messageStatusFail)
242				return nil, ErrRpciReceive
243			}
244		} else {
245			b := bytes.NewBuffer(make([]byte, 0, size))
246
247			for {
248				if size == 0 {
249					break
250				}
251
252				bp.CX.AsUInt32().High = messageTypeReceivePayload
253				bp.BX.AsUInt32().Low = messageStatusSuccess
254
255				out = bp.InOut()
256				if (out.CX.AsUInt32().High & messageStatusSuccess) == 0 {
257					if (out.CX.AsUInt32().High & messageStatusCheckPoint) != 0 {
258						Debugf("A checkpoint occurred. Retrying the operation")
259						goto retry
260					}
261
262					Errorf("Message: Unable to receive a message over the communication channel %d", c.id)
263					c.reply(messageTypeReceivePayload, messageStatusFail)
264					return nil, ErrRpciReceive
265				}
266
267				if out.DX.AsUInt32().High != messageTypeSendPayload {
268					Errorf("Message: Protocol error. Expected a MESSAGE_TYPE_SENDPAYLOAD from vmware")
269					c.reply(messageTypeReceivePayload, messageStatusFail)
270					return nil, ErrRpciReceive
271				}
272
273				Debugf("Received %#v", out.BX.AsUInt32().Word())
274
275				switch size {
276				case 1:
277					err = binary.Write(b, binary.LittleEndian, uint8(out.BX.AsUInt32().Low))
278					size = size - 1
279
280				case 2:
281					err = binary.Write(b, binary.LittleEndian, uint16(out.BX.AsUInt32().Low))
282					size = size - 2
283
284				case 3:
285					err = binary.Write(b, binary.LittleEndian, uint16(out.BX.AsUInt32().Low))
286					if err != nil {
287						c.reply(messageTypeReceivePayload, messageStatusFail)
288						return nil, ErrRpciReceive
289					}
290					err = binary.Write(b, binary.LittleEndian, uint8(out.BX.AsUInt32().High))
291					size = size - 3
292
293				default:
294					err = binary.Write(b, binary.LittleEndian, out.BX.AsUInt32().Word())
295					size = size - 4
296				}
297
298				if err != nil {
299					Errorf(err.Error())
300					c.reply(messageTypeReceivePayload, messageStatusFail)
301					return nil, ErrRpciReceive
302				}
303			}
304
305			buf = b.Bytes()
306		}
307	}
308
309	c.reply(messageTypeReceiveStatus, messageStatusSuccess)
310
311	return buf, nil
312}
313
314func (c *Channel) reply(messageType, messageStatus uint16) {
315	bp := &bdoor.BackdoorProto{}
316
317	bp.BX.AsUInt32().Low = messageStatus
318	bp.CX.AsUInt32().High = messageType
319	bp.CX.AsUInt32().Low = bdoor.CommandMessage
320	bp.DX.AsUInt32().High = c.id
321	bp.SI.AsUInt32().SetWord(c.cookie.High.Word())
322	bp.DI.AsUInt32().SetWord(c.cookie.Low.Word())
323
324	out := bp.InOut()
325
326	/* OUT: Status */
327	if (out.CX.AsUInt32().High & messageStatusSuccess) == 0 {
328		if messageStatus == messageStatusSuccess {
329			Errorf("reply Message: Unable to send a message over the communication channel %d", c.id)
330		} else {
331			Errorf("reply Message: Unable to signal an error of reception over the communication channel %d", c.id)
332		}
333	}
334}
335