* Copyright (c) University of British Columbia, 1984
* Copyright (c) 1990 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* the Laboratory for Computation Vision and the Computer Science Department
* of the University of British Columbia.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* @(#)pk_subr.c 7.16 (Berkeley) 6/6/91
int pk_sendspace
= 1024 * 2 + 8;
int pk_recvspace
= 1024 * 2 + 8;
struct pklcd_q pklcd_q
= {&pklcd_q
, &pklcd_q
};
* Attach X.25 protocol to socket, allocate logical channel descripter
* and buffer space, and enter LISTEN state if we are to accept
* IN-COMMING CALL packets.
register struct pklcd
*lcp
;
register int error
= ENOBUFS
;
MALLOC(lcp
, struct pklcd
*, sizeof (*lcp
), M_PCB
, M_NOWAIT
);
bzero ((caddr_t
)lcp
, sizeof (*lcp
));
insque (&lcp
-> lcd_q
, &pklcd_q
);
lcp
-> lcd_state
= READY
;
lcp
-> lcd_send
= pk_output
;
error
= soreserve (so
, pk_sendspace
, pk_recvspace
);
if (so
-> so_options
& SO_ACCEPTCONN
)
lcp
-> lcd_state
= LISTEN
;
sbreserve (&lcp
-> lcd_sb
, pk_sendspace
);
so
-> so_pcb
= (caddr_t
) lcp
;
* Disconnect X.25 protocol from socket.
register struct pklcd
*lcp
;
register struct socket
*so
= lcp
-> lcd_so
;
register struct pklcd
*l
, *p
;
switch (lcp
-> lcd_state
) {
for (p
= 0, l
= pk_listenhead
; l
&& l
!= lcp
; p
= l
, l
= l
-> lcd_listen
);
pk_listenhead
= l
-> lcd_listen
;
p
-> lcd_listen
= l
-> lcd_listen
;
pk_clear (lcp
, 241, 0); /* Normal Disconnect */
* Close an X.25 Logical Channel. Discard all space held by the
* connection and internal descriptors. Wake up any sleepers.
register struct socket
*so
= lcp
-> lcd_so
;
/* sofree (so); /* gak!!! you can't do that here */
* Create a template to be used to send X.25 packets on a logical
* channel. It allocates an mbuf and fills in a skeletal packet
* depending on its type. This packet is passed to pk_output where
* the remainer of the packet is filled in.
register struct x25_packet
*xp
;
MGETHDR (m
, M_DONTWAIT
, MT_HEADER
);
* Efficiency hack: leave a four byte gap at the beginning
* of the packet level header with the hope that this will
* be enough room for the link level to insert its header.
m
-> m_data
+= max_linkhdr
;
m
-> m_pkthdr
.len
= m
-> m_len
= PKHEADERLN
;
xp
= mtod (m
, struct x25_packet
*);
*(long *)xp
= 0; /* ugly, but fast */
xp
-> fmt_identifier
= 1;
/* xp -> lc_group_number = 0;*/
xp
-> packet_type
= type
;
* This routine restarts all the virtual circuits. Actually,
* the virtual circuits are not "restarted" as such. Instead,
* any active switched circuit is simply returned to READY
pk_restart (pkp
, restart_cause
)
register struct pkcb
*pkp
;
register struct pklcd
*lcp
;
/* Restart all logical channels. */
for (i
= 1; i
<= pkp
-> pk_maxlcn
; ++i
)
if ((lcp
= pkp
-> pk_chan
[i
]) != NULL
) {
lcp
-> lcd_so
-> so_error
= ENETRESET
;
lcp
-> lcd_state
= READY
;
lcp
-> lcd_upper (lcp
, 0);
pkp
-> pk_state
= DTE_SENT_RESTART
;
m
= lcp
-> lcd_template
= pk_template (lcp
-> lcd_lcn
, X25_RESTART
);
m
-> m_pkthdr
.len
= m
-> m_len
+= 2;
mtod (m
, struct x25_packet
*) -> packet_data
= 0; /* DTE only */
mtod (m
, octet
*)[4] = restart_cause
;
* This procedure frees up the Logical Channel Descripter.
register struct pklcd
*lcp
;
lcp
-> lcd_pkp
-> pk_chan
[lcp
-> lcd_lcn
] = NULL
;
free ((caddr_t
)lcp
, M_PCB
);
* Bind a address and protocol value to a socket. The important
* part is the protocol value - the first four characters of the
register struct pkcb
*pkp
;
register struct pklcd
*pp
;
register struct sockaddr_x25
*sa
;
if (lcp
-> lcd_ceaddr
) /* XXX */
if (pk_checksockaddr (nam
))
sa
= mtod (nam
, struct sockaddr_x25
*);
* If the user wishes to accept calls only from a particular
* net (net != 0), make sure the net is known
for (pkp
= pkcbhead
; ; pkp
= pkp
-> pk_next
) {
if (pkp
-> pk_xcp
-> xc_addr
.x25_net
== sa
-> x25_net
)
* For ISO's sake permit default listeners, but only one such . . .
for (pp
= pk_listenhead
; pp
; pp
= pp
-> lcd_listen
) {
register struct sockaddr_x25
*sa2
= pp
-> lcd_ceaddr
;
if ((sa2
-> x25_udlen
== sa
-> x25_udlen
) &&
(sa2
-> x25_udlen
== 0 ||
(bcmp (sa2
-> x25_udata
, sa
-> x25_udata
,
min (sa2
-> x25_udlen
, sa
-> x25_udlen
)) == 0)))
lcp
-> lcd_ceaddr
= &lcp
-> lcd_laddr
;
* Include a bound control block in the list of listeners.
register struct pklcd
*lcp
;
register struct pklcd
**pp
;
if (lcp
-> lcd_ceaddr
== 0)
lcp
-> lcd_state
= LISTEN
;
* Add default listener at end, any others at start.
if (lcp
-> lcd_ceaddr
-> x25_udlen
== 0) {
for (pp
= &pk_listenhead
; *pp
; )
pp
= &((*pp
) -> lcd_listen
);
lcp
-> lcd_listen
= pk_listenhead
;
* Include a listening control block for the benefit of other protocols.
pk_protolisten (spi
, spilen
, callee
)
register struct pklcd
*lcp
= pk_attach ((struct socket
*)0);
register struct mbuf
*nam
;
register struct sockaddr_x25
*sa
;
if (nam
= m_getclr (MT_SONAME
, M_DONTWAIT
)) {
sa
= mtod (nam
, struct sockaddr_x25
*);
sa
-> x25_family
= AF_CCITT
;
sa
-> x25_len
= nam
-> m_len
= sizeof (*sa
);
sa
-> x25_udlen
= spilen
;
sa
-> x25_udata
[0] = spi
;
lcp
-> lcd_upper
= callee
;
lcp
-> lcd_flags
= X25_MBS_HOLD
;
if ((error
= pk_bind (lcp
, nam
)) == 0)
return error
; /* Hopefully Zero !*/
* Associate a logical channel descriptor with a network.
* Fill in the default network specific parameters and then
* set any parameters explicitly specified by the user or
register struct pkcb
*pkp
;
register struct pklcd
*lcp
;
register struct sockaddr_x25
*sa
;
lcp
-> lcd_packetsize
= pkp
-> pk_xcp
-> xc_psize
;
lcp
-> lcd_windowsize
= pkp
-> pk_xcp
-> xc_pwsize
;
lcp
-> lcd_rsn
= MODULUS
- 1;
pkp
-> pk_chan
[lcp
-> lcd_lcn
] = lcp
;
if (sa
-> x25_opts
.op_psize
)
lcp
-> lcd_packetsize
= sa
-> x25_opts
.op_psize
;
sa
-> x25_opts
.op_psize
= lcp
-> lcd_packetsize
;
if (sa
-> x25_opts
.op_wsize
)
lcp
-> lcd_windowsize
= sa
-> x25_opts
.op_wsize
;
sa
-> x25_opts
.op_wsize
= lcp
-> lcd_windowsize
;
sa
-> x25_net
= pkp
-> pk_xcp
-> xc_addr
.x25_net
;
lcp
-> lcd_flags
|= sa
-> x25_opts
.op_flags
;
lcp
-> lcd_stime
= time
.tv_sec
;
register struct pklcd
*lcp
;
register struct sockaddr_x25
*sa
;
register struct pkcb
*pkp
;
if (sa
-> x25_addr
[0] == '\0')
for (pkp
= pkcbhead
; ; pkp
= pkp
-> pk_next
) {
* use first net configured (last in list
* headed by pkcbhead) if net is zero
* This is clearly bogus for many llc2's sharing
* the same xcp; we will replace this with a
if (sa
-> x25_net
== 0 && pkp
-> pk_next
== 0)
if (sa
-> x25_net
== pkp
-> pk_xcp
-> xc_addr
.x25_net
)
if (pkp
-> pk_state
!= DTE_READY
)
if ((lcp
-> lcd_lcn
= pk_getlcn (pkp
)) == 0)
lcp
-> lcd_ceaddr
= & lcp
-> lcd_faddr
;
pk_assoc (pkp
, lcp
, lcp
-> lcd_ceaddr
);
soisconnecting (lcp
-> lcd_so
);
lcp
-> lcd_template
= pk_template (lcp
-> lcd_lcn
, X25_CALL
);
pk_callrequest (lcp
, lcp
-> lcd_ceaddr
, pkp
-> pk_xcp
);
return (*pkp
-> pk_ia
-> ia_start
) (lcp
);
* Build the rest of the CALL REQUEST packet. Fill in calling
* address, facilities fields and the user data field.
pk_callrequest (lcp
, sa
, xcp
)
register struct sockaddr_x25
*sa
;
register struct x25config
*xcp
;
register struct x25_calladdr
*a
;
register struct mbuf
*m
= lcp
-> lcd_template
;
register struct x25_packet
*xp
= mtod (m
, struct x25_packet
*);
if (lcp
-> lcd_flags
& X25_DBIT
)
a
= (struct x25_calladdr
*) &xp
-> packet_data
;
b
.cp
= (octet
*) a
-> address_field
;
a
-> called_addrlen
= to_bcd (&b
, sa
, xcp
);
a
-> calling_addrlen
= to_bcd (&b
, &xcp
-> xc_addr
, xcp
);
m
-> m_pkthdr
.len
= m
-> m_len
+= b
.cp
- (octet
*) a
;
if (lcp
-> lcd_facilities
) {
(m
-> m_next
= lcp
-> lcd_facilities
) -> m_pkthdr
.len
;
lcp
-> lcd_facilities
= 0;
pk_build_facilities (m
, sa
, (int)xcp
-> xc_type
);
m_copyback (m
, m
-> m_pkthdr
.len
, sa
-> x25_udlen
, sa
-> x25_udata
);
pk_build_facilities (m
, sa
, type
)
cp
= mtod (m
, octet
*) + m
-> m_len
;
revcharge
= sa
-> x25_opts
.op_flags
& X25_REVERSE_CHARGE
? 1 : 0;
* This is specific to Datapac X.25(1976) DTEs. International
* calls must have the "hi priority" bit on.
if (type
== X25_1976
&& sa
-> x25_opts
.op_psize
== X25_PS128
)
*fcp
++ = FACILITIES_REVERSE_CHARGE
;
*fcp
++ = FACILITIES_PACKETSIZE
;
*fcp
++ = sa
-> x25_opts
.op_psize
;
*fcp
++ = sa
-> x25_opts
.op_psize
;
*fcp
++ = FACILITIES_WINDOWSIZE
;
*fcp
++ = sa
-> x25_opts
.op_wsize
;
*fcp
++ = sa
-> x25_opts
.op_wsize
;
m
-> m_pkthdr
.len
= (m
-> m_len
+= *cp
+ 1);
register struct bcdinfo
*b
;
register struct x25config
*xcp
;
register char *x
= sa
-> x25_addr
;
unsigned start
= b
-> posn
;
* The nodnic and prepnd0 stuff looks tedious,
* but it does allow full X.121 addresses to be used,
* which is handy for routing info (& OSI type 37 addresses).
if (xcp
-> xc_addr
.x25_net
&& (xcp
-> xc_nodnic
|| xcp
-> xc_prepnd0
)) {
char dnicname
[sizeof(long) * NBBY
/3 + 2];
register char *p
= dnicname
;
sprintf (p
, "%d", xcp
-> xc_addr
.x25_net
& 0x7fff);
for (; *p
; p
++) /* *p == 0 means dnic matched */
if (*p
|| xcp
-> xc_nodnic
== 0)
if (*p
&& xcp
-> xc_prepnd0
) {
if ((b
-> posn
)++ & 0x01)
if ((b
-> posn
)++ & 0x01)
*(b
-> cp
)++ |= *x
++ & 0x0F;
return ((b
-> posn
) - start
);
* This routine gets the first available logical channel number. The
* search is from the highest number to lowest number (DTE).
register struct pkcb
*pkp
;
for (i
= pkp
-> pk_maxlcn
; i
> 0; --i
)
if (pkp
-> pk_chan
[i
] == NULL
)
* This procedure sends a CLEAR request packet. The lc state is
pk_clear (lcp
, diagnostic
, abortive
)
register struct pklcd
*lcp
;
register struct mbuf
*m
= pk_template (lcp
-> lcd_lcn
, X25_CLEAR
);
mtod (m
, struct x25_packet
*) -> packet_data
= 0;
mtod (m
, octet
*)[4] = diagnostic
;
if (lcp
-> lcd_facilities
) {
m
-> m_next
= lcp
-> lcd_facilities
;
m
-> m_pkthdr
.len
+= m
-> m_next
-> m_len
;
lcp
-> lcd_facilities
= 0;
struct socket
*so
= lcp
-> lcd_so
;
struct sockbuf
*sb
= so
? & so
-> so_snd
: & lcp
-> lcd_sb
;
* This procedure generates RNR's or RR's to inhibit or enable
* inward data flow, if the current state changes (blocked ==> open or
* vice versa), or if forced to generate one. One forces RNR's to ack data.
pk_flowcontrol (lcp
, inhibit
, forced
)
register struct pklcd
*lcp
;
inhibit
= (inhibit
!= 0);
if (lcp
== 0 || lcp
-> lcd_state
!= DATA_TRANSFER
||
(forced
== 0 && lcp
-> lcd_rxrnr_condition
== inhibit
))
lcp
-> lcd_rxrnr_condition
= inhibit
;
pk_template (lcp
-> lcd_lcn
, inhibit
? X25_RNR
: X25_RR
);
* This procedure sends a RESET request packet. It re-intializes
pk_reset (lcp
, diagnostic
)
register struct pklcd
*lcp
;
register struct socket
*so
= lcp
-> lcd_so
;
if (lcp
-> lcd_state
!= DATA_TRANSFER
)
so
-> so_error
= ECONNRESET
;
lcp
-> lcd_reset_condition
= TRUE
;
/* Reset all the control variables for the channel. */
lcp
-> lcd_window_condition
= lcp
-> lcd_rnr_condition
=
lcp
-> lcd_intrconf_pending
= FALSE
;
lcp
-> lcd_rsn
= MODULUS
- 1;
lcp
-> lcd_output_window
= lcp
-> lcd_input_window
=
lcp
-> lcd_last_transmitted_pr
= 0;
m
= lcp
-> lcd_template
= pk_template (lcp
-> lcd_lcn
, X25_RESET
);
m
-> m_pkthdr
.len
= m
-> m_len
+= 2;
mtod (m
, struct x25_packet
*) -> packet_data
= 0;
mtod (m
, octet
*)[4] = diagnostic
;
* This procedure frees all data queued for output or delivery on a
register struct pklcd
*lcp
;
register struct socket
*so
;
m_freem (lcp
-> lcd_template
);
m_freem (lcp
-> lcd_cps
);
if (lcp
-> lcd_facilities
) {
m_freem (lcp
-> lcd_facilities
);
lcp
-> lcd_facilities
= 0;
if (so
= lcp
-> lcd_so
) {
sbflush (&lcp
-> lcd_sb
);
* This procedure handles all local protocol procedure errors.
pk_procerror (error
, lcp
, errstr
, diagnostic
)
register struct pklcd
*lcp
;
pk_message (lcp
-> lcd_lcn
, lcp
-> lcd_pkp
-> pk_xcp
, errstr
);
lcp
-> lcd_so
-> so_error
= ECONNABORTED
;
soisdisconnecting (lcp
-> lcd_so
);
pk_clear (lcp
, diagnostic
, 1);
pk_reset (lcp
, diagnostic
);
* This procedure is called during the DATA TRANSFER state to check
* and process the P(R) values received in the DATA, RR OR RNR
register struct socket
*so
= lcp
-> lcd_so
;
if (lcp
-> lcd_output_window
== pr
)
if (lcp
-> lcd_output_window
< lcp
-> lcd_ssn
) {
if (pr
< lcp
-> lcd_output_window
|| pr
> lcp
-> lcd_ssn
) {
pk_procerror (RESET
, lcp
,
"p(r) flow control error", 2);
if (pr
< lcp
-> lcd_output_window
&& pr
> lcp
-> lcd_ssn
) {
pk_procerror (RESET
, lcp
,
"p(r) flow control error #2", 2);
lcp
-> lcd_output_window
= pr
; /* Rotate window. */
if (lcp
-> lcd_window_condition
== TRUE
)
lcp
-> lcd_window_condition
= FALSE
;
if (so
&& ((so
-> so_snd
.sb_flags
& SB_WAIT
) || so
-> so_snd
.sb_sel
))
* This procedure decodes the X.25 level 3 packet returning a
* code to be used in switchs or arrays.
register struct x25_packet
*xp
;
if (xp
-> fmt_identifier
!= 1)
* Make sure that the logical channel group number is 0.
* This restriction may be removed at some later date.
if (xp
-> lc_group_number
!= 0)
* Test for data packet first.
if (!(xp
-> packet_type
& DATA_PACKET_DESIGNATOR
))
* Test if flow control packet (RR or RNR).
if (!(xp
-> packet_type
& RR_OR_RNR_PACKET_DESIGNATOR
))
switch (xp
-> packet_type
& 0x1f) {
* Determine the rest of the packet types.
switch (xp
-> packet_type
) {
case X25_INTERRUPT_CONFIRM
:
case X25_RESTART_CONFIRM
:
* A restart packet has been received. Print out the reason
pk_restartcause (pkp
, xp
)
register struct x25_packet
*xp
;
register struct x25config
*xcp
= pkp
-> pk_xcp
;
register int lcn
= LCN(xp
);
switch (xp
-> packet_data
) {
case X25_RESTART_LOCAL_PROCEDURE_ERROR
:
pk_message (lcn
, xcp
, "restart: local procedure error");
case X25_RESTART_NETWORK_CONGESTION
:
pk_message (lcn
, xcp
, "restart: network congestion");
case X25_RESTART_NETWORK_OPERATIONAL
:
pk_message (lcn
, xcp
, "restart: network operational");
pk_message (lcn
, xcp
, "restart: unknown cause");
EXRESET
, EXROUT
, 0, EXRRPE
, 0, EXRLPE
, 0, EXRNCG
* A reset packet has arrived. Return the cause to the user.
register struct x25_packet
*xp
;
register struct pklcd
*lcp
=
register int code
= xp
-> packet_data
;
if (code
> MAXRESETCAUSE
)
pk_message(LCN(xp
), lcp
-> lcd_pkp
, "reset code 0x%x, diagnostic 0x%x",
xp
-> packet_data
, 4[(u_char
*)xp
]);
lcp
-> lcd_so
-> so_error
= Reset_cause
[code
];
EXCLEAR
, EXCBUSY
, 0, EXCINV
, 0, EXCNCG
, 0,
0, 0, EXCOUT
, 0, EXCAB
, 0, EXCNOB
, 0, 0, 0, EXCRPE
,
0, EXCLPE
, 0, 0, 0, 0, 0, EXCRRC
* A clear packet has arrived. Return the cause to the user.
register struct x25_packet
*xp
;
register struct pklcd
*lcp
=
register int code
= xp
-> packet_data
;
if (code
> MAXCLEARCAUSE
)
lcp
-> lcd_so
-> so_error
= Clear_cause
[code
];
register struct x25config
*xcp
;
return (xcp
-> xc_addr
.x25_addr
);
pk_message (lcn
, xcp
, fmt
, a1
, a2
, a3
, a4
, a5
, a6
)
printf ("X.25(%s): lcn %d: ", format_ntn (xcp
), lcn
);
printf ("X.25: lcn %d: ", lcn
);
printf ("X.25(%s): ", format_ntn (xcp
));
printf (fmt
, a1
, a2
, a3
, a4
, a5
, a6
);
pk_fragment (lcp
, m0
, qbit
, mbit
, wait
)
register struct pklcd
*lcp
;
register struct mbuf
*m
= m0
;
register struct x25_packet
*xp
;
register struct sockbuf
*sb
;
struct mbuf
*head
= 0, *next
, **mp
= &head
, *m_split ();
int totlen
, psize
= 1 << (lcp
-> lcd_packetsize
);
if (m
-> m_flags
& M_PKTHDR
== 0)
totlen
= m
-> m_pkthdr
.len
;
sb
= lcp
-> lcd_so
? &lcp
-> lcd_so
-> so_snd
: & lcp
-> lcd_sb
;
if ((next
= m_split (m
, psize
, wait
)) == 0)
M_PREPEND(m
, PKHEADERLN
, wait
);
xp
= mtod (m
, struct x25_packet
*);
if (lcp
-> lcd_flags
& X25_DBIT
)
xp
-> fmt_identifier
= 1;
xp
-> packet_type
= X25_DATA
;
SET_LCN(xp
, lcp
-> lcd_lcn
);
if (next
|| (mbit
&& (totlen
== psize
||
(lcp
-> lcd_flags
& X25_DBIT
))))
for (m
= head
; m
; m
= next
) {
panic ("pk_fragment null mbuf after wait");
for (m
= head
; m
; m
= next
) {
register struct mbuf
*m0
;
register struct mbuf
*m
, *n
;
unsigned len
= len0
, remain
;
for (m
= m0
; m
&& len
> m
-> m_len
; m
= m
-> m_next
)
remain
= m
-> m_len
- len
;
if (m0
-> m_flags
& M_PKTHDR
) {
MGETHDR(n
, wait
, m0
-> m_type
);
n
-> m_pkthdr
.rcvif
= m0
-> m_pkthdr
.rcvif
;
n
-> m_pkthdr
.len
= m0
-> m_pkthdr
.len
- len0
;
m0
-> m_pkthdr
.len
= len0
;
if (m
-> m_flags
& M_EXT
)
/* m can't be the lead packet */
n
-> m_next
= m_split (m
, len
, wait
);
} else if (remain
== 0) {
MGET(n
, wait
, m
-> m_type
);
if (m
-> m_flags
& M_EXT
) {
mclrefcnt
[mtocl (m
-> m_ext
.ext_buf
)]++;
n
-> m_data
= m
-> m_data
+ len
;
bcopy (mtod (m
, caddr_t
) + len
, mtod (n
, caddr_t
), remain
);
n
-> m_next
= m
-> m_next
;