/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.

package std.net

@When[os == "Windows"]
import std.sync.AtomicBool

/*
 * This API supplies users with the basic functionality to create and use sockets of different domains and types.
 * For example, based on this API user should be able to create sockets in following domains: AF_PACKET, AF_NETLINK,
 * and in following types: SOCK_RAW, SOCK_SEQPACKET.
 * This is a low-level API, not recommended for general and direct usage. Users are encouraged to construct their own
 * higher level APIs based on this one to access a socket of specific domain and/or type.
 * For general usage of TCP, UDP, UDS in stream or datagram mode, it's encouraged to use existing corresponding
 * Cangjie APIs rather than this one.
 * Implementation note:
 * This API is an thin interface between Cangjie code and system calls related to socket.
 * To enable users to access these system calls, the implementation makes sure that calling these wrapped cangjie API
 * will not block underlying system thread.
 */
@When[backend == "cjnative"]
public class RawSocket {
    let sockImpl: RawSocketImpl
    private var isClosed: Bool = false
    private var readTimeout_: ?Duration = None
    private var writeTimeout_: ?Duration = None
    var localAddr_: ?RawAddress = None
    var remoteAddr_: ?RawAddress = None

    prop sockFd: Int64 {
        get() {
            this.sockImpl.sockFd
        }
    }

    /* Get local socket address */
    @Deprecated[message: "Use `public prop localAddress: RawAddress` instead."]
    public prop localAddr: RawAddress {
        get() {
            throwIfClosed()
            localAddr_ ?? getLocalAddress()
        }
    }

    /* Get local socket address */
    public prop localAddress: RawAddress {
        get() {
            throwIfClosed()
            localAddr_ ?? getLocalAddress()
        }
    }

    /* Get remote socket address */
    @Deprecated[message: "Use `public prop remoteAddress: RawAddress` instead."]
    public prop remoteAddr: RawAddress {
        get() {
            throwIfClosed()
            remoteAddr_ ?? getPeerAddress()
        }
    }

    /* Get remote socket address */
    public prop remoteAddress: RawAddress {
        get() {
            throwIfClosed()
            remoteAddr_ ?? getPeerAddress()
        }
    }

    /* Get or set timeout for socket read */
    public mut prop readTimeout: ?Duration {
        get() {
            throwIfClosed()
            this.readTimeout_
        }
        set(v) {
            throwIfClosed()
            this.readTimeout_ = v?.throwIfNegative("Read timeout")
        }
    }

    /* Get or set timeout for socket write */
    public mut prop writeTimeout: ?Duration {
        get() {
            throwIfClosed()
            this.writeTimeout_
        }
        set(v) {
            throwIfClosed()
            this.writeTimeout_ = v?.throwIfNegative("Write timeout")
        }
    }

    private init(fd: Int64) {
        this.sockImpl = RawSocketImpl(fd)
    }

    /**
     * Create a socket with specific domain, type and protocol.
     *
     * @param domain The socket communication domain defined in struct SocketDomain.
     * @param sockType The socket type defined in struct SocketType.
     * @param protocol The socket protocol defined in struct ProtocolType.
     *
     * @throws SocketException if fail to create socket.
     */
    public init(domain: SocketDomain, `type`: SocketType, protocol: ProtocolType) {
        unsafe {
            let cNet = LibC.mallocCString("raw")
            var sockFd = CJ_MRT_SockCreate(domain.val, `type`.val, protocol.val, cNet)
            LibC.free(cNet)
            if (sockFd < 0) {
                socketProcessErrno(ErrnoLabel.CreateSock)
            }
            this.sockImpl = RawSocketImpl(sockFd)
        }
    }

    /**
     * Assign an address to the socket.
     *
     * @param addr The socket address stored in byte array.
     *
     * @throws SocketException if fail to bind address.
     */
    public func bind(addr: RawAddress): Unit {
        throwIfClosed()
        match (localAddr_) {
            case Some(_) => throw SocketException("Failed to bind: The socket has bound address.")
            case None =>
                this.sockImpl.bind(addr)
                localAddr_ = getLocalAddress()
        }
    }

