Skip to content

Commit c67f895

Browse files
committed
capture the full raw response for Reply/Notificaiton
1 parent a098c34 commit c67f895

File tree

4 files changed

+113
-51
lines changed

4 files changed

+113
-51
lines changed

msg.go

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ import (
99
"golang.org/x/exp/slices"
1010
)
1111

12+
var (
13+
RPCReplyName = xml.Name{
14+
Space: "urn:ietf:params:xml:ns:netconf:base:1.0",
15+
Local: "rpc-reply",
16+
}
17+
18+
NofificationName = xml.Name{
19+
Space: "urn:ietf:params:xml:ns:netconf:notification:1.0",
20+
Local: "notification",
21+
}
22+
)
23+
1224
// RawXML captures the raw xml for the given element. Used to process certain
1325
// elements later.
1426
type RawXML []byte
@@ -69,13 +81,38 @@ type Reply struct {
6981
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 rpc-reply"`
7082
MessageID uint64 `xml:"message-id,attr"`
7183
Errors RPCErrors `xml:"rpc-error,omitempty"`
72-
Body []byte `xml:",innerxml"`
84+
85+
raw []byte `xml:"-"`
86+
}
87+
88+
func ParseReply(data []byte) (*Reply, error) {
89+
reply := Reply{
90+
raw: data,
91+
}
92+
if err := xml.Unmarshal(data, &reply); err != nil {
93+
return nil, fmt.Errorf("couldn't parse reply: %v", err)
94+
}
95+
96+
return &reply, nil
7397
}
7498

75-
// Decode will decode the body of a reply into a value pointed to by v. This is
76-
// a simple wrapper around xml.Unmarshal.
99+
// Decode will decode the entire `rpc-reply` into a value pointed to by v. This
100+
// is a simple wrapper around xml.Unmarshal.
77101
func (r Reply) Decode(v interface{}) error {
78-
return xml.Unmarshal(r.Body, v)
102+
if r.raw == nil {
103+
return fmt.Errorf("empty reply")
104+
}
105+
return xml.Unmarshal(r.raw, v)
106+
}
107+
108+
// Raw returns the native message as it came from the server
109+
func (r Reply) Raw() []byte {
110+
return r.raw
111+
}
112+
113+
// String returns the message as string.
114+
func (r Reply) String() string {
115+
return string(r.raw)
79116
}
80117

