Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support block wise transfer options #56

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 95 additions & 25 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,32 +79,40 @@ const (
ProxyingNotSupported COAPCode = 165
)

// Response Codes for Block-Wise Transfer in CoAP (RFC 7959)
const (
Continue COAPCode = 95
RequestEntityIncomplete COAPCode = 136
)

var codeNames = [256]string{
GET: "GET",
POST: "POST",
PUT: "PUT",
DELETE: "DELETE",
Created: "Created",
Deleted: "Deleted",
Valid: "Valid",
Changed: "Changed",
Content: "Content",
BadRequest: "BadRequest",
Unauthorized: "Unauthorized",
BadOption: "BadOption",
Forbidden: "Forbidden",
NotFound: "NotFound",
MethodNotAllowed: "MethodNotAllowed",
NotAcceptable: "NotAcceptable",
PreconditionFailed: "PreconditionFailed",
RequestEntityTooLarge: "RequestEntityTooLarge",
UnsupportedMediaType: "UnsupportedMediaType",
InternalServerError: "InternalServerError",
NotImplemented: "NotImplemented",
BadGateway: "BadGateway",
ServiceUnavailable: "ServiceUnavailable",
GatewayTimeout: "GatewayTimeout",
ProxyingNotSupported: "ProxyingNotSupported",
GET: "GET",
POST: "POST",
PUT: "PUT",
DELETE: "DELETE",
Created: "Created",
Deleted: "Deleted",
Valid: "Valid",
Changed: "Changed",
Content: "Content",
BadRequest: "BadRequest",
Unauthorized: "Unauthorized",
BadOption: "BadOption",
Forbidden: "Forbidden",
NotFound: "NotFound",
MethodNotAllowed: "MethodNotAllowed",
NotAcceptable: "NotAcceptable",
PreconditionFailed: "PreconditionFailed",
RequestEntityTooLarge: "RequestEntityTooLarge",
UnsupportedMediaType: "UnsupportedMediaType",
InternalServerError: "InternalServerError",
NotImplemented: "NotImplemented",
BadGateway: "BadGateway",
ServiceUnavailable: "ServiceUnavailable",
GatewayTimeout: "GatewayTimeout",
ProxyingNotSupported: "ProxyingNotSupported",
Continue: "Continue",
RequestEntityIncomplete: "Request Entity Incomplete",
}

func init() {
Expand Down Expand Up @@ -173,6 +181,12 @@ const (
Size1 OptionID = 60
)

// Block-Wise Transfer Option IDs (RFC7959 section 6.).
const (
Block2 OptionID = 23
Block1 OptionID = 27
)

// Option value format (RFC7252 section 3.2)
type valueFormat uint8

Expand Down Expand Up @@ -207,6 +221,8 @@ var optionDefs = [256]optionDef{
ProxyURI: optionDef{valueFormat: valueString, minLen: 1, maxLen: 1034},
ProxyScheme: optionDef{valueFormat: valueString, minLen: 1, maxLen: 255},
Size1: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 4},
Block2: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 3},
Block1: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 3},
}

// MediaType specifies the content type of a message.
Expand Down Expand Up @@ -350,6 +366,60 @@ func (m Message) IsConfirmable() bool {
return m.Type == Confirmable
}

// IsBlock2 returns true if this message contains Block2 option.
func (m Message) IsBlock2() bool {
_, ok := m.Option(Block2).(uint32)
return ok
}

// Block2 returns a block number, the block size and true when other blocks follow.
func (m Message) Block2() (uint32, uint32, bool) {
return m.decodeBlock(Block2)
}

// SetBlock2 sets Block2 option.
func (m *Message) SetBlock2(num, szx uint32, more bool) {
m.encodeBlock(Block2, num, szx, more)
}

// IsBlock2 returns true if this message contains Block2 option.
func (m Message) IsBlock1() bool {
_, ok := m.Option(Block1).(uint32)
return ok
}

// Block1 returns a block number, the block size and true when other blocks follow.
func (m Message) Block1() (uint32, uint32, bool) {
return m.decodeBlock(Block1)
}

// SetBlock1 sets Block1 option.
func (m *Message) SetBlock1(num, szx uint32, more bool) {
m.encodeBlock(Block1, num, szx, more)
}

func (m *Message) encodeBlock(blockType OptionID, num, szx uint32, more bool) {
val := (num * 16) | (szx - 4)
if more {
val |= 8
}
m.SetOption(blockType, val)
}

func (m Message) decodeBlock(blockType OptionID) (uint32, uint32, bool) {
val, ok := m.Option(blockType).(uint32)
if !ok {
return 0, 0, false
}
num := val / 16
szx := (val & 7) + 4
more := false
if val&8 > 0 {
more = true
}
return num, szx, more
}

// Options gets all the values for the given option.
func (m Message) Options(o OptionID) []interface{} {
var rv []interface{}
Expand Down
194 changes: 194 additions & 0 deletions message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,3 +806,197 @@ func TestEncodeMessageWithAllOptions(t *testing.T) {
}
assertEqualMessages(t, req, parsedMsg)
}