    /**
     * Mark the socket as a passive socket to accept incoming connection.
     *
     * @param backlog The maximum length of the pending connections queue.
     *
     * @throws SocketException if fail to listen.
     */
    public func listen(backlog: Int32): Unit {
        throwIfClosed()
        let sockFd = unsafe { CJ_MRT_SockListen(this.sockFd, backlog) }
        if (sockFd < 0) {
            socketProcessErrno(ErrnoLabel.Listen)
        }
        this.sockImpl.sockFd = sockFd
    }

    /**
     * Accept a connecting socket, waiting for one if there are no pending connection requests within the specified timeout.
     * If the timeout is `None`, then acception attempts will continue without time limit.
     * If the timeout less than or equal to Duration.Zero, it will timeout immediately.
     *
     * @param timeout The maxium waiting duration for getting a connection.
     * @return RawSocket for communication.
     *
     * @throws IllegalArgumentException if timeout is negative.
     * @throws SocketTimeoutException if the specified timeout ellapsed before any connection request were made.
     * @throws SocketException when other error occurs.
     */
    public func accept(timeout!: ?Duration = None): RawSocket {
        let val = getTimeout(timeout)
        throwIfClosed()
        let size = try {
            this.localAddress.addr.size
        } catch (e: SocketException) {
            throw SocketException("Failed to accept: ${e.message}.")
        }
        let remoteAddr = Array<Byte>(size, repeat: 0)
        let (handle, addrPtr) = getAddrPointer(remoteAddr)
        let sockFd = if (val < 0) {
            unsafe { CJ_MRT_SockAccept(this.sockFd, addrPtr) }
        } else {
            unsafe { CJ_MRT_SockAcceptTimeout(this.sockFd, addrPtr, UInt64(val)) }
        }
        releaseAddrPointer(handle, addrPtr)
        if (sockFd < 0) {
            socketProcessErrno(ErrnoLabel.Accept)
        }
        let connSock = RawSocket(sockFd)
        connSock.localAddr_ = this.localAddr_
        connSock.remoteAddr_ = RawAddress(remoteAddr)
        return connSock
    }

    /**
     * Connect the socket to the address specified by addr within the specified timeout.
     * If the timeout is `None`, then acception attempts will continue without time limit.
     *
     * @param addr The peer socket address.
     * @param timeout The maxium duration for connection.
     *
     * @throws IllegalArgumentException if timeout is negative.
     * @throws SocketTimeoutException if the specified timeout ellapsed before acception was made.
     * @throws SocketException when other error occurs.
     */
    public func connect(addr: RawAddress, timeout!: ?Duration = None): Unit {
        throwIfClosed()
        let val = getTimeout(timeout)
        // connect will not try to connect when timeout == 0
        if (val == 0) {
            throw SocketTimeoutException("Failed to connect: connect timeout.")
        }
        this.sockImpl.connect(addr, val)
        this.localAddr_ = getLocalAddress()
    }

    /* Close the socket */
    public func close(): Unit {
        if (this.isClosed) {
            return
        }
        this.isClosed = true
        this.sockImpl.close()
    }

    /**
     * Transmit a message specified by buffer to another socket.
     *
     * @param addr The peer address.
     * @param buffer The array data to be send.
     * @param flag The flag of how to invoke this func.
     *
     * @throws SocketTimeoutException when timeout.
     * @throws SocketException when other error occurs.
     */
    public func sendTo(addr: RawAddress, buffer: Array<Byte>, flags: Int32): Unit {
        throwIfClosed()
        let timeout = this.writeTimeout_?.toNanoseconds() ?? -1
        return sockImpl.sendTo(addr.addr, buffer, flags, timeout)
    }

