github.com/decred/dcrlnd@v0.7.6/internal/testutils/ipc.go (about) 1 package testutils 2 3 import ( 4 "bufio" 5 "encoding/binary" 6 "fmt" 7 "io" 8 "os" 9 ) 10 11 // ipcPipePair holds both ends of an IPC pipe used to communicate with dcrd. 12 type ipcPipePair struct { 13 r *os.File 14 w *os.File 15 16 // Whether to close the R and/or W ends. 17 closeR, closeW bool 18 } 19 20 // close closes the required ends of the pipe and returns the first error. 21 func (p ipcPipePair) close() error { 22 var errR, errW error 23 if p.closeR { 24 errR = p.r.Close() 25 } 26 if p.closeW { 27 errW = p.w.Close() 28 } 29 if errR == nil { 30 return errW 31 } 32 return errR 33 } 34 35 // newIPCPipePair creates a new IPC pipe pair. 36 func newIPCPipePair(closeR, closeW bool) (ipcPipePair, error) { 37 r, w, err := os.Pipe() 38 if err != nil { 39 return ipcPipePair{}, err 40 } 41 return ipcPipePair{r: r, w: w, closeR: closeR, closeW: closeW}, nil 42 } 43 44 // pipeMessage is a generic interface for dcrd pipe messages. 45 type pipeMessage interface{} 46 47 // boundJSONRPCListenAddrEvent is a pipeMessage that tracks the json RPC 48 // address of the underlying dcrwallet instance. 49 type boundJSONRPCListenAddrEvent string 50 51 // boundGRPCListenAddrEvent is a pipeMessage that tracks the RPC address of the 52 // underlying dcrwallet instance. 53 type boundGRPCListenAddrEvent string 54 55 // nextIPCMessage returns the next dcrd IPC message read from the passed 56 // reading-end pipe. 57 // 58 // For unknown messages, this returns an empty pipeMessage instead of an error. 59 func nextIPCMessage(r io.Reader) (pipeMessage, error) { 60 var emptyMsg pipeMessage 61 const protocolVersion = 1 62 63 // Bufferize reads from the underlying file. 64 r = bufio.NewReader(r) 65 66 // Decode the header. 67 var bProto [1]byte 68 var bLenType [1]byte 69 var bType [255]byte 70 var bLenPay [4]byte 71 72 // Enforce the protocol version. 73 if _, err := io.ReadFull(r, bProto[:]); err != nil { 74 return emptyMsg, fmt.Errorf("unable to read protocol: %v", err) 75 } 76 gotProtoVersion := bProto[0] 77 if gotProtoVersion != protocolVersion { 78 return emptyMsg, fmt.Errorf("protocol version mismatch: %d != %d", 79 gotProtoVersion, protocolVersion) 80 } 81 82 // Decode rest of header. 83 if _, err := io.ReadFull(r, bLenType[:]); err != nil { 84 return emptyMsg, fmt.Errorf("unable to read type length: %v", err) 85 } 86 lenType := bLenType[0] 87 if _, err := io.ReadFull(r, bType[:lenType]); err != nil { 88 return emptyMsg, fmt.Errorf("unable to read type: %v", err) 89 } 90 if _, err := io.ReadFull(r, bLenPay[:]); err != nil { 91 return emptyMsg, fmt.Errorf("unable to read payload length: %v", err) 92 } 93 94 // The existing IPC messages are small, so reading the entire message 95 // in an in-memory buffer is feasible today. 96 lenPay := binary.LittleEndian.Uint32(bLenPay[:]) 97 payload := make([]byte, lenPay) 98 if _, err := io.ReadFull(r, payload); err != nil { 99 return emptyMsg, fmt.Errorf("unable to read payload: %v", err) 100 } 101 102 // Decode the payload based on the type. 103 typ := string(bType[:lenType]) 104 switch typ { 105 case "jsonrpclistener": 106 return boundJSONRPCListenAddrEvent(string(payload)), nil 107 case "grpclistener": 108 return boundGRPCListenAddrEvent(string(payload)), nil 109 default: 110 // Other message types are unsupported but don't cause a read 111 // error. 112 return emptyMsg, nil 113 } 114 }