package smb

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"strings"

	"imuslab.com/smb/driver/mod/smb/encoder"

	"imuslab.com/smb/driver/mod/util"
)

// 此文件提供ms-rpce封装
// DCE/RPC RPC over SMB 协议实现
// https://pubs.opengroup.org/onlinepubs/9629399/

// RPC over SMB 标准头
// type PDUHeader struct {
// 	StructureSize          uint16
// 	DataOffset             uint16 `smb:"offset:Buffer"`
// 	WriteLength            uint32 `smb:"len:Buffer"`
// 	FileOffset             []byte `smb:"fixed:8"`
// 	FileId                 []byte `smb:"fixed:16"` //16字节，服务端返回句柄
// 	Channel                uint32
// 	RemainingBytes         uint32
// 	WriteChannelInfoOffset uint16
// 	WriteChannelInfoLength uint16
// 	WriteFlags             uint32
// 	Buffer                 interface{} //写入的数据
// }

// DCE/RPC 标准头
type PDUHeaderStruct struct {
	Version            uint8
	VersionMinor       uint8
	PacketType         PDUType
	PacketFlags        uint8
	DataRepresentation uint32 //4字节，小端排序，0x10
	FragLength         uint16 //2字节，整个结构的长度
	AuthLength         uint16
	CallId             uint32

	Buffer interface{} //PDUBindStruct //PDUBindAckStruct//PDUResponseStruct
}

// 函数绑定结构
type PDUBindStruct struct {
	//PDUHeader
	MaxXmitFrag uint16 //4字节，发送大小协商
	MaxRecvFrag uint16 //4字节，接收大小协商
	AssocGroup  uint32
	NumCtxItems uint8
	Reserved    uint8
	Reserved2   uint16
	CtxItem     PDUCtxEItem
}

// PDU CtxItem结构
type PDUCtxEItem struct {
	ContextId      uint16
	NumTransItems  uint8
	Reserved       uint8
	AbstractSyntax PDUSyntaxID
	TransferSyntax PDUSyntaxID
}

type PDUSyntaxID struct {
	UUID    []byte `smb:"fixed:16"`
	Version uint32
}

type PDURequestStruct struct {
	//PDUHeader
	AllocHint uint32
	ContextID uint16
	Opnum     uint16      //15 is NetSharedEnumAll
	Buffer    interface{} //写入的数据
}
type PointerToServerUnc struct {
	ReferentId  uint32
	MaxCount    uint32
	Offset      uint32
	ActualCount uint32
	ServerUnc   string
}

var _ encoder.BinaryMarshallable = Status(0)

func (c *PointerToServerUnc) MarshalBinary(meta *encoder.Metadata) ([]byte, error) {
	buf := bytes.NewBuffer(nil)
	serverUNC := encoder.ToUnicode(c.ServerUnc)
	c.MaxCount = uint32(len(serverUNC))
	c.ActualCount = uint32(len(serverUNC))
	binary.Write(buf, binary.LittleEndian, c.ReferentId)
	binary.Write(buf, binary.LittleEndian, c.MaxCount)
	binary.Write(buf, binary.LittleEndian, c.Offset)
	binary.Write(buf, binary.LittleEndian, c.ActualCount)
	if len(c.ServerUnc) > 0 {
		buf.Write(serverUNC)
		return buf.Bytes(), nil
	}
	return buf.Bytes(), nil
}
func (c *PointerToServerUnc) UnmarshalBinary(data []byte, meta *encoder.Metadata) (interface{}, error) {
	buf := bytes.NewBuffer(data)
	binary.Read(buf, binary.LittleEndian, &c.ReferentId)
	binary.Read(buf, binary.LittleEndian, &c.MaxCount)
	binary.Read(buf, binary.LittleEndian, &c.Offset)
	binary.Read(buf, binary.LittleEndian, &c.ActualCount)
	name := make([]byte, c.ActualCount*2)
	buf.Read(name)
	c.ServerUnc, _ = encoder.FromUnicode(name)
	meta.CurrOffset += uint64(16 + len(name))
	// logx.Infof("unc: [%v]", c.ServerUnc)
	return c, nil
}