    /**
     * Receive a message and store it in buffer.
     *
     * @param buffer The buffer for message to be stored in.
     * @param flag The flag of how to invoke this func.
     * @return tuple of peer address and the number of bytes sent.
     *
     * @throws SocketTimeoutException when timeout.
     * @throws SocketException when other error occurs.
     */
    public func receiveFrom(buffer: Array<Byte>, flags: Int32): (RawAddress, Int64) {
        throwIfClosed()
        let timeout = this.readTimeout_?.toNanoseconds() ?? -1
        let size = try {
            this.localAddress.addr.size
        } catch (e: SocketException) {
            throw SocketException("Failed to receive data: ${e.message}.")
        }
        return sockImpl.receiveFrom(size, buffer, flags, timeout)
    }

    /**
     * Transmit a message specified by buffer to another socket. This api is only used for connected socket.
     *
     * @param buffer The array data to be send.
     * @param flag The flag of how to invoke this func.
     *
     * @throws SocketTimeoutException when timeout.
     * @throws SocketException when other error occurs.
     */
    public func send(buffer: Array<Byte>, flags: Int32): Unit {
        throwIfClosed()
        let timeout = this.writeTimeout_?.toNanoseconds() ?? -1
        return sockImpl.send(buffer, flags, timeout)
    }

    /**
     * Receive a message and store it in buffer. This api is only used for connected socket.
     *
     * @param buffer The buffer for message to be stored in.
     * @param flag The flag of how to invoke this func.
     * @return the number of bytes sent.
     *
     * @throws SocketTimeoutException when timeout.
     * @throws SocketException when other error occurs.
     */
    public func receive(buffer: Array<Byte>, flags: Int32): Int64 {
        throwIfClosed()
        let timeout = this.readTimeout_?.toNanoseconds() ?? -1
        return sockImpl.receive(buffer, flags, timeout)
    }

    /**
     * Set socket option with value specified by @p value in byte array form.
     *
     * @param level Socket option level defined in struct OptionLevel or Int32 value.
     * @param option Socket option name defined in struct OptionName or Int32 value.
     * @param value The value of option name in CPointer form.
     * @param len The length of option name struct.

     * @throws SocketException if fail to set socket option.
     */
    public unsafe func setSocketOption(level: Int32, option: Int32, value: CPointer<Byte>, len: Int32): Unit {
        throwIfClosed()
        let ret = CJ_SockOptionSet(this.sockFd, level, option, CPointer<Unit>(value), len)
        if (ret != 0) {
            let message = formatError(CJ_SockErrnoGet())
            throw SocketException("Failed to set socket option ${option}: ${message}")
        }
    }

    /**
     * Get socket option value.
     *
     * @param level Socket option level defined in struct OptionLevel or Int32 value.
     * @param option Socket option name defined in struct OptionName or Int32 value.
     * @param value The value of option name to be stored in.
     * @param len The length of value to be stored in.
     *
     * @throws SocketException if fail to get socket option.
     */
    public unsafe func getSocketOption(level: Int32, option: Int32, value: CPointer<Byte>, len: CPointer<Int32>): Unit {
        throwIfClosed()
        let ret = CJ_SockOptionGet(this.sockFd, level, option, CPointer<Unit>(value), len)
        if (ret != 0) {
            let message = formatError(CJ_SockErrnoGet())
            throw SocketException("Failed to get socket option ${option}: ${message}")
        }
    }

    private func throwIfClosed(): Unit {
        if (isClosed) {
            SocketException.throwClosedException()
        }
    }

    private func getLocalAddress(): RawAddress {
        throwIfClosed()
        let addr = getAddress(this.sockFd, localAddrGet, "local")
        let localAddr = unsafe { addr.toRawAddress() }
        unsafe { addr.free() }
        return localAddr
    }

    private func getPeerAddress(): RawAddress {
        throwIfClosed()
        let addr = getAddress(this.sockFd, peerAddrGet, "remote")
        let peerAddr = unsafe { addr.toRawAddress() }
        unsafe { addr.free() }
        return peerAddr
    }
}

