2024-03-31 09:31:50 +11:00
|
|
|
/*
|
|
|
|
Copyright 2024 Josh Deprez
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2024-03-17 12:37:53 +11:00
|
|
|
package aurp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
|
|
|
// OpenReq is used to open a one-way connection between AIRs.
|
|
|
|
type OpenReqPacket struct {
|
2024-03-17 13:31:26 +11:00
|
|
|
Header
|
2024-03-17 12:37:53 +11:00
|
|
|
|
|
|
|
Version uint16 // currently always 1
|
|
|
|
Options Options
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *OpenReqPacket) WriteTo(w io.Writer) (int64, error) {
|
|
|
|
a := acc(w)
|
2024-03-17 13:31:26 +11:00
|
|
|
a.writeTo(&p.Header)
|
2024-03-17 12:37:53 +11:00
|
|
|
a.write16(p.Version)
|
|
|
|
a.writeTo(p.Options)
|
|
|
|
return a.ret()
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseOpenReq(p []byte) (*OpenReqPacket, error) {
|
|
|
|
if len(p) < 3 {
|
|
|
|
return nil, fmt.Errorf("insufficient input length %d for Open-Req packet", len(p))
|
|
|
|
}
|
|
|
|
opts, err := parseOptions(p[2:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &OpenReqPacket{
|
|
|
|
Version: binary.BigEndian.Uint16(p[:2]),
|
|
|
|
Options: opts,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OpenRsp is used to respond to Open-Req.
|
|
|
|
type OpenRspPacket struct {
|
2024-03-17 13:31:26 +11:00
|
|
|
Header
|
2024-03-17 12:37:53 +11:00
|
|
|
|
|
|
|
RateOrErrCode int16
|
|
|
|
Options Options
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *OpenRspPacket) WriteTo(w io.Writer) (int64, error) {
|
|
|
|
a := acc(w)
|
2024-03-17 13:31:26 +11:00
|
|
|
a.writeTo(&p.Header)
|
2024-03-17 12:37:53 +11:00
|
|
|
a.write16(uint16(p.RateOrErrCode))
|
|
|
|
a.writeTo(p.Options)
|
|
|
|
return a.ret()
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseOpenRsp(p []byte) (*OpenRspPacket, error) {
|
|
|
|
if len(p) < 3 {
|
|
|
|
return nil, fmt.Errorf("insufficient input length %d for Open-Rsp packet", len(p))
|
|
|
|
}
|
|
|
|
opts, err := parseOptions(p[2:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &OpenRspPacket{
|
|
|
|
RateOrErrCode: int16(binary.BigEndian.Uint16(p[:2])),
|
|
|
|
Options: opts,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OptionTuple is used to pass option information in Open-Req and Open-Rsp
|
|
|
|
// packets.
|
|
|
|
type OptionTuple struct {
|
|
|
|
// Length uint8 = 1(for Type) + len(Data)
|
|
|
|
Type OptionType
|
|
|
|
Data []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ot *OptionTuple) WriteTo(w io.Writer) (int64, error) {
|
|
|
|
if len(ot.Data) > 254 {
|
|
|
|
return 0, fmt.Errorf("option tuple data too long [%d > 254]", len(ot.Data))
|
|
|
|
}
|
|
|
|
|
|
|
|
a := acc(w)
|
|
|
|
a.write([]byte{
|
|
|
|
byte(len(ot.Data) + 1),
|
|
|
|
byte(ot.Type),
|
|
|
|
})
|
|
|
|
a.write(ot.Data)
|
|
|
|
return a.ret()
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseOptionTuple(p []byte) (OptionTuple, []byte, error) {
|
|
|
|
if len(p) < 2 {
|
|
|
|
return OptionTuple{}, p, fmt.Errorf("insufficient input length %d for option tuple", len(p))
|
|
|
|
}
|
|
|
|
olen := int(p[0]) + 1
|
2024-04-24 12:18:22 +10:00
|
|
|
p = p[1:]
|
2024-03-17 12:37:53 +11:00
|
|
|
if len(p) < olen {
|
|
|
|
return OptionTuple{}, p, fmt.Errorf("insufficient input for option tuple data length %d", olen)
|
|
|
|
}
|
|
|
|
return OptionTuple{
|
2024-04-24 12:18:22 +10:00
|
|
|
Type: OptionType(p[0]),
|
|
|
|
Data: p[1:olen],
|
2024-03-17 12:37:53 +11:00
|
|
|
}, p[olen:], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OptionType is used to distinguish different options.
|
|
|
|
type OptionType uint8
|
|
|
|
|
|
|
|
// Various option types
|
|
|
|
const (
|
|
|
|
OptionTypeAuthentication OptionType = 0x01
|
|
|
|
// All other types reserved
|
|
|
|
)
|
|
|
|
|
|
|
|
type Options []OptionTuple
|
|
|
|
|
|
|
|
func (o Options) WriteTo(w io.Writer) (int64, error) {
|
|
|
|
if len(o) > 255 {
|
|
|
|
return 0, fmt.Errorf("too many options [%d > 255]", len(o))
|
|
|
|
}
|
|
|
|
|
|
|
|
a := acc(w)
|
|
|
|
a.write8(uint8(len(o)))
|
|
|
|
for _, ot := range o {
|
|
|
|
a.writeTo(&ot)
|
|
|
|
}
|
|
|
|
return a.ret()
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseOptions(p []byte) (Options, error) {
|
|
|
|
if len(p) < 1 {
|
|
|
|
return nil, fmt.Errorf("insufficint input length %d for options", len(p))
|
|
|
|
}
|
|
|
|
optc := p[0]
|
|
|
|
opts := make([]OptionTuple, optc)
|
|
|
|
for i := range optc {
|
|
|
|
ot, np, err := parseOptionTuple(p)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("parsing option %d: %w", i, err)
|
|
|
|
}
|
|
|
|
opts[i] = ot
|
|
|
|
p = np
|
|
|
|
}
|
|
|
|
// TODO: warn about trailing data?
|
|
|
|
return opts, nil
|
|
|
|
}
|