package gomemcached import ( "encoding/binary" "fmt" "io" ) // The maximum reasonable body length to expect. // Anything larger than this will result in an error. // The current limit, 20MB, is the size limit supported by ep-engine. var MaxBodyLen = int(20 * 1024 * 1024) // MCRequest is memcached Request type MCRequest struct { // The command being issued Opcode CommandCode // The CAS (if applicable, or 0) Cas uint64 // An opaque value to be returned with this request Opaque uint32 // The vbucket to which this command belongs VBucket uint16 // Command extras, key, and body Extras, Key, Body, ExtMeta []byte // Datatype identifier DataType uint8 } // Size gives the number of bytes this request requires. func (req *MCRequest) Size() int { return HDR_LEN + len(req.Extras) + len(req.Key) + len(req.Body) + len(req.ExtMeta) } // A debugging string representation of this request func (req MCRequest) String() string { return fmt.Sprintf("{MCRequest opcode=%s, bodylen=%d, key='%s'}", req.Opcode, len(req.Body), req.Key) } func (req *MCRequest) fillHeaderBytes(data []byte) int { pos := 0 data[pos] = REQ_MAGIC pos++ data[pos] = byte(req.Opcode) pos++ binary.BigEndian.PutUint16(data[pos:pos+2], uint16(len(req.Key))) pos += 2 // 4 data[pos] = byte(len(req.Extras)) pos++ // Data type if req.DataType != 0 { data[pos] = byte(req.DataType) } pos++ binary.BigEndian.PutUint16(data[pos:pos+2], req.VBucket) pos += 2 // 8 binary.BigEndian.PutUint32(data[pos:pos+4], uint32(len(req.Body)+len(req.Key)+len(req.Extras)+len(req.ExtMeta))) pos += 4 // 12 binary.BigEndian.PutUint32(data[pos:pos+4], req.Opaque) pos += 4 // 16 if req.Cas != 0 { binary.BigEndian.PutUint64(data[pos:pos+8], req.Cas) } pos += 8 if len(req.Extras) > 0 { copy(data[pos:pos+len(req.Extras)], req.Extras) pos += len(req.Extras) } if len(req.Key) > 0 { copy(data[pos:pos+len(req.Key)], req.Key) pos += len(req.Key) } return pos } // HeaderBytes will return the wire representation of the request header // (with the extras and key). func (req *MCRequest) HeaderBytes() []byte { data := make([]byte, HDR_LEN+len(req.Extras)+len(req.Key)) req.fillHeaderBytes(data) return data } // Bytes will return the wire representation of this request. func (req *MCRequest) Bytes() []byte { data := make([]byte, req.Size()) pos := req.fillHeaderBytes(data) if len(req.Body) > 0 { copy(data[pos:pos+len(req.Body)], req.Body) } if len(req.ExtMeta) > 0 { copy(data[pos+len(req.Body):pos+len(req.Body)+len(req.ExtMeta)], req.ExtMeta) } return data } // Transmit will send this request message across a writer. func (req *MCRequest) Transmit(w io.Writer) (n int, err error) { if len(req.Body) < 128 { n, err = w.Write(req.Bytes()) } else { n, err = w.Write(req.HeaderBytes()) if err == nil { m := 0 m, err = w.Write(req.Body) n += m } } return } // Receive will fill this MCRequest with the data from a reader. func (req *MCRequest) Receive(r io.Reader, hdrBytes []byte) (int, error) { if len(hdrBytes) < HDR_LEN { hdrBytes = []byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} } n, err := io.ReadFull(r, hdrBytes) if err != nil { return n, err } if hdrBytes[0] != RES_MAGIC && hdrBytes[0] != REQ_MAGIC { return n, fmt.Errorf("bad magic: 0x%02x", hdrBytes[0]) } klen := int(binary.BigEndian.Uint16(hdrBytes[2:])) elen := int(hdrBytes[4]) // Data type at 5 req.DataType = uint8(hdrBytes[5]) req.Opcode = CommandCode(hdrBytes[1]) // Vbucket at 6:7 req.VBucket = binary.BigEndian.Uint16(hdrBytes[6:]) totalBodyLen := int(binary.BigEndian.Uint32(hdrBytes[8:])) req.Opaque = binary.BigEndian.Uint32(hdrBytes[12:]) req.Cas = binary.BigEndian.Uint64(hdrBytes[16:]) if totalBodyLen > 0 { buf := make([]byte, totalBodyLen) m, err := io.ReadFull(r, buf) n += m if err == nil { if req.Opcode >= TAP_MUTATION && req.Opcode <= TAP_CHECKPOINT_END && len(buf) > 1 { // In these commands there is "engine private" // data at the end of the extras. The first 2 // bytes of extra data give its length. elen += int(binary.BigEndian.Uint16(buf)) } req.Extras = buf[0:elen] req.Key = buf[elen : klen+elen] // get the length of extended metadata extMetaLen := 0 if elen > 29 { extMetaLen = int(binary.BigEndian.Uint16(req.Extras[28:30])) } bodyLen := totalBodyLen - klen - elen - extMetaLen if bodyLen > MaxBodyLen { return n, fmt.Errorf("%d is too big (max %d)", bodyLen, MaxBodyLen) } req.Body = buf[klen+elen : klen+elen+bodyLen] req.ExtMeta = buf[klen+elen+bodyLen:] } } return n, err }