@When[os != "Windows" && backend == "cjnative"]
class RawSocketImpl {
    var sockFd: Int64

    init(sockFd: Int64) {
        this.sockFd = sockFd
    }

    func bind(addr: RawAddress): Unit {
        let (handle, addrPtr) = getAddrPointer(addr.addr)
        let sockFd = unsafe { CJ_MRT_SockBind(this.sockFd, addrPtr, 0) }
        releaseAddrPointer(handle, addrPtr)
        if (sockFd < 0) {
            socketProcessErrno(ErrnoLabel.Bind)
        }
        this.sockFd = sockFd
    }

    func connect(addr: RawAddress, timeout: Int64): Unit {
        let (handle, remotePtr) = getAddrPointer(addr.addr)
        let localPtr = CPointer<SockAddr>()
        let sockFd = if (timeout < 0) {
            unsafe { CJ_MRT_SockConnect(this.sockFd, localPtr, remotePtr) }
        } else {
            unsafe { CJ_MRT_SockConnectTimeout(this.sockFd, localPtr, remotePtr, UInt64(timeout)) }
        }
        releaseAddrPointer(handle, remotePtr)
        if (sockFd < 0) {
            socketProcessErrno(ErrnoLabel.Connect)
        }
        this.sockFd = sockFd
    }

    func sendTo(addr: Array<Byte>, buffer: Array<Byte>, flags: Int32, timeout: Int64): Unit {
        let totalSize = buffer.size
        var totalSent: Int64 = 0
        while (totalSent < totalSize) {
            let sizeToSend: Int64 = getOnceSendSize(totalSize - totalSent)
            var sentSize: Int32 = 0
            unsafe {
                let (handle, addrPtr) = getAddrPointer(addr)
                let bufCp = acquireArrayRawData(buffer)
                sentSize = CJ_MRT_SockSendtoNonBlock(
                    this.sockFd,
                    bufCp.pointer + totalSent,
                    UInt32(sizeToSend),
                    flags,
                    addrPtr
                )
                releaseArrayRawData(bufCp)
                releaseAddrPointer(handle, addrPtr)
                if (sentSize > 0) {
                    totalSent += Int64(sentSize)
                    continue
                } else if (sentSize == 0) {
                    throw SocketException("The socket is closed.")
                } else {
                    processSendResult(timeout)
                }
            }
        }
    }

    func send(buffer: Array<Byte>, flags: Int32, timeout: Int64): Unit {
        let totalSize = buffer.size
        var totalSent: Int64 = 0
        while (totalSent < totalSize) {
            let sizeToSend: Int64 = getOnceSendSize(totalSize - totalSent)
            var sentSize: Int32 = 0
            unsafe {
                let bufCp = acquireArrayRawData(buffer)
                sentSize = CJ_MRT_SockSendNonBlock(
                    sockFd,
                    bufCp.pointer + totalSent,
                    UInt32(sizeToSend),
                    flags
                )
                releaseArrayRawData(bufCp)
                if (sentSize > 0) {
                    totalSent += Int64(sentSize)
                    continue
                } else if (sentSize == 0) {
                    throw SocketException("The socket is closed.")
                } else {
                    processSendResult(timeout)
                }
            }
        }
    }

    func receiveFrom(size: Int64, buffer: Array<Byte>, flags: Int32, timeout: Int64): (RawAddress, Int64) {
        var recvSize: Int32 = 0
        let addrArr = Array<Byte>(size, repeat: 0)
        while (true) {
            unsafe {
                let (handle, addrPtr) = getAddrPointer(addrArr)
                let bufCp = acquireArrayRawData(buffer)
                recvSize = CJ_MRT_SockRecvfromNonBlock(sockFd, bufCp.pointer, UInt32(buffer.size), flags, addrPtr)
                releaseArrayRawData(bufCp)
                releaseAddrPointer(handle, addrPtr)
                if (recvSize > 0) {
                    break
                } else if (recvSize == 0) {
                    return (RawAddress(addrArr), 0)
                } else {
                    processRecvResult(timeout)
                }
            }
        }
        return (RawAddress(addrArr), Int64(recvSize))
    }

