@cryptotaxi247 / kubo / commits / 06b651c45

Service + request

Juan Batiz-Benet committed Sep 11, 2014 at 03:47 UTC 06b651c4545ae67c10ca0b26c30fd329e6b60e2d
7 files changed +551
net/service/Makefile new
+8
@@ -0,0 +1,8 @@
1 +
2 +all: request.pb.go
3 +
4 +request.pb.go: request.proto
5 + protoc --gogo_out=. --proto_path=../../../../../:/usr/local/opt/protobuf/include:. $<
6 +
7 +clean:
8 + rm request.pb.go
net/service/request.go new
+127
@@ -0,0 +1,127 @@
1 +package service
2 +
3 +import (
4 + crand "crypto/rand"
5 +
6 + msg "github.com/jbenet/go-ipfs/net/message"
7 + peer "github.com/jbenet/go-ipfs/peer"
8 +
9 + proto "code.google.com/p/goprotobuf/proto"
10 +)
11 +
12 +const (
13 + // IDSize is the size of the ID in bytes.
14 + IDSize int = 4
15 +)
16 +
17 +// RequestID is a field that identifies request-response flows.
18 +type RequestID []byte
19 +
20 +// Request turns a RequestID into a Request (unsetting first bit)
21 +func (r RequestID) Request() RequestID {
22 + if r == nil {
23 + return nil
24 + }
25 + r2 := make([]byte, len(r))
26 + copy(r2, r)
27 + r2[0] = r[0] & 0x7F // unset first bit for request
28 + return RequestID(r2)
29 +}
30 +
31 +// Response turns a RequestID into a Response (setting first bit)
32 +func (r RequestID) Response() RequestID {
33 + if r == nil {
34 + return nil
35 + }
36 + r2 := make([]byte, len(r))
37 + copy(r2, r)
38 + r2[0] = r[0] | 0x80 // set first bit for response
39 + return RequestID(r2)
40 +}
41 +
42 +// IsRequest returns whether a RequestID identifies a request
43 +func (r RequestID) IsRequest() bool {
44 + if r == nil {
45 + return false
46 + }
47 + return !r.IsResponse()
48 +}
49 +
50 +// IsResponse returns whether a RequestID identifies a response
51 +func (r RequestID) IsResponse() bool {
52 + if r == nil {
53 + return false
54 + }
55 + return bool(r[0]&0x80 == 0x80)
56 +}
57 +
58 +// RandomRequestID creates and returns a new random request ID
59 +func RandomRequestID() (RequestID, error) {
60 + buf := make([]byte, IDSize)
61 + _, err := crand.Read(buf)
62 + return RequestID(buf).Request(), err
63 +}
64 +
65 +// RequestMap is a map of Requests. the key = (peer.ID concat RequestID).
66 +type RequestMap map[string]*Request
67 +
68 +// Request objects are used to multiplex request-response flows.
69 +type Request struct {
70 +
71 + // ID is the RequestID identifying this Request-Response Flow.
72 + ID RequestID
73 +
74 + // PeerID identifies the peer from whom to expect the response.
75 + PeerID peer.ID
76 +
77 + // Response is the channel of incoming responses.
78 + Response chan *msg.Message
79 +}
80 +
81 +// NewRequest creates a request for given peer.ID
82 +func NewRequest(pid peer.ID) (*Request, error) {
83 + id, err := RandomRequestID()
84 + if err != nil {
85 + return nil, err
86 + }
87 +
88 + return &Request{
89 + ID: id,
90 + PeerID: pid,
91 + Response: make(chan *msg.Message, 1),
92 + }, nil
93 +}
94 +
95 +// Key returns the RequestKey for this request. Use with maps.
96 +func (r *Request) Key() string {
97 + return RequestKey(r.PeerID, r.ID)
98 +}
99 +
100 +// RequestKey is the peer.ID concatenated with the RequestID. Use with maps.
101 +func RequestKey(pid peer.ID, rid RequestID) string {
102 + return string(pid) + string(rid.Request()[:])
103 +}
104 +
105 +func wrapData(data []byte, rid RequestID) ([]byte, error) {
106 + // Marshal
107 + pbm := new(PBRequest)
108 + pbm.Data = data
109 + pbm.Tag = rid
110 + b, err := proto.Marshal(pbm)
111 + if err != nil {
112 + return nil, err
113 + }
114 +
115 + return b, nil
116 +}
117 +
118 +func unwrapData(data []byte) ([]byte, RequestID, error) {
119 + // Unmarshal
120 + pbm := new(PBRequest)
121 + err := proto.Unmarshal(data, pbm)
122 + if err != nil {
123 + return nil, nil, err
124 + }
125 +
126 + return pbm.GetData(), pbm.GetTag(), nil
127 +}
net/service/request.pb.go new
+50
@@ -0,0 +1,50 @@
1 +// Code generated by protoc-gen-gogo.
2 +// source: request.proto
3 +// DO NOT EDIT!
4 +
5 +/*
6 +Package service is a generated protocol buffer package.
7 +
8 +It is generated from these files:
9 + request.proto
10 +
11 +It has these top-level messages:
12 + PBRequest
13 +*/
14 +package service
15 +
16 +import proto "code.google.com/p/gogoprotobuf/proto"
17 +import json "encoding/json"
18 +import math "math"
19 +
20 +// Reference proto, json, and math imports to suppress error if they are not otherwise used.
21 +var _ = proto.Marshal
22 +var _ = &json.SyntaxError{}
23 +var _ = math.Inf
24 +
25 +type PBRequest struct {
26 + Data []byte `protobuf:"bytes,1,req" json:"Data,omitempty"`
27 + Tag []byte `protobuf:"bytes,3,opt" json:"Tag,omitempty"`
28 + XXX_unrecognized []byte `json:"-"`
29 +}
30 +
31 +func (m *PBRequest) Reset() { *m = PBRequest{} }
32 +func (m *PBRequest) String() string { return proto.CompactTextString(m) }
33 +func (*PBRequest) ProtoMessage() {}
34 +
35 +func (m *PBRequest) GetData() []byte {
36 + if m != nil {
37 + return m.Data
38 + }
39 + return nil
40 +}
41 +
42 +func (m *PBRequest) GetTag() []byte {
43 + if m != nil {
44 + return m.Tag
45 + }
46 + return nil
47 +}
48 +
49 +func init() {
50 +}
net/service/request.proto new
+6
@@ -0,0 +1,6 @@
1 +package service;
2 +
3 +message PBRequest {
4 + required bytes Data = 1;
5 + optional bytes Tag = 3;
6 +}
net/service/request_test.go new
+41
@@ -0,0 +1,41 @@
1 +package service
2 +
3 +import (
4 + "bytes"
5 + "testing"
6 +)
7 +
8 +func TestMarshaling(t *testing.T) {
9 +
10 + test := func(d1 []byte, rid1 RequestID) {
11 + d2, err := wrapData(d1, rid1)
12 + if err != nil {
13 + t.Error(err)
14 + }
15 +
16 + d3, rid2, err := unwrapData(d2)
17 + if err != nil {
18 + t.Error(err)
19 + }
20 +
21 + d4, err := wrapData(d3, rid1)
22 + if err != nil {
23 + t.Error(err)
24 + }
25 +
26 + if !bytes.Equal(rid2, rid1) {
27 + t.Error("RequestID fail")
28 + }
29 +
30 + if !bytes.Equal(d1, d3) {
31 + t.Error("unmarshalled data should be the same")
32 + }
33 +
34 + if !bytes.Equal(d2, d4) {
35 + t.Error("marshalled data should be the same")
36 + }
37 + }
38 +
39 + test([]byte("foo"), []byte{1, 2, 3, 4})
40 + test([]byte("bar"), nil)
41 +}
net/service/service.go new
+194
@@ -0,0 +1,194 @@
1 +package service
2 +
3 +import (
4 + "errors"
5 + "sync"
6 +
7 + msg "github.com/jbenet/go-ipfs/net/message"
8 + u "github.com/jbenet/go-ipfs/util"
9 +
10 + context "code.google.com/p/go.net/context"
11 +)
12 +
13 +// Handler is an interface that objects must implement in order to handle
14 +// a service's requests.
15 +type Handler interface {
16 +
17 + // HandleMessage receives an incoming message, and potentially returns
18 + // a response message to send back.
19 + HandleMessage(context.Context, *msg.Message) (*msg.Message, error)
20 +}
21 +
22 +// Service is a networking component that protocols can use to multiplex
23 +// messages over the same channel, and to issue + handle requests.
24 +type Service struct {
25 + // Handler is the object registered to handle incoming requests.
26 + Handler Handler
27 +
28 + // Requests are all the pending requests on this service.
29 + Requests RequestMap
30 + RequestsLock sync.RWMutex
31 +
32 + // cancel is the function to stop the Service
33 + cancel context.CancelFunc
34 +
35 + // Message Pipe (connected to the outside world)
36 + *msg.Pipe
37 +}
38 +
39 +// NewService creates a service object with given type ID and Handler
40 +func NewService(ctx context.Context, h Handler) *Service {
41 + s := &Service{
42 + Handler: h,
43 + Requests: RequestMap{},
44 + Pipe: msg.NewPipe(10),
45 + }
46 +
47 + go s.handleIncomingMessages(ctx)
48 +
49 + return s
50 +}
51 +
52 +// Start kicks off the Service goroutines.
53 +func (s *Service) Start(ctx context.Context) error {
54 + if s.cancel != nil {
55 + return errors.New("Service already started.")
56 + }
57 +
58 + // make a cancellable context.
59 + ctx, s.cancel = context.WithCancel(ctx)
60 +
61 + go s.handleIncomingMessages(ctx)
62 + return nil
63 +}
64 +
65 +// Stop stops Service activity.
66 +func (s *Service) Stop() {
67 + s.cancel()
68 + s.cancel = context.CancelFunc(nil)
69 +}
70 +
71 +// SendMessage sends a message out
72 +func (s *Service) SendMessage(ctx context.Context, m *msg.Message, rid RequestID) error {
73 +
74 + // serialize ServiceMessage wrapper
75 + data, err := wrapData(m.Data, rid)
76 + if err != nil {
77 + return err
78 + }
79 +
80 + // send message
81 + m2 := &msg.Message{Peer: m.Peer, Data: data}
82 + select {
83 + case s.Outgoing <- m2:
84 + case <-ctx.Done():
85 + return ctx.Err()
86 + }
87 +
88 + return nil
89 +}
90 +
91 +// SendRequest sends a request message out and awaits a response.
92 +func (s *Service) SendRequest(ctx context.Context, m *msg.Message) (*msg.Message, error) {
93 +
94 + // create a request
95 + r, err := NewRequest(m.Peer.ID)
96 + if err != nil {
97 + return nil, err
98 + }
99 +
100 + // register Request
101 + s.RequestsLock.Lock()
102 + s.Requests[r.Key()] = r
103 + s.RequestsLock.Unlock()
104 +
105 + // defer deleting this request
106 + defer func() {
107 + s.RequestsLock.Lock()
108 + delete(s.Requests, r.Key())
109 + s.RequestsLock.Unlock()
110 + }()
111 +
112 + // check if we should bail after waiting for mutex
113 + select {
114 + default:
115 + case <-ctx.Done():
116 + return nil, ctx.Err()
117 + }
118 +
119 + // Send message
120 + s.SendMessage(ctx, m, r.ID)
121 +
122 + // wait for response
123 + m = nil
124 + err = nil
125 + select {
126 + case m = <-r.Response:
127 + case <-ctx.Done():
128 + err = ctx.Err()
129 + }
130 +
131 + return m, err
132 +}
133 +
134 +// handleIncoming consumes the messages on the s.Incoming channel and
135 +// routes them appropriately (to requests, or handler).
136 +func (s *Service) handleIncomingMessages(ctx context.Context) {
137 + for {
138 + select {
139 + case m := <-s.Incoming:
140 + go s.handleIncomingMessage(ctx, m)
141 +
142 + case <-ctx.Done():
143 + return
144 + }
145 + }
146 +}
147 +
148 +func (s *Service) handleIncomingMessage(ctx context.Context, m *msg.Message) {
149 +
150 + // unwrap the incoming message
151 + data, rid, err := unwrapData(m.Data)
152 + if err != nil {
153 + u.PErr("de-serializing error: %v\n", err)
154 + }
155 + m2 := &msg.Message{Peer: m.Peer, Data: data}
156 +
157 + // if it's a request (or has no RequestID), handle it
158 + if rid == nil || rid.IsRequest() {
159 + r1, err := s.Handler.HandleMessage(ctx, m2)
160 + if err != nil {
161 + u.PErr("handled message yielded error %v\n", err)
162 + return
163 + }
164 +
165 + // if handler gave us a response, send it back out!
166 + if r1 != nil {
167 + err := s.SendMessage(ctx, r1, rid.Response())
168 + if err != nil {
169 + u.PErr("error sending response message: %v\n", err)
170 + }
171 + }
172 + return
173 + }
174 +
175 + // Otherwise, it is a response. handle it.
176 + if !rid.IsResponse() {
177 + u.PErr("RequestID should identify a response here.\n")
178 + }
179 +
180 + key := RequestKey(m.Peer.ID, RequestID(rid))
181 + s.RequestsLock.RLock()
182 + r, found := s.Requests[key]
183 + s.RequestsLock.RUnlock()
184 +
185 + if !found {
186 + u.PErr("no request key %v (timeout?)\n", []byte(key))
187 + return
188 + }
189 +
190 + select {
191 + case r.Response <- m2:
192 + case <-ctx.Done():
193 + }
194 +}
net/service/service_test.go new
+125
@@ -0,0 +1,125 @@
1 +package service
2 +
3 +import (
4 + "bytes"
5 + "testing"
6 + "time"
7 +
8 + msg "github.com/jbenet/go-ipfs/net/message"
9 + peer "github.com/jbenet/go-ipfs/peer"
10 +
11 + context "code.google.com/p/go.net/context"
12 + mh "github.com/jbenet/go-multihash"
13 +)
14 +
15 +// ReverseHandler reverses all Data it receives and sends it back.
16 +type ReverseHandler struct{}
17 +
18 +func (t *ReverseHandler) HandleMessage(ctx context.Context, m *msg.Message) (
19 + *msg.Message, error) {
20 +
21 + d := m.Data
22 + for i, j := 0, len(d)-1; i < j; i, j = i+1, j-1 {
23 + d[i], d[j] = d[j], d[i]
24 + }
25 +
26 + return &msg.Message{Peer: m.Peer, Data: d}, nil
27 +}
28 +
29 +func newPeer(t *testing.T, id string) *peer.Peer {
30 + mh, err := mh.FromHexString(id)
31 + if err != nil {
32 + t.Error(err)
33 + return nil
34 + }
35 +
36 + return &peer.Peer{ID: peer.ID(mh)}
37 +}
38 +
39 +func TestServiceHandler(t *testing.T) {
40 + ctx := context.Background()
41 + h := &ReverseHandler{}
42 + s := NewService(ctx, h)
43 + peer1 := newPeer(t, "11140beec7b5ea3f0fdbc95d0dd47f3c5bc275aaaaaa")
44 +
45 + d, err := wrapData([]byte("beep"), nil)
46 + if err != nil {
47 + t.Error(err)
48 + }
49 +
50 + m1 := &msg.Message{Peer: peer1, Data: d}
51 + s.Incoming <- m1
52 + m2 := <-s.Outgoing
53 +
54 + d, rid, err := unwrapData(m2.Data)
55 + if err != nil {
56 + t.Error(err)
57 + }
58 +
59 + if rid != nil {
60 + t.Error("RequestID should be nil")
61 + }
62 +
63 + if !bytes.Equal(d, []byte("peeb")) {
64 + t.Errorf("service handler data incorrect: %v != %v", d, "oof")
65 + }
66 +}
67 +
68 +func TestServiceRequest(t *testing.T) {
69 + ctx := context.Background()
70 + s1 := NewService(ctx, &ReverseHandler{})
71 + s2 := NewService(ctx, &ReverseHandler{})
72 + peer1 := newPeer(t, "11140beec7b5ea3f0fdbc95d0dd47f3c5bc275aaaaaa")
73 +
74 + // patch services together
75 + go func() {
76 + for {
77 + select {
78 + case m := <-s1.Outgoing:
79 + s2.Incoming <- m
80 + case m := <-s2.Outgoing:
81 + s1.Incoming <- m
82 + case <-ctx.Done():
83 + return
84 + }
85 + }
86 + }()
87 +
88 + m1 := &msg.Message{Peer: peer1, Data: []byte("beep")}
89 + m2, err := s1.SendRequest(ctx, m1)
90 + if err != nil {
91 + t.Error(err)
92 + }
93 +
94 + if !bytes.Equal(m2.Data, []byte("peeb")) {
95 + t.Errorf("service handler data incorrect: %v != %v", m2.Data, "oof")
96 + }
97 +}
98 +
99 +func TestServiceRequestTimeout(t *testing.T) {
100 + ctx, _ := context.WithTimeout(context.Background(), time.Millisecond)
101 + s1 := NewService(ctx, &ReverseHandler{})
102 + s2 := NewService(ctx, &ReverseHandler{})
103 + peer1 := newPeer(t, "11140beec7b5ea3f0fdbc95d0dd47f3c5bc275aaaaaa")
104 +
105 + // patch services together
106 + go func() {
107 + for {
108 + <-time.After(time.Millisecond)
109 + select {
110 + case m := <-s1.Outgoing:
111 + s2.Incoming <- m
112 + case m := <-s2.Outgoing:
113 + s1.Incoming <- m
114 + case <-ctx.Done():
115 + return
116 + }
117 + }
118 + }()
119 +
120 + m1 := &msg.Message{Peer: peer1, Data: []byte("beep")}
121 + m2, err := s1.SendRequest(ctx, m1)
122 + if err == nil || m2 != nil {
123 + t.Error("should've timed out")
124 + }
125 +}