type PointerToResumeHandle struct {
	ReferentID   uint32
	ResumeHandle uint32
}
type NetShareEnumAllRequest struct {
	PointerToServerUnc

	Reserved       uint16
	PointertoLevel uint32 //level 1

	PointerToCtr PointerToCtr //[]byte `smb:"fixed:16"`
	MaxBuffer    uint32
	PointerToResumeHandle
}
type NetShareEnumAllResponse struct {
	PointertoLevel uint32        //level 1
	PointerToCtr   *PointerToCtr //  []byte `smb:"fixed:16"`

	// Reserved              uint16
	PointerToTotalentries uint32

	PointerToResumeHandle uint32
	Werr_OK               uint32
}
type PointerToCtr struct {
	Ctr            uint32
	Ctr1ReferentID uint32 //ctr1
	Ctr1Count      uint32
	info           *srvsvc_NetShareInfo1
}

func (p *PointerToCtr) SetInfo(names, comments []string) error {
	if len(names) != len(comments) {
		return fmt.Errorf("names not equal comments")
	}

	var Array []srvsvc_NetShareInfo1_ArrayItem
	for i := 0; i < len(names); i++ {
		Array = append(Array, srvsvc_NetShareInfo1_ArrayItem{
			PointerToName_Name:    []rune(names[i]),
			PointerToComment_Name: []rune(comments[i]),
		})
	}
	p.info = &srvsvc_NetShareInfo1{
		Array: Array,
	}
	return nil
}

var _ encoder.BinaryMarshallable = Status(0)

