#
#
# Nim's Runtime Library
# (c) Copyright 2021 Nim contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module provides an implementation of the streams interface for sockets.
## It contains two separate implementations, a
## `ReadSocketStream <#ReadSocketStream>`_ and a
## `WriteSocketStream <#WriteSocketStream>`_.
##
## The `ReadSocketStream` only supports reading, peeking, and seeking.
## It reads into a buffer, so even by
## seeking backwards it will only read the same position a single time from the
## underlying socket. To clear the buffer and free the data read into it you
## can call `resetStream`, this will also reset the position back to 0 but
## won't do anything to the underlying socket.
##
## The `WriteSocketStream` allows both reading and writing, but it performs the
## reads on the internal buffer. So by writing to the buffer you can then read
## back what was written but without receiving anything from the socket. You
## can also set the position and overwrite parts of the buffer, and to send
## anything over the socket you need to call `flush` at which point you can't
## write anything to the buffer before the point of the flush (but it can still
## be read). Again to empty the underlying buffer you need to call
## `resetStream`.
##
## Examples
## ========
##
## .. code-block:: Nim
## import std/socketstreams
##
## var
## socket = newSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
## stream = newReadSocketStream(socket)
## socket.sendTo("127.0.0.1", Port(12345), "SOME REQUEST")
## echo stream.readLine() # Will call `recv`
## stream.setPosition(0)
## echo stream.readLine() # Will return the read line from the buffer
## stream.resetStream() # Buffer is now empty, position is 0
## echo stream.readLine() # Will call `recv` again
## stream.close() # Closes the socket
##
## .. code-block:: Nim
##
## import std/socketstreams
##
## var socket = newSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
## socket.connect("127.0.0.1", Port(12345))
## var sendStream = newWriteSocketStream(socket)
## sendStream.write "NOM"
## sendStream.setPosition(1)
## echo sendStream.peekStr(2) # OM
## sendStream.write "I"
## sendStream.setPosition(0)
## echo sendStream.readStr(3) # NIM
## echo sendStream.getPosition() # 3
## sendStream.flush() # This actually performs the writing to the socket
## sendStream.setPosition(1)
## sendStream.write "I" # Throws an error as we can't write into an already sent buffer
import net, streams
type
ReadSocketStream* = ref ReadSocketStreamObj
ReadSocketStreamObj* = object of StreamObj
data: Socket
pos: int
buf: seq[byte]
WriteSocketStream* = ref WriteSocketStreamObj
WriteSocketStreamObj* = object of ReadSocketStreamObj
lastFlush: int
proc rsAtEnd(s: Stream): bool =
return false
proc rsSetPosition(s: Stream, pos: int) =
var s = ReadSocketStream(s)
s.pos = pos
proc rsGetPosition(s: Stream): int =
var s = ReadSocketStream(s)
return s.pos
proc rsPeekData(s: Stream, buffer: pointer, bufLen: int): int =
let s = ReadSocketStream(s)
if bufLen > 0:
let oldLen = s.buf.len
s.buf.setLen(max(s.pos + bufLen, s.buf.len))
if s.pos + bufLen > oldLen:
result = s.data.recv(s.buf[oldLen].addr, s.buf.len - oldLen)
if result > 0:
result += oldLen - s.pos
else:
result = bufLen
copyMem(buffer, s.buf[s.pos].addr, result)
proc rsReadData(s: Stream, buffer: pointer, bufLen: int): int =
result = s.rsPeekData(buffer, bufLen)
var s = ReadSocketStream(s)
s.pos += bufLen
proc rsReadDataStr(s: Stream, buffer: var string, slice: Slice[int]): int =
var s = ReadSocketStream(s)
result = slice.b + 1 - slice.a
if result > 0:
result = s.rsReadData(buffer[slice.a].addr, result)
inc(s.pos, result)
else:
result = 0
proc wsWriteData(s: Stream, buffer: pointer, bufLen: int) =
var s = WriteSocketStream(s)
if s.pos < s.lastFlush:
raise newException(IOError, "Unable to write into buffer that has already been sent")
if s.buf.len < s.pos + bufLen:
s.buf.setLen(s.pos + bufLen)
copyMem(s.buf[s.pos].addr, buffer, bufLen)
s.pos += bufLen
proc wsPeekData(s: Stream, buffer: pointer, bufLen: int): int =
var s = WriteSocketStream(s)
result = bufLen
if result > 0:
if s.pos > s.buf.len or s.pos == s.buf.len or s.pos + bufLen > s.buf.len:
raise newException(IOError, "Unable to read past end of write buffer")
else:
copyMem(buffer, s.buf[s.pos].addr, bufLen)
proc wsReadData(s: Stream, buffer: pointer, bufLen: int): int =
result = s.wsPeekData(buffer, bufLen)
var s = ReadSocketStream(s)
s.pos += bufLen
proc wsAtEnd(s: Stream): bool =
var s = WriteSocketStream(s)
return s.pos == s.buf.len
proc wsFlush(s: Stream) =
var s = WriteSocketStream(s)
discard s.data.send(s.buf[s.lastFlush].addr, s.buf.len - s.lastFlush)
s.lastFlush = s.buf.len
proc rsClose(s: Stream) =
{.cast(tags: []).}:
var s = ReadSocketStream(s)
s.data.close()
proc newReadSocketStream*(s: Socket): owned ReadSocketStream =
result = ReadSocketStream(data: s, pos: 0,
closeImpl: rsClose,
atEndImpl: rsAtEnd,
setPositionImpl: rsSetPosition,
getPositionImpl: rsGetPosition,
readDataImpl: rsReadData,
peekDataImpl: rsPeekData,
readDataStrImpl: rsReadDataStr)
proc resetStream*(s: ReadSocketStream) =
s.buf = @[]
s.pos = 0
proc newWriteSocketStream*(s: Socket): owned WriteSocketStream =
result = WriteSocketStream(data: s, pos: 0,
closeImpl: rsClose,
atEndImpl: wsAtEnd,
setPositionImpl: rsSetPosition,
getPositionImpl: rsGetPosition,
writeDataImpl: wsWriteData,
readDataImpl: wsReadData,
peekDataImpl: wsPeekData,
flushImpl: wsFlush)
proc resetStream*(s: WriteSocketStream) =
s.buf = @[]
s.pos = 0
s.lastFlush = 0