1 /*
2  * Copyright (c) 2023 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "util/logger.h"
17 
18 #include "codegen/rust_code_emitter.h"
19 
20 #include "securec.h"
21 #include "util/file.h"
22 
23 namespace OHOS {
24 namespace Idl {
25 namespace {
26 const uint32_t WRAP_ANCHOR = 4;
27 }
28 
EmitInterface()29 void RustCodeEmitter::EmitInterface()
30 {
31     String filePath = String::Format("%s/%s.rs", directory_.string(), interfaceName_.string());
32     File file(filePath, File::WRITE);
33     StringBuilder sb;
34     EmitInterface(sb);
35     String data = sb.ToString();
36     file.WriteData(data.string(), data.GetLength());
37     file.Flush();
38     file.Close();
39 }
40 
EmitInterfaceProxy()41 void RustCodeEmitter::EmitInterfaceProxy()
42 {
43     return;
44 }
45 
EmitInterfaceStub()46 void RustCodeEmitter::EmitInterfaceStub()
47 {
48     return;
49 }
50 
EmitInterface(StringBuilder & sb)51 void RustCodeEmitter::EmitInterface(StringBuilder& sb)
52 {
53     if (metaInterface_->license_) {
54         EmitLicense(sb);
55         sb.Append("\n");
56     }
57     EmitMacros(sb);
58     EmitHeaders(sb);
59     sb.Append("\n");
60     EmitCommands(sb);
61     sb.Append("\n");
62     EmitRemoteObject(sb);
63     sb.Append("\n");
64     EmitBrokers(sb);
65     sb.Append("\n");
66     EmitRemoteRequest(sb);
67     sb.Append("\n");
68     EmitStub(sb);
69     sb.Append("\n");
70     EmitProxy(sb);
71 }
72 
EmitLicense(StringBuilder & sb)73 void RustCodeEmitter::EmitLicense(StringBuilder& sb)
74 {
75     sb.Append(metaInterface_->license_).Append("\n");
76 }
77 
EmitMacros(StringBuilder & sb)78 void RustCodeEmitter::EmitMacros(StringBuilder& sb)
79 {
80     sb.Append("#![allow(missing_docs)]\n");
81     sb.Append("#![allow(unused_variables)]\n");
82     sb.Append("#![allow(unused_mut)]\n");
83     sb.Append("\n");
84 }
EmitHeaders(StringBuilder & sb)85 void RustCodeEmitter::EmitHeaders(StringBuilder& sb)
86 {
87     EmitCommonHeaders(sb);
88     EmitIPCHeaders(sb);
89     if (EmitCustomHeaders(sb)) {
90         sb.Append("\n");
91     }
92 }
93 
EmitIPCHeaders(StringBuilder & sb)94 void RustCodeEmitter::EmitIPCHeaders(StringBuilder& sb)
95 {
96     sb.Append("extern crate ipc_rust;\n");
97     sb.Append("\n");
98     sb.Append("use ipc_rust::{\n");
99     sb.Append("    IRemoteBroker, IRemoteObj, RemoteStub, Result,\n");
100     sb.Append("    RemoteObj, define_remote_object, FIRST_CALL_TRANSACTION\n");
101     sb.Append("};\n");
102     sb.Append("use ipc_rust::{MsgParcel, BorrowedMsgParcel};\n");
103     sb.Append("\n");
104 }
105 
EmitCommonHeaders(StringBuilder & sb)106 void RustCodeEmitter::EmitCommonHeaders(StringBuilder& sb)
107 {
108     bool useMap = false;
109     for (int i = 0; i < metaComponent_->typeNumber_; i++) {
110         MetaType* mt = metaComponent_->types_[i];
111         switch (mt->kind_) {
112             case TypeKind::Map: {
113                 if (!useMap) {
114                     sb.Append("use std::collections::HashMap;\n");
115                     useMap = true;
116                 }
117                 break;
118             }
119 
120             default:
121                 break;
122         }
123     }
124     if (useMap) {
125         sb.Append("\n");
126     }
127 }
128 
TrimDot(const String & fpnp)129 String RustCodeEmitter::TrimDot(const String& fpnp)
130 {
131     if (fpnp.IsEmpty()) {
132         return nullptr;
133     }
134 
135     int left = 0;
136     int right = fpnp.GetLength() - 1;
137     while (fpnp[left] == ' ' || fpnp[left] == '.') {
138         left++;
139     }
140 
141     while (fpnp[right] == ' ' || fpnp[right] == '.') {
142         right--;
143     }
144 
145     if (left >= right) {
146         return nullptr;
147     }
148 
149     return fpnp.Substring(left, right + 1);
150 }
151 
GeneratePath(const String & fpnp)152 String RustCodeEmitter::GeneratePath(const String& fpnp)
153 {
154     int pos = fpnp.IndexOf("..");
155     if (pos == -1) {
156         String path = TrimDot(fpnp);
157         if (path.IsEmpty()) {
158             return nullptr;
159         }
160         return path.Replace(".", "::");
161     }
162 
163     String path = TrimDot(fpnp.Substring(0, pos + 1));
164     String file = TrimDot(fpnp.Substring(pos));
165     if (path.IsEmpty()) {
166         return nullptr;
167     }
168 
169     if (path.IndexOf("..") != -1 || file.IndexOf("..") != -1) {
170         return nullptr;
171     }
172 
173     StringBuilder realPath;
174     realPath.Append(path.Replace(".", "::")).Append("::{");
175     realPath.Append(file.Replace(".", ", "));
176     realPath.Append("}");
177 
178     return realPath.ToString();
179 }
180 
AppendRealPath(StringBuilder & sb,const String & fpnpp)181 bool RustCodeEmitter::AppendRealPath(StringBuilder& sb, const String& fpnpp)
182 {
183     String result = GeneratePath(fpnpp);
184     if (result.IsEmpty()) {
185         return false;
186     }
187     sb.Append("use ").Append(result).Append(";\n");
188     return true;
189 }
190 
EmitCustomHeaders(StringBuilder & sb)191 bool RustCodeEmitter::EmitCustomHeaders(StringBuilder& sb)
192 {
193     uint32_t custom = false;
194     for (int i = 0; i < metaComponent_->sequenceableNumber_; i++) {
195         MetaSequenceable* ms = metaComponent_->sequenceables_[i];
196         bool addPathMsRes = AppendRealPath(sb, String(ms->namespace_) + String(ms->name_));
197         custom |= static_cast<uint32_t>(addPathMsRes);
198     }
199 
200     for (int i = 0; i < metaComponent_->interfaceNumber_; i++) {
201         MetaInterface* mi = metaComponent_->interfaces_[i];
202         if (mi->external_) {
203             bool addPathMiRes = AppendRealPath(sb, String(mi->namespace_) + String(mi->name_));
204             custom |= static_cast<uint32_t>(addPathMiRes);
205         }
206     }
207     return static_cast<bool>(custom);
208 }
209 
EmitCommands(StringBuilder & sb)210 void RustCodeEmitter::EmitCommands(StringBuilder& sb)
211 {
212     EmitCommandEnums(sb);
213 }
214 
AppendCommandEnums(StringBuilder & sb)215 void RustCodeEmitter::AppendCommandEnums(StringBuilder& sb)
216 {
217     if (metaInterface_->methodNumber_ > 0) {
218         sb.AppendFormat("    %s  = FIRST_CALL_TRANSACTION,\n",
219             GetCodeFromMethod(metaInterface_->methods_[0]->name_).string());
220     }
221 
222     for (int i = 1; i < metaInterface_->methodNumber_; i++) {
223         MetaMethod* mm = metaInterface_->methods_[i];
224         sb.AppendFormat("    %s,\n", GetCodeFromMethod(mm->name_).string(), i);
225     }
226 }
227 
GetCodeFromMethod(const char * name)228 String RustCodeEmitter::GetCodeFromMethod(const char* name)
229 {
230     StringBuilder sb;
231     sb.Append("Code");
232     const char* p = name;
233     bool hasUpper = false;
234     while (p != nullptr && *p != '\0') {
235         if (*p != '_') {
236             if (!hasUpper) {
237                 sb.Append(toupper(*p));
238                 hasUpper = true;
239             } else {
240                 sb.Append(*p);
241             }
242         } else {
243             hasUpper = false;
244         }
245         p++;
246     }
247     return sb.ToString();
248 }
249 
GetNameFromParameter(const char * name)250 String RustCodeEmitter::GetNameFromParameter(const char* name)
251 {
252     StringBuilder sb;
253     const char* p = name;
254     bool start = true;
255     while (p != nullptr && *p != '\0') {
256         if (start) {
257             if (isupper(*p)) {
258                 sb.Append('p');
259             }
260             start = false;
261         }
262 
263         if (isupper(*p)) {
264             sb.Append('_');
265             sb.Append(tolower(*p));
266         } else {
267             sb.Append(*p);
268         }
269         p++;
270     }
271     return sb.ToString();
272 }
273 
EmitCommandEnums(StringBuilder & sb)274 void RustCodeEmitter::EmitCommandEnums(StringBuilder& sb)
275 {
276     sb.AppendFormat("pub enum %sCode {\n", interfaceName_.string());
277     AppendCommandEnums(sb);
278     sb.Append("}\n");
279 }
280 
EmitRemoteObject(StringBuilder & sb)281 void RustCodeEmitter::EmitRemoteObject(StringBuilder& sb)
282 {
283     sb.Append("define_remote_object!(\n");
284     if (interfaceFullName_.StartsWith(".")) {
285         sb.AppendFormat("    %s[\"%s\"] {\n", interfaceName_.string(), interfaceName_.string());
286     } else {
287         sb.AppendFormat("    %s[\"%s\"] {\n", interfaceName_.string(), interfaceFullName_.string());
288     }
289     sb.AppendFormat("        stub: %s(on_remote_request),\n", stubName_.string());
290     sb.AppendFormat("        proxy: %s,\n", proxyName_.string());
291     sb.Append("    }\n");
292     sb.Append(");\n");
293 }
294 
EmitBrokers(StringBuilder & sb)295 void RustCodeEmitter::EmitBrokers(StringBuilder& sb)
296 {
297     sb.AppendFormat("pub trait %s: IRemoteBroker {\n", interfaceName_.string());
298     AppendBrokerMethods(sb);
299     sb.Append("}\n");
300 }
301 
WrapLine(StringBuilder & sb,int index,const String & prefix)302 void RustCodeEmitter::WrapLine(StringBuilder& sb, int index, const String& prefix)
303 {
304     if ((index + 1) % WRAP_ANCHOR == 0) {
305         sb.AppendFormat(",\n%s", prefix.string());
306     } else {
307         sb.Append(", ");
308     }
309 }
310 
AppendBrokerMethods(StringBuilder & sb)311 void RustCodeEmitter::AppendBrokerMethods(StringBuilder& sb)
312 {
313     for (int i = 0; i < metaInterface_->methodNumber_; i++) {
314         MetaMethod* mm = metaInterface_->methods_[i];
315         sb.AppendFormat("    fn %s(&self", mm->name_);
316         for (int i = 0; i < mm->parameterNumber_; i++) {
317             WrapLine(sb, i, "        ");
318             AppendBrokerParameters(sb, mm->parameters_[i]);
319         }
320         sb.AppendFormat(") -> Result<%s>;\n", ConvertType(metaComponent_->types_[mm->returnTypeIndex_]).string());
321     }
322 }
323 
AppendBrokerParameters(StringBuilder & sb,MetaParameter * mp)324 void RustCodeEmitter::AppendBrokerParameters(StringBuilder& sb, MetaParameter* mp)
325 {
326     sb.AppendFormat("%s: &%s",
327         GetNameFromParameter(mp->name_).string(), ConvertType(metaComponent_->types_[mp->typeIndex_], true).string());
328 }
329 
ConvertType(MetaType * mt,bool pt)330 String RustCodeEmitter::ConvertType(MetaType* mt, bool pt)
331 {
332     switch (mt->kind_) {
333         case TypeKind::Unknown:
334         case TypeKind::Void:
335             return "()";
336         case TypeKind::Char:
337             return "char";
338         case TypeKind::Boolean:
339             return "bool";
340         case TypeKind::Byte:
341             return "i8";
342         case TypeKind::Short:
343             return "i16";
344         case TypeKind::Integer:
345             return "i32";
346         case TypeKind::Long:
347             return "i64";
348         case TypeKind::Float:
349             return "f32";
350         case TypeKind::Double:
351             return "f64";
352         case TypeKind::String:
353             return pt ? "str" : "String";
354         case TypeKind::Sequenceable:
355             return  metaComponent_->sequenceables_[mt->index_]->name_;
356         case TypeKind::Interface:
357             return metaComponent_->interfaces_[mt->index_]->name_;
358         case TypeKind::Map:
359             return String::Format("HashMap<%s, %s>",
360                 ConvertType(metaComponent_->types_[mt->nestedTypeIndexes_[0]]).string(),
361                 ConvertType(metaComponent_->types_[mt->nestedTypeIndexes_[1]]).string());
362         case TypeKind::List:
363         case TypeKind::Array:
364             return  String::Format((pt ? "[%s]" : "Vec<%s>"),
365                 ConvertType(metaComponent_->types_[mt->nestedTypeIndexes_[0]]).string());
366         default:
367             return "()";
368     }
369 }
370 
EmitRemoteRequest(StringBuilder & sb)371 void RustCodeEmitter::EmitRemoteRequest(StringBuilder& sb)
372 {
373     sb.AppendFormat("fn on_remote_request(stub: &dyn %s, code: u32, data: &BorrowedMsgParcel,\n",
374         interfaceName_.string());
375     sb.Append("    reply: &mut BorrowedMsgParcel) -> Result<()> {\n");
376     sb.Append("    match code {\n");
377     AddRemoteRequestMethods(sb);
378     sb.Append("        _ => Err(-1)\n");
379     sb.Append("    }\n");
380     sb.Append("}\n");
381 }
382 
AddRemoteRequestParameters(StringBuilder & sb,MetaMethod * mm)383 void RustCodeEmitter::AddRemoteRequestParameters(StringBuilder& sb, MetaMethod* mm)
384 {
385     for (int i = 0; i < mm->parameterNumber_; i++) {
386         MetaParameter* mp = mm->parameters_[i];
387         sb.AppendFormat("&%s", GetNameFromParameter(mp->name_).string());
388         if (i + 1 != mm->parameterNumber_) {
389             WrapLine(sb, i, "                ");
390         }
391     }
392 }
393 
ReadListFromParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)394 void RustCodeEmitter::ReadListFromParcel(StringBuilder& sb, MetaType* mt, const String& result,
395     const String& name, const String& prefix)
396 {
397     sb.Append(prefix).AppendFormat("let %s : %s = %s.read()?;\n",
398         name.string(), ConvertType(mt).string(), result.string());
399 }
400 
ReadMapFromParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)401 void RustCodeEmitter::ReadMapFromParcel(StringBuilder& sb, MetaType* mt, const String& result,
402     const String& name, const String& prefix)
403 {
404     sb.Append(prefix).AppendFormat("let mut %s = HashMap::new();\n", name.string());
405     sb.Append(prefix).AppendFormat("let len = %s.read()?;\n", result.string());
406     sb.Append(prefix).Append("for i in 0..len {\n");
407     StringBuilder k;
408     StringBuilder v;
409     k.Append(name).Append("k");
410     v.Append(name).Append("v");
411     ReadFromParcel(sb, metaComponent_->types_[mt->nestedTypeIndexes_[0]],
412         result, k.ToString().string(), prefix + "    ");
413     ReadFromParcel(sb, metaComponent_->types_[mt->nestedTypeIndexes_[1]],
414         result, v.ToString().string(), prefix + "    ");
415     sb.Append(prefix + "    ").AppendFormat("%s.insert(%s, %s);\n",
416         name.string(), k.ToString().string(), v.ToString().string());
417     sb.Append(prefix).Append("}\n");
418 }
419 
ReadFromParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)420 void RustCodeEmitter::ReadFromParcel(StringBuilder& sb, MetaType* mt, const String& result,
421     const String& name, const String& prefix)
422 {
423     if (mt->kind_ == TypeKind::Map) {
424         ReadMapFromParcel(sb, mt, result, name, prefix);
425     } else if (mt->kind_ == TypeKind::List || mt->kind_ == TypeKind::Array) {
426         ReadListFromParcel(sb, mt, result, name, prefix);
427     } else {
428         sb.Append(prefix).AppendFormat("let %s : %s = %s.read()?;\n",
429             name.string(), ConvertType(mt).string(), result.string());
430     }
431 }
432 
WriteListToParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)433 void RustCodeEmitter::WriteListToParcel(StringBuilder& sb, MetaType* mt, const String& result,
434     const String& name, const String& prefix)
435 {
436     sb.Append(prefix).AppendFormat("%s.write(&%s)?;\n", result.string(), name.string());
437 }
438 
WriteMapToParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)439 void RustCodeEmitter::WriteMapToParcel(StringBuilder& sb, MetaType* mt, const String& result,
440     const String& name, const String& prefix)
441 {
442     sb.Append(prefix).AppendFormat("%s.write(&(%s.len() as u32))?;\n", result.string(), name.string());
443     sb.Append(prefix).AppendFormat("for (key, value) in %s.iter() {\n", name.string());
444     WriteToParcel(sb, metaComponent_->types_[mt->nestedTypeIndexes_[0]], result, "key", prefix + "    ");
445     WriteToParcel(sb, metaComponent_->types_[mt->nestedTypeIndexes_[1]], result, "value", prefix + "    ");
446     sb.Append(prefix).Append("}\n");
447 }
448 
WriteToParcel(StringBuilder & sb,MetaType * mt,const String & result,const String & name,const String & prefix)449 void RustCodeEmitter::WriteToParcel(StringBuilder& sb, MetaType* mt, const String& result,
450     const String& name, const String& prefix)
451 {
452     if (mt->kind_ == TypeKind::Map) {
453         WriteMapToParcel(sb, mt, result, name, prefix);
454     } else if (mt->kind_ == TypeKind::List || mt->kind_ == TypeKind::Array) {
455         WriteListToParcel(sb, mt, result, name, prefix);
456     } else {
457         sb.Append(prefix).AppendFormat("%s.write(&%s)?;\n", result.string(), name.string());
458     }
459 }
460 
AddRemoteRequestMethods(StringBuilder & sb)461 void RustCodeEmitter::AddRemoteRequestMethods(StringBuilder& sb)
462 {
463     for (int i = 0; i < metaInterface_->methodNumber_; i++) {
464         MetaMethod* mm = metaInterface_->methods_[i];
465         sb.AppendFormat("        %d => {\n", i + 1);
466         for (int j = 0; j < mm->parameterNumber_; j++) {
467             ReadFromParcel(sb, metaComponent_->types_[mm->parameters_[j]->typeIndex_], "data",
468                 GetNameFromParameter(mm->parameters_[j]->name_), "            ");
469         }
470         MetaType* mt = metaComponent_->types_[mm->returnTypeIndex_];
471         if (mt->kind_ != TypeKind::Unknown && mt->kind_ != TypeKind::Void) {
472             sb.AppendFormat("            let result = stub.%s(", mm->name_);
473         } else {
474             sb.AppendFormat("            stub.%s(", mm->name_);
475         }
476         AddRemoteRequestParameters(sb, mm);
477         sb.Append(")?;\n");
478         if (mt->kind_ != TypeKind::Unknown && mt->kind_ != TypeKind::Void) {
479             WriteToParcel(sb, mt, "reply", "result", "            ");
480         }
481         sb.Append("            Ok(())\n");
482         sb.Append("        }\n");
483     }
484 }
485 
EmitStub(StringBuilder & sb)486 void RustCodeEmitter::EmitStub(StringBuilder& sb)
487 {
488     sb.AppendFormat("impl %s for RemoteStub<%s> {\n", interfaceName_.string(), stubName_.string());
489     AppendStubMethods(sb);
490     sb.Append("}\n");
491 }
492 
AppendStubParameters(StringBuilder & sb,MetaMethod * mm)493 void RustCodeEmitter::AppendStubParameters(StringBuilder& sb, MetaMethod* mm)
494 {
495     for (int i = 0; i < mm->parameterNumber_; i++) {
496         sb.Append(GetNameFromParameter(mm->parameters_[i]->name_));
497         if (i + 1 != mm->parameterNumber_) {
498             WrapLine(sb, i, "                ");
499         }
500     }
501 }
502 
AppendStubMethods(StringBuilder & sb)503 void RustCodeEmitter::AppendStubMethods(StringBuilder& sb)
504 {
505     for (int i = 0; i < metaInterface_->methodNumber_; i++) {
506         MetaMethod* mm = metaInterface_->methods_[i];
507         sb.AppendFormat("    fn %s(&self", mm->name_);
508         for (int i = 0; i < mm->parameterNumber_; i++) {
509             WrapLine(sb, i, "        ");
510             AppendBrokerParameters(sb, mm->parameters_[i]);
511         }
512 
513         sb.AppendFormat(") -> Result<%s> {\n",
514             ConvertType(metaComponent_->types_[mm->returnTypeIndex_]).string());
515         sb.AppendFormat("        self.0.%s(", mm->name_);
516         AppendStubParameters(sb, mm);
517         sb.Append(")\n");
518         sb.Append("    }\n");
519         if (i != metaInterface_->methodNumber_ - 1) {
520             sb.Append("\n");
521         }
522     }
523 }
524 
EmitProxy(StringBuilder & sb)525 void RustCodeEmitter::EmitProxy(StringBuilder& sb)
526 {
527     sb.AppendFormat("impl %s for %s {\n", interfaceName_.string(), proxyName_.string());
528     AppendProxyMethods(sb);
529     sb.Append("}\n");
530 }
531 
AppendProxyMethods(StringBuilder & sb)532 void RustCodeEmitter::AppendProxyMethods(StringBuilder& sb)
533 {
534     for (int i = 0; i < metaInterface_->methodNumber_; i++) {
535         MetaMethod* mm = metaInterface_->methods_[i];
536         sb.AppendFormat("    fn %s(&self", mm->name_);
537         for (int i = 0; i < mm->parameterNumber_; i++) {
538             WrapLine(sb, i, "        ");
539             AppendBrokerParameters(sb, mm->parameters_[i]);
540         }
541         sb.AppendFormat(") -> Result<%s> {\n",
542             ConvertType(metaComponent_->types_[mm->returnTypeIndex_]).string());
543         sb.Append("        let mut data = MsgParcel::new().expect(\"MsgParcel should success\");\n");
544         for (int j = 0; j < mm->parameterNumber_; j++) {
545             WriteToParcel(sb, metaComponent_->types_[mm->parameters_[j]->typeIndex_], "data",
546                 GetNameFromParameter(mm->parameters_[j]->name_), "        ");
547         }
548         MetaType* mt = metaComponent_->types_[mm->returnTypeIndex_];
549         if (mt->kind_ == TypeKind::Unknown || mt->kind_ == TypeKind::Void) {
550             sb.AppendFormat("        let _reply = self.remote.send_request(%sCode", interfaceName_.string());
551         } else {
552             sb.AppendFormat("        let reply = self.remote.send_request(%sCode", interfaceName_.string());
553         }
554         sb.AppendFormat("::%s as u32, &data, ", GetCodeFromMethod(mm->name_).string());
555         if ((mm->properties_ & METHOD_PROPERTY_ONEWAY) != 0) {
556             sb.Append("true");
557         } else {
558             sb.Append("false");
559         }
560         sb.Append(")?;\n");
561         if (mt->kind_ == TypeKind::Unknown || mt->kind_ == TypeKind::Void) {
562             sb.Append("        ").Append("Ok(())\n");
563         } else {
564             ReadFromParcel(sb, mt, "reply", "result", "        ");
565             sb.Append("        ").Append("Ok(result)\n");
566         }
567         sb.Append("    }\n");
568 
569         if (i != metaInterface_->methodNumber_ - 1) {
570             sb.Append("\n");
571         }
572     }
573 }
574 }
575 }
576