func (c *PointerToCtr) MarshalBinary(meta *encoder.Metadata) ([]byte, error) {

	c.Ctr = 0x0001
	if c.info != nil {
		c.Ctr1Count = c.info.MaxCount
	}
	c.Ctr1ReferentID = 0x0001

	buf := bytes.NewBuffer(nil)
	binary.Write(buf, binary.LittleEndian, c.Ctr)
	binary.Write(buf, binary.LittleEndian, c.Ctr1ReferentID)
	binary.Write(buf, binary.LittleEndian, c.Ctr1Count)
	if c.info == nil {
		binary.Write(buf, binary.LittleEndian, uint32(0))
		return buf.Bytes(), nil
	}

	info := c.info
	info.MaxCount = uint32(len(info.Array))
	info.ReferentID = 0x0001

	binary.Write(buf, binary.LittleEndian, info.ReferentID)
	binary.Write(buf, binary.LittleEndian, info.MaxCount)

	for i := 0; i < int(info.MaxCount); i++ {
		item := &info.Array[i]
		item.PointerToName_ReferenceID = 0x001
		item.PointerToComment_ReferenceID = 0x001

		binary.Write(buf, binary.LittleEndian, item.PointerToName_ReferenceID)
		binary.Write(buf, binary.LittleEndian, item.Type)
		binary.Write(buf, binary.LittleEndian, item.PointerToComment_ReferenceID)
	}

	for i := 0; i < int(info.MaxCount); i++ {
		item := &info.Array[i]

		if true {
			//name
			size := uint32(len(item.PointerToName_Name))
			item.PointerToName_MaxCount = size + 1
			item.PointerToName_Offset = 0
			item.PointerToName_ActualCount = size + 1

			// logx.Printf("name: %v, len: %v", item.PointerToName_Name, len(item.PointerToName_Name))
			// for i, str := range item.PointerToName_Name {
			// 	logx.Printf("i: %v, char: %v", i, string(str))
			// }

			binary.Write(buf, binary.LittleEndian, item.PointerToName_MaxCount)
			binary.Write(buf, binary.LittleEndian, item.PointerToName_Offset)
			binary.Write(buf, binary.LittleEndian, item.PointerToName_ActualCount)
			name := encoder.ToUnicode(string(item.PointerToName_Name))
			buf.Write(name)
			//ouput 0x after name
			buf.Write(make([]byte, 2))

			if item.PointerToName_ActualCount%2 == 1 {
				//单数就对齐一下, 需要把对齐的写一下.
				padbuf := make([]byte, 2)
				buf.Write(padbuf)
			}
		}
		if true {
			//comment
			size := uint32(len(item.PointerToComment_Name))
			item.PointerToComment_MaxCount = size + 1
			item.PointerToComment_Offset = 0
			item.PointerToComment_ActualCount = size + 1
			binary.Write(buf, binary.LittleEndian, item.PointerToComment_MaxCount)
			binary.Write(buf, binary.LittleEndian, item.PointerToComment_Offset)
			binary.Write(buf, binary.LittleEndian, item.PointerToComment_ActualCount)
			name := encoder.ToUnicode(string(item.PointerToComment_Name))
			buf.Write(name)
			//ouput 0x after name
			buf.Write(make([]byte, 2))

			isEnd := int(info.MaxCount)-1 == i
			if item.PointerToComment_ActualCount%2 == 1 && !isEnd {
				//单数就对齐一下, 需要把对齐的写一下.
				padbuf := make([]byte, 2)
				buf.Write(padbuf)
			}
		}
	}

	return buf.Bytes(), nil
}
func (c *PointerToCtr) UnmarshalBinary(data []byte, meta *encoder.Metadata) (interface{}, error) {
	buf := bytes.NewBuffer(data)
	binary.Read(buf, binary.LittleEndian, &c.Ctr)
	binary.Read(buf, binary.LittleEndian, &c.Ctr1ReferentID)
	binary.Read(buf, binary.LittleEndian, &c.Ctr1Count)
	if c.Ctr1Count == 0 {
		meta.CurrOffset += 16
		return c, nil
	}

	info := &srvsvc_NetShareInfo1{}
	c.info = info
	binary.Read(buf, binary.LittleEndian, &info.ReferentID)
	binary.Read(buf, binary.LittleEndian, &info.MaxCount)

	info.Array = make([]srvsvc_NetShareInfo1_ArrayItem, int(info.MaxCount))
	for i := 0; i < int(info.MaxCount); i++ {
		item := &info.Array[i]
		binary.Read(buf, binary.LittleEndian, &item.PointerToComment_ReferenceID)
		binary.Read(buf, binary.LittleEndian, &item.Type)
		binary.Read(buf, binary.LittleEndian, &item.PointerToComment_ReferenceID)
	}

	for i := 0; i < int(info.MaxCount); i++ {
		item := &info.Array[i]

		if true {
			binary.Read(buf, binary.LittleEndian, &item.PointerToName_MaxCount)
			binary.Read(buf, binary.LittleEndian, &item.PointerToName_Offset)
			binary.Read(buf, binary.LittleEndian, &item.PointerToName_ActualCount)

			namebuf := make([]byte, (item.PointerToName_ActualCount)*2)
			buf.Read(namebuf)
			if item.PointerToName_ActualCount%2 == 1 {
				//单数就对齐一下, 需要把对齐的读取出来.
				padbuf := make([]byte, 2)
				buf.Read(padbuf)
			}

			name, err := encoder.FromUnicode(namebuf)
			if err != nil {
				return nil, err
			}
			if name[len(name)-1] == 0x00 {
				name = name[:len(name)-1]
			}
			printstr := func(name string) {
				fmt.Printf("name len: %v", len(name))
				for i, str := range name {
					fmt.Printf("string i: %v, char: %v", i, string(str))
				}
				r := []rune(name)
				fmt.Println(r)
				for i, str := range r {
					fmt.Printf("run i: %v, char: %v", i, string(str))
				}
			}

			printstr(name)

			item.PointerToName_Name = []rune(name)
		}
		if true {
			binary.Read(buf, binary.LittleEndian, &item.PointerToComment_MaxCount)
			binary.Read(buf, binary.LittleEndian, &item.PointerToComment_Offset)
			binary.Read(buf, binary.LittleEndian, &item.PointerToComment_ActualCount)

			namebuf := make([]byte, (item.PointerToComment_ActualCount)*2)
			buf.Read(namebuf)
			if item.PointerToComment_ActualCount%2 == 1 {
				//单数就对齐一下, 需要把对齐的读取出来.
				padbuf := make([]byte, 2)
				buf.Read(padbuf)
			}

			name, err := encoder.FromUnicode(namebuf)
			if err != nil {
				return nil, err
			}
			if name[len(name)-1] == 0x00 {
				name = name[:len(name)-1]
			}
			item.PointerToComment_Name = []rune(name)
		}
	}

	return c, nil
}

type srvsvc_NetShareInfo1_ArrayItem struct {
	PointerToName_ReferenceID    uint32
	Type                         uint32
	PointerToComment_ReferenceID uint32

	PointerToName_MaxCount    uint32
	PointerToName_Offset      uint32
	PointerToName_ActualCount uint32
	PointerToName_Name        []rune //   string

	PointerToComment_MaxCount    uint32
	PointerToComment_Offset      uint32
	PointerToComment_ActualCount uint32
	PointerToComment_Name        []rune //   string
}
type srvsvc_NetShareInfo1 struct {
	ReferentID uint32
	MaxCount   uint32

	Array []srvsvc_NetShareInfo1_ArrayItem
}

