func (d *datagram) opcode() opcode {
	return opcode(binary.BigEndian.Uint16(d.buf[:2]))
}
Opcode from all datagrams
package tftp  
import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"strings"
)
type opcode uint16
func (o opcode) String() string {
	name, ok := opcodeStrings[o]
	if ok {
		return name
	}
	return fmt.Sprintf("UNKNOWN_OPCODE_%v", uint16(o))
}
type ErrorCode uint16
func (e ErrorCode) String() string {
	name, ok := errorStrings[e]
	if ok {
		return name
	}
	return fmt.Sprintf("UNKNOWN_ERROR_%v", uint16(e))
}
const (
	opCodeRRQ   opcode = 0x1  
	opCodeWRQ   opcode = 0x2  
	opCodeDATA  opcode = 0x3  
	opCodeACK   opcode = 0x4  
	opCodeERROR opcode = 0x5  
	opCodeOACK  opcode = 0x6  
	ErrCodeNotDefined ErrorCode = 0x0
	ErrCodeFileNotFound ErrorCode = 0x1
	ErrCodeAccessViolation ErrorCode = 0x2
	ErrCodeDiskFull ErrorCode = 0x3
	ErrCodeIllegalOperation ErrorCode = 0x4
	ErrCodeUnknownTransferID ErrorCode = 0x5
	ErrCodeFileAlreadyExists ErrorCode = 0x6
	ErrCodeNoSuchUser ErrorCode = 0x7
	ModeNetASCII TransferMode = "netascii"
	ModeOctet TransferMode = "octet"
	modeMail  TransferMode = "mail"
	optBlocksize    = "blksize"
	optTimeout      = "timeout"
	optTransferSize = "tsize"
	optWindowSize   = "windowsize"
)
type TransferMode string
var (
	errorStrings = map[ErrorCode]string{
		ErrCodeNotDefined:        "NOT_DEFINED",
		ErrCodeFileNotFound:      "FILE_NOT_FOUND",
		ErrCodeAccessViolation:   "ACCESS_VIOLATION",
		ErrCodeDiskFull:          "DISK_FULL",
		ErrCodeIllegalOperation:  "ILLEGAL_OPERATION",
		ErrCodeUnknownTransferID: "UNKNOWN_TRANSFER_ID",
		ErrCodeFileAlreadyExists: "FILE_ALREADY_EXISTS",
		ErrCodeNoSuchUser:        "NO_SUCH_USER",
	}
	opcodeStrings = map[opcode]string{
		opCodeRRQ:   "READ_REQUEST",
		opCodeWRQ:   "WRITE_REQUEST",
		opCodeDATA:  "DATA",
		opCodeACK:   "ACK",
		opCodeERROR: "ERROR",
		opCodeOACK:  "OPTION_ACK",
	}
)
type datagram struct {
	buf    []byte
	offset int
}
func (d datagram) String() string {
	if err := d.validate(); err != nil {
		return fmt.Sprintf("INVALID_DATAGRAM[Error: %q]", err.Error())
	}
	switch o := d.opcode(); o {
	case opCodeRRQ, opCodeWRQ:
		return fmt.Sprintf("%s[Filename: %q; Mode: %q; Options: %s]", o, d.filename(), d.mode(), d.options())
	case opCodeDATA:
		return fmt.Sprintf("%s[Block: %d; Data Length: %d]", o, d.block(), len(d.data()))
	case opCodeOACK:
		return fmt.Sprintf("%s[Options: %s]", o, d.options())
	case opCodeACK:
		return fmt.Sprintf("%s[Block: %d]", o, d.block())
	case opCodeERROR:
		return fmt.Sprintf("%s[Code: %s; Message: %q]", o, d.errorCode(), d.errMsg())
	default:
		return o.String()
	}
}
func (d *datagram) setBytes(b []byte) {
	d.buf = b
	d.offset = len(b)
}
func (d *datagram) bytes() []byte {
	return d.buf[:d.offset]
}
func (d *datagram) reset(size int) {
	if len(d.buf) < size {
		d.buf = make([]byte, size)
	}
	d.offset = 0
}
func (d *datagram) writeAck(block uint16) {
	d.reset(2 + 2)
	d.writeUint16(uint16(opCodeACK))
	d.writeUint16(block)
}
func (d *datagram) writeData(block uint16, data []byte) {
	d.reset(2 + 2 + len(data))
	d.writeUint16(uint16(opCodeDATA))
	d.writeUint16(block)
	d.writeBytes(data)
}
func (d *datagram) writeError(code ErrorCode, msg string) {
	d.reset(2 + 2 + len(msg) + 1)
	d.writeUint16(uint16(opCodeERROR))
	d.writeUint16(uint16(code))
	d.writeString(msg)
	d.writeNull()
}
func (d *datagram) writeReadReq(filename string, mode TransferMode, options map[string]string) {
	d.writeReq(opCodeRRQ, filename, mode, options)
}
func (d *datagram) writeWriteReq(filename string, mode TransferMode, options map[string]string) {
	d.writeReq(opCodeWRQ, filename, mode, options)
}
func (d *datagram) writeOptionAck(options map[string]string) {
	optLen := 0
	for opt, val := range options {
		optLen += len(opt) + 1 + len(val) + 1
	}
	d.reset(2 + optLen)
	d.writeUint16(uint16(opCodeOACK))
	for opt, val := range options {
		d.writeOption(opt, val)
	}
}
func (d *datagram) writeReq(o opcode, filename string, mode TransferMode, options map[string]string) {
	optLen := 0
	for opt, val := range options {
		optLen += len(opt) + 1 + len(val) + 1
	}
	d.reset(2 + len(filename) + 1 + len(mode) + 1 + optLen)
	d.writeUint16(uint16(o))
	d.writeString(filename)
	d.writeNull()
	d.writeString(string(mode))
	d.writeNull()
	for opt, val := range options {
		d.writeOption(opt, val)
	}
}
func (d *datagram) block() uint16 {
	return binary.BigEndian.Uint16(d.buf[2:4])
}
func (d *datagram) data() []byte {
	return d.buf[4:d.offset]
}
func (d *datagram) errorCode() ErrorCode {
	return ErrorCode(binary.BigEndian.Uint16(d.buf[2:4]))
}
func (d *datagram) errMsg() string {
	end := d.offset - 1
	return string(d.buf[4:end])
}
func (d *datagram) filename() string {
	offset := bytes.IndexByte(d.buf[2:], 0x0) + 2
	return string(d.buf[2:offset])
}
func (d *datagram) mode() TransferMode {
	fields := bytes.Split(d.buf[2:], []byte{0x0})
	return TransferMode(fields[1])
}