diff options
author | Sean Dague | 2007-07-16 15:40:11 +0000 |
---|---|---|
committer | Sean Dague | 2007-07-16 15:40:11 +0000 |
commit | 2a3c79df83e800d5dfe75a1a3b140ed81da2b1d6 (patch) | |
tree | e3f80ad51736cf17e856547b1bcf956010927434 /OpenSim/Framework/Data.MySQL/MySQLGridData.cs | |
parent | *Trunk compiles now (diff) | |
download | opensim-SC_OLD-2a3c79df83e800d5dfe75a1a3b140ed81da2b1d6.zip opensim-SC_OLD-2a3c79df83e800d5dfe75a1a3b140ed81da2b1d6.tar.gz opensim-SC_OLD-2a3c79df83e800d5dfe75a1a3b140ed81da2b1d6.tar.bz2 opensim-SC_OLD-2a3c79df83e800d5dfe75a1a3b140ed81da2b1d6.tar.xz |
changed to native line ending encoding
Diffstat (limited to 'OpenSim/Framework/Data.MySQL/MySQLGridData.cs')
-rw-r--r-- | OpenSim/Framework/Data.MySQL/MySQLGridData.cs | 574 |
1 files changed, 287 insertions, 287 deletions
diff --git a/OpenSim/Framework/Data.MySQL/MySQLGridData.cs b/OpenSim/Framework/Data.MySQL/MySQLGridData.cs index ef643d2..09f5e83 100644 --- a/OpenSim/Framework/Data.MySQL/MySQLGridData.cs +++ b/OpenSim/Framework/Data.MySQL/MySQLGridData.cs | |||
@@ -1,287 +1,287 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (c) Contributors, http://www.openmetaverse.org/ | 2 | * Copyright (c) Contributors, http://www.openmetaverse.org/ |
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | 3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. |
4 | * | 4 | * |
5 | * Redistribution and use in source and binary forms, with or without | 5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are met: | 6 | * modification, are permitted provided that the following conditions are met: |
7 | * * Redistributions of source code must retain the above copyright | 7 | * * Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. | 8 | * notice, this list of conditions and the following disclaimer. |
9 | * * Redistributions in binary form must reproduce the above copyright | 9 | * * Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the | 10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. | 11 | * documentation and/or other materials provided with the distribution. |
12 | * * Neither the name of the OpenSim Project nor the | 12 | * * Neither the name of the OpenSim Project nor the |
13 | * names of its contributors may be used to endorse or promote products | 13 | * names of its contributors may be used to endorse or promote products |
14 | * derived from this software without specific prior written permission. | 14 | * derived from this software without specific prior written permission. |
15 | * | 15 | * |
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS AND ANY | 16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS AND ANY |
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | 19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | * | 26 | * |
27 | */ | 27 | */ |
28 | using System; | 28 | using System; |
29 | using System.Collections.Generic; | 29 | using System.Collections.Generic; |
30 | using System.Data; | 30 | using System.Data; |
31 | using System.Security.Cryptography; | 31 | using System.Security.Cryptography; |
32 | using System.Text; | 32 | using System.Text; |
33 | using libsecondlife; | 33 | using libsecondlife; |
34 | 34 | ||
35 | namespace OpenSim.Framework.Data.MySQL | 35 | namespace OpenSim.Framework.Data.MySQL |
36 | { | 36 | { |
37 | /// <summary> | 37 | /// <summary> |
38 | /// A MySQL Interface for the Grid Server | 38 | /// A MySQL Interface for the Grid Server |
39 | /// </summary> | 39 | /// </summary> |
40 | public class MySQLGridData : IGridData | 40 | public class MySQLGridData : IGridData |
41 | { | 41 | { |
42 | /// <summary> | 42 | /// <summary> |
43 | /// MySQL Database Manager | 43 | /// MySQL Database Manager |
44 | /// </summary> | 44 | /// </summary> |
45 | private MySQLManager database; | 45 | private MySQLManager database; |
46 | 46 | ||
47 | /// <summary> | 47 | /// <summary> |
48 | /// Initialises the Grid Interface | 48 | /// Initialises the Grid Interface |
49 | /// </summary> | 49 | /// </summary> |
50 | public void Initialise() | 50 | public void Initialise() |
51 | { | 51 | { |
52 | IniFile GridDataMySqlFile = new IniFile("mysql_connection.ini"); | 52 | IniFile GridDataMySqlFile = new IniFile("mysql_connection.ini"); |
53 | string settingHostname = GridDataMySqlFile.ParseFileReadValue("hostname"); | 53 | string settingHostname = GridDataMySqlFile.ParseFileReadValue("hostname"); |
54 | string settingDatabase = GridDataMySqlFile.ParseFileReadValue("database"); | 54 | string settingDatabase = GridDataMySqlFile.ParseFileReadValue("database"); |
55 | string settingUsername = GridDataMySqlFile.ParseFileReadValue("username"); | 55 | string settingUsername = GridDataMySqlFile.ParseFileReadValue("username"); |
56 | string settingPassword = GridDataMySqlFile.ParseFileReadValue("password"); | 56 | string settingPassword = GridDataMySqlFile.ParseFileReadValue("password"); |
57 | string settingPooling = GridDataMySqlFile.ParseFileReadValue("pooling"); | 57 | string settingPooling = GridDataMySqlFile.ParseFileReadValue("pooling"); |
58 | string settingPort = GridDataMySqlFile.ParseFileReadValue("port"); | 58 | string settingPort = GridDataMySqlFile.ParseFileReadValue("port"); |
59 | 59 | ||
60 | database = new MySQLManager(settingHostname, settingDatabase, settingUsername, settingPassword, settingPooling, settingPort); | 60 | database = new MySQLManager(settingHostname, settingDatabase, settingUsername, settingPassword, settingPooling, settingPort); |
61 | } | 61 | } |
62 | 62 | ||
63 | /// <summary> | 63 | /// <summary> |
64 | /// Shuts down the grid interface | 64 | /// Shuts down the grid interface |
65 | /// </summary> | 65 | /// </summary> |
66 | public void Close() | 66 | public void Close() |
67 | { | 67 | { |
68 | database.Close(); | 68 | database.Close(); |
69 | } | 69 | } |
70 | 70 | ||
71 | /// <summary> | 71 | /// <summary> |
72 | /// Returns the plugin name | 72 | /// Returns the plugin name |
73 | /// </summary> | 73 | /// </summary> |
74 | /// <returns>Plugin name</returns> | 74 | /// <returns>Plugin name</returns> |
75 | public string getName() | 75 | public string getName() |
76 | { | 76 | { |
77 | return "MySql OpenGridData"; | 77 | return "MySql OpenGridData"; |
78 | } | 78 | } |
79 | 79 | ||
80 | /// <summary> | 80 | /// <summary> |
81 | /// Returns the plugin version | 81 | /// Returns the plugin version |
82 | /// </summary> | 82 | /// </summary> |
83 | /// <returns>Plugin version</returns> | 83 | /// <returns>Plugin version</returns> |
84 | public string getVersion() | 84 | public string getVersion() |
85 | { | 85 | { |
86 | return "0.1"; | 86 | return "0.1"; |
87 | } | 87 | } |
88 | 88 | ||
89 | /// <summary> | 89 | /// <summary> |
90 | /// Returns all the specified region profiles within coordates -- coordinates are inclusive | 90 | /// Returns all the specified region profiles within coordates -- coordinates are inclusive |
91 | /// </summary> | 91 | /// </summary> |
92 | /// <param name="xmin">Minimum X coordinate</param> | 92 | /// <param name="xmin">Minimum X coordinate</param> |
93 | /// <param name="ymin">Minimum Y coordinate</param> | 93 | /// <param name="ymin">Minimum Y coordinate</param> |
94 | /// <param name="xmax">Maximum X coordinate</param> | 94 | /// <param name="xmax">Maximum X coordinate</param> |
95 | /// <param name="ymax">Maximum Y coordinate</param> | 95 | /// <param name="ymax">Maximum Y coordinate</param> |
96 | /// <returns></returns> | 96 | /// <returns></returns> |
97 | public SimProfileData[] GetProfilesInRange(uint xmin, uint ymin, uint xmax, uint ymax) | 97 | public SimProfileData[] GetProfilesInRange(uint xmin, uint ymin, uint xmax, uint ymax) |
98 | { | 98 | { |
99 | try | 99 | try |
100 | { | 100 | { |
101 | lock (database) | 101 | lock (database) |
102 | { | 102 | { |
103 | Dictionary<string, string> param = new Dictionary<string, string>(); | 103 | Dictionary<string, string> param = new Dictionary<string, string>(); |
104 | param["?xmin"] = xmin.ToString(); | 104 | param["?xmin"] = xmin.ToString(); |
105 | param["?ymin"] = ymin.ToString(); | 105 | param["?ymin"] = ymin.ToString(); |
106 | param["?xmax"] = xmax.ToString(); | 106 | param["?xmax"] = xmax.ToString(); |
107 | param["?ymax"] = ymax.ToString(); | 107 | param["?ymax"] = ymax.ToString(); |
108 | 108 | ||
109 | IDbCommand result = database.Query("SELECT * FROM regions WHERE locX >= ?xmin AND locX <= ?xmax AND locY >= ?ymin AND locY <= ?ymax", param); | 109 | IDbCommand result = database.Query("SELECT * FROM regions WHERE locX >= ?xmin AND locX <= ?xmax AND locY >= ?ymin AND locY <= ?ymax", param); |
110 | IDataReader reader = result.ExecuteReader(); | 110 | IDataReader reader = result.ExecuteReader(); |
111 | 111 | ||
112 | SimProfileData row; | 112 | SimProfileData row; |
113 | 113 | ||
114 | List<SimProfileData> rows = new List<SimProfileData>(); | 114 | List<SimProfileData> rows = new List<SimProfileData>(); |
115 | 115 | ||
116 | while ((row = database.readSimRow(reader)) != null) | 116 | while ((row = database.readSimRow(reader)) != null) |
117 | { | 117 | { |
118 | rows.Add(row); | 118 | rows.Add(row); |
119 | } | 119 | } |
120 | reader.Close(); | 120 | reader.Close(); |
121 | result.Dispose(); | 121 | result.Dispose(); |
122 | 122 | ||
123 | return rows.ToArray(); | 123 | return rows.ToArray(); |
124 | 124 | ||
125 | } | 125 | } |
126 | } | 126 | } |
127 | catch (Exception e) | 127 | catch (Exception e) |
128 | { | 128 | { |
129 | database.Reconnect(); | 129 | database.Reconnect(); |
130 | Console.WriteLine(e.ToString()); | 130 | Console.WriteLine(e.ToString()); |
131 | return null; | 131 | return null; |
132 | } | 132 | } |
133 | } | 133 | } |
134 | 134 | ||
135 | /// <summary> | 135 | /// <summary> |
136 | /// Returns a sim profile from it's location | 136 | /// Returns a sim profile from it's location |
137 | /// </summary> | 137 | /// </summary> |
138 | /// <param name="handle">Region location handle</param> | 138 | /// <param name="handle">Region location handle</param> |
139 | /// <returns>Sim profile</returns> | 139 | /// <returns>Sim profile</returns> |
140 | public SimProfileData GetProfileByHandle(ulong handle) | 140 | public SimProfileData GetProfileByHandle(ulong handle) |
141 | { | 141 | { |
142 | try | 142 | try |
143 | { | 143 | { |
144 | lock (database) | 144 | lock (database) |
145 | { | 145 | { |
146 | Dictionary<string, string> param = new Dictionary<string, string>(); | 146 | Dictionary<string, string> param = new Dictionary<string, string>(); |
147 | param["?handle"] = handle.ToString(); | 147 | param["?handle"] = handle.ToString(); |
148 | 148 | ||
149 | IDbCommand result = database.Query("SELECT * FROM regions WHERE regionHandle = ?handle", param); | 149 | IDbCommand result = database.Query("SELECT * FROM regions WHERE regionHandle = ?handle", param); |
150 | IDataReader reader = result.ExecuteReader(); | 150 | IDataReader reader = result.ExecuteReader(); |
151 | 151 | ||
152 | SimProfileData row = database.readSimRow(reader); | 152 | SimProfileData row = database.readSimRow(reader); |
153 | reader.Close(); | 153 | reader.Close(); |
154 | result.Dispose(); | 154 | result.Dispose(); |
155 | 155 | ||
156 | return row; | 156 | return row; |
157 | } | 157 | } |
158 | } | 158 | } |
159 | catch (Exception e) | 159 | catch (Exception e) |
160 | { | 160 | { |
161 | database.Reconnect(); | 161 | database.Reconnect(); |
162 | Console.WriteLine(e.ToString()); | 162 | Console.WriteLine(e.ToString()); |
163 | return null; | 163 | return null; |
164 | } | 164 | } |
165 | } | 165 | } |
166 | 166 | ||
167 | /// <summary> | 167 | /// <summary> |
168 | /// Returns a sim profile from it's UUID | 168 | /// Returns a sim profile from it's UUID |
169 | /// </summary> | 169 | /// </summary> |
170 | /// <param name="uuid">The region UUID</param> | 170 | /// <param name="uuid">The region UUID</param> |
171 | /// <returns>The sim profile</returns> | 171 | /// <returns>The sim profile</returns> |
172 | public SimProfileData GetProfileByLLUUID(LLUUID uuid) | 172 | public SimProfileData GetProfileByLLUUID(LLUUID uuid) |
173 | { | 173 | { |
174 | try | 174 | try |
175 | { | 175 | { |
176 | lock (database) | 176 | lock (database) |
177 | { | 177 | { |
178 | Dictionary<string, string> param = new Dictionary<string, string>(); | 178 | Dictionary<string, string> param = new Dictionary<string, string>(); |
179 | param["?uuid"] = uuid.ToStringHyphenated(); | 179 | param["?uuid"] = uuid.ToStringHyphenated(); |
180 | 180 | ||
181 | IDbCommand result = database.Query("SELECT * FROM regions WHERE uuid = ?uuid", param); | 181 | IDbCommand result = database.Query("SELECT * FROM regions WHERE uuid = ?uuid", param); |
182 | IDataReader reader = result.ExecuteReader(); | 182 | IDataReader reader = result.ExecuteReader(); |
183 | 183 | ||
184 | SimProfileData row = database.readSimRow(reader); | 184 | SimProfileData row = database.readSimRow(reader); |
185 | reader.Close(); | 185 | reader.Close(); |
186 | result.Dispose(); | 186 | result.Dispose(); |
187 | 187 | ||
188 | return row; | 188 | return row; |
189 | } | 189 | } |
190 | } | 190 | } |
191 | catch (Exception e) | 191 | catch (Exception e) |
192 | { | 192 | { |
193 | database.Reconnect(); | 193 | database.Reconnect(); |
194 | Console.WriteLine(e.ToString()); | 194 | Console.WriteLine(e.ToString()); |
195 | return null; | 195 | return null; |
196 | } | 196 | } |
197 | } | 197 | } |
198 | 198 | ||
199 | /// <summary> | 199 | /// <summary> |
200 | /// Adds a new profile to the database | 200 | /// Adds a new profile to the database |
201 | /// </summary> | 201 | /// </summary> |
202 | /// <param name="profile">The profile to add</param> | 202 | /// <param name="profile">The profile to add</param> |
203 | /// <returns>Successful?</returns> | 203 | /// <returns>Successful?</returns> |
204 | public DataResponse AddProfile(SimProfileData profile) | 204 | public DataResponse AddProfile(SimProfileData profile) |
205 | { | 205 | { |
206 | lock (database) | 206 | lock (database) |
207 | { | 207 | { |
208 | if (database.insertRegion(profile)) | 208 | if (database.insertRegion(profile)) |
209 | { | 209 | { |
210 | return DataResponse.RESPONSE_OK; | 210 | return DataResponse.RESPONSE_OK; |
211 | } | 211 | } |
212 | else | 212 | else |
213 | { | 213 | { |
214 | return DataResponse.RESPONSE_ERROR; | 214 | return DataResponse.RESPONSE_ERROR; |
215 | } | 215 | } |
216 | } | 216 | } |
217 | } | 217 | } |
218 | 218 | ||
219 | /// <summary> | 219 | /// <summary> |
220 | /// DEPRECIATED. Attempts to authenticate a region by comparing a shared secret. | 220 | /// DEPRECIATED. Attempts to authenticate a region by comparing a shared secret. |
221 | /// </summary> | 221 | /// </summary> |
222 | /// <param name="uuid">The UUID of the challenger</param> | 222 | /// <param name="uuid">The UUID of the challenger</param> |
223 | /// <param name="handle">The attempted regionHandle of the challenger</param> | 223 | /// <param name="handle">The attempted regionHandle of the challenger</param> |
224 | /// <param name="authkey">The secret</param> | 224 | /// <param name="authkey">The secret</param> |
225 | /// <returns>Whether the secret and regionhandle match the database entry for UUID</returns> | 225 | /// <returns>Whether the secret and regionhandle match the database entry for UUID</returns> |
226 | public bool AuthenticateSim(LLUUID uuid, ulong handle, string authkey) | 226 | public bool AuthenticateSim(LLUUID uuid, ulong handle, string authkey) |
227 | { | 227 | { |
228 | bool throwHissyFit = false; // Should be true by 1.0 | 228 | bool throwHissyFit = false; // Should be true by 1.0 |
229 | 229 | ||
230 | if (throwHissyFit) | 230 | if (throwHissyFit) |
231 | throw new Exception("CRYPTOWEAK AUTHENTICATE: Refusing to authenticate due to replay potential."); | 231 | throw new Exception("CRYPTOWEAK AUTHENTICATE: Refusing to authenticate due to replay potential."); |
232 | 232 | ||
233 | SimProfileData data = GetProfileByLLUUID(uuid); | 233 | SimProfileData data = GetProfileByLLUUID(uuid); |
234 | 234 | ||
235 | return (handle == data.regionHandle && authkey == data.regionSecret); | 235 | return (handle == data.regionHandle && authkey == data.regionSecret); |
236 | } | 236 | } |
237 | 237 | ||
238 | /// <summary> | 238 | /// <summary> |
239 | /// NOT YET FUNCTIONAL. Provides a cryptographic authentication of a region | 239 | /// NOT YET FUNCTIONAL. Provides a cryptographic authentication of a region |
240 | /// </summary> | 240 | /// </summary> |
241 | /// <remarks>This requires a security audit.</remarks> | 241 | /// <remarks>This requires a security audit.</remarks> |
242 | /// <param name="uuid"></param> | 242 | /// <param name="uuid"></param> |
243 | /// <param name="handle"></param> | 243 | /// <param name="handle"></param> |
244 | /// <param name="authhash"></param> | 244 | /// <param name="authhash"></param> |
245 | /// <param name="challenge"></param> | 245 | /// <param name="challenge"></param> |
246 | /// <returns></returns> | 246 | /// <returns></returns> |
247 | public bool AuthenticateSim(LLUUID uuid, ulong handle, string authhash, string challenge) | 247 | public bool AuthenticateSim(LLUUID uuid, ulong handle, string authhash, string challenge) |
248 | { | 248 | { |
249 | SHA512Managed HashProvider = new SHA512Managed(); | 249 | SHA512Managed HashProvider = new SHA512Managed(); |
250 | ASCIIEncoding TextProvider = new ASCIIEncoding(); | 250 | ASCIIEncoding TextProvider = new ASCIIEncoding(); |
251 | 251 | ||
252 | byte[] stream = TextProvider.GetBytes(uuid.ToStringHyphenated() + ":" + handle.ToString() + ":" + challenge); | 252 | byte[] stream = TextProvider.GetBytes(uuid.ToStringHyphenated() + ":" + handle.ToString() + ":" + challenge); |
253 | byte[] hash = HashProvider.ComputeHash(stream); | 253 | byte[] hash = HashProvider.ComputeHash(stream); |
254 | 254 | ||
255 | return false; | 255 | return false; |
256 | } | 256 | } |
257 | 257 | ||
258 | public ReservationData GetReservationAtPoint(uint x, uint y) | 258 | public ReservationData GetReservationAtPoint(uint x, uint y) |
259 | { | 259 | { |
260 | try | 260 | try |
261 | { | 261 | { |
262 | lock (database) | 262 | lock (database) |
263 | { | 263 | { |
264 | Dictionary<string, string> param = new Dictionary<string, string>(); | 264 | Dictionary<string, string> param = new Dictionary<string, string>(); |
265 | param["?x"] = x.ToString(); | 265 | param["?x"] = x.ToString(); |
266 | param["?y"] = y.ToString(); | 266 | param["?y"] = y.ToString(); |
267 | IDbCommand result = database.Query("SELECT * FROM reservations WHERE resXMin <= ?x AND resXMax >= ?x AND resYMin <= ?y AND resYMax >= ?y", param); | 267 | IDbCommand result = database.Query("SELECT * FROM reservations WHERE resXMin <= ?x AND resXMax >= ?x AND resYMin <= ?y AND resYMax >= ?y", param); |
268 | IDataReader reader = result.ExecuteReader(); | 268 | IDataReader reader = result.ExecuteReader(); |
269 | 269 | ||
270 | ReservationData row = database.readReservationRow(reader); | 270 | ReservationData row = database.readReservationRow(reader); |
271 | reader.Close(); | 271 | reader.Close(); |
272 | result.Dispose(); | 272 | result.Dispose(); |
273 | 273 | ||
274 | return row; | 274 | return row; |
275 | } | 275 | } |
276 | } | 276 | } |
277 | catch (Exception e) | 277 | catch (Exception e) |
278 | { | 278 | { |
279 | database.Reconnect(); | 279 | database.Reconnect(); |
280 | Console.WriteLine(e.ToString()); | 280 | Console.WriteLine(e.ToString()); |
281 | return null; | 281 | return null; |
282 | } | 282 | } |
283 | } | 283 | } |
284 | } | 284 | } |
285 | 285 | ||
286 | 286 | ||
287 | } | 287 | } |