    func receive(buffer: Array<Byte>, flags: Int32, timeout: Int64): Int64 {
        var recvSize: Int32 = 0
        while (true) {
            unsafe {
                let bufCp = acquireArrayRawData(buffer)
                recvSize = CJ_MRT_SockRecvNonBlock(sockFd, bufCp.pointer, UInt32(buffer.size), flags)
                releaseArrayRawData(bufCp)
                if (recvSize > 0) {
                    break
                } else if (recvSize == 0) {
                    return 0
                } else {
                    processRecvResult(timeout)
                }
            }
        }
        return Int64(recvSize)
    }

    func processSendResult(timeout: Int64): Unit {
        unsafe {
            if (CJ_SockErrnoGet() != ERRNO_SOCK_EAGAIN) {
                socketProcessErrno(ErrnoLabel.Write)
            } else {
                let waitCode = if (timeout == -1) {
                    CJ_MRT_SockWaitSend(this.sockFd)
                } else {
                    CJ_MRT_SockWaitSendTimeout(this.sockFd, UInt64(timeout))
                }
                if (waitCode != 0) {
                    socketProcessErrno(ErrnoLabel.Write)
                }
            }
        }
    }

    func processRecvResult(timeout: Int64): Unit {
        unsafe {
            if (CJ_SockErrnoGet() != ERRNO_SOCK_EAGAIN) {
                socketProcessErrno(ErrnoLabel.Read)
            } else {
                let waitCode = if (timeout == -1) {
                    CJ_MRT_SockWaitRecv(this.sockFd)
                } else {
                    CJ_MRT_SockWaitRecvTimeout(this.sockFd, UInt64(timeout))
                }
                if (waitCode != 0) {
                    socketProcessErrno(ErrnoLabel.Read)
                }
            }
        }
    }

    func close(): Unit {
        unsafe { CJ_MRT_SockClose(this.sockFd) }
    }
}

@When[os == "Windows" && backend == "cjnative"]
class RawSocketImpl {
    var sockFd: Int64
    var isBound: Bool = false
    protected var socketBufferPtr: CPointer<SocketBuffer> = CPointer<SocketBuffer>()
    var bufferInitialized: AtomicBool = AtomicBool(false)

    init(sockFd: Int64) {
        this.sockFd = sockFd
    }

    func bufferInit() {
        if (bufferInitialized.compareAndSwap(false, true)) {
            let readBufferSize = SOCK_READ_BUFFER_SIZE
            let writeBufferSize = SOCK_WRITE_BUFFER_SIZE
            socketBufferPtr = unsafe { CJ_SOCKET_BufferInit(this.sockFd, readBufferSize, writeBufferSize) }
            if (socketBufferPtr.isNull()) {
                throw SocketException("Failed to initialize socket read and write buffer.")
            }
        }
    }

    func bind(addr: RawAddress): Unit {
        let (handle, addrPtr) = getAddrPointer(addr.addr)
        let sockFd = unsafe { CJ_MRT_SockBind(this.sockFd, addrPtr, 0) }
        releaseAddrPointer(handle, addrPtr)
        if (sockFd < 0) {
            socketProcessErrno(ErrnoLabel.Bind)
        }
        this.sockFd = sockFd
        this.isBound = true
    }

