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