func TestEncodeMessageWithBlock1(t *testing.T) {
req := Message{
Type: Confirmable,
Code: GET,
MessageID: 12345,
}

if req.IsBlock1() {
t.Fatalf("Error Block1 FOUND in request")
}

blockNum := uint32(0)
blockSzx := uint32(10)
blockMore := true

req.SetBlock1(blockNum, blockSzx, blockMore)

if !req.IsBlock1() {
t.Fatalf("Error Block1 NOT FOUND in request")
}

data, err := req.MarshalBinary()
if err != nil {
t.Fatalf("Error encoding request: %v", err)
}

res, err := ParseMessage(data)
if err != nil {
t.Fatalf("Error parsing binary packet: %v", err)
}
assertEqualMessages(t, req, res)

if !res.IsBlock1() {
t.Fatalf("Error Block1 NOT FOUND in response")
}

resBlockNum, resBlockSzx, resBlockMore := res.Block1()

if resBlockNum != blockNum {
t.Fatalf("Error block NUM %d != %d", blockNum, resBlockNum)
}
if resBlockSzx != blockSzx {
t.Fatalf("Error block SZX %d != %d", blockSzx, resBlockSzx)
}
if resBlockMore != blockMore {
t.Fatalf("Error block more %v != %v", blockMore, resBlockMore)
}
}

func TestEncodeMessageWithBlock2(t *testing.T) {
req := Message{
Type: Confirmable,
Code: GET,
MessageID: 12345,
}

if req.IsBlock2() {
t.Fatalf("Error Block2 FOUND in request")
}

blockNum := uint32(0)
blockSzx := uint32(10)
blockMore := false

req.SetBlock2(blockNum, blockSzx, blockMore)

if !req.IsBlock2() {
t.Fatalf("Error Block2 NOT FOUND in request")
}

data, err := req.MarshalBinary()
if err != nil {
t.Fatalf("Error encoding request: %v", err)
}

res, err := ParseMessage(data)
if err != nil {
t.Fatalf("Error parsing binary packet: %v", err)
}
assertEqualMessages(t, req, res)

if !res.IsBlock2() {
t.Fatalf("Error Block2 NOT FOUND in response")
}

resBlockNum, resBlockSzx, resBlockMore := res.Block2()

if resBlockNum != blockNum {
t.Fatalf("Error block NUM %d != %d", blockNum, resBlockNum)
}
if resBlockSzx != blockSzx {
t.Fatalf("Error block SZX %d != %d", blockSzx, resBlockSzx)
}
if resBlockMore != blockMore {
t.Fatalf("Error block more %v != %v", blockMore, resBlockMore)
}
}

func TestEncodeMessageWithBlockOptions(t *testing.T) {
req := Message{
Type: Confirmable,
Code: GET,
MessageID: 12345,
}

if req.IsBlock1() {
t.Fatalf("Error Block1 FOUND in request")
}
if req.IsBlock2() {
t.Fatalf("Error Block2 FOUND in request")
}

block1Num := uint32(0)
block1Szx := uint32(10)
block1More := true

req.SetBlock1(block1Num, block1Szx, block1More)

if !req.IsBlock1() {
t.Fatalf("Error Block1 NOT FOUND in request")
}

block2Num := uint32(0)
block2Szx := uint32(10)
block2More := false

req.SetBlock2(block2Num, block2Szx, block2More)

if !req.IsBlock2() {
t.Fatalf("Error Block2 NOT FOUND in request")
}

data, err := req.MarshalBinary()
if err != nil {
t.Fatalf("Error encoding request: %v", err)
}

res, err := ParseMessage(data)
if err != nil {
t.Fatalf("Error parsing binary packet: %v", err)
}
assertEqualMessages(t, req, res)

if !res.IsBlock1() {
t.Fatalf("Error Block1 NOT FOUND in response")
}
if !res.IsBlock2() {
t.Fatalf("Error Block2 NOT FOUND in response")
}

resBlock1Num, resBlock1Szx, resBlock1More := res.Block1()

if resBlock1Num != block1Num {
t.Fatalf("Error block1 NUM %d != %d", block1Num, resBlock1Num)
}
if resBlock1Szx != block1Szx {
t.Fatalf("Error block1 SZX %d != %d", block1Szx, resBlock1Szx)
}
if resBlock1More != block1More {
t.Fatalf("Error block1 more %v != %v", block1More, resBlock1More)
}

resBlock2Num, resBlock2Szx, resBlock2More := res.Block2()

if resBlock2Num != block2Num {
t.Fatalf("Error block2 NUM %d != %d", block2Num, resBlock2Num)
}
if resBlock2Szx != block2Szx {
t.Fatalf("Error block2 SZX %d != %d", block2Szx, resBlock2Szx)
}
if resBlock2More != block2More {
t.Fatalf("Error block2 more %v != %v", block2More, resBlock2More)
}
}

func TestBlockEncoding(t *testing.T) {
req := Message{
Type: Confirmable,
Code: GET,
MessageID: 12345,
}
// Range of valid szx is <4;10>
for szx := uint32(4); szx <= 10; szx++ {
req.SetBlock1(0, szx, false)
if !req.IsBlock1() {
t.Fatalf("Error Block1 not set!")
}
req.SetBlock2(0, szx, false)
if !req.IsBlock2() {
t.Fatalf("Error Block2 not set!")
}
}
}