Compare commits

...

4 Commits

13 changed files with 217 additions and 303 deletions

View File

@ -1,14 +1,24 @@
pub struct AtemPacket {
length: u16,
flags: u8,
session_id: u16,
remote_packet_id: u16,
body: Vec<u8>,
use core::{fmt::Display, str};
// TODO: we don't need itertools once https://github.com/rust-lang/rust/issues/79524 lands
use itertools::Itertools;
/// The "hello" packet to start communication with the ATEM
pub const COMMAND_CONNECT_HELLO: [u8; 20] = [
0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
pub struct AtemPacket<T: AsRef<[u8]>> {
buf: T,
}
#[derive(Debug)]
pub enum AtemPacketErr {
TooShort(String),
LengthDiffers(String),
/// The packet was too short
TooShort { got: usize },
/// The packet's stated and actual lengths were different
LengthDiffers { expected: u16, got: usize },
}
#[derive(PartialEq)]
@ -32,64 +42,122 @@ impl From<PacketFlag> for u8 {
}
}
impl AtemPacket {
impl<T: AsRef<[u8]>> AtemPacket<T> {
pub fn new_checked(buf: T) -> Result<Self, AtemPacketErr> {
let len = buf.as_ref().len();
if len < 12 {
return Err(AtemPacketErr::TooShort {
got: buf.as_ref().len(),
});
}
let p = Self { buf };
if p.length() as usize != len {
return Err(AtemPacketErr::LengthDiffers {
expected: p.length(),
got: len,
});
}
Ok(p)
}
pub fn length(&self) -> u16 {
self.length
u16::from_be_bytes(self.buf.as_ref()[0..=1].try_into().unwrap()) & 0x07ff
}
pub fn flags(&self) -> u8 {
self.flags
self.buf.as_ref()[0] >> 3
}
pub fn session_id(&self) -> u16 {
self.session_id
u16::from_be_bytes(self.buf.as_ref()[2..=3].try_into().unwrap())
}
pub fn ack_number(&self) -> u16 {
u16::from_be_bytes(self.buf.as_ref()[4..=5].try_into().unwrap())
}
pub fn remote_sequence_number(&self) -> u16 {
u16::from_be_bytes(self.buf.as_ref()[9..=10].try_into().unwrap())
}
pub fn remote_packet_id(&self) -> u16 {
self.remote_packet_id
}
pub fn body(&self) -> Vec<u8> {
self.body.clone()
u16::from_be_bytes(self.buf.as_ref()[10..=11].try_into().unwrap())
}
/// Return true if this packet has the given [`PacketFlag`]
pub fn has_flag(&self, flag: PacketFlag) -> bool {
self.flags & u8::from(flag) > 0
self.flags() & u8::from(flag) > 0
}
/// Get an iterator over the `Field`s in this packet.
///
/// Returns None if this is a packet without fields.
pub fn fields(&self) -> Option<Fields> {
// TODO: do we only ever get newsessionid during the handshake (i.e. not in a packet with fields)?
if self.has_flag(PacketFlag::NewSessionId) {
None
} else {
Some(Fields {
data: self.body(),
offset: 0,
})
}
}
pub fn body(&self) -> &[u8] {
&self.buf.as_ref()[12..]
}
}
impl TryFrom<&[u8]> for AtemPacket {
type Error = AtemPacketErr;
fn try_from(buffer: &[u8]) -> Result<Self, Self::Error> {
if buffer.len() < 12 {
return Err(AtemPacketErr::TooShort(format!(
"Invalid packet from ATEM {:x?}",
buffer
)));
}
let length = u16::from_be_bytes(buffer[0..2].try_into().unwrap()) & 0x07ff;
if length as usize != buffer.len() {
return Err(AtemPacketErr::LengthDiffers(format!(
"Length of message differs, expected {} got {}",
length,
buffer.len()
)));
}
let flags = buffer[0] >> 3;
let session_id = u16::from_be_bytes(buffer[2..4].try_into().unwrap());
let remote_packet_id = u16::from_be_bytes(buffer[10..12].try_into().unwrap());
let body = buffer[12..].to_vec();
Ok(AtemPacket {
length,
flags,
session_id,
remote_packet_id,
body,
})
#[cfg(feature = "std")]
impl<T: AsRef<[u8]>> Display for AtemPacket<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"sid: {}, len: {}, fields: ({})",
self.session_id(),
self.length(),
self.fields()
.and_then(|f| Some(
f.map(|f| str::from_utf8(f.r#type).unwrap())
.intersperse(", ")
.collect::<String>()
))
.unwrap_or("none".into())
)
}
}
/// An ATEM protocol field - a 4-ascii character type plus variable length data
pub struct Field<'a> {
r#type: &'a [u8; 4],
data: &'a [u8],
}
/// Created by [`AtemPacket::fields`]
pub struct Fields<'a> {
data: &'a [u8],
offset: usize,
}
impl<'a> Iterator for Fields<'a> {
type Item = Field<'a>;
fn next(&mut self) -> Option<Self::Item> {
let remain = self.data.len() - self.offset;
if remain == 0 {
return None;
} else if remain < 8 {
// TODO: is 8 indeed the minimum size for something here? (i.e. no field data)
panic!("Oh no");
}
let length =
u16::from_be_bytes(self.data[self.offset..=self.offset + 1].try_into().unwrap());
// TODO: sanity check length
let r#type: &[u8; 4] = self.data[self.offset + 4..=self.offset + 7]
.try_into()
.unwrap();
let data = &self.data[self.offset + 8..self.offset + (length as usize)];
self.offset += (length as usize);
Some(Field { r#type, data })
}
}

View File

@ -1,3 +1,4 @@
use core::net::SocketAddr;
use std::{io, sync::Arc, thread::yield_now};
use tokio::{sync::RwLock, task::JoinHandle};
@ -17,8 +18,8 @@ pub enum AtemSocketConnectionError {
}
impl AtemSocket {
pub fn new() -> Self {
let socket = AtemSocketInner::new();
pub fn new<A: Into<SocketAddr>>(addr: A) -> Self {
let socket = AtemSocketInner::new(addr.into());
let socket = Arc::new(RwLock::new(socket));
let socket_clone = Arc::clone(&socket);
@ -37,12 +38,8 @@ impl AtemSocket {
}
}
pub async fn connect(
&mut self,
address: String,
port: u16,
) -> Result<(), AtemSocketConnectionError> {
self.socket.write().await.connect(address, port).await?;
pub async fn connect(&mut self) -> Result<(), AtemSocketConnectionError> {
self.socket.write().await.connect().await?;
Ok(())
}
@ -60,9 +57,3 @@ impl AtemSocket {
.await;
}
}
impl Default for AtemSocket {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,13 +1,13 @@
use core::net::SocketAddr;
use std::{
io,
net::SocketAddr,
time::{Duration, SystemTime},
};
use log::debug;
use log::{debug, trace};
use tokio::net::UdpSocket;
use crate::atem_lib::atem_util;
use crate::atem_lib::atem_packet::{AtemPacket, PacketFlag, COMMAND_CONNECT_HELLO};
const IN_FLIGHT_TIMEOUT: u64 = 60;
const CONNECTION_TIMEOUT: u64 = 5000;
@ -39,27 +39,6 @@ impl Into<u8> for ConnectionState {
}
}
#[derive(PartialEq)]
enum PacketFlag {
AckRequest,
NewSessionId,
IsRetransmit,
RetransmitRequest,
AckReply,
}
impl From<PacketFlag> for u8 {
fn from(flag: PacketFlag) -> Self {
match flag {
PacketFlag::AckRequest => 0x01,
PacketFlag::NewSessionId => 0x02,
PacketFlag::IsRetransmit => 0x04,
PacketFlag::RetransmitRequest => 0x08,
PacketFlag::AckReply => 0x10,
}
}
}
#[derive(Clone)]
struct InFlightPacket {
packet_id: u16,
@ -89,8 +68,7 @@ pub struct AtemSocketInner {
session_id: u16,
socket: Option<UdpSocket>,
address: String,
port: u16,
address: SocketAddr,
last_received_at: SystemTime,
last_received_packed_id: u16,
@ -113,7 +91,7 @@ enum AtemSocketWriteError {
}
impl AtemSocketInner {
pub fn new() -> Self {
pub fn new(addr: SocketAddr) -> Self {
AtemSocketInner {
connection_state: ConnectionState::Closed,
reconnect_timer: None,
@ -123,8 +101,7 @@ impl AtemSocketInner {
session_id: 0,
socket: None,
address: "0.0.0.0".to_string(),
port: 0,
address: addr,
last_received_at: SystemTime::now(),
last_received_packed_id: 0,
@ -134,15 +111,9 @@ impl AtemSocketInner {
}
}
pub async fn connect(&mut self, address: String, port: u16) -> Result<(), io::Error> {
self.address = address.clone();
self.port = port;
pub async fn connect(&mut self) -> Result<(), io::Error> {
let socket = UdpSocket::bind("0.0.0.0:0").await?;
let remote_addr = format!("{}:{}", address, port)
.parse::<SocketAddr>()
.unwrap();
socket.connect(remote_addr).await?;
socket.connect(self.address).await?;
self.socket = Some(socket);
self.start_timers();
@ -152,7 +123,7 @@ impl AtemSocketInner {
self.in_flight = vec![];
debug!("Reconnect");
self.send_packet(&atem_util::COMMAND_CONNECT_HELLO).await;
self.send_packet(&COMMAND_CONNECT_HELLO).await;
self.connection_state = ConnectionState::SynSent;
Ok(())
@ -218,7 +189,7 @@ impl AtemSocketInner {
async fn restart_connection(&mut self) {
self.disconnect();
self.connect(self.address.clone(), self.port).await.ok();
self.connect().await.ok();
}
pub async fn tick(&mut self) {
@ -277,30 +248,23 @@ impl AtemSocketInner {
}
async fn recieved_packet(&mut self, packet: &[u8]) {
debug!("RECV {:x?}", packet);
trace!("RX Raw: {:x?}", packet);
if packet.len() < 12 {
debug!("Invalid packet from ATEM {:x?}", packet);
return;
}
let checked = match AtemPacket::new_checked(packet) {
Ok(p) => p,
Err(e) => {
debug!("Invalid packet ({:?}): {:x?}", e, packet);
return;
}
};
debug!("RX: {}", checked);
self.last_received_at = SystemTime::now();
let length = u16::from_be_bytes(packet[0..2].try_into().unwrap()) & 0x07ff;
if length as usize != packet.len() {
debug!(
"Length of message differs, expected {} got {}",
length,
packet.len()
);
return;
}
self.session_id = checked.session_id();
let remote_packet_id = checked.remote_packet_id();
let flags = packet[0] >> 3;
self.session_id = u16::from_be_bytes(packet[2..4].try_into().unwrap());
let remote_packet_id = u16::from_be_bytes(packet[10..12].try_into().unwrap());
if flags & u8::from(PacketFlag::NewSessionId) > 0 {
if checked.flags() & u8::from(PacketFlag::NewSessionId) > 0 {
debug!("New session");
self.connection_state = ConnectionState::Established;
self.last_received_packed_id = remote_packet_id;
@ -309,20 +273,20 @@ impl AtemSocketInner {
}
if self.connection_state == ConnectionState::Established {
if flags & u8::from(PacketFlag::RetransmitRequest) > 0 {
if checked.has_flag(PacketFlag::RetransmitRequest) {
let from_packet_id = u16::from_be_bytes(packet[6..8].try_into().unwrap());
debug!("Retransmit request: {:x?}", from_packet_id);
self.retransmit_from(from_packet_id).await;
}
if flags & u8::from(PacketFlag::AckRequest) > 0 {
if checked.has_flag(PacketFlag::AckRequest) {
if remote_packet_id == (self.last_received_packed_id + 1) % MAX_PACKET_ID {
self.last_received_packed_id = remote_packet_id;
self.send_or_queue_ack().await;
if length > 12 {
self.on_command_received(&packet[12..], remote_packet_id);
if checked.length() > 12 {
self.on_command_received(checked.body(), remote_packet_id);
}
} else if self
.is_packet_covered_by_ack(self.last_received_packed_id, remote_packet_id)
@ -331,12 +295,12 @@ impl AtemSocketInner {
}
}
if flags & u8::from(PacketFlag::IsRetransmit) > 0 {
if checked.has_flag(PacketFlag::IsRetransmit) {
debug!("ATEM retransmitted packet {:x?}", remote_packet_id);
}
if flags & u8::from(PacketFlag::AckReply) > 0 {
let ack_packet_id = u16::from_be_bytes(packet[4..6].try_into().unwrap());
if checked.has_flag(PacketFlag::AckReply) {
let ack_packet_id = checked.ack_number();
let mut acked_commands: Vec<AckedPacket> = vec![];
self.in_flight = self

View File

@ -1,4 +0,0 @@
pub const COMMAND_CONNECT_HELLO: [u8; 20] = [
0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];

View File

@ -1,4 +1,3 @@
mod atem_packet;
pub mod atem_packet;
pub mod atem_socket;
mod atem_socket_inner;
pub mod atem_util;

View File

@ -2,5 +2,5 @@
pub struct ColorGeneratorState {
pub hue: u64,
pub saturation: u64,
pub luma: u64
pub luma: u64,
}

View File

@ -1,14 +1,17 @@
use std::collections::HashMap;
use crate::enums::{ExternalPortType, FairlightAnalogInputLevel, FairlightAudioMixOption, FairlightAudioSourceType, FairlightInputConfiguration, FairlightInputType};
use crate::enums::{
ExternalPortType, FairlightAnalogInputLevel, FairlightAudioMixOption, FairlightAudioSourceType,
FairlightInputConfiguration, FairlightInputType,
};
#[derive(Getters, new)]
pub struct FairlightAudioDynamicsState {
pub make_up_gain: Option<u64>,
pub limiter: Option<FairlightAudioLimiterState>,
pub compressor: Option<FairlightAudioCompressorState>,
pub expander: Option<FairlightAudioExpanderState>
pub expander: Option<FairlightAudioExpanderState>,
}
#[derive(Getters, new)]
@ -17,7 +20,7 @@ pub struct FairlightAudioLimiterState {
pub threshold: u64,
pub attack: u64,
pub hold: u64,
pub release: u64
pub release: u64,
}
#[derive(Getters, new)]
@ -27,7 +30,7 @@ pub struct FairlightAudioCompressorState {
pub ratio: u64,
pub attack: u64,
pub hold: u64,
pub release: u64
pub release: u64,
}
#[derive(Getters, new)]
@ -39,7 +42,7 @@ pub struct FairlightAudioExpanderState {
pub ratio: u64,
pub attack: u64,
pub hold: u64,
pub release: u64
pub release: u64,
}
#[derive(Getters, new)]
@ -47,22 +50,22 @@ pub struct FairlightAudioEqualizerBandState {
pub band_enabled: bool,
supported_shapes: Vec<u64>, // TODO
pub shape: u64, // TODO
pub shape: u64, // TODO
supported_frequency_ranges: Vec<u64>, // TODO
pub frequency_ranges: u64, // TODO
pub frequency_ranges: u64, // TODO
pub frequency: u64,
pub gain: u64,
pub q_factor: u64
pub q_factor: u64,
}
#[derive(Getters, new)]
pub struct FairlightAudioMasterChannelPropertiesState {
// Gain in decibel, -Infinity to +6dB
pub fader_gain: u64,
pub follow_fade_to_black: bool
pub follow_fade_to_black: bool,
}
#[derive(Getters, new)]
@ -78,21 +81,21 @@ pub struct FairlightAudioMonitorChannel {
pub gain: u64,
pub input_master_gain: u64,
pub input_talkback_gain: u64,
pub input_sidetone_gain: u64
pub input_sidetone_gain: u64,
}
#[derive(Getters, new)]
pub struct FairlightAudioSource {
pub properties: Option<FairlightAudioSourcePropertiesState>,
pub equalizer: Option<FairlightAudioEqualizerState>,
pub dynamics: Option<FairlightAudioDynamicsState>
pub dynamics: Option<FairlightAudioDynamicsState>,
}
#[derive(Getters, new)]
pub struct FairlightAudioEqualizerState {
pub enabled: bool,
pub gain: u64,
bands: Vec<FairlightAudioEqualizerBandState>
bands: Vec<FairlightAudioEqualizerBandState>,
}
#[derive(Getters, new)]
@ -110,13 +113,13 @@ pub struct FairlightAudioSourcePropertiesState {
pub fader_gain: u64,
supported_mix_options: Vec<FairlightAudioMixOption>,
pub mix_option: FairlightAudioMixOption
pub mix_option: FairlightAudioMixOption,
}
#[derive(Getters, new)]
pub struct FairlightAudioInput {
pub properties: Option<FairlightAudioInputProperties>,
pub sources: HashMap<String, FairlightAudioSource>
pub sources: HashMap<String, FairlightAudioSource>,
}
#[derive(Getters, new)]
@ -128,7 +131,7 @@ pub struct FairlightAudioInputProperties {
pub active_configuration: FairlightInputConfiguration,
supported_input_levels: Vec<FairlightAnalogInputLevel>,
pub activeInputLevel: FairlightAnalogInputLevel
pub activeInputLevel: FairlightAnalogInputLevel,
}
#[derive(Getters, new)]
@ -137,5 +140,5 @@ pub struct AtemFairlightAudioState {
pub master: Option<FairlightAudioMasterChannel>,
pub monitor: Option<FairlightAudioMonitorChannel>,
pub audio_follow_video_crossfade_transition_enabled: Option<bool>
pub audio_follow_video_crossfade_transition_enabled: Option<bool>,
}

View File

@ -10,5 +10,5 @@ pub struct InputChannel {
pub external_port_type: ExternalPortType,
internal_port_type: InternalPortType,
source_availability: SourceAvailability,
me_availability: MeAvailability
me_availability: MeAvailability,
}

View File

@ -11,7 +11,7 @@ pub struct RecordingState {
pub duration: Option<Timecode>,
pub disks: HashMap<u64, RecordingDiskProperties>
pub disks: HashMap<u64, RecordingDiskProperties>,
}
#[derive(Getters, new)]
@ -19,7 +19,7 @@ pub struct RecordingDiskProperties {
pub disk_id: u64,
pub volume_name: String,
pub recording_time_available: u64,
pub status: RecordingDiskStatus
pub status: RecordingDiskStatus,
}
#[derive(Getters, new)]
@ -27,7 +27,7 @@ pub struct RecordingStateStatus {
pub state: RecordingStatus,
pub error: RecordingError,
pub recording_time_available: u64
pub recording_time_available: u64,
}
#[derive(Getters, new)]
@ -37,5 +37,5 @@ pub struct RecordingStateProperties {
pub working_set_1_disk_id: u64,
pub working_set_2_disk_id: u64,
pub record_in_all_cameras: bool
pub record_in_all_cameras: bool,
}

View File

@ -8,19 +8,19 @@ pub struct StreamingState {
pub stats: Option<StreamingStateStats>,
pub service: StreamingServiceProperties,
pub duration: Option<Timecode>
pub duration: Option<Timecode>,
}
#[derive(Getters, new)]
pub struct StreamingStateStatus {
state: StreamingStatus,
error: StreamingError
error: StreamingError,
}
#[derive(Getters, new)]
pub struct StreamingStateStats {
cache_used: u64,
encoding_bitrate: u64
encoding_bitrate: u64,
}
pub struct StreamingServiceProperties {
@ -28,5 +28,5 @@ pub struct StreamingServiceProperties {
pub url: String,
pub key: String,
bitrates: (u64, u64)
bitrates: (u64, u64),
}

View File

@ -1,4 +1,4 @@
use std::time::Duration;
use std::{env::args, net::Ipv4Addr, time::Duration};
use atem_connection_rs::{
atem_lib::atem_socket::AtemSocket,
@ -18,8 +18,11 @@ async fn main() {
let switch_to_source_1 = ProgramInput::new(0, 1);
let switch_to_source_2 = ProgramInput::new(0, 2);
let mut atem = AtemSocket::new();
atem.connect("127.0.0.1".to_string(), 9910).await.ok();
let mut atem = AtemSocket::new((
args().skip(1).next().unwrap().parse::<Ipv4Addr>().unwrap(),
9910,
));
atem.connect().await.ok();
let mut tracking_id = 0;
loop {

View File

@ -1,70 +1,15 @@
{
"nodes": {
"devshell": {
"naersk": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1705332421,
"narHash": "sha256-USpGLPme1IuqG78JNqSaRabilwkCyHmVWY0M9vYyqEA=",
"owner": "numtide",
"repo": "devshell",
"rev": "83cb93d6d063ad290beee669f4badf9914cc16ec",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1698420672,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
"lastModified": 1745925850,
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
"owner": "nix-community",
"repo": "naersk",
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
"type": "github"
},
"original": {
@ -75,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1704161960,
"narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=",
"lastModified": 1749401433,
"narHash": "sha256-HXIQzULIG/MEUW2Q/Ss47oE3QrjxvpUX7gUl4Xp6lnc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "63143ac2c9186be6d9da6035fa22620018c85932",
"rev": "08fcb0dcb59df0344652b38ea6326a2d8271baff",
"type": "github"
},
"original": {
@ -91,12 +36,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1705883077,
"narHash": "sha256-ByzHHX3KxpU1+V0erFy8jpujTufimh6KaS/Iv3AciHk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5f5210aa20e343b7e35f40c033000db0ef80d7b9",
"type": "github"
"lastModified": 1733412085,
"narHash": "sha256-FillH0qdWDt/nlO6ED7h4cmN+G9uXwGjwmCnHs0QVYM=",
"path": "/nix/store/5wvrlpfrwlmg24ywkybvidyzcvki6bwx-source",
"rev": "4dc2fc4e62dbf62b84132fe526356fbac7b03541",
"type": "path"
},
"original": {
"id": "nixpkgs",
@ -105,25 +49,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1705883077,
"narHash": "sha256-ByzHHX3KxpU1+V0erFy8jpujTufimh6KaS/Iv3AciHk=",
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5f5210aa20e343b7e35f40c033000db0ef80d7b9",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1681358109,
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
"type": "github"
},
"original": {
@ -135,24 +65,22 @@
},
"root": {
"inputs": {
"devshell": "devshell",
"naersk": "naersk",
"nixpkgs": "nixpkgs_3",
"nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay",
"utils": "utils"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_4"
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1705976279,
"narHash": "sha256-Zx97bJ3+O8IP70uJPD//rRsr8bcxICISMTZUT/L9eFk=",
"lastModified": 1749436897,
"narHash": "sha256-OkDtaCGQQVwVFz5HWfbmrMJR99sFIMXHCHEYXzUJEJY=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "f889dc31ef97835834bdc3662394ebdb3c96b974",
"rev": "e7876c387e35dc834838aff254d8e74cf5bd4f19",
"type": "github"
},
"original": {
@ -176,46 +104,16 @@
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems_3"
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {

View File

@ -3,7 +3,6 @@
inputs = {
utils.url = "github:numtide/flake-utils";
devshell.url = "github:numtide/devshell";
naersk.url = "github:nix-community/naersk";
rust-overlay.url = "github:oxalica/rust-overlay";
};
@ -13,7 +12,6 @@
nixpkgs,
utils,
naersk,
devshell,
rust-overlay,
}:
utils.lib.eachDefaultSystem (system: let
@ -35,16 +33,10 @@
apps.default = utils.lib.mkApp {drv = packages.default;};
# Provide a dev env with rust and rust-analyzer
devShells.default = let
pkgs = import nixpkgs {
inherit system;
overlays = [devshell.overlays.default];
};
in
pkgs.devshell.mkShell {
devShells.default = pkgs.mkShell {
packages = with pkgs; [(rust.override {extensions = ["rust-src"];}) rust-analyzer gcc];
};
formatter = pkgs.alejandra;
formatter = pkgs.nixfmt-rfc-style;
});
}