-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from cloudstruct/feature/initial
Initial structure and implementation
- Loading branch information
Showing
10 changed files
with
419 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
*.dll | ||
*.so | ||
*.dylib | ||
/go-ouroboros-network | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
BINARY=go-ouroboros-network | ||
|
||
# Determine root directory | ||
ROOT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) | ||
|
||
# Gather all .go files for use in dependencies below | ||
GO_FILES=$(shell find $(ROOT_DIR) -name '*.go') | ||
|
||
# Build our program binary | ||
# Depends on GO_FILES to determine when rebuild is needed | ||
$(BINARY): $(GO_FILES) | ||
# Needed to fetch new dependencies and add them to go.mod | ||
go mod tidy | ||
go build -o $(BINARY) ./cmd/$(BINARY) | ||
|
||
.PHONY: build image | ||
|
||
# Alias for building program binary | ||
build: $(BINARY) | ||
|
||
clean: | ||
rm -f $(BINARY) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
# go-ouroboros | ||
A Go client implementation of the Cardano Ouroboros protocol | ||
# go-ouroboros-network | ||
|
||
A Go client implementation of the Cardano Ouroboros network protocol | ||
|
||
This is loosely based on the [official Haskell implementation](https://github.com/input-output-hk/ouroboros-network) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package main | ||
|
||
import ( | ||
"crypto/tls" | ||
"flag" | ||
"fmt" | ||
"github.com/cloudstruct/go-ouroboros-network" | ||
"io" | ||
"net" | ||
"os" | ||
) | ||
|
||
const ( | ||
TESTNET_MAGIC = 1097911063 | ||
MAINNET_MAGIC = 764824073 | ||
) | ||
|
||
type cmdFlags struct { | ||
socket string | ||
address string | ||
useTls bool | ||
networkMagic int | ||
testnet bool | ||
mainnet bool | ||
} | ||
|
||
func main() { | ||
f := cmdFlags{} | ||
flag.StringVar(&f.socket, "socket", "", "UNIX socket path to connect to") | ||
flag.StringVar(&f.address, "address", "", "TCP address to connect to in address:port format") | ||
flag.BoolVar(&f.useTls, "tls", false, "enable TLS") | ||
flag.IntVar(&f.networkMagic, "network-magic", 0, "network magic value") | ||
flag.BoolVar(&f.testnet, "testnet", false, fmt.Sprintf("alias for -network-magic=%d", TESTNET_MAGIC)) | ||
flag.BoolVar(&f.mainnet, "mainnet", false, fmt.Sprintf("alias for -network-magic=%d", MAINNET_MAGIC)) | ||
flag.Parse() | ||
|
||
var conn io.ReadWriteCloser | ||
var err error | ||
var dialProto string | ||
var dialAddress string | ||
if f.socket != "" { | ||
dialProto = "unix" | ||
dialAddress = f.socket | ||
} else if f.address != "" { | ||
dialProto = "tcp" | ||
dialAddress = f.address | ||
} else { | ||
fmt.Printf("You must specify one of -socket or -address\n\n") | ||
flag.PrintDefaults() | ||
os.Exit(1) | ||
} | ||
if f.useTls { | ||
conn, err = tls.Dial(dialProto, dialAddress, nil) | ||
} else { | ||
conn, err = net.Dial(dialProto, dialAddress) | ||
} | ||
if err != nil { | ||
fmt.Printf("Connection failed: %s\n", err) | ||
os.Exit(1) | ||
} | ||
if f.networkMagic == 0 { | ||
if f.testnet { | ||
f.networkMagic = TESTNET_MAGIC | ||
} else if f.mainnet { | ||
f.networkMagic = MAINNET_MAGIC | ||
} else { | ||
fmt.Printf("You must specify one of -testnet, -mainnet, or -network-magic\n\n") | ||
flag.PrintDefaults() | ||
os.Exit(1) | ||
} | ||
} | ||
oOpts := &ouroboros.OuroborosOptions{ | ||
Conn: conn, | ||
NetworkMagic: uint32(f.networkMagic), | ||
} | ||
o, err := ouroboros.New(oOpts) | ||
if err != nil { | ||
fmt.Printf("ERROR: %s\n", err) | ||
os.Exit(1) | ||
} | ||
go func() { | ||
for { | ||
err := <-o.ErrorChan | ||
fmt.Printf("ERROR: %s\n", err) | ||
os.Exit(1) | ||
} | ||
}() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/cloudstruct/go-ouroboros-network | ||
|
||
go 1.16 | ||
|
||
require github.com/fxamacker/cbor/v2 v2.3.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
github.com/fxamacker/cbor/v2 v2.3.0 h1:aM45YGMctNakddNNAezPxDUpv38j44Abh+hifNuqXik= | ||
github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= | ||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= | ||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package handshake | ||
|
||
import ( | ||
"fmt" | ||
"github.com/cloudstruct/go-ouroboros-network/utils" | ||
) | ||
|
||
const ( | ||
PROTOCOL_ID_SENDER = 0x0000 | ||
PROTOCOL_ID_RECEIVER = 0x8000 | ||
MESSAGE_TYPE_REQUEST = 0 | ||
MESSAGE_TYPE_RESPONSE_ACCEPT = 1 | ||
MESSAGE_TYPE_RESPONSE_REFUSE = 2 | ||
REFUSE_REASON_VERSION_MISMATCH = 0 | ||
REFUSE_REASON_DECODE_ERROR = 1 | ||
REFUSE_REASON_REFUSED = 2 | ||
) | ||
|
||
type handshakeMessage struct { | ||
_ struct{} `cbor:",toarray"` | ||
MessageType uint8 | ||
} | ||
|
||
type handshakeRequest struct { | ||
handshakeMessage | ||
VersionMap map[uint16]uint32 | ||
} | ||
|
||
type handshakeResponseAccept struct { | ||
handshakeMessage | ||
Version uint16 | ||
NetworkMagic uint32 | ||
} | ||
|
||
type handshakeResponseRefuse struct { | ||
handshakeMessage | ||
Reason []interface{} | ||
} | ||
|
||
type Handshake struct { | ||
sendChan chan []byte | ||
recvChan chan []byte | ||
} | ||
|
||
func New(sendChan chan []byte, recvChan chan []byte) *Handshake { | ||
h := &Handshake{ | ||
sendChan: sendChan, | ||
recvChan: recvChan, | ||
} | ||
return h | ||
} | ||
|
||
func (h *Handshake) Start(versions []uint16, networkMagic uint32) (uint16, error) { | ||
// Create our request | ||
versionMap := make(map[uint16]uint32) | ||
for _, version := range versions { | ||
versionMap[version] = networkMagic | ||
} | ||
data := handshakeRequest{ | ||
handshakeMessage: handshakeMessage{ | ||
MessageType: MESSAGE_TYPE_REQUEST, | ||
}, | ||
VersionMap: versionMap, | ||
} | ||
dataBytes, err := utils.CborEncode(data) | ||
if err != nil { | ||
return 0, err | ||
} | ||
// Send request | ||
h.sendChan <- dataBytes | ||
// Wait for response | ||
respBytes := <-h.recvChan | ||
// Decode response into generic list until we can determine what type of response it is | ||
var resp []interface{} | ||
if err := utils.CborDecode(respBytes, &resp); err != nil { | ||
return 0, err | ||
} | ||
switch resp[0].(uint64) { | ||
case MESSAGE_TYPE_RESPONSE_ACCEPT: | ||
var respAccept handshakeResponseAccept | ||
if err := utils.CborDecode(respBytes, &respAccept); err != nil { | ||
return 0, err | ||
} | ||
return respAccept.Version, nil | ||
case MESSAGE_TYPE_RESPONSE_REFUSE: | ||
var respRefuse handshakeResponseRefuse | ||
if err := utils.CborDecode(respBytes, &respRefuse); err != nil { | ||
return 0, err | ||
} | ||
switch respRefuse.Reason[0].(uint64) { | ||
case REFUSE_REASON_VERSION_MISMATCH: | ||
return 0, fmt.Errorf("handshake failed: version mismatch") | ||
case REFUSE_REASON_DECODE_ERROR: | ||
return 0, fmt.Errorf("handshake failed: decode error: %s", respRefuse.Reason[2].(string)) | ||
case REFUSE_REASON_REFUSED: | ||
return 0, fmt.Errorf("handshake failed: refused: %s", respRefuse.Reason[2].(string)) | ||
} | ||
} | ||
return 0, fmt.Errorf("unexpected failure") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package ouroboros | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"io" | ||
"time" | ||
) | ||
|
||
type MessageHeader struct { | ||
Timestamp uint32 | ||
ProtocolId uint16 | ||
PayloadLength uint16 | ||
} | ||
|
||
type Message struct { | ||
MessageHeader | ||
Payload []byte | ||
} | ||
|
||
func newMessage(protocolId uint16, payload []byte) (*Message, error) { | ||
msgHeader := MessageHeader{ | ||
Timestamp: uint32(time.Now().UnixNano() & 0xffffffff), | ||
ProtocolId: protocolId, | ||
} | ||
msgHeader.PayloadLength = uint16(len(payload)) | ||
msg := &Message{ | ||
MessageHeader: msgHeader, | ||
Payload: payload, | ||
} | ||
return msg, nil | ||
} | ||
|
||
type Muxer struct { | ||
conn io.ReadWriteCloser | ||
errorChan chan error | ||
respChan chan *Message | ||
protocolSenders map[uint16]chan []byte | ||
protocolReceivers map[uint16]chan []byte | ||
} | ||
|
||
func NewMuxer(conn io.ReadWriteCloser) *Muxer { | ||
m := &Muxer{ | ||
conn: conn, | ||
errorChan: make(chan error, 10), | ||
respChan: make(chan *Message, 10), | ||
protocolSenders: make(map[uint16]chan []byte), | ||
protocolReceivers: make(map[uint16]chan []byte), | ||
} | ||
go m.readLoop() | ||
return m | ||
} | ||
|
||
func (m *Muxer) registerProtocol(senderProtocolId uint16, receiverProtocolId uint16) (chan []byte, chan []byte) { | ||
// Generate channels | ||
senderChan := make(chan []byte, 10) | ||
receiverChan := make(chan []byte, 10) | ||
// Record channels in protocol sender/receiver maps | ||
m.protocolSenders[senderProtocolId] = senderChan | ||
m.protocolReceivers[receiverProtocolId] = receiverChan | ||
// Start Goroutine to handle outbound messages | ||
go func() { | ||
for { | ||
payload := <-senderChan | ||
m.Send(senderProtocolId, payload) | ||
} | ||
}() | ||
return senderChan, receiverChan | ||
} | ||
|
||
func (m *Muxer) Send(protocolId uint16, payload []byte) error { | ||
msg, err := newMessage(protocolId, payload) | ||
if err != nil { | ||
return err | ||
} | ||
buf := &bytes.Buffer{} | ||
err = binary.Write(buf, binary.BigEndian, msg.MessageHeader) | ||
if err != nil { | ||
return err | ||
} | ||
buf.Write(msg.Payload) | ||
_, err = m.conn.Write(buf.Bytes()) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (m *Muxer) readLoop() { | ||
for { | ||
msgHeader := MessageHeader{} | ||
if err := binary.Read(m.conn, binary.BigEndian, &msgHeader); err != nil { | ||
m.errorChan <- err | ||
} | ||
msg := &Message{ | ||
MessageHeader: msgHeader, | ||
Payload: make([]byte, msgHeader.PayloadLength), | ||
} | ||
// We use ReadFull because it guarantees to read the expected number of bytes or | ||
// return an error | ||
if _, err := io.ReadFull(m.conn, msg.Payload); err != nil { | ||
m.errorChan <- err | ||
} | ||
// Send message payload to proper receiver | ||
m.protocolReceivers[msgHeader.ProtocolId] <- msg.Payload | ||
} | ||
} |
Oops, something went wrong.