type PDUResponseStruct struct {
	//PDUHeader
	AllocHint uint32
	ContextID uint16
	Opnum     uint16      //15 is NetSharedEnumAll
	Buffer    interface{} //写入的数据
}

// PDU CtxItem响应结构
type PDUCtxEItemResponseStruct struct {
	AckResult      uint16
	AckReason      uint16
	TransferSyntax []byte `smb:"fixed:16"` //16字节
	SyntaxVer      uint32
}

type PDUBindAckStruct struct {
	//https://pubs.opengroup.org/onlinepubs/9629399/chap12.htm

	MaxXmitFrag   uint16
	MaxRecvFrag   uint16
	AssocGroup    uint32
	ScndryAddrlen uint16
	ScndryAddr    []byte `smb:"count:ScndryAddrlen"` //取决管道的长度
	Reserved0     uint8  //这里的0-3我不确定会不会有问题.因为没找到文档.
	NumResults    uint8
	Reserved1     uint8
	Reserved2     uint8
	Reserved3     uint8
	CtxItem       PDUCtxEItemResponseStruct
}

type PDUType uint8

// PDU PacketType
// https://pubs.opengroup.org/onlinepubs/9629399/chap12.htm
const (
	PDURequest            PDUType = 0
	PDUPing               PDUType = 1
	PDUResponse           PDUType = 2
	PDUFault              PDUType = 3
	PDUWorking            PDUType = 4
	PDUNoCall             PDUType = 5
	PDUReject             PDUType = 6
	PDUAck                PDUType = 7
	PDUCl_Cancel          PDUType = 8
	PDUFack               PDUType = 9
	PDUCancel_Ack         PDUType = 10
	PDUBind               PDUType = 11
	PDUBind_Ack           PDUType = 12
	PDUBind_Nak           PDUType = 13
	PDUAlter_Context      PDUType = 14
	PDUAlter_Context_Resp PDUType = 15
	PDUShutdown           PDUType = 17
	PDUCo_Cancel          PDUType = 18
	PDUOrphaned           PDUType = 19
)

var _ encoder.BinaryMarshallable = PDUType(0)

func (c PDUType) MarshalBinary(meta *encoder.Metadata) ([]byte, error) {
	return MarshalBinary(c, meta, c)
}
func (c PDUType) UnmarshalBinary(data []byte, meta *encoder.Metadata) (interface{}, error) {
	return UnmarshalBinary(c, data, meta, c)
}

// PDU PacketFlags
// https://pubs.opengroup.org/onlinepubs/9629399/chap12.htm
const (
	PDUFlagReserved_01 = 0x01
	PDUFlagLastFrag    = 0x02
	PDUFlagPending     = 0x03
	PDUFlagFrag        = 0x04
	PDUFlagNoFack      = 0x08
	PDUFlagMayBe       = 0x10
	PDUFlagIdemPotent  = 0x20
	PDUFlagBroadcast   = 0x40
	PDUFlagReserved_80 = 0x80
)

// NDR 传输标准
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/b6090c2b-f44a-47a1-a13b-b82ade0137b2
const (
	NDRSyntax   = "8a885d04-1ceb-11c9-9fe8-08002b104860" //Version 02, NDR64 data representation protocol
	NDR64Syntax = "71710533-BEBA-4937-8319-B5DBEF9CCC36" //Version 01, NDR64 data representation protocol
)

// 处理PDU uuid 转成字节数组
func PDUUuidFromBytes(uuid string) []byte {
	s := strings.ReplaceAll(uuid, "-", "")
	b, _ := hex.DecodeString(s)
	r := []byte{b[3], b[2], b[1], b[0], b[5], b[4], b[7], b[6], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]}
	return r
}

func DcerpcRead(ctx *DataCtx, r *ReadRequest) (interface{}, error) {
	data, err := DcerpcReadRaw(ctx)
	if err != nil {
		return nil, err
	}

	resp := ReadResponse{
		Header:        r.Header,
		StructureSize: 0x0011,
		DataOffset:    0x0050,
		DataLength:    uint32(len(data)),
		Data:          data,
	}
	return &resp, nil
}