81118
// Err will return go error(s) from a Reply that are of the given severities. If
@@ -121,13 +158,38 @@ func (r Reply) Err(severity ...ErrSeverity) error {
121158
type Notification struct {
122159
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:netconf:notification:1.0 notification"`
123160
EventTime time.Time `xml:"eventTime"`
124-
Body []byte `xml:",innerxml"`
161+
162+
raw []byte `xml:"-"`
125163
}
126164

127-
// Decode will decode the body of a noticiation into a value pointed to by v.
165+
func ParseNotification(data []byte) (*Notification, error) {
166+
notif := Notification{
167+
raw: data,
168+
}
169+
if err := xml.Unmarshal(data, &notif); err != nil {
170+
return nil, fmt.Errorf("couldn't parse reply: %v", err)
171+
}
172+
173+
return &notif, nil
174+
}
175+
176+
// Decode will decode the entire `noticiation` into a value pointed to by v.
128177
// This is a simple wrapper around xml.Unmarshal.
129-
func (r Notification) Decode(v interface{}) error {
130-
return xml.Unmarshal(r.Body, v)
178+
func (n Notification) Decode(v interface{}) error {
179+
if n.raw == nil {
180+
return fmt.Errorf("empty reply")
181+
}
182+
return xml.Unmarshal(n.raw, v)
183+
}
184+
185+
// Raw returns the native message as it came from the server
186+
func (n Notification) Raw() []byte {
187+
return n.raw
188+
}
189+
190+
// String returns the message as string.
191+
func (n Notification) String() string {
192+
return string(n.raw)
131193
}
132194

133195
type ErrSeverity string

msg_test.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -251,17 +251,6 @@ func TestUnmarshalRPCReply(t *testing.T) {
251251
`),
252252
},
253253
},
254-
Body: []byte(`
255-
<rpc-error>
256-
<error-type>protocol</error-type>
257-
<error-tag>operation-failed</error-tag>
258-
<error-severity>error</error-severity>
259-
<error-message>syntax error, expecting &lt;candidate/&gt; or &lt;running/&gt;</error-message>
260-
<error-info>
261-
<bad-element>non-exist</bad-element>
262-
</error-info>
263-
</rpc-error>
264-
`),
265254
},
266255
},
267256
}

ops.go

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (b *ExtantBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
3333
return nil
3434
}
3535

36-
type OKResp struct {
36+
type OKReply struct {
3737
OK ExtantBool `xml:"ok"`
3838
}
3939

@@ -95,8 +95,7 @@ type GetConfigReq struct {
9595
}
9696

9797
type GetConfigReply struct {
98-
XMLName xml.Name `xml:"data"`
99-
Config []byte `xml:",innerxml"`
98+
Data []byte `xml:"data"`
10099
}
101100

102101
// GetConfig implements the <get-config> rpc operation defined in [RFC6241 7.1].
@@ -113,7 +112,7 @@ func (s *Session) GetConfig(ctx context.Context, source Datastore) ([]byte, erro
113112
return nil, err
114113
}
115114

116-
return resp.Config, nil
115+
return resp.Data, nil
117116
}
118117

119118
// MergeStrategy defines the strategies for merging configuration in a
@@ -272,7 +271,7 @@ func (s *Session) EditConfig(ctx context.Context, target Datastore, config any,
272271
opt.apply(&req)
273272
}
274273

275-
var resp OKResp
274+
var resp OKReply
276275
return s.Call(ctx, &req, &resp)
277276
}
278277

@@ -297,7 +296,7 @@ func (s *Session) CopyConfig(ctx context.Context, source, target any) error {
297296
Target: target,
298297
}
299298

300-
var resp OKResp
299+
var resp OKReply
301300
return s.Call(ctx, &req, &resp)
302301
}
303302

@@ -311,7 +310,7 @@ func (s *Session) DeleteConfig(ctx context.Context, target Datastore) error {
311310
Target: target,
312311
}
313312

314-
var resp OKResp
313+
var resp OKReply
315314
return s.Call(ctx, &req, &resp)
316315
}
317316

@@ -326,7 +325,7 @@ func (s *Session) Lock(ctx context.Context, target Datastore) error {
326325
Target: target,
327326
}
328327

329-
var resp OKResp
328+
var resp OKReply
330329
return s.Call(ctx, &req, &resp)
331330
}
332331

@@ -336,7 +335,7 @@ func (s *Session) Unlock(ctx context.Context, target Datastore) error {
336335
Target: target,
337336
}
338337

339-
var resp OKResp
338+
var resp OKReply
340339
return s.Call(ctx, &req, &resp)
341340
}
342341

@@ -356,7 +355,7 @@ func (s *Session) KillSession(ctx context.Context, sessionID uint32) error {
356355
SessionID: sessionID,
357356
}
358357

359-
var resp OKResp
358+
var resp OKReply
360359
return s.Call(ctx, &req, &resp)
361360
}
362361

@@ -370,7 +369,7 @@ func (s *Session) Validate(ctx context.Context, source any) error {
370369
Source: source,
371370
}
372371

373-
var resp OKResp
372+
var resp OKReply
374373
return s.Call(ctx, &req, &resp)
375374
}
376375

@@ -444,7 +443,7 @@ func (s *Session) Commit(ctx context.Context, opts ...CommitOption) error {
444443
return fmt.Errorf("PersistID cannot be used with Confirmed/ConfirmedTimeout or Persist options")
445444
}
446445

447-
var resp OKResp
446+
var resp OKReply
448447
return s.Call(ctx, &req, &resp)
449448
}
450449

@@ -466,7 +465,7 @@ func (s *Session) CancelCommit(ctx context.Context, opts ...CancelCommitOption)
466465
opt.applyCancelCommit(&req)
467466
}
468467

469-
var resp OKResp
468+
var resp OKReply
470469
return s.Call(ctx, &req, &resp)
471470
}
472471

@@ -509,6 +508,6 @@ func (s *Session) CreateSubscription(ctx context.Context, opts ...CreateSubscrip
509508
}
510509
// TODO: eventual custom notifications rpc logic, e.g. create subscription only if notification capability is present
511510

512-
var resp OKResp
511+
var resp OKReply
513512
return s.Call(ctx, &req, &resp)
514513
}

session.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package netconf
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/xml"
67
"errors"
@@ -189,30 +190,26 @@ func (s *Session) recvMsg() error {
189190
return err
190191
}
191192
defer r.Close()
192-
dec := xml.NewDecoder(r)
193193

194-
root, err := startElement(dec)
194+
msg, err := io.ReadAll(r)
195195
if err != nil {
196196
return err
197197
}
198198

199-
const (
200-
ncNamespace = "urn:ietf:params:xml:ns:netconf:base:1.0"
201-
notifNamespace = "urn:ietf:params:xml:ns:netconf:notification:1.0"
202-
)
199+
return s.parseMsg(msg)
200+
}
201+
202+
func (s *Session) parseMsg(msg []byte) error {
203+
dec := xml.NewDecoder(bytes.NewReader(msg))
204+
205+
root, err := startElement(dec)
206+
if err != nil {
207+
return err
208+
}
203209

204210
switch root.Name {
205-
case xml.Name{Space: notifNamespace, Local: "notification"}:
206-
if s.notificationHandler == nil {
207-
return nil
208-
}
209-
var notif Notification
210-
if err := dec.DecodeElement(&notif, root); err != nil {
211-
return fmt.Errorf("failed to decode notification message: %w", err)
212-
}
213-
s.notificationHandler(notif)
214-
case xml.Name{Space: ncNamespace, Local: "rpc-reply"}:
215-
var reply Reply
211+
case RPCReplyName:
212+
reply := Reply{raw: msg}
216213
if err := dec.DecodeElement(&reply, root); err != nil {
217214
// What should we do here? Kill the connection?
218215
return fmt.Errorf("failed to decode rpc-reply message: %w", err)
@@ -228,6 +225,17 @@ func (s *Session) recvMsg() error {
228225
case <-req.ctx.Done():
229226
return fmt.Errorf("message %d context canceled: %s", reply.MessageID, req.ctx.Err().Error())
230227
}
228+
229+
case NofificationName:
230+
if s.notificationHandler == nil {
231+
return nil
232+
}
233+
notif := Notification{raw: msg}
234+
if err := dec.DecodeElement(&notif, root); err != nil {
235+
return fmt.Errorf("failed to decode notification message: %w", err)
236+
}
237+
s.notificationHandler(notif)
238+
231239
default:
232240
return fmt.Errorf("unknown message type: %q", root.Name.Local)
233241
}
@@ -342,6 +350,8 @@ func (s *Session) Call(ctx context.Context, req any, resp any) error {
342350
return err
343351
}
344352

353+
// Return any <rpc-error>. This defaults to a severity of `error` (warning
354+
// are omitted).
345355
if err := reply.Err(); err != nil {
346356
return err
347357
}
@@ -377,7 +387,9 @@ func (s *Session) Close(ctx context.Context) error {
377387
}
378388
}
379389

380-
if callErr != io.EOF {
390+
// it's ok if we are already closed
391+
if !errors.Is(callErr, io.EOF) &&
392+
!errors.Is(callErr, ErrClosed) {
381393
return callErr
382394
}
383395

0 commit comments

Comments
 (0)