nimbus-eth2/beacon_chain/faststreams_backend.nim

119 lines
4.0 KiB
Nim

type
LibP2PInputStream = ref object of InputStream
conn: Connection
const
closingErrMsg = "Failed to close LibP2P stream"
readingErrMsg = "Failed to read from LibP2P stream"
proc libp2pReadOnce(s: LibP2PInputStream,
dst: pointer, dstLen: Natural): Future[Natural] {.async.} =
fsTranslateErrors readingErrMsg:
try:
return implementSingleRead(s.buffers, dst, dstLen, ReadFlags {},
readStartAddr, readLen):
await s.conn.readOnce(readStartAddr, readLen)
except LPStreamEOFError:
s.buffers.eofReached = true
proc libp2pCloseWait(s: LibP2PInputStream) {.async.} =
fsTranslateErrors closingErrMsg:
await safeClose(s.conn)
# TODO: Use the Raising type here
let libp2pInputVTable = InputStreamVTable(
readSync: proc (s: InputStream, dst: pointer, dstLen: Natural): Natural
{.nimcall, gcsafe, raises: [IOError, Defect].} =
doAssert(false, "synchronous reading is not allowed")
,
readAsync: proc (s: InputStream, dst: pointer, dstLen: Natural): Future[Natural]
{.nimcall, gcsafe, raises: [IOError, Defect].} =
fsTranslateErrors "Unexpected exception from merely forwarding a future":
return libp2pReadOnce(Libp2pInputStream s, dst, dstLen)
,
closeSync: proc (s: InputStream)
{.nimcall, gcsafe, raises: [IOError, Defect].} =
fsTranslateErrors closingErrMsg:
s.closeFut = Libp2pInputStream(s).conn.close()
,
closeAsync: proc (s: InputStream): Future[void]
{.nimcall, gcsafe, raises: [IOError, Defect].} =
fsTranslateErrors "Unexpected exception from merely forwarding a future":
return libp2pCloseWait(Libp2pInputStream s)
)
func libp2pInput*(conn: Connection,
pageSize = defaultPageSize): AsyncInputStream =
AsyncInputStream LibP2PInputStream(
vtable: vtableAddr libp2pInputVTable,
buffers: initPageBuffers(pageSize),
conn: conn)
proc readSizePrefix(s: AsyncInputStream, maxSize: uint64): Future[int] {.async.} =
trace "about to read msg size prefix"
var parser: VarintParser[uint64, ProtoBuf]
while s.readable:
case parser.feedByte(s.read)
of Done:
let res = parser.getResult
if res > maxSize:
trace "size prefix outside of range", res
return -1
else:
trace "got size prefix", res
return int(res)
of Overflow:
trace "size prefix overflow"
return -1
of Incomplete:
continue
proc readSszValue(s: AsyncInputStream, MsgType: type): Future[MsgType] {.async.} =
let size = await s.readSizePrefix(uint64(MAX_CHUNK_SIZE))
if size > 0 and s.readable(size):
s.withReadableRange(size, r):
return r.readValue(SSZ, MsgType)
else:
raise newException(CatchableError,
"Failed to read an incoming message size prefix")
proc readResponseCode(s: AsyncInputStream): Future[Result[bool, string]] {.async.} =
if s.readable:
let responseCode = s.read
static: assert responseCode.type.low == 0
if responseCode > ResponseCode.high.byte:
return err("Invalid response code")
case ResponseCode(responseCode):
of InvalidRequest, ServerError:
return err(await s.readSszValue(string))
of Success:
return ok true
else:
return ok false
proc readChunk(s: AsyncInputStream,
MsgType: typedesc): Future[Option[MsgType]] {.async.} =
let rc = await s.readResponseCode()
if rc.isOk:
if rc[]:
return some(await readSszValue(s, MsgType))
else:
trace "Failed to read response code",
reason = rc.error
proc readResponse(s: AsyncInputStream,
MsgType: type): Future[Option[MsgType]] {.gcsafe, async.} =
when MsgType is seq:
type E = ElemType(MsgType)
var results: MsgType
while true:
let nextRes = await s.readChunk(E)
if nextRes.isNone: break
results.add nextRes.get
if results.len > 0:
return some(results)
else:
return await s.readChunk(MsgType)