# Getting Started Tutorial
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Prerequisites](#prerequisites)
- [Tutorial Overview](#tutorial-overview)
- [Your First Schema](#your-first-schema)
- [Adding Constraints](#adding-constraints)
- [Working with Optional Fields](#working-with-optional-fields)
- [Using Tagged Fields](#using-tagged-fields)
- [Multi-File Projects](#multi-file-projects)
- [Real-World Example: Simple Protocol](#real-world-example-simple-protocol)
- [Next Steps](#next-steps)
- [Common Patterns](#common-patterns)
- [Pattern 1: Validated Wrapper Types](#pattern-1-validated-wrapper-types)
- [Pattern 2: Extensible Protocols with CHOICE](#pattern-2-extensible-protocols-with-choice)
- [Pattern 3: Nested Structures](#pattern-3-nested-structures)
- [Troubleshooting](#troubleshooting)
This tutorial will walk you through using synta-codegen to generate Rust code from ASN.1 schemas, from simple examples to real-world use cases.
## Prerequisites
- Rust 2021 edition or later
- Basic understanding of ASN.1 concepts
- synta-codegen installed (`cargo install synta-codegen`)
## Tutorial Overview
1. [Your First Schema](#your-first-schema)
2. [Adding Constraints](#adding-constraints)
3. [Working with Optional Fields](#working-with-optional-fields)
4. [Using Tagged Fields](#using-tagged-fields)
5. [Multi-File Projects](#multi-file-projects)
6. [Real-World Example: Simple Protocol](#real-world-example-simple-protocol)
---
## Your First Schema
Let's start with a simple ASN.1 schema for a user profile:
**user.asn1:**
```asn1
UserModule DEFINITIONS ::= BEGIN
User ::= SEQUENCE {
id INTEGER,
username IA5String,
email IA5String,
active BOOLEAN
}
END
```
**Generate Rust code:**
```bash
synta-codegen user.asn1 -o user.rs
```
**Generated code highlights:**
```rust
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "derive", derive(Asn1Sequence))]
pub struct User {
pub id: Integer,
pub username: IA5String,
pub email: IA5String,
pub active: Boolean,
}
```
**Using the generated code:**
```rust,ignore
use synta::{Encode, Decode};
// Create a user
let user = User {
id: Integer::from(42),
username: IA5String::from("alice"),
email: IA5String::from("alice@example.com"),
active: Boolean::new(true),
};
// Encode to DER
let encoded = synta::encode_der(&user)?;
// Decode back
let decoded: User = synta::decode_der(&encoded)?;
assert_eq!(user, decoded);
```
---
## Adding Constraints
Constraints validate data at construction time. Let's add some validation:
**user-constrained.asn1:**
```asn1
UserModule DEFINITIONS ::= BEGIN
UserId ::= INTEGER (1..999999)
Username ::= IA5String (SIZE (3..32) FROM ("a".."z" | "A".."Z" | "0".."9" | "-" | "_"))
Email ::= IA5String (SIZE (5..255))
User ::= SEQUENCE {
id UserId,
username Username,
email Email,
active BOOLEAN
}
END
```
**Generate and use:**
```bash
synta-codegen user-constrained.asn1 -o user_constrained.rs
```
**The generated types enforce constraints:**
```rust
// Valid
let user_id = UserId::new(Integer::from(42))?;
// Invalid - will return error
let invalid_id = UserId::new(Integer::from(9999999));
assert!(invalid_id.is_err()); // Out of range!
// Valid username
let username = Username::new(IA5String::from("alice_123"))?;
// Invalid - will return error
let invalid_username = Username::new(IA5String::from("a")); // Too short!
assert!(invalid_username.is_err());
```
---
## Working with Optional Fields
Many protocols need optional fields. Here's how to handle them:
**profile.asn1:**
```asn1
ProfileModule DEFINITIONS ::= BEGIN
Profile ::= SEQUENCE {
username IA5String,
displayName UTF8String OPTIONAL,
bio UTF8String OPTIONAL,
website IA5String OPTIONAL,
verified BOOLEAN DEFAULT FALSE
}
END
```
**Generated code:**
```rust
pub struct Profile {
pub username: IA5String,
pub display_name: Option<Utf8String>,
pub bio: Option<Utf8String>,
pub website: Option<IA5String>,
pub verified: Boolean,
}
impl Default for Profile {
fn default() -> Self {
Self {
username: IA5String::from(""), // TODO: Required field
display_name: None,
bio: None,
website: None,
verified: Boolean::new(false), // DEFAULT value
}
}
}
```
**Usage:**
```rust
let profile = Profile {
username: IA5String::from("alice"),
display_name: Some(Utf8String::from("Alice")),
bio: None, // Optional fields can be None
website: None,
verified: Boolean::new(true),
};
```
---
## Using Tagged Fields
Tagged fields are essential for protocols that need backward compatibility or context-specific types:
**message.asn1:**
```asn1
MessageModule DEFINITIONS ::= BEGIN
Message ::= SEQUENCE {
messageId INTEGER,
sender IA5String,
priority [0] INTEGER OPTIONAL,
expires [1] GeneralizedTime OPTIONAL,
metadata [2] OCTET STRING OPTIONAL
}
END
```
**Generated code:**
```rust
pub struct Message {
pub message_id: Integer,
pub sender: IA5String,
#[cfg_attr(feature = "derive", asn1(tag(0, explicit)))]
pub priority: Option<Integer>,
#[cfg_attr(feature = "derive", asn1(tag(1, explicit)))]
pub expires: Option<GeneralizedTime>,
#[cfg_attr(feature = "derive", asn1(tag(2, explicit)))]
pub metadata: Option<OctetString>,
}
```
**Usage:**
```rust
let message = Message {
message_id: Integer::from(1),
sender: IA5String::from("alice"),
priority: Some(Integer::from(5)), // Tagged [0]
expires: None,
metadata: Some(OctetString::from(b"extra data")),
};
```
---
## Multi-File Projects
Real projects often split schemas across multiple files:
**base-types.asn1:**
```asn1
BaseTypes DEFINITIONS ::= BEGIN
EXPORTS UserId, Username;
UserId ::= INTEGER (1..999999)
Username ::= IA5String (SIZE (3..32))
END
```
**user.asn1:**
```asn1
UserModule DEFINITIONS ::= BEGIN
IMPORTS UserId, Username FROM BaseTypes;
User ::= SEQUENCE {
id UserId,
username Username,
email IA5String
}
END
```
**Generate with imports:**
```bash
# Generate base types first
synta-codegen base-types.asn1 --crate-imports -o src/base_types.rs
# Generate user module with imports
synta-codegen user.asn1 --crate-imports -o src/user.rs
```
**Generated user.rs includes:**
```rust
use crate::base_types::{UserId, Username};
pub struct User {
pub id: UserId,
pub username: Username,
pub email: IA5String,
}
```
**Project structure:**
```
my-project/
├── Cargo.toml
├── src/
│ ├── lib.rs
│ ├── base_types.rs (generated)
│ └── user.rs (generated)
└── schemas/
├── base-types.asn1
└── user.asn1
```
For the complete multi-module guide including `build.rs` integration, module name
conversion, and multi-schema output directories, see
[rust-generation.md Section 9](rust-generation.md#9-buildrs-integration).
---
## Real-World Example: Simple Protocol
Let's create a simple authentication protocol:
**auth-protocol.asn1:**
```asn1
AuthProtocol DEFINITIONS ::= BEGIN
MessageType ::= INTEGER {
loginRequest (1),
loginResponse (2),
logoutRequest (3),
logoutResponse (4)
}
Timestamp ::= GeneralizedTime
LoginRequest ::= SEQUENCE {
username IA5String (SIZE (3..32)),
password OCTET STRING (SIZE (8..128)),
timestamp Timestamp
}
LoginResponse ::= SEQUENCE {
success BOOLEAN,
token OCTET STRING OPTIONAL,
message UTF8String OPTIONAL
}
LogoutRequest ::= SEQUENCE {
token OCTET STRING
}
LogoutResponse ::= SEQUENCE {
success BOOLEAN
}
Message ::= SEQUENCE {
messageType MessageType,
payload CHOICE {
loginReq [0] LoginRequest,
loginResp [1] LoginResponse,
logoutReq [2] LogoutRequest,
logoutResp [3] LogoutResponse
}
}
END
```
**Generate:**
```bash
synta-codegen auth-protocol.asn1 -o src/auth_protocol.rs
```
**Client usage example:**
```rust,ignore
use synta::{encode_der, decode_der};
use auth_protocol::*;
// Create login request
let login_req = LoginRequest {
username: IA5String::from("alice"),
password: OctetString::from(b"secure_password"),
timestamp: GeneralizedTime::now(),
};
let message = Message {
message_type: Integer::from(MessageType::LOGIN_REQUEST),
payload: Payload::LoginReq(login_req),
};
// Encode and send
let encoded = encode_der(&message)?;
send_to_server(&encoded);
// Receive and decode response
let response_bytes = receive_from_server();
let response: Message = decode_der(&response_bytes)?;
match response.payload {
Payload::LoginResp(resp) => {
if resp.success.value() {
println!("Login successful!");
if let Some(token) = resp.token {
save_token(token);
}
} else {
eprintln!("Login failed: {:?}", resp.message);
}
}
_ => eprintln!("Unexpected response type"),
}
```
---
## Next Steps
Now that you understand the basics:
1. **Read [ASN.1 Support](asn1-support.md)** - Complete ASN.1 syntax reference
2. **Read [Rust Generation](rust-generation.md)** - Constraints, naming, tagging, build.rs, multi-module
3. **See [Best Practices](best-practices.md)** - Tips for production use
## Common Patterns
### Pattern 1: Validated Wrapper Types
```asn1
Port ::= INTEGER (1..65535)
### Pattern 2: Extensible Protocols with CHOICE
```asn1
Request ::= CHOICE {
v1 [0] RequestV1,
v2 [1] RequestV2
}
```
### Pattern 3: Nested Structures
```asn1
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signature BIT STRING
}
```
## Troubleshooting
**Problem**: Generated code doesn't compile
**Solution**: Check that you have the synta library as a dependency:
```toml
[dependencies]
synta = "0.1"
```
**Problem**: Constraint validation failing
**Solution**: Check the constraint ranges and ensure your values are valid. Use `new_unchecked()` only if you're certain the value is valid.
**Problem**: Import resolution errors
**Solution**: Ensure you generate modules in dependency order (base modules first, then dependent modules).
---
For more help, see:
- [Limitations](limitations.md)
- [Troubleshooting Guide](troubleshooting.md)
- [API Reference](api-reference.md)