Skip to content

Commit 4483d0c

Browse files
committed
pyln: new module pyln.proto.message.bolts
This contains the CSVs for the current bolts (autogenerated). It's a separate module because I expect it to be updated alongside the spec. Signed-off-by: Rusty Russell <[email protected]> Changelog-Added: pyln: new module pyln.proto.message.bolts
1 parent b680723 commit 4483d0c

File tree

5 files changed

+367
-1
lines changed

5 files changed

+367
-1
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#! /usr/bin/make
2+
3+
SPECDIR := ../../../../../../../lightning-rfc
4+
5+
# Only consider specs which have types in them.
6+
SPECS := $(shell fgrep -l '1. type' $(SPECDIR)/[0-9]*.md)
7+
8+
bolts.py: $(SPECS) Makefile
9+
for f in $(SPECS); do SPECNUM=`basename $$f | sed 's/-.*//'`; echo bolt_$${SPECNUM}_csv = '['; python3 $(SPECDIR)/tools/extract-formats.py $$f | sed 's/\(.*\)/ "\1",/'; echo ']'; done > $@
10+
chmod a+x $@
11+
12+
CSVFILES = $(SPECS:%.md=bolt%.py)
13+
14+
refresh: $(CSVFILES)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from .bolts import bolt_01_csv, bolt_02_csv, bolt_04_csv, bolt_07_csv
2+
3+
__version__ = '0.0.1'
4+
5+
__all__ = [
6+
"bolt_01_csv",
7+
"bolt_02_csv",
8+
"bolt_04_csv",
9+
"bolt_07_csv",
10+
]
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
bolt_01_csv = [
2+
"msgtype,init,16",
3+
"msgdata,init,gflen,u16,",
4+
"msgdata,init,globalfeatures,byte,gflen",
5+
"msgdata,init,flen,u16,",
6+
"msgdata,init,features,byte,flen",
7+
"msgdata,init,tlvs,init_tlvs,",
8+
"tlvtype,init_tlvs,networks,1",
9+
"tlvdata,init_tlvs,networks,chains,chain_hash,...",
10+
"msgtype,error,17",
11+
"msgdata,error,channel_id,channel_id,",
12+
"msgdata,error,len,u16,",
13+
"msgdata,error,data,byte,len",
14+
"msgtype,ping,18",
15+
"msgdata,ping,num_pong_bytes,u16,",
16+
"msgdata,ping,byteslen,u16,",
17+
"msgdata,ping,ignored,byte,byteslen",
18+
"msgtype,pong,19",
19+
"msgdata,pong,byteslen,u16,",
20+
"msgdata,pong,ignored,byte,byteslen",
21+
"tlvtype,n1,tlv1,1",
22+
"tlvdata,n1,tlv1,amount_msat,tu64,",
23+
"tlvtype,n1,tlv2,2",
24+
"tlvdata,n1,tlv2,scid,short_channel_id,",
25+
"tlvtype,n1,tlv3,3",
26+
"tlvdata,n1,tlv3,node_id,point,",
27+
"tlvdata,n1,tlv3,amount_msat_1,u64,",
28+
"tlvdata,n1,tlv3,amount_msat_2,u64,",
29+
"tlvtype,n1,tlv4,254",
30+
"tlvdata,n1,tlv4,cltv_delta,u16,",
31+
"tlvtype,n2,tlv1,0",
32+
"tlvdata,n2,tlv1,amount_msat,tu64,",
33+
"tlvtype,n2,tlv2,11",
34+
"tlvdata,n2,tlv2,cltv_expiry,tu32,",
35+
]
36+
bolt_02_csv = [
37+
"msgtype,open_channel,32",
38+
"msgdata,open_channel,chain_hash,chain_hash,",
39+
"msgdata,open_channel,temporary_channel_id,byte,32",
40+
"msgdata,open_channel,funding_satoshis,u64,",
41+
"msgdata,open_channel,push_msat,u64,",
42+
"msgdata,open_channel,dust_limit_satoshis,u64,",
43+
"msgdata,open_channel,max_htlc_value_in_flight_msat,u64,",
44+
"msgdata,open_channel,channel_reserve_satoshis,u64,",
45+
"msgdata,open_channel,htlc_minimum_msat,u64,",
46+
"msgdata,open_channel,feerate_per_kw,u32,",
47+
"msgdata,open_channel,to_self_delay,u16,",
48+
"msgdata,open_channel,max_accepted_htlcs,u16,",
49+
"msgdata,open_channel,funding_pubkey,point,",
50+
"msgdata,open_channel,revocation_basepoint,point,",
51+
"msgdata,open_channel,payment_basepoint,point,",
52+
"msgdata,open_channel,delayed_payment_basepoint,point,",
53+
"msgdata,open_channel,htlc_basepoint,point,",
54+
"msgdata,open_channel,first_per_commitment_point,point,",
55+
"msgdata,open_channel,channel_flags,byte,",
56+
"msgdata,open_channel,tlvs,open_channel_tlvs,",
57+
"tlvtype,open_channel_tlvs,upfront_shutdown_script,0",
58+
"tlvdata,open_channel_tlvs,upfront_shutdown_script,shutdown_scriptpubkey,byte,...",
59+
"msgtype,accept_channel,33",
60+
"msgdata,accept_channel,temporary_channel_id,byte,32",
61+
"msgdata,accept_channel,dust_limit_satoshis,u64,",
62+
"msgdata,accept_channel,max_htlc_value_in_flight_msat,u64,",
63+
"msgdata,accept_channel,channel_reserve_satoshis,u64,",
64+
"msgdata,accept_channel,htlc_minimum_msat,u64,",
65+
"msgdata,accept_channel,minimum_depth,u32,",
66+
"msgdata,accept_channel,to_self_delay,u16,",
67+
"msgdata,accept_channel,max_accepted_htlcs,u16,",
68+
"msgdata,accept_channel,funding_pubkey,point,",
69+
"msgdata,accept_channel,revocation_basepoint,point,",
70+
"msgdata,accept_channel,payment_basepoint,point,",
71+
"msgdata,accept_channel,delayed_payment_basepoint,point,",
72+
"msgdata,accept_channel,htlc_basepoint,point,",
73+
"msgdata,accept_channel,first_per_commitment_point,point,",
74+
"msgdata,accept_channel,tlvs,accept_channel_tlvs,",
75+
"tlvtype,accept_channel_tlvs,upfront_shutdown_script,0",
76+
"tlvdata,accept_channel_tlvs,upfront_shutdown_script,shutdown_scriptpubkey,byte,...",
77+
"msgtype,funding_created,34",
78+
"msgdata,funding_created,temporary_channel_id,byte,32",
79+
"msgdata,funding_created,funding_txid,sha256,",
80+
"msgdata,funding_created,funding_output_index,u16,",
81+
"msgdata,funding_created,signature,signature,",
82+
"msgtype,funding_signed,35",
83+
"msgdata,funding_signed,channel_id,channel_id,",
84+
"msgdata,funding_signed,signature,signature,",
85+
"msgtype,funding_locked,36",
86+
"msgdata,funding_locked,channel_id,channel_id,",
87+
"msgdata,funding_locked,next_per_commitment_point,point,",
88+
"msgtype,shutdown,38",
89+
"msgdata,shutdown,channel_id,channel_id,",
90+
"msgdata,shutdown,len,u16,",
91+
"msgdata,shutdown,scriptpubkey,byte,len",
92+
"msgtype,closing_signed,39",
93+
"msgdata,closing_signed,channel_id,channel_id,",
94+
"msgdata,closing_signed,fee_satoshis,u64,",
95+
"msgdata,closing_signed,signature,signature,",
96+
"msgtype,update_add_htlc,128",
97+
"msgdata,update_add_htlc,channel_id,channel_id,",
98+
"msgdata,update_add_htlc,id,u64,",
99+
"msgdata,update_add_htlc,amount_msat,u64,",
100+
"msgdata,update_add_htlc,payment_hash,sha256,",
101+
"msgdata,update_add_htlc,cltv_expiry,u32,",
102+
"msgdata,update_add_htlc,onion_routing_packet,byte,1366",
103+
"msgtype,update_fulfill_htlc,130",
104+
"msgdata,update_fulfill_htlc,channel_id,channel_id,",
105+
"msgdata,update_fulfill_htlc,id,u64,",
106+
"msgdata,update_fulfill_htlc,payment_preimage,byte,32",
107+
"msgtype,update_fail_htlc,131",
108+
"msgdata,update_fail_htlc,channel_id,channel_id,",
109+
"msgdata,update_fail_htlc,id,u64,",
110+
"msgdata,update_fail_htlc,len,u16,",
111+
"msgdata,update_fail_htlc,reason,byte,len",
112+
"msgtype,update_fail_malformed_htlc,135",
113+
"msgdata,update_fail_malformed_htlc,channel_id,channel_id,",
114+
"msgdata,update_fail_malformed_htlc,id,u64,",
115+
"msgdata,update_fail_malformed_htlc,sha256_of_onion,sha256,",
116+
"msgdata,update_fail_malformed_htlc,failure_code,u16,",
117+
"msgtype,commitment_signed,132",
118+
"msgdata,commitment_signed,channel_id,channel_id,",
119+
"msgdata,commitment_signed,signature,signature,",
120+
"msgdata,commitment_signed,num_htlcs,u16,",
121+
"msgdata,commitment_signed,htlc_signature,signature,num_htlcs",
122+
"msgtype,revoke_and_ack,133",
123+
"msgdata,revoke_and_ack,channel_id,channel_id,",
124+
"msgdata,revoke_and_ack,per_commitment_secret,byte,32",
125+
"msgdata,revoke_and_ack,next_per_commitment_point,point,",
126+
"msgtype,update_fee,134",
127+
"msgdata,update_fee,channel_id,channel_id,",
128+
"msgdata,update_fee,feerate_per_kw,u32,",
129+
"msgtype,channel_reestablish,136",
130+
"msgdata,channel_reestablish,channel_id,channel_id,",
131+
"msgdata,channel_reestablish,next_commitment_number,u64,",
132+
"msgdata,channel_reestablish,next_revocation_number,u64,",
133+
"msgdata,channel_reestablish,your_last_per_commitment_secret,byte,32",
134+
"msgdata,channel_reestablish,my_current_per_commitment_point,point,",
135+
]
136+
bolt_04_csv = [
137+
"tlvtype,tlv_payload,amt_to_forward,2",
138+
"tlvdata,tlv_payload,amt_to_forward,amt_to_forward,tu64,",
139+
"tlvtype,tlv_payload,outgoing_cltv_value,4",
140+
"tlvdata,tlv_payload,outgoing_cltv_value,outgoing_cltv_value,tu32,",
141+
"tlvtype,tlv_payload,short_channel_id,6",
142+
"tlvdata,tlv_payload,short_channel_id,short_channel_id,short_channel_id,",
143+
"tlvtype,tlv_payload,payment_data,8",
144+
"tlvdata,tlv_payload,payment_data,payment_secret,byte,32",
145+
"tlvdata,tlv_payload,payment_data,total_msat,tu64,",
146+
"msgtype,invalid_realm,PERM|1",
147+
"msgtype,temporary_node_failure,NODE|2",
148+
"msgtype,permanent_node_failure,PERM|NODE|2",
149+
"msgtype,required_node_feature_missing,PERM|NODE|3",
150+
"msgtype,invalid_onion_version,BADONION|PERM|4",
151+
"msgdata,invalid_onion_version,sha256_of_onion,sha256,",
152+
"msgtype,invalid_onion_hmac,BADONION|PERM|5",
153+
"msgdata,invalid_onion_hmac,sha256_of_onion,sha256,",
154+
"msgtype,invalid_onion_key,BADONION|PERM|6",
155+
"msgdata,invalid_onion_key,sha256_of_onion,sha256,",
156+
"msgtype,temporary_channel_failure,UPDATE|7",
157+
"msgdata,temporary_channel_failure,len,u16,",
158+
"msgdata,temporary_channel_failure,channel_update,byte,len",
159+
"msgtype,permanent_channel_failure,PERM|8",
160+
"msgtype,required_channel_feature_missing,PERM|9",
161+
"msgtype,unknown_next_peer,PERM|10",
162+
"msgtype,amount_below_minimum,UPDATE|11",
163+
"msgdata,amount_below_minimum,htlc_msat,u64,",
164+
"msgdata,amount_below_minimum,len,u16,",
165+
"msgdata,amount_below_minimum,channel_update,byte,len",
166+
"msgtype,fee_insufficient,UPDATE|12",
167+
"msgdata,fee_insufficient,htlc_msat,u64,",
168+
"msgdata,fee_insufficient,len,u16,",
169+
"msgdata,fee_insufficient,channel_update,byte,len",
170+
"msgtype,incorrect_cltv_expiry,UPDATE|13",
171+
"msgdata,incorrect_cltv_expiry,cltv_expiry,u32,",
172+
"msgdata,incorrect_cltv_expiry,len,u16,",
173+
"msgdata,incorrect_cltv_expiry,channel_update,byte,len",
174+
"msgtype,expiry_too_soon,UPDATE|14",
175+
"msgdata,expiry_too_soon,len,u16,",
176+
"msgdata,expiry_too_soon,channel_update,byte,len",
177+
"msgtype,incorrect_or_unknown_payment_details,PERM|15",
178+
"msgdata,incorrect_or_unknown_payment_details,htlc_msat,u64,",
179+
"msgdata,incorrect_or_unknown_payment_details,height,u32,",
180+
"msgtype,final_incorrect_cltv_expiry,18",
181+
"msgdata,final_incorrect_cltv_expiry,cltv_expiry,u32,",
182+
"msgtype,final_incorrect_htlc_amount,19",
183+
"msgdata,final_incorrect_htlc_amount,incoming_htlc_amt,u64,",
184+
"msgtype,channel_disabled,UPDATE|20",
185+
"msgtype,expiry_too_far,21",
186+
"msgtype,invalid_onion_payload,PERM|22",
187+
"msgdata,invalid_onion_payload,type,varint,",
188+
"msgdata,invalid_onion_payload,offset,u16,",
189+
"msgtype,mpp_timeout,23",
190+
]
191+
bolt_07_csv = [
192+
"msgtype,announcement_signatures,259",
193+
"msgdata,announcement_signatures,channel_id,channel_id,",
194+
"msgdata,announcement_signatures,short_channel_id,short_channel_id,",
195+
"msgdata,announcement_signatures,node_signature,signature,",
196+
"msgdata,announcement_signatures,bitcoin_signature,signature,",
197+
"msgtype,channel_announcement,256",
198+
"msgdata,channel_announcement,node_signature_1,signature,",
199+
"msgdata,channel_announcement,node_signature_2,signature,",
200+
"msgdata,channel_announcement,bitcoin_signature_1,signature,",
201+
"msgdata,channel_announcement,bitcoin_signature_2,signature,",
202+
"msgdata,channel_announcement,len,u16,",
203+
"msgdata,channel_announcement,features,byte,len",
204+
"msgdata,channel_announcement,chain_hash,chain_hash,",
205+
"msgdata,channel_announcement,short_channel_id,short_channel_id,",
206+
"msgdata,channel_announcement,node_id_1,point,",
207+
"msgdata,channel_announcement,node_id_2,point,",
208+
"msgdata,channel_announcement,bitcoin_key_1,point,",
209+
"msgdata,channel_announcement,bitcoin_key_2,point,",
210+
"msgtype,node_announcement,257",
211+
"msgdata,node_announcement,signature,signature,",
212+
"msgdata,node_announcement,flen,u16,",
213+
"msgdata,node_announcement,features,byte,flen",
214+
"msgdata,node_announcement,timestamp,u32,",
215+
"msgdata,node_announcement,node_id,point,",
216+
"msgdata,node_announcement,rgb_color,byte,3",
217+
"msgdata,node_announcement,alias,byte,32",
218+
"msgdata,node_announcement,addrlen,u16,",
219+
"msgdata,node_announcement,addresses,byte,addrlen",
220+
"msgtype,channel_update,258",
221+
"msgdata,channel_update,signature,signature,",
222+
"msgdata,channel_update,chain_hash,chain_hash,",
223+
"msgdata,channel_update,short_channel_id,short_channel_id,",
224+
"msgdata,channel_update,timestamp,u32,",
225+
"msgdata,channel_update,message_flags,byte,",
226+
"msgdata,channel_update,channel_flags,byte,",
227+
"msgdata,channel_update,cltv_expiry_delta,u16,",
228+
"msgdata,channel_update,htlc_minimum_msat,u64,",
229+
"msgdata,channel_update,fee_base_msat,u32,",
230+
"msgdata,channel_update,fee_proportional_millionths,u32,",
231+
"msgdata,channel_update,htlc_maximum_msat,u64,,option_channel_htlc_max",
232+
"msgtype,query_short_channel_ids,261,gossip_queries",
233+
"msgdata,query_short_channel_ids,chain_hash,chain_hash,",
234+
"msgdata,query_short_channel_ids,len,u16,",
235+
"msgdata,query_short_channel_ids,encoded_short_ids,byte,len",
236+
"msgdata,query_short_channel_ids,tlvs,query_short_channel_ids_tlvs,",
237+
"tlvtype,query_short_channel_ids_tlvs,query_flags,1",
238+
"tlvdata,query_short_channel_ids_tlvs,query_flags,encoding_type,u8,",
239+
"tlvdata,query_short_channel_ids_tlvs,query_flags,encoded_query_flags,byte,...",
240+
"msgtype,reply_short_channel_ids_end,262,gossip_queries",
241+
"msgdata,reply_short_channel_ids_end,chain_hash,chain_hash,",
242+
"msgdata,reply_short_channel_ids_end,full_information,byte,",
243+
"msgtype,query_channel_range,263,gossip_queries",
244+
"msgdata,query_channel_range,chain_hash,chain_hash,",
245+
"msgdata,query_channel_range,first_blocknum,u32,",
246+
"msgdata,query_channel_range,number_of_blocks,u32,",
247+
"msgdata,query_channel_range,tlvs,query_channel_range_tlvs,",
248+
"tlvtype,query_channel_range_tlvs,query_option,1",
249+
"tlvdata,query_channel_range_tlvs,query_option,query_option_flags,varint,",
250+
"msgtype,reply_channel_range,264,gossip_queries",
251+
"msgdata,reply_channel_range,chain_hash,chain_hash,",
252+
"msgdata,reply_channel_range,first_blocknum,u32,",
253+
"msgdata,reply_channel_range,number_of_blocks,u32,",
254+
"msgdata,reply_channel_range,full_information,byte,",
255+
"msgdata,reply_channel_range,len,u16,",
256+
"msgdata,reply_channel_range,encoded_short_ids,byte,len",
257+
"msgdata,reply_channel_range,tlvs,reply_channel_range_tlvs,",
258+
"tlvtype,reply_channel_range_tlvs,timestamps_tlv,1",
259+
"tlvdata,reply_channel_range_tlvs,timestamps_tlv,encoding_type,u8,",
260+
"tlvdata,reply_channel_range_tlvs,timestamps_tlv,encoded_timestamps,byte,...",
261+
"tlvtype,reply_channel_range_tlvs,checksums_tlv,3",
262+
"tlvdata,reply_channel_range_tlvs,checksums_tlv,checksums,channel_update_checksums,...",
263+
"subtype,channel_update_timestamps",
264+
"subtypedata,channel_update_timestamps,timestamp_node_id_1,u32,",
265+
"subtypedata,channel_update_timestamps,timestamp_node_id_2,u32,",
266+
"subtype,channel_update_checksums",
267+
"subtypedata,channel_update_checksums,checksum_node_id_1,u32,",
268+
"subtypedata,channel_update_checksums,checksum_node_id_2,u32,",
269+
"msgtype,gossip_timestamp_filter,265,gossip_queries",
270+
"msgdata,gossip_timestamp_filter,chain_hash,chain_hash,",
271+
"msgdata,gossip_timestamp_filter,first_timestamp,u32,",
272+
"msgdata,gossip_timestamp_filter,timestamp_range,u32,",
273+
]

