Skip to content

Commit

Permalink
Begin support for RFC 7959 Block-Wise Transfers
Browse files Browse the repository at this point in the history
  • Loading branch information
NZSmartie committed Dec 19, 2017
1 parent fab984b commit 461c4e4
Show file tree
Hide file tree
Showing 8 changed files with 659 additions and 2 deletions.
122 changes: 122 additions & 0 deletions src/CoAPNet/CoapBlockStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using CoAPNet.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CoAPNet
{
public class CoapBlockStream : Stream
{
private readonly CoapClient _client;

private readonly ByteQueue _reader = new ByteQueue();
private readonly Task _readerTask;

private readonly ByteQueue _writer = new ByteQueue();
private readonly Task _writerTask;
private readonly AsyncAutoResetEvent _writerEvent = new AsyncAutoResetEvent(false);
private int _writeBlockNumber = 0;

private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

public readonly CoapMessage _baseMessage;

private bool _endOfStream = false;

// TODO: check blocksize for valid value in 16,32,...,1024
public int BlockSize { get; set; } = 256;


public override bool CanRead => true;

public override bool CanSeek => false;

public override bool CanWrite => true;

public override long Length => throw new NotSupportedException();

public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }


public CoapBlockStream(CoapClient client, CoapMessage baseMessage = null)
{
_client = client;
_baseMessage = baseMessage;

_writerTask = WriteBlocksAsync();
}

private async Task WriteBlocksAsync()
{
var token = _cancellationTokenSource.Token;

while (!token.IsCancellationRequested && !_endOfStream)
{
await _writerEvent.WaitAsync(token);

while (_writer.Length > BlockSize || (_endOfStream && _writer.Length > 0))
{
var message = _baseMessage.Clone();

message.Id = 0;
message.Options.Add(new Options.Block1(_writeBlockNumber++, BlockSize, _writer.Length > BlockSize));

message.Payload = new byte[_writer.Length < BlockSize ? _writer.Length : BlockSize];
_writer.Dequeue(message.Payload, 0, BlockSize);

await _client.SendAsync(message);
}
}
}

public override void Flush()
{
_writerEvent.Set();
}

public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}

public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}

public override void SetLength(long value)
{
throw new NotSupportedException();
}

public override void Write(byte[] buffer, int offset, int count)
{
_writer.Enqueue(buffer, offset, count);
_writerEvent.Set();
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
_endOfStream = true;

if (_writer.Length <= BlockSize)
_writerEvent.Set();
else
_cancellationTokenSource.Cancel();

try
{
_writerTask.Wait();
}
catch (AggregateException) { }

}
base.Dispose(disposing);
}
}
}
3 changes: 3 additions & 0 deletions src/CoAPNet/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ public static class CoapRegisteredOptionNumber
public const int UriQuery = 15;
public const int Accept = 17;
public const int LocationQuery = 20;
public const int Block1 = 27;
public const int Block2 = 23;
public const int ProxyUri = 35;
public const int ProxyScheme = 39;
public const int Size1 = 60;
public const int Size2 = 28;
}

/// <summary>
Expand Down
151 changes: 151 additions & 0 deletions src/CoAPNet/Options/BlockWise.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;

namespace CoAPNet.Options
{
public class BlockBase : CoapOption
{
internal List<Tuple<int, int>> SupportedBlockSizes = new List<Tuple<int, int>>
{
Tuple.Create(0, 16),
Tuple.Create(1, 32),
Tuple.Create(2, 64),
Tuple.Create(3, 128),
Tuple.Create(4, 256),
Tuple.Create(5, 512),
Tuple.Create(6, 1024),
// Value of 7, 2048 is reserved
};

public BlockBase(int optionNumber, int blockNumber, int blockSize, bool more) : this(optionNumber)
{
SetStuff(blockNumber, SupportedBlockSizes.First(b => b.Item2 == blockSize).Item1, more);
}

internal BlockBase(int optionNumber)
:base (optionNumber, 0, 3, false, OptionType.UInt)
{ }

public int BlockSize
{
get
{
var n = 0;
if (_length == 1)
n = (int)(ValueUInt & 0xE0) >> 5;
if (_length == 2)
n = (int)(ValueUInt & 0xE000) >> 13;
if (_length == 3)
n = (int)(ValueUInt & 0xE00000) >> 21;

return SupportedBlockSizes.First(b => b.Item1 == n).Item2;
}
set
{
SetStuff(BlockNumber, SupportedBlockSizes.First(b => b.Item2 == value).Item1, IsMoreFollowing);
}
}

public bool IsMoreFollowing
{
get
{
if (_length == 0)
return false;
if (_length == 1)
return (ValueUInt & 0x10) != 0;
if (_length == 2)
return (ValueUInt & 0x1000) != 0;
else if (_length == 3)
return (ValueUInt & 0x100000) != 0;
throw new ArgumentOutOfRangeException();
}
set
{
SetStuff(BlockNumber, BlockSize, value);
}
}

public int BlockNumber
{
get
{
if (_length == 0)
return 0;
if (_length == 1)
return (int)(ValueUInt & 0x0F);
if (_length == 2)
return (int)(ValueUInt & 0x0FFF);
else if (_length == 3)
return (int)(ValueUInt & 0x0FFFFF);
throw new ArgumentOutOfRangeException();
}
set
{
if (value < 0)
throw new ArgumentOutOfRangeException("Can not be less than 0");
if (value > 0x0FFFFF)
throw new ArgumentOutOfRangeException("Can not be larger than 1,048,575 (2^20 - 1)");

SetStuff(value, BlockSize, IsMoreFollowing);
}
}

internal void SetStuff(int number, int size, bool more)
{
var last = ((size & 0x07) << 5) | (more ? 0x10 : 0x00);

if (number <= 0x0F)
{
ValueUInt = (uint)(last | (number & 0x0F)) & 0xFF;
}
else if (number <= 0x0FFF)
{
ValueUInt = (uint)((last << 8) | (number & 0x0FFF)) & 0xFFFF;
}
else if (number <= 0x0FFFFF)
{
ValueUInt = (uint)((last << 16) | (number & 0x0FFFFF)) & 0xFFFFFF;
}
else
{
throw new InvalidOperationException();
}
}
}

public sealed class Block1 : BlockBase
{
public Block1()
: base(CoapRegisteredOptionNumber.Block1)
{ }

public Block1(int blockNumber = 0, int blockSize = 256, bool more = false)
: base(CoapRegisteredOptionNumber.Block1, blockNumber, blockSize, more)
{ }
}

public sealed class Block2 : BlockBase
{
public Block2()
: base(CoapRegisteredOptionNumber.Block2)
{ }

public Block2(int blockNumber = 0, int blockSize = 256, bool more = false)
: base(CoapRegisteredOptionNumber.Block2, blockNumber, blockSize, more)
{ }
}

public sealed class Size2 : CoapOption
{
public Size2() : base(CoapRegisteredOptionNumber.Size2, 0, 4, false, OptionType.UInt, 0u)
{ }

public Size2(uint value) : this()
{
ValueUInt = value;
}
}
}
4 changes: 4 additions & 0 deletions src/CoAPNet/Options/OptionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ public OptionFactory()
Register<MaxAge>();
Register<ETag>();
Register<Size1>();
Register<Size2>();

Register<IfMatch>();
Register<IfNoneMatch>();

Register<Block1>();
Register<Block2>();
}

/// <summary>
Expand Down
Loading

0 comments on commit 461c4e4

Please sign in to comment.