    func connect(addr: RawAddress, timeout: Int64) {
        let (remoteHd, remotePtr) = getAddrPointer(addr.addr)
        let af = Int16(addr.addr[0])
        let sockTypeVal = try {
            getSocketOptionInt32(this.sockFd, SOL_SOCKET, SOCK_TYPE)
        } catch (e: SocketException) {
            releaseAddrPointer(remoteHd, remotePtr)
            throw SocketException("Failed to connect: ${e.message}.")
        }
        let (localHd, localPtr) = if (!this.isBound && isConnectionOriented(sockTypeVal)) {
            let localArr: Array<Byte>
            match {
                case af == AF_INET =>
                    localArr = Array<Byte>(IPV4_ADDR_LEN, repeat: 0)
                    localArr[0] = UInt8(AF_INET)
                case af == AF_INET6 =>
                    localArr = Array<Byte>(IPV6_ADDR_LEN, repeat: 0)
                    localArr[0] = UInt8(AF_INET6)
                case _ =>
                    releaseAddrPointer(remoteHd, remotePtr)
                    throw SocketException("Failed to connect: Unsupported address family ${af}.")
            }
            getAddrPointer(localArr)
        } else {
            (CPointerHandle<Byte>(), CPointer<SockAddr>())
        }
        let sockFd = if (timeout < 0) {
            unsafe { CJ_MRT_SockConnect(this.sockFd, localPtr, remotePtr) }
        } else {
            unsafe { CJ_MRT_SockConnectTimeout(this.sockFd, localPtr, remotePtr, UInt64(timeout)) }
        }
        if (!localPtr.isNull()) {
            releaseAddrPointer(localHd, localPtr)
        }
        releaseAddrPointer(remoteHd, remotePtr)
        if (sockFd < 0) {
            socketProcessErrno(ErrnoLabel.Connect)
        }
        this.sockFd = sockFd
    }

    func sendTo(addr: Array<Byte>, buffer: Array<Byte>, flags: Int32, timeout: Int64): Unit {
        this.bufferInit()
        var copyLen: Int32 = 0
        unsafe {
            let bufCp = acquireArrayRawData(buffer)
            // bufCp.pointer copy to socketBufferPtr
            copyLen = CJ_SOCKET_BufferWCopy(
                this.socketBufferPtr,
                bufCp.pointer,
                Int64(SOCK_WRITE_BUFFER_SIZE),
                Int32(buffer.size)
            )
            releaseArrayRawData(bufCp)
        }
        if (copyLen == 0) {
            throw SocketException("The socket is closed.")
        } else if (copyLen < 0) {
            throw SocketException("Failed to write data: native socket copy data error.")
        } else {
            unsafe {
                let (handle, addrPtr) = getAddrPointer(addr)
                let writeLen = CJ_SOCKET_BufferSendto(this.socketBufferPtr, 0, Int32(buffer.size), timeout, addrPtr,
                    flags)
                releaseAddrPointer(handle, addrPtr)
                if (writeLen < 0) {
                    socketProcessErrno(ErrnoLabel.Write)
                }
            }
        }
    }

    func send(buffer: Array<Byte>, flags: Int32, timeout: Int64): Unit {
        let writeSize = buffer.size
        var writeToBufferSize: Int64 = 0
        bufferInit()
        while (writeToBufferSize < writeSize) {
            let bufferOnceSize: Int64 = getOnceSendSize(writeSize - writeToBufferSize)
            var copyLen: Int32 = 0
            unsafe {
                let bufCp = acquireArrayRawData(buffer)
                // bufCp.pointer copy to socketBufferPtr
                copyLen = CJ_SOCKET_BufferWCopy(
                    this.socketBufferPtr,
                    bufCp.pointer + writeToBufferSize,
                    Int64(SOCK_WRITE_BUFFER_SIZE),
                    Int32(bufferOnceSize)
                )
                releaseArrayRawData(bufCp)
            }
            if (copyLen == 0) {
                throw SocketException("The socket is closed.")
            } else if (copyLen < 0) {
                throw SocketException("Failed to write data: native socket copy data error.")
            }
            writeToBufferSize += bufferOnceSize
            var bufferSentSize: Int64 = 0
            while (bufferSentSize < bufferOnceSize) {
                let writeLen = unsafe {
                    CJ_SOCKET_BufferWrite(
                        this.socketBufferPtr,
                        UIntNative(bufferSentSize),
                        Int32(bufferOnceSize - bufferSentSize),
                        timeout,
                        flags
                    )
                }
                if (writeLen == 0) {
                    throw SocketException("The socket is closed.")
                } else if (writeLen < 0) {
                    socketProcessErrno(ErrnoLabel.Write)
                }
                bufferSentSize += Int64(writeLen)
            }
        }
    }