contrib/pyln-proto/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
author='Christian Decker',
1818
author_email='[email protected]',
1919
license='MIT',
20-
packages=['pyln.proto', 'pyln.proto.message'],
20+
packages=['pyln.proto', 'pyln.proto.message', 'pyln.proto.message.bolts'],
2121
scripts=[],
2222
zip_safe=True,
2323
install_requires=requirements)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#! /usr/bin/python3
2+
from pyln.proto.message import Message, MessageNamespace
3+
from pyln.proto.message.bolts import bolt_01_csv, bolt_02_csv, bolt_04_csv, bolt_07_csv
4+
5+
6+
def test_bolt_01_csv_tlv():
7+
ns = MessageNamespace(bolt_01_csv)
8+
9+
n1 = ns.get_tlvtype('n1')
10+
11+
# FIXME: Test failure cases too!
12+
for t in [['0x', ''],
13+
['0x21 00', '33='],
14+
['0xfd0201 00', '513='],
15+
['0xfd00fd 00', '253='],
16+
['0xfd00ff 00', '255='],
17+
['0xfe02000001 00', '33554433='],
18+
['0xff0200000000000001 00', '144115188075855873='],
19+
['0x01 00', 'tlv1={amount_msat=0}'],
20+
['0x01 01 01', 'tlv1={amount_msat=1}'],
21+
['0x01 02 0100', 'tlv1={amount_msat=256}'],
22+
['0x01 03 010000', 'tlv1={amount_msat=65536}'],
23+
['0x01 04 01000000', 'tlv1={amount_msat=16777216}'],
24+
['0x01 05 0100000000', 'tlv1={amount_msat=4294967296}'],
25+
['0x01 06 010000000000', 'tlv1={amount_msat=1099511627776}'],
26+
['0x01 07 01000000000000', 'tlv1={amount_msat=281474976710656}'],
27+
['0x01 08 0100000000000000', 'tlv1={amount_msat=72057594037927936}'],
28+
['0x02 08 0000000000000226', 'tlv2={scid=0x0x550}'],
29+
['0x03 31 023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb00000000000000010000000000000002', 'tlv3={node_id=023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb,amount_msat_1=1,amount_msat_2=2}'],
30+
['0xfd00fe 02 0226', 'tlv4={cltv_delta=550}']]:
31+
msg = bytes.fromhex(t[0][2:].replace(' ', ''))
32+
33+
val, size = n1.val_from_bin(msg, None)
34+
assert size == len(msg)
35+
assert n1.val_to_str(val, None) == '{' + t[1] + '}'
36+
37+
38+
def test_bolt_01_csv():
39+
ns = MessageNamespace(bolt_01_csv)
40+
# string [expected string]
41+
for t in [['init globalfeatures= features=80',
42+
'init globalfeatures= features=80 tlvs={}'],
43+
['init globalfeatures= features=80 tlvs={}'],
44+
['init globalfeatures= features=80 tlvs={networks={chains=[6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000]}}'],
45+
['init globalfeatures= features=80 tlvs={networks={chains=[6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000,1fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000]}}'],
46+
['error channel_id=0000000000000000000000000000000000000000000000000000000000000000 data=00'],
47+
['ping num_pong_bytes=0 ignored='],
48+
['ping num_pong_bytes=3 ignored=0000'],
49+
['pong ignored='],
50+
['pong ignored=000000']]:
51+
m = Message.from_str(ns, t[0])
52+
b = m.to_bin()
53+
m2 = Message.from_bin(ns, b)
54+
assert m2.to_str() == t[-1]
55+
56+
57+
def test_bolt_02_csv():
58+
MessageNamespace(bolt_02_csv)
59+
# FIXME: Add tests.
60+
61+
62+
def test_bolt_04_csv():
63+
MessageNamespace(bolt_04_csv)
64+
# FIXME: Add tests.
65+
66+
67+
def test_bolt_07_csv():
68+
MessageNamespace(bolt_07_csv)
69+
# FIXME: Add tests.

0 commit comments

Comments
 (0)