func DcerpcReadRaw(ctx *DataCtx) ([]byte, error) {

	pdb := ctx.session.pdb
	var pdudata []byte
	var err error
	switch pdb.PacketType {
	case PDUBind:
		bind := pdb.Buffer.(PDUBindStruct)
		ScndryAddr := append([]byte("\\pipe\\srvsvc"), 0x0)
		NDRSyntaxData := PDUUuidFromBytes(NDRSyntax)

		var pduAck = &PDUBindAckStruct{
			MaxXmitFrag:   bind.MaxXmitFrag,
			MaxRecvFrag:   bind.MaxRecvFrag,
			AssocGroup:    bind.AssocGroup,
			ScndryAddrlen: uint16(len(ScndryAddr)),
			ScndryAddr:    ScndryAddr, //[]byte `smb:"count:ScndryAddrlen"` //取决管道的长度
			NumResults:    1,          //uint8
			CtxItem: PDUCtxEItemResponseStruct{
				AckResult:      0,
				TransferSyntax: NDRSyntaxData,
				SyntaxVer:      2,
			},
		}

		fragLength := uint16(16 + util.SizeOfStruct(*pduAck))

		var pdu = PDUHeaderStruct{
			Version:      pdb.Version,
			VersionMinor: pdb.VersionMinor,
			PacketType:   PDUBind_Ack, //Bind_ack

			PacketFlags:        pdb.PacketFlags,
			DataRepresentation: pdb.DataRepresentation,
			FragLength:         fragLength, //   68, //  pdb.FragLength,
			AuthLength:         pdb.AuthLength,
			CallId:             pdb.CallId,
			Buffer:             pduAck,
		}

		pdudata, err = encoder.Marshal(pdu)
		if err != nil {
			return nil, err
		}
	case PDURequest:

		// request := pdb.Buffer.(PDURequestStruct)
		// var info = &srvsvc_NetShareInfo1{}

		ptc := &PointerToCtr{}

		var names = []string{
			// "IPC$",
			// "Documents",
		}
		var comments = []string{
			// "IPC Service",
			// "",
		}

		for _, anchor := range ctx.session.anchors {
			names = append(names, anchor.Name)
			comment := ""
			if anchor.Name == NamedPipeShareName {
				comment = "IPC Service"
			}
			comments = append(comments, comment)
		}

		ptc.SetInfo(names, comments)

		shareEnumAllResp := NetShareEnumAllResponse{
			PointertoLevel:        1,
			PointerToTotalentries: 2,
			PointerToCtr:          ptc,
		}

		ptcBuf, err := encoder.Marshal(shareEnumAllResp)
		if err != nil {
			return nil, err
		}

		var response = &PDUResponseStruct{
			AllocHint: uint32(len(ptcBuf)),
			Buffer:    shareEnumAllResp,
			// Buffer:    &NetShareEnumAllResponse{PointerToCtr: ptc},
		}

		fragLength := uint16(24 + len(ptcBuf))

		var pdu = PDUHeaderStruct{
			Version:      pdb.Version,
			VersionMinor: pdb.VersionMinor,
			PacketType:   PDUResponse,

			PacketFlags:        pdb.PacketFlags,
			DataRepresentation: pdb.DataRepresentation,
			FragLength:         fragLength, //  68, //  pdb.FragLength,
			AuthLength:         pdb.AuthLength,
			CallId:             pdb.CallId,
			Buffer:             response,
		}

		pdudata, err = encoder.Marshal(pdu)
		if err != nil {
			return nil, err
		}

	default:
		return nil, fmt.Errorf("Not Support")
	}

	return pdudata, nil
}

func DcerpcWrite(ctx *DataCtx, r *WriteRequest) (interface{}, error) {

	buf := r.Data

	fragType := PDUType(buf[2])
	switch fragType {
	case PDUBind:
		var bind = &PDUBindStruct{}
		var pdb = PDUHeaderStruct{
			Buffer: bind,
		}
		if err := encoder.Unmarshal(buf, &pdb); err != nil {
			return nil, err
		}
		ctx.session.pdb = pdb
	case PDURequest:
		var PointerToCtr PointerToCtr

		var pdb = PDUHeaderStruct{
			Buffer: &PDURequestStruct{
				Buffer: &NetShareEnumAllRequest{
					PointerToCtr: PointerToCtr,
				},
			},
		}
		if err := encoder.Unmarshal(buf, &pdb); err != nil {
			return nil, err
		}
		ctx.session.pdb = pdb

	default:
		return nil, fmt.Errorf("Not Support")
	}

	resp := WriteResponse{
		Count:         uint32(len(buf)),
		Header:        r.Header,
		StructureSize: 0x0011,
	}
	return &resp, nil
}