    func receiveFrom(size: Int64, buffer: Array<Byte>, flags: Int32, timeout: Int64): (RawAddress, Int64) {
        this.bufferInit()
        let (address, readLen) = unsafe {
            var addr = SockAddr()
            try {
                let r = CJ_SOCKET_BufferRecvFrom(this.socketBufferPtr, 0, Int32(buffer.size), timeout, inout addr, flags
                )
                if (r < 0) {
                    socketProcessErrno(ErrnoLabel.Read)
                }
                // Can not get address if timeout
                (addr.toRawAddress(), r)
            } finally {
                addr.free()
            }
        }
        var copyLen: Int32 = 0
        unsafe {
            let bufCp = acquireArrayRawData(buffer)
            // socketBufferPtr copy to bufCp.pointer
            copyLen = CJ_SOCKET_BufferRCopy(this.socketBufferPtr, bufCp.pointer, buffer.size, readLen)
            releaseArrayRawData(bufCp)
        }
        return (address, Int64(readLen))
    }

    func receive(buffer: Array<Byte>, flags: Int32, timeout: Int64): Int64 {
        bufferInit()
        let readLen: Int32 = unsafe { CJ_SOCKET_BufferRead(this.socketBufferPtr, 0, Int32(buffer.size), timeout, flags) }
        if (readLen < 0) {
            socketProcessErrno(ErrnoLabel.Read)
        }
        if (readLen == 0) {
            return 0
        } else {
            var copyLen: Int32 = 0
            unsafe {
                let bufCp = acquireArrayRawData(buffer)
                // socketBufferPtr copy to bufCp.pointer
                copyLen = CJ_SOCKET_BufferRCopy(this.socketBufferPtr, bufCp.pointer, buffer.size, readLen)
                releaseArrayRawData(bufCp)
            }
            if (copyLen == 0) {
                return 0
            } else if (copyLen < 0) {
                throw SocketException("Failed to read data: native socket copy data error.")
            }
            return Int64(copyLen)
        }
    }

    func isConnectionOriented(sockType: Int32): Bool {
        return sockType == SOCK_STREAM || sockType == SOCK_RDM || sockType == SOCK_SEQPACKET
    }

    func close(): Unit {
        match (socketBufferPtr.isNull()) {
            case true => unsafe { CJ_MRT_SockClose(sockFd) }
            case false => unsafe { CJ_SOCKET_BufferClose(socketBufferPtr, sockFd) } // does CJ_MRT_SockClose inside
        }
    }
}

@When[os == "Windows"]
func getSocketOptionInt32(sockFd: Int64, level: Int32, option: Int32): Int32 {
    unsafe {
        var sockType: CPointer<Int32> = LibC.malloc<Int32>()
        if (sockType.isNull()) {
            throw SocketException("Failed to allocate memory.")
        }
        sockType.write(0)
        var len: Int32 = 4
        let ret = CJ_SockOptionGet(sockFd, level, option, CPointer<Unit>(sockType), inout len)
        if (ret != 0) {
            LibC.free(sockType)
            let errno = CJ_SockErrnoGet()
            let message = if (errno != 0) {
                formatError(errno)
            } else {
                "Socket option get failed with unknown error"
            }
            throw SocketException(message)
        }
        let value = sockType.read()
        LibC.free(sockType)
        return value
    }
}

func getOnceSendSize(size: Int64): Int64 {
    if (size < Int64(SOCK_WRITE_BUFFER_SIZE)) {
        size
    } else {
        Int64(SOCK_WRITE_BUFFER_SIZE)
    }
}