-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsosi_api.go
603 lines (565 loc) · 16.7 KB
/
sosi_api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
/*
Definition of the external interface of the package, based on the
constructs defined in the standard 'net' package.
Copyright 2014-2019 Daniele Pala <[email protected]>
This file is part of sosi.
sosi is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
sosi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with sosi. If not, see <http://www.gnu.org/licenses/>.
*/
package sosi
import (
"bytes"
"encoding/binary"
"errors"
"net"
"strings"
"time"
"tosi"
)
// DialOpt contains options to be used by the DialOptSOSI function during
// connection establishment. In particular, it contains: A Connection Identifier
// parameter group to enable the identification of the session connection, the
// maximum TSDU size for both directions of transfer, and initial user data
// to be sent during connection establishment.
type DialOpt struct {
ConnID // Connection Identifier variables
MaxTSDUSizeOut uint16 // max TSDU size from local to remote
MaxTSDUSizeIn uint16 // max TSDU size from remote to local
Data []byte // initial user data
}
// SOSIConn is an implementation of the net.Conn interface
// for SOSI network connections. If the parameter 'Reused' is true,
// than the structure represents a transport connection which has
// been kept open for reuse.
type SOSIConn struct {
Duplex bool // Is this a duplex connection?
Reused bool // Is this a connection kept for reuse?
MaxTSDUSizeOut uint16 // max TSDU size from initiator to responder
MaxTSDUSizeIn uint16 // max TSDU size from responder to initiator
Token int // token status
laddr, raddr SOSIAddr // local and remote address
vn byte // selected version number
tosiConn tosi.TOSIConn // TOSI connection
userData // read buffer
}
// structure holding data from TOSI which hasn't been returned to the user yet
type userData struct {
readBuf []byte // read buffer
endOfSSDU bool // is this data the last part of an SSDU?
}
// SOSIAddr represents the address of a SOSI end point.
type SOSIAddr struct {
tosi.TOSIAddr // TOSI address
Ssel []byte // session selector (optional)
}
// SOSIListener is a SOSI network listener. Clients should typically use
// variables of type net.Listener instead of assuming SOSI.
type SOSIListener struct {
addr *SOSIAddr
tosiListener tosi.TOSIListener
}
// DialSOSI connects to the remote address raddr on the network net, which must
// be "sosi", "sosi4", or "sosi6".
// If loc is not nil, it is used as the local address for the connection.
func DialSOSI(net string, loc, rem *SOSIAddr) (*SOSIConn, error) {
return DialOptSOSI(net, loc, rem, DialOpt{})
}
// RedialSOSI connects to a remote endpoint by reusing an already
// existing connection.
func RedialSOSI(reused *SOSIConn, op DialOpt) (*SOSIConn, error) {
cv := parseOptions(&reused.laddr, &reused.raddr, op)
return dial(&reused.tosiConn, &reused.laddr, &reused.raddr, cv)
}
// DialOptSOSI is the same as DialSOSI, but it takes an additional 'options'
// parameter.
func DialOptSOSI(snet string, loc, rem *SOSIAddr, op DialOpt) (*SOSIConn, error) {
if rem == nil {
return nil, errors.New("invalid remote address")
}
TOSInet := sosiToTOSInet(snet)
var tosiLaddr *tosi.TOSIAddr
if loc != nil {
tosiLaddr = &loc.TOSIAddr
} else {
tosiLaddr = nil
}
// try to establish a TOSI connection
tconn, err := tosi.DialTOSI(TOSInet, tosiLaddr, &rem.TOSIAddr)
if err != nil {
return nil, err
}
cv := parseOptions(loc, rem, op)
return dial(tconn, loc, rem, cv)
}
// setup ISO connection vars
func parseOptions(loc, rem *SOSIAddr, op DialOpt) (cv cnVars) {
cv.ConnID = op.ConnID // Connection Identifier
if len(cv.ConnID.SSUsrRef) > urMaxLen {
op.ConnID.SSUsrRef = op.ConnID.SSUsrRef[:urMaxLen]
}
if len(cv.ConnID.ComRef) > crMaxLen {
op.ConnID.ComRef = op.ConnID.ComRef[:crMaxLen]
}
if len(cv.ConnID.RefInfo) > infoMaxLen {
op.ConnID.RefInfo = op.ConnID.RefInfo[:infoMaxLen]
}
cv.protOpt = poExtendedConc // Protocol Options
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, op.MaxTSDUSizeOut)
binary.Write(buf, binary.BigEndian, op.MaxTSDUSizeIn)
copy(cv.maxTSDUSize[:], buf.Bytes()) // TSDU maximum size
cv.version = vnTwo // Version Number
cv.sesUserReq[1] = surValue // Session User Requirements
if loc != nil {
cv.locSSEL = loc.Ssel // Calling Session Selector
}
cv.remSSEL = rem.Ssel // Called Session Selector
cv.userData = op.Data // User Data / Extended User Data
if len(cv.userData) <= udMaxLen {
cv.version += vnOne
} else {
if len(cv.userData) > udMaxExt {
cv.dataOverflow = true // Data Overflow
cv.ovfData = cv.userData[udMaxExt:]
cv.userData = cv.userData[:udMaxExt]
}
}
return
}
// dial opens a session with the remote address rem using the transport
// connection t. If loc is not nil, it is used as the local address
// for the session connection. The parameters of the connection request
// are taken as inputs from the caller.
func dial(t *tosi.TOSIConn, loc, rem *SOSIAddr, cv cnVars) (*SOSIConn, error) {
_, err := t.Write(cn(cv)) // send a CN
if err != nil {
return nil, err
}
// try to read a TSDU in response
tsdu, _, err := t.ReadTSDU()
if err != nil {
return nil, err
}
// REFUSE SPDU
if isRF(tsdu) {
err = errors.New("Connection request refused")
valid, v := validateRF(tsdu, 0)
if !valid {
t.Close()
return nil, err
}
if v.tdisc == 0 {
reused := &SOSIConn{
Reused: true,
tosiConn: *t,
raddr: *rem}
if loc != nil {
reused.laddr = *loc
}
return reused, err
} else {
t.Close()
return nil, err
}
}
if cv.dataOverflow {
// OVERFLOW ACCEPT SPDU
if isOA(tsdu) {
// process OA
c, err := handleOA(tsdu, t, loc, rem, cv)
if err != nil {
return c, err
}
if loc == nil {
var tosiAddr = t.LocalAddr().(*tosi.TOSIAddr)
c.laddr.TOSIAddr = *tosiAddr
} else {
c.laddr = *loc
}
c.raddr = *rem
return c, err
}
} else {
// ACCEPT SPDU
if isAC(tsdu) {
// process AC
c, err := handleAC(tsdu, t, cv)
if err != nil {
return c, err
}
if loc == nil {
var tosiAddr = t.LocalAddr().(*tosi.TOSIAddr)
c.laddr.TOSIAddr = *tosiAddr
} else {
c.laddr = *loc
}
c.raddr = *rem
return c, err
}
}
return nil, err
}
// parse an AC, handling errors
func handleAC(tsdu []byte, tconn *tosi.TOSIConn, cv cnVars) (*SOSIConn, error) {
// we have an AC, check if it is valid
valid, av := validateAC(tsdu, cv)
if !valid {
// we got an invalid AC
// refuse the connection
tconn.Close()
return nil, errors.New("received an invalid AC")
}
// all ok, connection established
sconn := createSessionConn(cv, av)
sconn.tosiConn = *tconn
return sconn, nil
}
// parse an OA, handling errors
func handleOA(tsdu []byte, tconn *tosi.TOSIConn, loc, rem *SOSIAddr, cv cnVars) (*SOSIConn, error) {
// we have an OA, check if it is valid
valid := validateOA(tsdu, cv)
if !valid {
// we got an invalid OA
// refuse the connection
tconn.Close()
return nil, errors.New("received an invalid OA")
}
// send CDOs with remaining user data
for cv.ovfData != nil {
cdoData := cv.ovfData
encItem := eiMiddle
if len(cdoData) > udMaxLenCdo {
cdoData = cv.ovfData[:udMaxLenCdo]
cv.ovfData = cv.ovfData[udMaxLenCdo:]
} else {
cv.ovfData = nil
encItem = eiEnd
}
reply := cdo(encItem, cdoData) // reply with a CDO
_, err := tconn.Write(reply) // send the CDO
if err != nil {
return nil, err
}
}
// try to read a TSDU in response
tsdu, _, err := tconn.ReadTSDU()
if err != nil {
return nil, err
}
// ACCEPT SPDU
if isAC(tsdu) {
// process AC
c, err := handleAC(tsdu, tconn, cv)
if err != nil {
return c, err
}
if loc == nil {
var tosiAddr = tconn.LocalAddr().(*tosi.TOSIAddr)
c.laddr.TOSIAddr = *tosiAddr
} else {
c.laddr = *loc
}
c.raddr = *rem
return c, err
}
return nil, err
}
// convert a SOSI net to a TOSI net.
func sosiToTOSInet(sosi string) (tosi string) {
switch sosi {
case "sosi":
tosi = "tosi"
case "sosi4":
tosi = "tosi4"
case "sosi6":
tosi = "tosi6"
default:
tosi = ""
}
return
}
// Network returns the address's network name, "sosi".
func (a *SOSIAddr) Network() string {
return "sosi"
}
func (a *SOSIAddr) String() string {
return a.TOSIAddr.String() + ":" + string(a.Ssel)
}
// ResolveSOSIAddr parses addr as a SOSI address of the form tosi:ssel and
// resolves domain names to numeric addresses on the network snet,
// which must be "sosi", "sosi4" or "sosi6".
// The tosi part must be a valid TOSI address of the form tcp:tsel, enclosed
// in square brackets, as in [[127.0.0.1:80]:20].
// A literal IPv6 host address must be enclosed in square brackets,
// as in "[::]:80". ssel is the "session selector", which can be an arbitrary
// sequence of bytes. Thus '[[127.0.0.1:80]:20]:hello' is a valid address.
func ResolveSOSIAddr(snet, addr string) (sosiAddr *SOSIAddr, err error) {
// after the last ':' we have the SSEL
index := strings.LastIndex(addr, ":")
if index < 0 {
return nil, errors.New("invalid address")
}
tAddr := addr[:index]
var ssel string
if len(addr) > (index + 1) {
ssel = addr[index+1:]
}
tosiNet := sosiToTOSInet(snet)
tosiAddr, err := tosi.ResolveTOSIAddr(tosiNet, tAddr)
if err != nil {
return nil, err
}
sosiAddr = &SOSIAddr{TOSIAddr: *tosiAddr}
if ssel != "" {
sosiAddr.Ssel = []byte(ssel)
}
return sosiAddr, nil
}
// Close closes the SOSI connection.
// TODO: implement the closing sequence.
func (c *SOSIConn) Close() error {
return c.tosiConn.Close()
}
// LocalAddr returns the local network address.
func (c *SOSIConn) LocalAddr() net.Addr {
return &c.laddr
}
// Read implements the net.Conn Read method.
// TODO: implement this
func (c *SOSIConn) Read(b []byte) (n int, err error) {
if b == nil {
return
}
// see if there's something in the read buffer
if c.readBuf != nil {
copy(b, c.readBuf)
if len(b) < len(c.readBuf) {
// Cannot return the whole SDU
n = len(b)
c.readBuf = c.readBuf[len(b):]
} else {
n = len(c.readBuf)
c.readBuf = nil
}
return n, nil
}
// try to read a GT+DT
tsdu, _, err := c.tosiConn.ReadTSDU()
if err != nil {
return 0, err
}
if c.MaxTSDUSizeIn > 0 {
if len(tsdu) > int(c.MaxTSDUSizeIn) {
return 0, err
}
}
dt := getData(tsdu)
if dt == nil {
c.tosiConn.Write(ab(1, 0, nil, nil))
}
copy(b, dt)
return len(dt), nil
}
// RemoteAddr returns the remote network address.
func (c *SOSIConn) RemoteAddr() net.Addr {
return &c.raddr
}
// SetDeadline implements the net.Conn SetDeadline method.
func (c *SOSIConn) SetDeadline(t time.Time) error {
return c.tosiConn.SetDeadline(t)
}
// SetReadDeadline implements the net.Conn SetReadDeadline method.
func (c *SOSIConn) SetReadDeadline(t time.Time) error {
return c.tosiConn.SetReadDeadline(t)
}
// SetWriteDeadline implements the Conn SetWriteDeadline method.
func (c *SOSIConn) SetWriteDeadline(t time.Time) error {
return c.tosiConn.SetWriteDeadline(t)
}
// Write implements the net.Conn Write method.
// Segmenting of SSDUs takes place under the following circumstances:
// a) when a maximum TSDU size has been selected, in which case a data SSDU
// or a typed data SSDU may be mapped onto more than one SPDU;
// b) when Protocol Version 2 is proposed or selected and either:
// 1) the SPDU size would exceed the maximum TSDU size; or
// 2) the SPDU size would exceed 65539 octets for an SPDU to be sent on the
// transport normal flow or 16 octets for an SPDU to be sent on the
// transport expedited flow, in which case SSDUs other than data SSDUs,
// typed data SSDUs and expedited data SSDUs are mapped onto more than
// one SPDU.
// In all other cases, each SSDU is mapped one-to-one onto an SPDU.
func (c *SOSIConn) Write(b []byte) (n int, err error) {
if b == nil {
return
}
bufLen := len(b)
var maxWrite int
if c.MaxTSDUSizeOut > 0 {
maxWrite = int(c.MaxTSDUSizeOut)
} else {
maxWrite = bufLen
}
// if b is too big, split it into smaller chunks
if bufLen > maxWrite {
encItem := eiBegin
numWrites := (bufLen / maxWrite)
if (bufLen % maxWrite) > 0 {
numWrites += 1
}
for i := 0; i < numWrites; i++ {
if i == (numWrites - 1) {
encItem = eiEnd
}
start := maxWrite * i
end := maxWrite * (i + 1)
if end > bufLen {
end = bufLen
}
var part []byte
if c.Duplex == true {
part = dt(true, encItem, b[start:end])
} else {
part = append(gt(0, 0, nil), dt(true, encItem, b[start:end])...)
}
nPart, err := c.tosiConn.Write(part)
n = n + nPart
if err != nil {
return n, err
}
encItem = eiMiddle
}
return
}
return c.tosiConn.Write(append(gt(0, 0, nil), dt(false, eiBegin, b)...))
}
// ListenSOSI announces on the SOSI address loc and returns a SOSI listener.
// snet must be "sosi", "sosi4", or "sosi6".
func ListenSOSI(snet string, loc *SOSIAddr) (*SOSIListener, error) {
if loc == nil {
return nil, errors.New("invalid local address")
}
tosiAddr := loc.TOSIAddr
tosiNet := sosiToTOSInet(snet)
listener, err := tosi.ListenTOSI(tosiNet, &tosiAddr)
if err != nil {
return nil, err
}
return &SOSIListener{addr: loc, tosiListener: *listener}, nil
}
// Accept implements the Accept method in the net.Listener interface;
// it waits for the next call and returns a generic net.Conn.
func (l *SOSIListener) Accept() (net.Conn, error) {
sosi, _, err := l.AcceptSOSI()
return sosi, err
}
// AcceptSOSI is the same as Accept, but it also returns initial data sent by
// the caller during connection establishment. In fact, ISO/IEC 8326/8327
// allows the caller to send user data during connection establishment.
func (l *SOSIListener) AcceptSOSI() (net.Conn, []byte, error) {
// listen for TOSI connections
tconn, err := l.tosiListener.AcceptTOSI(nil)
if err != nil {
return nil, nil, err
}
// try to read a CN
tsdu, _, err := tconn.ReadTSDU()
if err != nil {
return nil, nil, err
}
if isCN(tsdu) {
sosi, data, err := cnReply(*l.addr, tsdu, *tconn)
return &sosi, data, err
}
tconn.Close()
if err == nil {
err = errors.New("received an invalid TSDU")
}
return nil, nil, err
}
// parse a CN, handling errors and sending an AC (or OA) in response.
// The function also returns the initial data sent by the caller during
// connection establishment, if present (nil otherwise).
func cnReply(addr SOSIAddr, tsdu []byte, t tosi.TOSIConn) (SOSIConn, []byte, error) {
var reply []byte
var repCv acVars
var data []byte
valid, cv := validateCN(tsdu, addr.Ssel)
if valid {
if cv.sesUserReq[1]&duplex == duplex {
repCv.sesUserReq[1] = duplex
} else {
repCv.sesUserReq[1] = halfDuplex
}
if cv.version == vnOne {
repCv.version = vnOne
} else {
repCv.version = vnTwo
// The Enclosure Item parameter, if present, shall indicate that the
// SPDU is the beginning, but not end of the SSDU. This parameter
// shall not be present if Protocol Version 1 is selected.
repCv.enclItem = eiBegin
}
if cv.dataOverflow == false {
reply = ac(repCv) // reply with an AC
data = cv.userData
} else {
reply = oa(cv.maxTSDUSize, vnTwo) // reply with an OA
t.Write(reply) // TODO: this can fail
data = cv.userData
last := false
for last == false {
// try to read a CDO
tsdu, _, err := t.ReadTSDU()
if err != nil {
return SOSIConn{}, nil, err
}
if isCDO(tsdu) {
valid, end, cdoData := validateCDO(tsdu)
last = end
if valid == false {
// handle this
return SOSIConn{}, nil, err
}
data = append(data, cdoData...)
} else {
return SOSIConn{}, nil, err
}
}
reply = ac(repCv) // reply with an AC
}
} else {
// reply with a REFUSE
}
_, err := t.Write(reply)
if valid && (err == nil) {
var MaxTSDUSizeIn uint16
buf := bytes.NewReader(cv.maxTSDUSize[0:2])
_ = binary.Read(buf, binary.BigEndian, &MaxTSDUSizeIn)
return SOSIConn{
MaxTSDUSizeIn: MaxTSDUSizeIn,
tosiConn: t,
laddr: addr}, data, nil
}
t.Close()
if err == nil {
err = errors.New("received an invalid CN")
}
return SOSIConn{}, nil, err
}
// Addr returns the listener's network address.
func (l *SOSIListener) Addr() net.Addr {
return l.addr
}
// Close stops listening on the SOSI address.
// Already Accepted connections are not closed.
func (l *SOSIListener) Close() error {
return l.tosiListener.Close()
}